6#include <cluster_linearize.h>
39 static constexpr auto MISSING = Pos(-1);
47 std::array<std::shared_ptr<TxGraph::Ref>, MAX_TRANSACTIONS> simmap;
49 std::map<const TxGraph::Ref*, Pos> simrevmap;
51 std::vector<std::shared_ptr<TxGraph::Ref>> removed;
53 std::optional<bool> oversized;
61 explicit SimTxGraph(
DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
64 SimTxGraph(
const SimTxGraph&)
noexcept =
default;
65 SimTxGraph& operator=(
const SimTxGraph&)
noexcept =
default;
66 SimTxGraph(SimTxGraph&&) noexcept = default;
67 SimTxGraph& operator=(SimTxGraph&&) noexcept = default;
73 if (!oversized.has_value()) {
80 if (component.Count() > max_cluster_count) oversized =
true;
108 auto it = simrevmap.find(ref);
109 if (it != simrevmap.end())
return it->second;
118 return simmap[pos].get();
126 MakeModified(simpos);
128 simmap[simpos] = std::make_shared<TxGraph::Ref>();
129 auto ptr = simmap[simpos].get();
130 simrevmap[ptr] = simpos;
137 auto par_pos =
Find(parent);
138 if (par_pos ==
MISSING)
return;
139 auto chl_pos =
Find(child);
140 if (chl_pos ==
MISSING)
return;
142 MakeModified(par_pos);
144 if (oversized.has_value() && !*oversized) oversized = std::nullopt;
150 auto pos =
Find(ref);
159 auto pos =
Find(ref);
163 simrevmap.erase(simmap[pos].get());
166 removed.push_back(std::move(simmap[pos]));
169 if (oversized.has_value() && *oversized) oversized = std::nullopt;
176 void DestroyTransaction(
TxGraph::Ref* ref,
bool reset_oversize)
178 auto pos =
Find(ref);
183 auto remove = std::partition(removed.begin(), removed.end(), [&](
auto& arg) { return arg.get() != ref; });
184 removed.erase(remove, removed.end());
188 simrevmap.erase(simmap[pos].get());
191 if (reset_oversize && oversized.has_value() && *oversized) {
192 oversized = std::nullopt;
199 SetType MakeSet(std::span<TxGraph::Ref* const> arg)
203 auto pos =
Find(ptr);
213 auto pos =
Find(arg);
220 void IncludeAncDesc(std::vector<TxGraph::Ref*>& arg,
bool desc)
222 std::vector<TxGraph::Ref*>
ret;
223 for (
auto ptr : arg) {
224 auto simpos =
Find(ptr);
227 ret.push_back(simmap[i].get());
236 for (
auto ptr :
ret) {
237 if (std::find(arg.begin(), arg.end(), ptr) == arg.end()) {
268 std::vector<SimTxGraph> sims;
270 sims.emplace_back(max_count);
273 struct BlockBuilderData
276 std::unique_ptr<TxGraph::BlockBuilder> builder;
278 SimTxGraph::SetType included;
280 SimTxGraph::SetType done;
284 BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
288 std::vector<BlockBuilderData> block_builders;
293 size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
295 size_t choices = tx_count[0] + sims[0].removed.size() + 1;
296 if (sims.size() == 2) {
297 tx_count[1] = sims[1].GetTransactionCount();
298 choices += tx_count[1] + sims[1].removed.size();
303 for (
size_t level = 0; level < sims.size(); ++level) {
304 auto& sim = sims[level];
305 if (choice < tx_count[level]) {
307 for (
auto i : sim.graph.Positions()) {
308 if (choice == 0)
return sim.GetRef(i);
313 choice -= tx_count[level];
315 if (choice < sim.removed.size()) {
317 return sim.removed[choice].get();
319 choice -= sim.removed.size();
329 auto get_diagram_fn = [&](
bool main_only) -> std::vector<FeeFrac> {
330 int level = main_only ? 0 : sims.size() - 1;
331 auto& sim = sims[level];
333 std::set<std::vector<TxGraph::Ref*>> clusters;
334 for (
auto i : sim.graph.Positions()) {
335 auto ref = sim.GetRef(i);
336 clusters.insert(real->GetCluster(*ref, main_only));
340 std::vector<FeeFrac> chunk_feerates;
341 for (
const auto& cluster : clusters) {
342 num_tx += cluster.size();
343 std::vector<SimTxGraph::Pos> linearization;
344 linearization.reserve(cluster.size());
345 for (
auto refptr : cluster) linearization.push_back(sim.Find(refptr));
347 chunk_feerates.push_back(chunk_feerate);
353 assert(num_tx == sim.GetTransactionCount());
356 std::sort(chunk_feerates.begin(), chunk_feerates.end(), std::greater{});
357 return chunk_feerates;
375 int builder_idx = block_builders.empty() ? -1 : int((orig_command & 3) % block_builders.size());
380 auto& top_sim = sims.back();
381 auto& sel_sim = use_main ? sims[0] : top_sim;
382 auto& main_sim = sims[0];
387 if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS &&
command-- == 0) {
403 auto ref = real->AddTransaction(feerate);
405 auto ref_loc = top_sim.AddTransaction(feerate);
407 *ref_loc = std::move(ref);
409 }
else if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() + top_sim.removed.size() > 1 &&
command-- == 0) {
411 auto par = pick_fn();
412 auto chl = pick_fn();
413 auto pos_par = top_sim.Find(par);
414 auto pos_chl = top_sim.Find(chl);
418 if (top_sim.graph.Ancestors(pos_par)[pos_chl])
break;
420 top_sim.AddDependency(par, chl);
421 real->AddDependency(*par, *chl);
423 }
else if ((block_builders.empty() || sims.size() > 1) && top_sim.removed.size() < 100 &&
command-- == 0) {
427 std::vector<TxGraph::Ref*> to_remove;
428 to_remove.push_back(pick_fn());
429 top_sim.IncludeAncDesc(to_remove, alt);
432 std::shuffle(to_remove.begin(), to_remove.end(), rng);
434 real->RemoveTransaction(*ptr);
435 top_sim.RemoveTransaction(ptr);
438 }
else if (sel_sim.removed.size() > 0 &&
command-- == 0) {
448 if (removed_pos != sel_sim.removed.size() - 1) {
449 std::swap(sel_sim.removed[removed_pos], sel_sim.removed.back());
451 sel_sim.removed.pop_back();
453 }
else if (block_builders.empty() &&
command-- == 0) {
455 std::vector<TxGraph::Ref*> to_destroy;
456 to_destroy.push_back(pick_fn());
462 auto old_size = to_destroy.size();
463 for (
auto& sim : sims) sim.IncludeAncDesc(to_destroy, alt);
464 if (to_destroy.size() == old_size)
break;
468 std::shuffle(to_destroy.begin(), to_destroy.end(), rng);
470 for (
size_t level = 0; level < sims.size(); ++level) {
471 sims[level].DestroyTransaction(ptr, level == sims.size() - 1);
475 }
else if (block_builders.empty() &&
command-- == 0) {
483 auto ref = pick_fn();
484 real->SetTransactionFee(*ref,
fee);
485 for (
auto& sim : sims) {
486 sim.SetTransactionFee(ref,
fee);
491 assert(real->GetTransactionCount(use_main) == sel_sim.GetTransactionCount());
495 auto ref = pick_fn();
496 bool exists = real->Exists(*ref, use_main);
498 assert(exists == should_exist);
502 assert(sel_sim.IsOversized() == real->IsOversized(use_main));
506 auto ref = pick_fn();
507 auto feerate = real->GetIndividualFeerate(*ref);
509 for (
auto& sim : sims) {
510 auto simpos = sim.Find(ref);
513 assert(feerate == sim.graph.FeeRate(simpos));
518 }
else if (!main_sim.IsOversized() &&
command-- == 0) {
520 auto ref = pick_fn();
521 auto feerate = real->GetMainChunkFeerate(*ref);
522 auto simpos = main_sim.Find(ref);
528 assert(feerate.
size >= main_sim.graph.FeeRate(simpos).size);
529 assert(feerate.
size <= main_sim.SumAll().size);
532 }
else if (!sel_sim.IsOversized() &&
command-- == 0) {
534 auto ref = pick_fn();
535 auto result = alt ? real->GetDescendants(*ref, use_main)
536 : real->GetAncestors(*ref, use_main);
537 assert(result.size() <= max_count);
538 auto result_set = sel_sim.MakeSet(result);
539 assert(result.size() == result_set.Count());
540 auto expect_set = sel_sim.GetAncDesc(ref, alt);
541 assert(result_set == expect_set);
543 }
else if (!sel_sim.IsOversized() &&
command-- == 0) {
545 std::vector<TxGraph::Ref*> refs;
549 for (
size_t i = 0; i <
count; ++i) {
553 std::shuffle(refs.begin(), refs.end(), rng);
555 auto result = alt ? real->GetDescendantsUnion(refs, use_main)
556 : real->GetAncestorsUnion(refs, use_main);
557 auto result_set = sel_sim.MakeSet(result);
558 assert(result.size() == result_set.Count());
560 SimTxGraph::SetType expect_set;
561 for (
TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
563 assert(result_set == expect_set);
565 }
else if (!sel_sim.IsOversized() &&
command-- == 0) {
567 auto ref = pick_fn();
568 auto result = real->GetCluster(*ref, use_main);
570 assert(result.size() <= max_count);
572 auto left = sel_sim.graph.Positions();
573 for (
auto refptr : result) {
574 auto simpos = sel_sim.Find(refptr);
578 assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
581 auto result_set = sel_sim.MakeSet(result);
582 assert(sel_sim.graph.IsConnected(result_set));
584 auto simpos = sel_sim.Find(ref);
586 assert(result_set[simpos]);
588 assert(result_set.None());
591 for (
auto i : result_set) {
592 assert(sel_sim.graph.Ancestors(i).IsSubsetOf(result_set));
593 assert(sel_sim.graph.Descendants(i).IsSubsetOf(result_set));
598 assert((sims.size() == 2) == real->HaveStaging());
600 }
else if (sims.size() < 2 &&
command-- == 0) {
602 sims.emplace_back(sims.back());
603 sims.back().modified = SimTxGraph::SetType{};
604 real->StartStaging();
606 }
else if (block_builders.empty() && sims.size() > 1 &&
command-- == 0) {
608 real->CommitStaging();
609 sims.erase(sims.begin());
611 }
else if (sims.size() > 1 &&
command-- == 0) {
613 real->AbortStaging();
618 sims.back().oversized = std::nullopt;
620 }
else if (!main_sim.IsOversized() &&
command-- == 0) {
622 auto ref_a = pick_fn();
623 auto ref_b = pick_fn();
624 auto sim_a = main_sim.Find(ref_a);
625 auto sim_b = main_sim.Find(ref_b);
628 auto cmp = real->CompareMainOrder(*ref_a, *ref_b);
630 if (sim_a != sim_b)
assert(cmp != 0);
632 if (main_sim.graph.Ancestors(sim_a)[sim_b])
assert(cmp >= 0);
633 if (main_sim.graph.Descendants(sim_a)[sim_b])
assert(cmp <= 0);
638 }
else if (!sel_sim.IsOversized() &&
command-- == 0) {
640 std::vector<TxGraph::Ref*> refs;
644 for (
size_t i = 0; i <
count; ++i) {
648 std::shuffle(refs.begin(), refs.end(), rng);
650 auto result = real->CountDistinctClusters(refs, use_main);
654 SimTxGraph::SetType sim_reps;
655 for (
auto ref : refs) {
657 auto simpos = sel_sim.Find(ref);
660 auto component = sel_sim.graph.GetConnectedComponent(sel_sim.graph.Positions(), simpos);
663 sim_reps.Set(component.First());
667 assert(result == sim_reps.Count());
673 }
else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() &&
command-- == 0) {
675 auto [real_main_diagram, real_staged_diagram] = real->GetMainStagingDiagrams();
676 auto real_sum_main = std::accumulate(real_main_diagram.begin(), real_main_diagram.end(),
FeeFrac{});
677 auto real_sum_staged = std::accumulate(real_staged_diagram.begin(), real_staged_diagram.end(),
FeeFrac{});
678 auto real_gain = real_sum_staged - real_sum_main;
679 auto sim_gain = sims[1].SumAll() - sims[0].SumAll();
683 assert(sim_gain == real_gain);
685 for (
size_t i = 1; i < real_main_diagram.size(); ++i) {
686 assert(FeeRateCompare(real_main_diagram[i], real_main_diagram[i - 1]) <= 0);
688 for (
size_t i = 1; i < real_staged_diagram.size(); ++i) {
689 assert(FeeRateCompare(real_staged_diagram[i], real_staged_diagram[i - 1]) <= 0);
692 }
else if (block_builders.size() < 4 && !main_sim.IsOversized() &&
command-- == 0) {
694 block_builders.emplace_back(real->GetBlockBuilder());
696 }
else if (!block_builders.empty() &&
command-- == 0) {
698 block_builders.erase(block_builders.begin() + builder_idx);
700 }
else if (!block_builders.empty() &&
command-- == 0) {
702 auto& builder_data = block_builders[builder_idx];
703 auto new_included = builder_data.included;
704 auto new_done = builder_data.done;
705 auto chunk = builder_data.builder->GetCurrentChunk();
708 if (!builder_data.last_feerate.IsEmpty()) {
709 assert(!(chunk->second >> builder_data.last_feerate));
711 builder_data.last_feerate = chunk->second;
716 auto simpos = main_sim.Find(ref);
719 sum_feerate += main_sim.graph.FeeRate(simpos);
721 assert(!new_done[simpos]);
722 new_done.Set(simpos);
724 new_included.Set(simpos);
725 assert(main_sim.graph.Ancestors(simpos).IsSubsetOf(new_included));
727 assert(sum_feerate == chunk->second);
731 if (builder_data.done == builder_data.included) {
732 assert(builder_data.done.Count() == main_sim.GetTransactionCount());
736 if ((orig_command % 7) >= 5) {
737 auto chunk2 = builder_data.builder->GetCurrentChunk();
741 if ((orig_command % 5) >= 3) {
743 builder_data.builder->Skip();
746 builder_data.builder->Include();
747 builder_data.included = new_included;
749 builder_data.done = new_done;
751 }
else if (!main_sim.IsOversized() &&
command-- == 0) {
753 auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
756 if (main_sim.GetTransactionCount() == 0) {
757 assert(worst_chunk.empty());
758 assert(worst_chunk_feerate.IsEmpty());
760 assert(!worst_chunk.empty());
761 SimTxGraph::SetType done;
765 auto simpos = main_sim.Find(ref);
767 sum += main_sim.graph.FeeRate(simpos);
772 assert(main_sim.graph.Descendants(simpos).IsSubsetOf(done));
785 if (!sims[0].IsOversized()) {
789 std::vector<SimTxGraph::Pos> vec1;
790 for (
auto i : sims[0].graph.
Positions()) vec1.push_back(i);
791 std::shuffle(vec1.begin(), vec1.end(), rng);
793 std::shuffle(vec2.begin(), vec2.end(), rng);
794 if (vec1 == vec2) std::next_permutation(vec2.begin(), vec2.end());
797 auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b)
noexcept {
798 return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
800 std::sort(vec1.begin(), vec1.end(), cmp);
801 std::sort(vec2.begin(), vec2.end(), cmp);
808 auto todo = sims[0].graph.Positions();
809 for (
auto i : vec1) {
817 for (
size_t pos = 0; pos < vec1.size(); ++pos) {
818 auto pos_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[pos]));
820 size_t before = rng.
randrange<
size_t>(pos);
821 auto before_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[before]));
822 assert(FeeRateCompare(before_feerate, pos_feerate) >= 0);
824 if (pos + 1 < vec1.size()) {
825 size_t after = pos + 1 + rng.
randrange<
size_t>(vec1.size() - 1 - pos);
826 auto after_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[after]));
827 assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
833 auto builder = real->GetBlockBuilder();
834 std::vector<SimTxGraph::Pos> vec_builder;
835 std::vector<TxGraph::Ref*> last_chunk;
837 while (
auto chunk = builder->GetCurrentChunk()) {
842 assert(real->GetMainChunkFeerate(*ref) == chunk->second);
844 sum += real->GetIndividualFeerate(*ref);
846 auto simpos = sims[0].Find(ref);
848 vec_builder.push_back(simpos);
851 last_chunk = std::move(chunk->first);
852 last_chunk_feerate = chunk->second;
855 assert(vec_builder == vec1);
858 std::reverse(last_chunk.begin(), last_chunk.end());
859 auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
860 assert(last_chunk == worst_chunk);
861 assert(last_chunk_feerate == worst_chunk_feerate);
865 auto main_real_diagram = get_diagram_fn(
true);
869 if (sims.size() >= 2 && !sims[1].IsOversized()) {
872 auto [main_cmp_diagram, stage_cmp_diagram] = real->GetMainStagingDiagrams();
874 for (
size_t i = 1; i < main_cmp_diagram.size(); ++i) {
875 assert(FeeRateCompare(main_cmp_diagram[i], main_cmp_diagram[i - 1]) <= 0);
877 for (
size_t i = 1; i < stage_cmp_diagram.size(); ++i) {
878 assert(FeeRateCompare(stage_cmp_diagram[i], stage_cmp_diagram[i - 1]) <= 0);
884 std::sort(main_cmp_diagram.begin(), main_cmp_diagram.end(), std::greater{});
885 std::sort(stage_cmp_diagram.begin(), stage_cmp_diagram.end(), std::greater{});
889 std::vector<FeeFrac> missing_main_cmp;
890 std::set_difference(main_real_diagram.begin(), main_real_diagram.end(),
891 main_cmp_diagram.begin(), main_cmp_diagram.end(),
892 std::inserter(missing_main_cmp, missing_main_cmp.end()),
894 assert(main_cmp_diagram.size() + missing_main_cmp.size() == main_real_diagram.size());
896 auto stage_real_diagram = get_diagram_fn(
false);
897 std::vector<FeeFrac> missing_stage_cmp;
898 std::set_difference(stage_real_diagram.begin(), stage_real_diagram.end(),
899 stage_cmp_diagram.begin(), stage_cmp_diagram.end(),
900 std::inserter(missing_stage_cmp, missing_stage_cmp.end()),
902 assert(stage_cmp_diagram.size() + missing_stage_cmp.size() == stage_real_diagram.size());
905 assert(missing_main_cmp == missing_stage_cmp);
912 for (
const auto& feerate : missing_main_cmp) missing_real += feerate;
913 FeeFrac missing_expected = sims[1].graph.FeeRate(sims[1].graph.Positions() - sims[1].modified);
920 assert(real->HaveStaging() == (sims.size() > 1));
924 for (
int main_only = 0; main_only < 2; ++main_only) {
925 auto& sim = main_only ? sims[0] : sims.back();
927 assert(real->IsOversized(main_only) == sim.IsOversized());
928 assert(real->GetTransactionCount(main_only) == sim.GetTransactionCount());
930 if (!sim.IsOversized()) {
931 auto todo = sim.graph.Positions();
935 auto component = sim.graph.FindConnectedComponent(todo);
938 for (
auto i : component) {
940 assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
942 auto expect_anc = sim.graph.Ancestors(i);
943 auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), main_only));
944 assert(anc.Count() <= max_count);
945 assert(anc == expect_anc);
947 auto expect_desc = sim.graph.Descendants(i);
948 auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), main_only));
949 assert(desc.Count() <= max_count);
950 assert(desc == expect_desc);
952 auto cluster = real->GetCluster(*sim.GetRef(i), main_only);
953 assert(cluster.size() <= max_count);
954 assert(sim.MakeSet(cluster) == component);
957 std::vector<DepGraphIndex> simlin;
958 SimTxGraph::SetType done;
960 auto simpos = sim.Find(ptr);
961 assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
963 assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
964 simlin.push_back(simpos);
968 if (sims.size() == 1 || main_only) {
971 for (
unsigned chunknum = 0; chunknum < simlinchunk.
NumChunksLeft(); ++chunknum) {
972 auto chunk = simlinchunk.
GetChunk(chunknum);
975 assert(sim.graph.IsConnected(chunk.transactions));
977 while (chunk.transactions.Any()) {
978 assert(chunk.transactions[simlin[idx]]);
979 chunk.transactions.Reset(simlin[idx]);
980 assert(chunk.feerate == real->GetMainChunkFeerate(*cluster[idx]));
994 block_builders.clear();
std::conditional_t<(BITS<=32), bitset_detail::IntBitSet< uint32_t >, std::conditional_t<(BITS<=std::numeric_limits< size_t >::digits), bitset_detail::IntBitSet< size_t >, bitset_detail::MultiIntBitSet< size_t,(BITS+std::numeric_limits< size_t >::digits - 1)/std::numeric_limits< size_t >::digits > > > BitSet
T ConsumeIntegralInRange(T min, T max)
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
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.
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).
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.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
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.
constexpr MaybeCoin MISSING
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Value Find(const std::map< Key, Value > &map, const Key &key)
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
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).
Tagged wrapper around FeeFrac to avoid unit confusion.
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
@ ZEROS
Seed with a compile time constant of zeros.
std::unique_ptr< TxGraph > MakeTxGraph(unsigned max_cluster_count) noexcept
Construct a new TxGraph with the specified limit on transactions within a cluster.
static constexpr unsigned MAX_CLUSTER_COUNT_LIMIT
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.