86template<
typename SetType>
87class SimpleCandidateFinder
97 m_depgraph(depgraph), m_todo{depgraph.
Positions()} {}
100 void MarkDone(SetType select)
noexcept { m_todo -= select; }
103 bool AllDone() const noexcept {
return m_todo.None(); }
113 std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations)
const noexcept
115 uint64_t iterations_left = max_iterations;
119 std::vector<std::pair<SetType, SetType>> queue;
121 queue.emplace_back(SetType{}, m_todo);
126 while (!queue.empty() && iterations_left) {
128 auto [inc, und] = queue.back();
131 bool inc_none = inc.None();
132 for (
auto split : und) {
140 queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
144 if (new_inc.feerate > best.feerate) best = new_inc;
149 return {std::move(best), max_iterations - iterations_left};
158template<
typename SetType>
159class ExhaustiveCandidateFinder
169 m_depgraph(depgraph), m_todo{depgraph.
Positions()} {}
172 void MarkDone(SetType select)
noexcept { m_todo -= select; }
175 bool AllDone() const noexcept {
return m_todo.None(); }
186 uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
188 for (uint64_t x = 1; x < limit; ++x) {
193 for (
auto i : m_todo) {
194 if (x_shifted & 1) txn |= m_depgraph.
Ancestors(i);
197 SetInfo cur(m_depgraph, txn & m_todo);
198 if (cur.feerate > best.feerate) best = cur;
210template<
typename SetType>
211std::pair<std::vector<DepGraphIndex>,
bool> SimpleLinearize(
const DepGraph<SetType>& depgraph, uint64_t max_iterations)
213 std::vector<DepGraphIndex> linearization;
214 SimpleCandidateFinder finder(depgraph);
218 auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
219 if (iterations_done == max_iterations) optimal =
false;
220 depgraph.
AppendTopo(linearization, candidate.transactions);
221 todo -= candidate.transactions;
222 finder.MarkDone(candidate.transactions);
223 max_iterations -= iterations_done;
225 return {std::move(linearization), optimal};
235template<
typename SetType>
239 std::vector<DepGraphIndex> linearization;
240 std::vector<FeeFrac> chunking;
242 std::vector<DepGraphIndex> perm_linearization;
249 TestBitSet perm_done;
250 while (topo_length < perm_linearization.size()) {
251 auto i = perm_linearization[topo_length];
253 if (!depgraph.
Ancestors(i).IsSubsetOf(perm_done))
break;
256 if (topo_length == perm_linearization.size()) {
263 if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) {
264 linearization = perm_linearization;
265 chunking = perm_chunking;
270 auto first_non_topo = perm_linearization.begin() + topo_length;
271 assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
272 std::reverse(first_non_topo + 1, perm_linearization.end());
274 }
while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
276 return linearization;
291 depgraph.
AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
298template<
typename SetType>
306 }
catch(
const std::ios_base::failure&) {}
307 if (mask != uint64_t(-1)) mask += non_empty;
310 for (
auto i : todo) {
321 if (non_empty &&
ret.None()) {
333 std::vector<DepGraphIndex> linearization;
338 TestBitSet potential_next;
339 for (
auto j : todo) {
340 if ((depgraph.
Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
341 potential_next.Set(j);
345 assert(potential_next.Any());
350 }
catch (
const std::ios_base::failure&) {}
351 idx %= potential_next.Count();
353 for (
auto j : potential_next) {
356 linearization.push_back(j);
364 return linearization;
390 if (children.Any()) {
403 return depgraph_tree;
418 std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
423 auto idx_fn = [&]() {
426 if (!sim[i].has_value())
continue;
427 if (offset == 0)
return i;
435 auto subset_fn = [&]() {
436 auto range = (uint64_t{1} << num_tx_sim) - 1;
438 auto mask_shifted = mask;
441 if (!sim[i].has_value())
continue;
442 if (mask_shifted & 1) {
447 assert(mask_shifted == 0);
452 auto set_fn = [&]() {
453 auto range = (uint64_t{1} << sim.size()) - 1;
457 if ((mask >> i) & 1) {
465 auto anc_update_fn = [&]() {
469 if (!sim[chl].has_value())
continue;
470 for (
auto par : sim[chl]->second) {
471 if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
472 sim[chl]->second |= sim[par]->second;
485 if (sim[i].has_value()) {
500 if (num_tx_sim < TestBitSet::Size() &&
command-- == 0) {
508 assert(!sim[idx].has_value());
510 if (!sim[i].has_value()) {
516 sim[idx] = {feerate, TestBitSet::Singleton(idx)};
519 }
else if (num_tx_sim > 0 &&
command-- == 0) {
522 auto parents = subset_fn();
526 sim[child]->second |= parents;
528 }
else if (num_tx_sim > 0 &&
command-- == 0) {
535 for (
auto i : del) check_fn(i);
540 if (sim[i].has_value()) {
543 sim[i] = std::nullopt;
545 sim[i]->second -= del;
555 assert(real.
PositionRange() < last_compaction_pos ? mem_after < mem_before : mem_after <= mem_before);
579 reader >> Using<DepGraphFormatter>(depgraph) >>
VARINT(par_code) >>
VARINT(chl_code);
580 }
catch (
const std::ios_base::failure&) {}
581 SanityCheck(depgraph);
587 if (depgraph.
TxCount() < 2)
return;
590 par_code %= depgraph.
TxCount();
599 auto ancestors = depgraph.
Ancestors(par) - TestBitSet::Singleton(par);
600 if (ancestors.None())
return;
601 chl_code %= ancestors.Count();
602 for (
auto i : ancestors) {
622 std::vector<DepGraphIndex> linearization;
624 reader >> Using<DepGraphFormatter>(depgraph);
625 }
catch (
const std::ios_base::failure&) {}
630 std::optional<DepGraphIndex> picked;
632 uint64_t picked_num{0};
634 reader >>
VARINT(picked_num);
635 }
catch (
const std::ios_base::failure&) {}
636 if (picked_num < todo.Size() && todo[picked_num]) {
646 assert(component.IsSubsetOf(todo));
650 if (picked)
assert(component[*picked]);
662 for (
auto i : component) {
668 for (
auto i : component) {
670 TestBitSet reachable = TestBitSet::Singleton(i);
673 TestBitSet new_reachable = reachable;
674 for (
auto j : new_reachable) {
675 new_reachable |= depgraph.
Ancestors(j) & todo;
678 if (new_reachable == reachable)
break;
679 reachable = new_reachable;
682 assert(component == reachable);
686 uint64_t subset_bits{0};
688 reader >>
VARINT(subset_bits);
689 }
catch (
const std::ios_base::failure&) {}
693 if (subset_bits & 1) subset.Set(i);
698 if (subset.None()) subset = TestBitSet::Singleton(todo.First());
714 reader >> Using<DepGraphFormatter>(depgraph);
715 }
catch (
const std::ios_base::failure&) {}
716 MakeConnected(depgraph);
717 SanityCheck(depgraph);
729 reader >> Using<DepGraphFormatter>(depgraph);
730 }
catch (
const std::ios_base::failure&) {}
733 auto linearization = ReadLinearization(depgraph, reader);
739 for (
size_t i = 1; i < chunking.size(); ++i) {
740 assert(!(chunking[i] >> chunking[i - 1]));
745 for (
const auto& chunk_feerate : chunking) {
750 accumulator.
Set(depgraph, idx);
771 reader >> Using<DepGraphFormatter>(depgraph);
772 }
catch (
const std::ios_base::failure&) {}
782 assert(best_anc.transactions.Any());
783 assert(best_anc.transactions.IsSubsetOf(todo));
784 assert(depgraph.
FeeRate(best_anc.transactions) == best_anc.feerate);
787 for (
auto i : best_anc.transactions) {
788 assert((depgraph.
Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
792 std::optional<SetInfo<TestBitSet>> real_best_anc;
793 for (
auto i : todo) {
795 if (!real_best_anc.has_value() || info.
feerate > real_best_anc->feerate) {
796 real_best_anc = info;
800 assert(real_best_anc.has_value());
801 assert(*real_best_anc == best_anc);
806 auto del_set = ReadTopologicalSet(depgraph, todo, reader,
true);
830 reader >> Using<DepGraphFormatter>(depgraph);
831 }
catch (
const std::ios_base::failure&) {}
835 SimpleCandidateFinder smp_finder(depgraph);
836 ExhaustiveCandidateFinder exh_finder(depgraph);
841 assert(!smp_finder.AllDone());
842 assert(!exh_finder.AllDone());
852 assert(found.transactions.Any());
853 assert(found.transactions.IsSubsetOf(todo));
854 assert(depgraph.
FeeRate(found.transactions) == found.feerate);
856 for (
auto i : found.transactions) {
863 assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
873 assert(anc.feerate <= found.feerate);
875 if (todo.Count() <= 12) {
878 auto exhaustive = exh_finder.FindCandidateSet();
879 assert(exhaustive.feerate == found.feerate);
884 auto read_topo = ReadTopologicalSet(depgraph, todo, reader,
true);
891 auto del_set = ReadTopologicalSet(depgraph, todo, reader,
true);
893 smp_finder.MarkDone(del_set);
894 exh_finder.MarkDone(del_set);
898 assert(smp_finder.AllDone());
899 assert(exh_finder.AllDone());
913 uint64_t rng_seed{0};
914 uint8_t make_connected{1};
916 reader >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
917 }
catch (
const std::ios_base::failure&) {}
920 if (make_connected) MakeConnected(depgraph);
924 SimpleCandidateFinder smp_finder(depgraph);
930 assert(!smp_finder.AllDone());
935 uint64_t max_iterations = 1;
937 reader >>
VARINT(max_iterations);
938 }
catch (
const std::ios_base::failure&) {}
939 max_iterations &= 0xfffff;
942 auto init_set = ReadTopologicalSet(depgraph, todo, reader,
false);
943 SetInfo init_best(depgraph, init_set);
946 auto [found, iterations_done] = src_finder.
FindCandidateSet(max_iterations, init_best);
947 bool optimal = iterations_done < max_iterations;
950 assert(iterations_done <= max_iterations);
951 assert(found.transactions.Any());
952 assert(found.transactions.IsSubsetOf(todo));
953 assert(depgraph.
FeeRate(found.transactions) == found.feerate);
956 for (
auto i : found.transactions) {
964 assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
968 if (iterations_done >= 1 && todo.Count() <= 63) {
969 Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count());
979 assert(found.feerate >= simple.feerate);
981 assert(found.feerate == simple.feerate);
986 assert(found.feerate >= anc.feerate);
990 auto read_topo = ReadTopologicalSet(depgraph, todo, reader,
true);
997 auto del_set = ReadTopologicalSet(depgraph, todo, reader,
true);
1000 smp_finder.MarkDone(del_set);
1005 assert(smp_finder.AllDone());
1018 reader >> Using<DepGraphFormatter>(depgraph);
1019 }
catch (
const std::ios_base::failure&) {}
1024 auto subset =
SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader,
false));
1027 auto linearization = ReadLinearization(depgraph, reader);
1034 while (todo.Any()) {
1038 std::vector<DepGraphIndex> linearization_left;
1039 for (
auto i : linearization) {
1040 if (todo[i]) linearization_left.push_back(i);
1053 TestBitSet combined;
1055 const auto& chunk_info = chunking.
GetChunk(i);
1057 assert(chunk_info.transactions.Any());
1059 if (i > 0)
assert(!(chunk_info.feerate >> chunking.
GetChunk(i - 1).feerate));
1061 assert(chunk_info.transactions.IsSubsetOf(todo));
1063 assert(depgraph.
FeeRate(chunk_info.transactions) == chunk_info.feerate);
1066 for (
auto j : linearization) {
1067 if (todo[j] && !combined[j]) {
1068 accumulator.
Set(depgraph, j);
1077 assert(!chunk_info.transactions.Overlaps(combined));
1078 combined |= chunk_info.transactions;
1080 for (
auto idx : chunk_info.transactions) {
1084 assert(combined == todo);
1091 TestBitSet intersect_anc;
1092 for (
auto idx : intersect.transactions) {
1093 intersect_anc |= (depgraph.
Ancestors(idx) & todo);
1095 assert(intersect.transactions == intersect_anc);
1097 assert(intersect.feerate == depgraph.
FeeRate(intersect.transactions));
1099 assert(intersect.transactions.Any() == subset.transactions.Any());
1101 assert(intersect.feerate >= subset.feerate);
1107 auto reintersect =
SetInfo(depgraph,
prefix & intersect.transactions);
1108 if (!reintersect.feerate.IsEmpty()) {
1109 assert(reintersect.feerate <= intersect.feerate);
1116 auto done = ReadTopologicalSet(depgraph, todo, reader,
true);
1119 subset =
SetInfo(depgraph, subset.transactions - done);
1133 uint64_t iter_count{0};
1136 reader >>
VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph);
1137 }
catch (
const std::ios_base::failure&) {}
1141 auto [linearization, optimal] = SimpleLinearize(depgraph, iter_count);
1142 SanityCheck(depgraph, linearization);
1148 const uint64_t n = depgraph.
TxCount();
1149 if (n <= 63 && (iter_count >> n)) {
1155 if (optimal && depgraph.
TxCount() <= 8) {
1156 auto exh_linearization = ExhaustiveLinearize(depgraph);
1160 assert(simple_chunking.size() == exh_chunking.size());
1165 auto read = ReadLinearization(depgraph, reader);
1180 uint64_t rng_seed{0};
1181 uint64_t iter_count{0};
1182 uint8_t make_connected{1};
1184 reader >>
VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
1185 }
catch (
const std::ios_base::failure&) {}
1188 if (make_connected) MakeConnected(depgraph);
1191 std::vector<DepGraphIndex> old_linearization;
1193 uint8_t have_old_linearization{0};
1195 reader >> have_old_linearization;
1196 }
catch(
const std::ios_base::failure&) {}
1197 if (have_old_linearization & 1) {
1198 old_linearization = ReadLinearization(depgraph, reader);
1199 SanityCheck(depgraph, old_linearization);
1204 iter_count &= 0x7ffff;
1205 auto [linearization, optimal, cost] =
Linearize(depgraph, iter_count, rng_seed, old_linearization);
1206 assert(cost <= iter_count);
1207 SanityCheck(depgraph, linearization);
1211 if (!old_linearization.empty()) {
1218 if (iter_count >= MaxOptimalLinearizationIters(depgraph.
TxCount())) {
1226 SanityCheck(depgraph, simple_linearization);
1232 if (simple_optimal)
assert(cmp == 0);
1235 if (cmp == 0)
assert(chunking.size() >= simple_chunking.size());
1238 auto read = ReadLinearization(depgraph, reader);
1253 reader >> Using<DepGraphFormatter>(depgraph);
1254 }
catch (
const std::ios_base::failure&) {}
1257 std::vector<DepGraphIndex> linearization;
1258 linearization = ReadLinearization(depgraph, reader);
1259 SanityCheck(depgraph, linearization);
1262 auto post_linearization = linearization;
1264 SanityCheck(depgraph, post_linearization);
1273 auto post_post_linearization = post_linearization;
1275 SanityCheck(depgraph, post_post_linearization);
1295 uint64_t rng_seed{0};
1297 uint8_t direction{0};
1299 reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
1300 }
catch (
const std::ios_base::failure&) {}
1302 auto depgraph_tree = BuildTreeGraph(depgraph_gen, direction);
1305 std::vector<DepGraphIndex> linearization;
1306 linearization = ReadLinearization(depgraph_tree, reader);
1307 SanityCheck(depgraph_tree, linearization);
1310 auto post_linearization = linearization;
1312 SanityCheck(depgraph_tree, post_linearization);
1322 auto post_post_linearization = post_linearization;
1324 SanityCheck(depgraph_tree, post_post_linearization);
1325 auto post_post_chunking =
ChunkLinearization(depgraph_tree, post_post_linearization);
1326 auto cmp_post =
CompareChunks(post_post_chunking, post_chunking);
1331 auto [opt_linearization, _optimal, _cost] =
Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
1350 uint64_t fee_inc_code;
1351 reader >> Using<DepGraphFormatter>(depgraph) >>
VARINT(fee_inc_code);
1352 fee_inc = fee_inc_code & 0x3ffff;
1353 }
catch (
const std::ios_base::failure&) {}
1354 if (depgraph.
TxCount() == 0)
return;
1357 auto lin = ReadLinearization(depgraph, reader);
1358 auto lin_leaf = ReadLinearization(depgraph, reader);
1362 std::vector<DepGraphIndex> lin_moved;
1363 for (
auto i : lin) {
1364 if (i != lin_leaf.back()) lin_moved.push_back(i);
1366 lin_moved.push_back(lin_leaf.back());
1370 SanityCheck(depgraph, lin_moved);
1374 depgraph.
FeeRate(lin_leaf.back()).
fee += fee_inc;
1386 reader >> Using<DepGraphFormatter>(depgraph);
1387 }
catch (
const std::ios_base::failure&) {}
1390 auto lin1 = ReadLinearization(depgraph, reader);
1391 auto lin2 = ReadLinearization(depgraph, reader);
1414 reader >> Using<DepGraphFormatter>(depgraph);
1415 }
catch (
const std::ios_base::failure&) {}
1418 std::vector<DepGraphIndex> linearization;
1421 while (todo.Any()) {
1426 }
catch (
const std::ios_base::failure&) {}
1427 val %= todo.Count();
1429 for (
auto idx : todo) {
1431 linearization.push_back(idx);
1443 size_t topo_prefix = 0;
1445 while (topo_prefix < linearization.size()) {
1448 if (todo.Overlaps(depgraph.
Ancestors(idx)))
break;
1453 auto linearization_fixed = linearization;
1456 SanityCheck(depgraph, linearization_fixed);
1459 assert(std::equal(linearization.begin(), linearization.begin() + topo_prefix,
1460 linearization_fixed.begin()));
1463 if (topo_prefix == linearization.size()) {
1464 assert(linearization == linearization_fixed);
#define Assume(val)
Assume is the identity function.
T ConsumeIntegralInRange(T min, T max)
Minimal stream for reading from an existing byte array by std::span.
Class encapsulating the state needed to find the best remaining ancestor set.
void MarkDone(SetType select) noexcept
Remove a set of transactions from the set of to-be-linearized ones.
DepGraphIndex NumRemaining() const noexcept
Count the number of remaining unlinearized transactions.
SetInfo< SetType > FindCandidateSet() const noexcept
Find the best (highest-feerate, smallest among those in case of a tie) ancestor set among the remaini...
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors,...
const SetType & Ancestors(DepGraphIndex i) const noexcept
Get the ancestors of a given transaction i.
SetType GetReducedChildren(DepGraphIndex i) const noexcept
Compute the (reduced) set of children of node i in this graph.
SetType GetReducedParents(DepGraphIndex i) const noexcept
Compute the (reduced) set of parents of node i in this graph.
void AppendTopo(std::vector< DepGraphIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
SetType GetConnectedComponent(const SetType &todo, DepGraphIndex tx) const noexcept
Get the connected component within the subset "todo" that contains tx (which must be in todo).
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
DepGraphIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position),...
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
size_t DynamicMemoryUsage() const noexcept
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Compact() noexcept
Reduce memory usage if possible.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
DepGraphIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
const SetInfo< SetType > & GetChunk(DepGraphIndex n) const noexcept
Access a chunk.
SetInfo< SetType > IntersectPrefixes(const SetInfo< SetType > &subset) const noexcept
Find the shortest intersection between subset and the prefixes of remaining chunks of the linearizati...
void MarkDone(SetType subset) noexcept
Remove some subset of transactions from the linearization.
Class encapsulating the state needed to perform search for good candidate sets.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
void MarkDone(const SetType &done) noexcept
Remove a subset of transactions from the cluster being linearized.
std::pair< SetInfo< SetType >, uint64_t > FindCandidateSet(uint64_t max_iterations, SetInfo< SetType > best) noexcept
Find a high-feerate topologically-valid subset of what remains of the cluster.
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
void FixLinearization(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization) noexcept
Make linearization topological, retaining its ordering where possible.
std::vector< DepGraphIndex > MergeLinearizations(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > lin1, std::span< const DepGraphIndex > lin2)
Merge two linearizations for the same cluster into one that is as good as both.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Data structure storing a fee and size, ordered by increasing fee/size.
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
A set of transactions together with their aggregate feerate.
FeeFrac feerate
Their combined fee and size.
void Set(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Add a transaction to this SetInfo (which must not yet be in it).
SetType transactions
The transactions in the set.
FUZZ_TARGET(clusterlin_depgraph_sim)
static constexpr auto MAX_SIMPLE_ITERATIONS
std::partial_ordering CompareChunks(std::span< const FeeFrac > chunks0, std::span< const FeeFrac > chunks1)
Compare the feerate diagrams implied by the provided sorted chunks data.