LCOV - code coverage report
Current view: top level - src/test/fuzz - txgraph.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 99.6 % 748 745
Test Date: 2026-02-16 04:39:30 Functions: 96.4 % 28 27
Branches: 72.6 % 1038 754

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <cluster_linearize.h>
       6                 :             : #include <test/fuzz/FuzzedDataProvider.h>
       7                 :             : #include <test/fuzz/fuzz.h>
       8                 :             : #include <test/util/cluster_linearize.h>
       9                 :             : #include <test/util/random.h>
      10                 :             : #include <txgraph.h>
      11                 :             : #include <util/bitset.h>
      12                 :             : #include <util/feefrac.h>
      13                 :             : 
      14                 :             : #include <algorithm>
      15                 :             : #include <cstdint>
      16                 :             : #include <iterator>
      17                 :             : #include <map>
      18                 :             : #include <memory>
      19                 :             : #include <set>
      20                 :             : #include <utility>
      21                 :             : 
      22                 :             : using namespace cluster_linearize;
      23                 :             : 
      24                 :             : namespace {
      25                 :             : 
      26                 :       31317 : struct SimTxObject : public TxGraph::Ref
      27                 :             : {
      28                 :             :     // Use random uint64_t as txids for this simulation (0 = empty object).
      29                 :             :     const uint64_t m_txid{0};
      30                 :             :     SimTxObject() noexcept = default;
      31                 :      315460 :     explicit SimTxObject(uint64_t txid) noexcept : m_txid(txid) {}
      32                 :             : };
      33                 :             : 
      34                 :             : /** Data type representing a naive simulated TxGraph, keeping all transactions (even from
      35                 :             :  *  disconnected components) in a single DepGraph. Unlike the real TxGraph, this only models
      36                 :             :  *  a single graph, and multiple instances are used to simulate main/staging. */
      37                 :             : struct SimTxGraph
      38                 :             : {
      39                 :             :     /** Maximum number of transactions to support simultaneously. Set this higher than txgraph's
      40                 :             :      *  cluster count, so we can exercise situations with more transactions than fit in one
      41                 :             :      *  cluster. */
      42                 :             :     static constexpr unsigned MAX_TRANSACTIONS = MAX_CLUSTER_COUNT_LIMIT * 2;
      43                 :             :     /** Set type to use in the simulation. */
      44                 :             :     using SetType = BitSet<MAX_TRANSACTIONS>;
      45                 :             :     /** Data type for representing positions within SimTxGraph::graph. */
      46                 :             :     using Pos = DepGraphIndex;
      47                 :             :     /** Constant to mean "missing in this graph". */
      48                 :             :     static constexpr auto MISSING = Pos(-1);
      49                 :             : 
      50                 :             :     /** The dependency graph (for all transactions in the simulation, regardless of
      51                 :             :      *  connectivity/clustering). */
      52                 :             :     DepGraph<SetType> graph;
      53                 :             :     /** For each position in graph, which SimTxObject it corresponds with (if any). Use shared_ptr
      54                 :             :      *  so that a SimTxGraph can be copied to create a staging one, while sharing Refs with
      55                 :             :      *  the main graph. */
      56                 :             :     std::array<std::shared_ptr<SimTxObject>, MAX_TRANSACTIONS> simmap;
      57                 :             :     /** For each TxGraph::Ref in graph, the position it corresponds with. */
      58                 :             :     std::map<const TxGraph::Ref*, Pos> simrevmap;
      59                 :             :     /** The set of SimTxObject entries that have been removed, but not yet destroyed. */
      60                 :             :     std::vector<std::shared_ptr<SimTxObject>> removed;
      61                 :             :     /** Whether the graph is oversized (true = yes, false = no, std::nullopt = unknown). */
      62                 :             :     std::optional<bool> oversized;
      63                 :             :     /** The configured maximum number of transactions per cluster. */
      64                 :             :     DepGraphIndex max_cluster_count;
      65                 :             :     /** Which transactions have been modified in the graph since creation, either directly or by
      66                 :             :      *  being in a cluster which includes modifications. Only relevant for the staging graph. */
      67                 :             :     SetType modified;
      68                 :             :     /** The configured maximum total size of transactions per cluster. */
      69                 :             :     uint64_t max_cluster_size;
      70                 :             :     /** Whether the corresponding real graph is known to be optimally linearized. */
      71                 :             :     bool real_is_optimal{false};
      72                 :             : 
      73                 :             :     /** Construct a new SimTxGraph with the specified maximum cluster count and size. */
      74                 :        4814 :     explicit SimTxGraph(DepGraphIndex cluster_count, uint64_t cluster_size) :
      75                 :        4814 :         max_cluster_count(cluster_count), max_cluster_size(cluster_size) {}
      76                 :             : 
      77                 :             :     // Permit copying and moving.
      78   [ +  -  +  - ]:       11817 :     SimTxGraph(const SimTxGraph&) noexcept = default;
      79                 :             :     SimTxGraph& operator=(const SimTxGraph&) noexcept = default;
      80                 :           0 :     SimTxGraph(SimTxGraph&&) noexcept = default;
      81                 :        4684 :     SimTxGraph& operator=(SimTxGraph&&) noexcept = default;
      82                 :             : 
      83                 :             :     /** Get the connected components within this simulated transaction graph. */
      84                 :      117242 :     std::vector<SetType> GetComponents()
      85                 :             :     {
      86                 :      117242 :         auto todo = graph.Positions();
      87                 :      117242 :         std::vector<SetType> ret;
      88                 :             :         // Iterate over all connected components of the graph.
      89         [ +  + ]:     6132968 :         while (todo.Any()) {
      90                 :     2949242 :             auto component = graph.FindConnectedComponent(todo);
      91         [ +  - ]:     2949242 :             ret.push_back(component);
      92                 :     2949242 :             todo -= component;
      93                 :             :         }
      94                 :      117242 :         return ret;
      95                 :           0 :     }
      96                 :             : 
      97                 :             :     /** Check whether this graph is oversized (contains a connected component whose number of
      98                 :             :      *  transactions exceeds max_cluster_count. */
      99                 :     4507031 :     bool IsOversized()
     100                 :             :     {
     101         [ +  + ]:     4507031 :         if (!oversized.has_value()) {
     102                 :             :             // Only recompute when oversized isn't already known.
     103                 :       87213 :             oversized = false;
     104         [ +  + ]:     2407570 :             for (auto component : GetComponents()) {
     105         [ +  + ]:     2320357 :                 if (component.Count() > max_cluster_count) oversized = true;
     106                 :     2320357 :                 uint64_t component_size{0};
     107         [ +  + ]:     5741110 :                 for (auto i : component) component_size += graph.FeeRate(i).size;
     108         [ +  + ]:     2320357 :                 if (component_size > max_cluster_size) oversized = true;
     109                 :       87213 :             }
     110                 :             :         }
     111                 :     4507031 :         return *oversized;
     112                 :             :     }
     113                 :             : 
     114                 :      844621 :     void MakeModified(DepGraphIndex index)
     115                 :             :     {
     116                 :     1689242 :         modified |= graph.GetConnectedComponent(graph.Positions(), index);
     117                 :      844621 :     }
     118                 :             : 
     119                 :             :     /** Determine the number of (non-removed) transactions in the graph. */
     120   [ +  +  +  +  :     5538470 :     DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
          -  +  -  +  +  
          +  +  +  -  +  
          -  +  -  +  -  
                      + ]
     121                 :             : 
     122                 :             :     /** Get the sum of all fees/sizes in the graph. */
     123                 :       32836 :     FeePerWeight SumAll() const
     124                 :             :     {
     125                 :       32836 :         FeePerWeight ret;
     126         [ +  + ]:     1182741 :         for (auto i : graph.Positions()) {
     127                 :     1149905 :             ret += graph.FeeRate(i);
     128                 :             :         }
     129                 :       32836 :         return ret;
     130                 :             :     }
     131                 :             : 
     132                 :             :     /** Get the position where ref occurs in this simulated graph, or -1 if it does not. */
     133                 :    13645569 :     Pos Find(const TxGraph::Ref* ref) const
     134                 :             :     {
     135                 :    13645569 :         auto it = simrevmap.find(ref);
     136         [ +  + ]:    13645569 :         if (it != simrevmap.end()) return it->second;
     137                 :             :         return MISSING;
     138                 :             :     }
     139                 :             : 
     140                 :             :     /** Given a position in this simulated graph, get the corresponding SimTxObject. */
     141                 :     7919654 :     SimTxObject* GetRef(Pos pos)
     142                 :             :     {
     143         [ -  + ]:     7919654 :         assert(graph.Positions()[pos]);
     144         [ -  + ]:     7919654 :         assert(simmap[pos]);
     145                 :     7919654 :         return simmap[pos].get();
     146                 :             :     }
     147                 :             : 
     148                 :             :     /** Add a new transaction to the simulation and the specified real graph. */
     149                 :      284143 :     void AddTransaction(TxGraph& txgraph, const FeePerWeight& feerate, uint64_t txid)
     150                 :             :     {
     151         [ -  + ]:      284143 :         assert(graph.TxCount() < MAX_TRANSACTIONS);
     152                 :      284143 :         auto simpos = graph.AddTransaction(feerate);
     153                 :      284143 :         real_is_optimal = false;
     154                 :      284143 :         MakeModified(simpos);
     155         [ -  + ]:      284143 :         assert(graph.Positions()[simpos]);
     156         [ -  + ]:      284143 :         simmap[simpos] = std::make_shared<SimTxObject>(txid);
     157                 :      284143 :         txgraph.AddTransaction(*simmap[simpos], feerate);
     158                 :      284143 :         auto ptr = simmap[simpos].get();
     159                 :      284143 :         simrevmap[ptr] = simpos;
     160                 :             :         // This may invalidate our cached oversized value.
     161   [ +  +  +  + ]:      284143 :         if (oversized.has_value() && !*oversized) oversized = std::nullopt;
     162                 :      284143 :     }
     163                 :             : 
     164                 :             :     /** Add a dependency between two positions in this graph. */
     165                 :      450997 :     void AddDependency(TxGraph::Ref* parent, TxGraph::Ref* child)
     166                 :             :     {
     167                 :      450997 :         auto par_pos = Find(parent);
     168         [ +  + ]:      450997 :         if (par_pos == MISSING) return;
     169                 :      446798 :         auto chl_pos = Find(child);
     170         [ +  + ]:      446798 :         if (chl_pos == MISSING) return;
     171                 :      443833 :         graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
     172                 :      443833 :         MakeModified(par_pos);
     173                 :      443833 :         real_is_optimal = false;
     174                 :             :         // This may invalidate our cached oversized value.
     175   [ +  +  +  + ]:      443833 :         if (oversized.has_value() && !*oversized) oversized = std::nullopt;
     176                 :             :     }
     177                 :             : 
     178                 :             :     /** Modify the transaction fee of a ref, if it exists. */
     179                 :       12259 :     void SetTransactionFee(TxGraph::Ref* ref, int64_t fee)
     180                 :             :     {
     181                 :       12259 :         auto pos = Find(ref);
     182         [ +  + ]:       12259 :         if (pos == MISSING) return;
     183                 :             :         // No need to invoke MakeModified, because this equally affects main and staging.
     184                 :        8909 :         real_is_optimal = false;
     185                 :        8909 :         graph.FeeRate(pos).fee = fee;
     186                 :             :     }
     187                 :             : 
     188                 :             :     /** Remove the transaction in the specified position from the graph. */
     189                 :      103543 :     void RemoveTransaction(TxGraph::Ref* ref)
     190                 :             :     {
     191                 :      103543 :         auto pos = Find(ref);
     192         [ +  + ]:      103543 :         if (pos == MISSING) return;
     193                 :       94474 :         MakeModified(pos);
     194                 :       94474 :         real_is_optimal = false;
     195                 :       94474 :         graph.RemoveTransactions(SetType::Singleton(pos));
     196                 :       94474 :         simrevmap.erase(simmap[pos].get());
     197                 :             :         // Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
     198                 :             :         // invoked until the simulation explicitly decided to do so.
     199                 :       94474 :         removed.push_back(std::move(simmap[pos]));
     200                 :       94474 :         simmap[pos].reset();
     201                 :             :         // This may invalidate our cached oversized value.
     202   [ +  +  +  + ]:       94474 :         if (oversized.has_value() && *oversized) oversized = std::nullopt;
     203                 :             :     }
     204                 :             : 
     205                 :             :     /** Destroy the transaction from the graph, including from the removed set. This will
     206                 :             :      *  trigger TxGraph::Ref::~Ref. reset_oversize controls whether the cached oversized
     207                 :             :      *  value is cleared (destroying does not clear oversizedness in TxGraph of the main
     208                 :             :      *  graph while staging exists). */
     209                 :       29678 :     void DestroyTransaction(TxGraph::Ref* ref, bool reset_oversize)
     210                 :             :     {
     211                 :       29678 :         auto pos = Find(ref);
     212         [ +  + ]:       29678 :         if (pos == MISSING) {
     213                 :             :             // Wipe the ref, if it exists, from the removed vector. Use std::partition rather
     214                 :             :             // than std::erase because we don't care about the order of the entries that
     215                 :             :             // remain.
     216   [ +  +  -  + ]:       75627 :             auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
     217                 :        7507 :             removed.erase(remove, removed.end());
     218                 :             :         } else {
     219                 :       22171 :             MakeModified(pos);
     220                 :       22171 :             graph.RemoveTransactions(SetType::Singleton(pos));
     221                 :       22171 :             real_is_optimal = false;
     222                 :       22171 :             simrevmap.erase(simmap[pos].get());
     223                 :       22171 :             simmap[pos].reset();
     224                 :             :             // This may invalidate our cached oversized value.
     225   [ +  +  +  +  :       22171 :             if (reset_oversize && oversized.has_value() && *oversized) {
                   +  + ]
     226                 :        3462 :                 oversized = std::nullopt;
     227                 :             :             }
     228                 :             :         }
     229                 :       29678 :     }
     230                 :             : 
     231                 :             :     /** Construct the set with all positions in this graph corresponding to the specified
     232                 :             :      *  TxGraph::Refs. All of them must occur in this graph and not be removed. */
     233                 :      994547 :     SetType MakeSet(std::span<TxGraph::Ref* const> arg)
     234                 :             :     {
     235                 :      994547 :         SetType ret;
     236         [ +  + ]:     7577479 :         for (TxGraph::Ref* ptr : arg) {
     237                 :     6582932 :             auto pos = Find(ptr);
     238         [ -  + ]:     6582932 :             assert(pos != Pos(-1));
     239                 :     6582932 :             ret.Set(pos);
     240                 :             :         }
     241                 :      994547 :         return ret;
     242                 :             :     }
     243                 :             : 
     244                 :             :     /** Get the set of ancestors (desc=false) or descendants (desc=true) in this graph. */
     245                 :       82272 :     SetType GetAncDesc(TxGraph::Ref* arg, bool desc)
     246                 :             :     {
     247                 :       82272 :         auto pos = Find(arg);
     248         [ +  + ]:       82272 :         if (pos == MISSING) return {};
     249         [ +  + ]:       57966 :         return desc ? graph.Descendants(pos) : graph.Ancestors(pos);
     250                 :             :     }
     251                 :             : 
     252                 :             :     /** Given a set of Refs (given as a vector of pointers), expand the set to include all its
     253                 :             :      *  ancestors (desc=false) or all its descendants (desc=true) in this graph. */
     254                 :       41779 :     void IncludeAncDesc(std::vector<TxGraph::Ref*>& arg, bool desc)
     255                 :             :     {
     256                 :       41779 :         std::vector<TxGraph::Ref*> ret;
     257         [ +  + ]:       91425 :         for (auto ptr : arg) {
     258                 :       49646 :             auto simpos = Find(ptr);
     259         [ +  + ]:       49646 :             if (simpos != MISSING) {
     260   [ +  +  +  + ]:       89296 :                 for (auto i : desc ? graph.Descendants(simpos) : graph.Ancestors(simpos)) {
     261         [ +  - ]:       56744 :                     ret.push_back(simmap[i].get());
     262                 :             :                 }
     263                 :             :             } else {
     264         [ +  - ]:       17094 :                 ret.push_back(ptr);
     265                 :             :             }
     266                 :             :         }
     267                 :             :         // Construct deduplicated version in input (do not use std::sort/std::unique for
     268                 :             :         // deduplication as it'd rely on non-deterministic pointer comparison).
     269         [ +  - ]:       41779 :         arg.clear();
     270         [ +  + ]:      115617 :         for (auto ptr : ret) {
     271         [ +  + ]:       73838 :             if (std::find(arg.begin(), arg.end(), ptr) == arg.end()) {
     272         [ +  - ]:       56611 :                 arg.push_back(ptr);
     273                 :             :             }
     274                 :             :         }
     275                 :       41779 :     }
     276                 :             : 
     277                 :             : 
     278                 :             :     /** Verify that set contains transactions from every oversized cluster, and nothing from
     279                 :             :      *  non-oversized ones. */
     280                 :       10158 :     bool MatchesOversizedClusters(const SetType& set)
     281                 :             :     {
     282   [ +  -  +  - ]:       20316 :         if (set.Any() && !IsOversized()) return false;
     283                 :             : 
     284                 :       10158 :         auto todo = graph.Positions();
     285         [ +  - ]:       20316 :         if (!set.IsSubsetOf(todo)) return false;
     286                 :             : 
     287                 :             :         // Walk all clusters, and make sure all of set doesn't come from non-oversized clusters
     288         [ +  + ]:      249052 :         while (todo.Any()) {
     289                 :      114368 :             auto component = graph.FindConnectedComponent(todo);
     290                 :             :             // Determine whether component is oversized, due to either the size or count limit.
     291                 :      114368 :             bool is_oversized = component.Count() > max_cluster_count;
     292                 :      114368 :             uint64_t component_size{0};
     293         [ +  + ]:      609821 :             for (auto i : component) component_size += graph.FeeRate(i).size;
     294                 :      114368 :             is_oversized |= component_size > max_cluster_size;
     295                 :             :             // Check whether overlap with set matches is_oversized.
     296         [ +  - ]:      228736 :             if (is_oversized != set.Overlaps(component)) return false;
     297                 :      114368 :             todo -= component;
     298                 :             :         }
     299                 :             :         return true;
     300                 :             :     }
     301                 :             : };
     302                 :             : 
     303                 :             : } // namespace
     304                 :             : 
     305         [ +  - ]:        5268 : FUZZ_TARGET(txgraph)
     306                 :             : {
     307                 :             :     // This is a big simulation test for TxGraph, which performs a fuzz-derived sequence of valid
     308                 :             :     // operations on a TxGraph instance, as well as on a simpler (mostly) reimplementation (see
     309                 :             :     // SimTxGraph above), comparing the outcome of functions that return a result, and finally
     310                 :             :     // performing a full comparison between the two.
     311                 :             : 
     312                 :        4814 :     SeedRandomStateForTest(SeedRand::ZEROS);
     313                 :        4814 :     FuzzedDataProvider provider(buffer.data(), buffer.size());
     314                 :             : 
     315                 :             :     /** Internal test RNG, used only for decisions which would require significant amount of data
     316                 :             :      *  to be read from the provider, without realistically impacting test sensitivity, and for
     317                 :             :      *  specialized test cases that are hard to perform more generically. */
     318                 :        4814 :     InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
     319                 :             : 
     320                 :             :     /** Variable used whenever an empty SimTxObject is needed. */
     321                 :        4814 :     SimTxObject empty_ref;
     322                 :             : 
     323                 :             :     /** The maximum number of transactions per (non-oversized) cluster we will use in this
     324                 :             :      *  simulation. */
     325                 :        4814 :     auto max_cluster_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
     326                 :             :     /** The maximum total size of transactions in a (non-oversized) cluster. */
     327                 :        4814 :     auto max_cluster_size = provider.ConsumeIntegralInRange<uint64_t>(1, 0x3fffff * MAX_CLUSTER_COUNT_LIMIT);
     328                 :             :     /** The number of iterations to consider a cluster acceptably linearized. */
     329                 :        4814 :     auto acceptable_iters = provider.ConsumeIntegralInRange<uint64_t>(0, 10000);
     330                 :             : 
     331                 :             :     /** The set of uint64_t "txid"s that have been assigned before. */
     332                 :        4814 :     std::set<uint64_t> assigned_txids;
     333                 :             : 
     334                 :             :     // Construct a real graph, and a vector of simulated graphs (main, and possibly staging).
     335                 :     3078085 :     auto fallback_order = [&](const TxGraph::Ref& a, const TxGraph::Ref& b) noexcept {
     336                 :     3073271 :         uint64_t txid_a = static_cast<const SimTxObject&>(a).m_txid;
     337                 :     3073271 :         uint64_t txid_b = static_cast<const SimTxObject&>(b).m_txid;
     338         [ -  + ]:     3073271 :         assert(assigned_txids.contains(txid_a));
     339         [ -  + ]:     3073271 :         assert(assigned_txids.contains(txid_b));
     340   [ +  -  +  + ]:     3073271 :         return txid_a <=> txid_b;
     341                 :        4814 :     };
     342                 :        4814 :     auto real = MakeTxGraph(
     343                 :             :         /*max_cluster_count=*/max_cluster_count,
     344                 :             :         /*max_cluster_size=*/max_cluster_size,
     345                 :             :         /*acceptable_iters=*/acceptable_iters,
     346                 :        4814 :         /*fallback_order=*/fallback_order);
     347                 :             : 
     348                 :        4814 :     std::vector<SimTxGraph> sims;
     349         [ +  - ]:        4814 :     sims.reserve(2);
     350         [ +  - ]:        4814 :     sims.emplace_back(max_cluster_count, max_cluster_size);
     351                 :             : 
     352                 :             :     /** Struct encapsulating information about a BlockBuilder that's currently live. */
     353                 :       13719 :     struct BlockBuilderData
     354                 :             :     {
     355                 :             :         /** BlockBuilder object from real. */
     356                 :             :         std::unique_ptr<TxGraph::BlockBuilder> builder;
     357                 :             :         /** The set of transactions marked as included in *builder. */
     358                 :             :         SimTxGraph::SetType included;
     359                 :             :         /** The set of transactions marked as included or skipped in *builder. */
     360                 :             :         SimTxGraph::SetType done;
     361                 :             :         /** The last chunk feerate returned by *builder. IsEmpty() if none yet. */
     362                 :             :         FeePerWeight last_feerate;
     363                 :             : 
     364                 :        4659 :         BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
     365                 :             :     };
     366                 :             : 
     367                 :             :     /** Currently active block builders. */
     368                 :        4814 :     std::vector<BlockBuilderData> block_builders;
     369                 :             : 
     370                 :             :     /** Function to pick any SimTxObject (for either sim in sims: from sim.simmap or sim.removed, or the
     371                 :             :      *  empty one). */
     372                 :      667561 :     auto pick_fn = [&]() noexcept -> SimTxObject* {
     373         [ -  + ]:      662747 :         size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
     374                 :             :         /** The number of possible choices. */
     375         [ -  + ]:      662747 :         size_t choices = tx_count[0] + sims[0].removed.size() + 1;
     376   [ -  +  +  + ]:      662747 :         if (sims.size() == 2) {
     377         [ -  + ]:      183576 :             tx_count[1] = sims[1].GetTransactionCount();
     378         [ -  + ]:      183576 :             choices += tx_count[1] + sims[1].removed.size();
     379                 :             :         }
     380                 :             :         /** Pick one of them. */
     381                 :      662747 :         auto choice = provider.ConsumeIntegralInRange<size_t>(0, choices - 1);
     382                 :             :         // Consider both main and (if it exists) staging.
     383   [ -  +  +  + ]:      825207 :         for (size_t level = 0; level < sims.size(); ++level) {
     384         [ +  + ]:      744990 :             auto& sim = sims[level];
     385         [ +  + ]:      744990 :             if (choice < tx_count[level]) {
     386                 :             :                 // Return from graph.
     387         [ +  - ]:     5438085 :                 for (auto i : sim.graph.Positions()) {
     388         [ +  + ]:     5438085 :                     if (choice == 0) return sim.GetRef(i);
     389                 :     4905385 :                     --choice;
     390                 :             :                 }
     391                 :           0 :                 assert(false);
     392                 :             :             } else {
     393                 :      212290 :                 choice -= tx_count[level];
     394                 :             :             }
     395   [ -  +  +  + ]:      212290 :             if (choice < sim.removed.size()) {
     396                 :             :                 // Return from removed.
     397                 :       49830 :                 return sim.removed[choice].get();
     398                 :             :             } else {
     399                 :      162460 :                 choice -= sim.removed.size();
     400                 :             :             }
     401                 :             :         }
     402                 :             :         // Return empty.
     403         [ -  + ]:       80217 :         assert(choice == 0);
     404                 :       80217 :         return &empty_ref;
     405                 :        4814 :     };
     406                 :             : 
     407                 :             :     /** Function to construct the correct fee-size diagram a real graph has based on its graph
     408                 :             :      *  order (as reported by GetCluster(), so it works for both main and staging). */
     409                 :       10623 :     auto get_diagram_fn = [&](TxGraph::Level level_select) -> std::vector<FeeFrac> {
     410   [ +  +  -  + ]:        5809 :         int level = level_select == TxGraph::Level::MAIN ? 0 : sims.size() - 1;
     411                 :        5809 :         auto& sim = sims[level];
     412                 :             :         // For every transaction in the graph, request its cluster, and throw them into a set.
     413                 :        5809 :         std::set<std::vector<TxGraph::Ref*>> clusters;
     414         [ +  + ]:      238152 :         for (auto i : sim.graph.Positions()) {
     415                 :      232343 :             auto ref = sim.GetRef(i);
     416         [ +  - ]:      464686 :             clusters.insert(real->GetCluster(*ref, level_select));
     417                 :             :         }
     418                 :             :         // Compute the chunkings of each (deduplicated) cluster.
     419                 :        5809 :         size_t num_tx{0};
     420                 :        5809 :         std::vector<FeeFrac> chunk_feerates;
     421         [ +  + ]:      157500 :         for (const auto& cluster : clusters) {
     422         [ -  + ]:      151691 :             num_tx += cluster.size();
     423                 :      151691 :             std::vector<SimTxGraph::Pos> linearization;
     424         [ +  - ]:      151691 :             linearization.reserve(cluster.size());
     425   [ +  -  +  + ]:      384034 :             for (auto refptr : cluster) linearization.push_back(sim.Find(refptr));
     426   [ -  +  +  + ]:      348943 :             for (const FeeFrac& chunk_feerate : ChunkLinearization(sim.graph, linearization)) {
     427         [ +  - ]:      197252 :                 chunk_feerates.push_back(chunk_feerate);
     428                 :      151691 :             }
     429                 :      151691 :         }
     430                 :             :         // Verify the number of transactions after deduplicating clusters. This implicitly verifies
     431                 :             :         // that GetCluster on each element of a cluster reports the cluster transactions in the same
     432                 :             :         // order.
     433   [ -  +  -  + ]:       11618 :         assert(num_tx == sim.GetTransactionCount());
     434                 :             :         // Sort by feerate only, since violating topological constraints within same-feerate
     435                 :             :         // chunks won't affect diagram comparisons.
     436                 :        5809 :         std::sort(chunk_feerates.begin(), chunk_feerates.end(), std::greater{});
     437                 :        5809 :         return chunk_feerates;
     438                 :       10623 :     };
     439                 :             : 
     440   [ +  +  +  + ]:      697576 :     LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
     441                 :             :         // Read a one-byte command.
     442                 :      692762 :         int command = provider.ConsumeIntegral<uint8_t>();
     443                 :      692762 :         int orig_command = command;
     444                 :             : 
     445                 :             :         // Treat the lowest bit of a command as a flag (which selects a variant of some of the
     446                 :             :         // operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
     447                 :             :         // the rest of the bits in command.
     448                 :      692762 :         bool alt = command & 1;
     449                 :      692762 :         TxGraph::Level level_select = (command & 2) ? TxGraph::Level::MAIN : TxGraph::Level::TOP;
     450                 :      692762 :         command >>= 2;
     451                 :             : 
     452                 :             :         /** Use the bottom 2 bits of command to select an entry in the block_builders vector (if
     453                 :             :          *  any). These use the same bits as alt/level_select, so don't use those in actions below
     454                 :             :          *  where builder_idx is used as well. */
     455   [ -  +  +  + ]:      692762 :         int builder_idx = block_builders.empty() ? -1 : int((orig_command & 3) % block_builders.size());
     456                 :             : 
     457                 :             :         // Provide convenient aliases for the top simulated graph (main, or staging if it exists),
     458                 :             :         // one for the simulated graph selected based on level_select (for operations that can operate
     459                 :             :         // on both graphs), and one that always refers to the main graph.
     460                 :      692762 :         auto& top_sim = sims.back();
     461         [ +  + ]:      692762 :         auto& sel_sim = level_select == TxGraph::Level::MAIN ? sims[0] : top_sim;
     462                 :      692762 :         auto& main_sim = sims[0];
     463                 :             : 
     464                 :             :         // Keep decrementing command for each applicable operation, until one is hit. Multiple
     465                 :             :         // iterations may be necessary.
     466                 :     1012156 :         while (true) {
     467   [ +  +  +  +  :     1166692 :             if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
             +  +  +  + ]
     468                 :             :                 // AddTransaction.
     469                 :      284143 :                 int64_t fee;
     470                 :      284143 :                 int32_t size;
     471         [ +  + ]:      284143 :                 if (alt) {
     472                 :             :                     // If alt is true, pick fee and size from the entire range.
     473                 :        8430 :                     fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     474                 :        8430 :                     size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
     475                 :             :                 } else {
     476                 :             :                     // Otherwise, use smaller range which consume fewer fuzz input bytes, as just
     477                 :             :                     // these are likely sufficient to trigger all interesting code paths already.
     478                 :      275713 :                     fee = provider.ConsumeIntegral<uint8_t>();
     479                 :      275713 :                     size = provider.ConsumeIntegralInRange<uint32_t>(1, 0xff);
     480                 :             :                 }
     481                 :      284143 :                 FeePerWeight feerate{fee, size};
     482                 :             :                 // Pick a novel txid (and not 0, which is reserved for empty_ref).
     483                 :      284143 :                 uint64_t txid;
     484                 :      284143 :                 do {
     485                 :      284143 :                     txid = rng.rand64();
     486   [ +  -  -  +  :      284143 :                 } while (txid == 0 || assigned_txids.contains(txid));
                   -  + ]
     487         [ +  - ]:      284143 :                 assigned_txids.insert(txid);
     488                 :             :                 // Create the transaction in the simulation and the real graph.
     489         [ +  - ]:      284143 :                 top_sim.AddTransaction(*real, feerate, txid);
     490                 :             :                 break;
     491   [ +  +  +  +  :      866040 :             } else if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
          -  +  +  +  +  
                      + ]
     492                 :             :                 // AddDependency.
     493                 :       43680 :                 auto par = pick_fn();
     494                 :       43680 :                 auto chl = pick_fn();
     495                 :       43680 :                 auto pos_par = top_sim.Find(par);
     496                 :       43680 :                 auto pos_chl = top_sim.Find(chl);
     497         [ +  + ]:       43680 :                 if (pos_par != SimTxGraph::MISSING && pos_chl != SimTxGraph::MISSING) {
     498                 :             :                     // Determine if adding this would introduce a cycle (not allowed by TxGraph),
     499                 :             :                     // and if so, skip.
     500         [ +  + ]:       36516 :                     if (top_sim.graph.Ancestors(pos_par)[pos_chl]) break;
     501                 :             :                 }
     502                 :       33965 :                 top_sim.AddDependency(par, chl);
     503                 :       33965 :                 top_sim.real_is_optimal = false;
     504                 :       33965 :                 real->AddDependency(*par, *chl);
     505                 :       33965 :                 break;
     506   [ +  +  +  +  :      820126 :             } else if ((block_builders.empty() || sims.size() > 1) && top_sim.removed.size() < 100 && command-- == 0) {
          -  +  +  +  +  
                      + ]
     507                 :             :                 // RemoveTransaction. Either all its ancestors or all its descendants are also
     508                 :             :                 // removed (if any), to make sure TxGraph's reordering of removals and dependencies
     509                 :             :                 // has no effect.
     510                 :       16377 :                 std::vector<TxGraph::Ref*> to_remove;
     511         [ +  - ]:       16377 :                 to_remove.push_back(pick_fn());
     512         [ +  - ]:       16377 :                 top_sim.IncludeAncDesc(to_remove, alt);
     513                 :             :                 // The order in which these ancestors/descendants are removed should not matter;
     514                 :             :                 // randomly shuffle them.
     515                 :       16377 :                 std::shuffle(to_remove.begin(), to_remove.end(), rng);
     516         [ +  + ]:       35596 :                 for (TxGraph::Ref* ptr : to_remove) {
     517                 :       19219 :                     real->RemoveTransaction(*ptr);
     518         [ +  - ]:       19219 :                     top_sim.RemoveTransaction(ptr);
     519                 :             :                 }
     520                 :       16377 :                 break;
     521   [ -  +  +  +  :      684333 :             } else if (sel_sim.removed.size() > 0 && command-- == 0) {
                   +  + ]
     522                 :             :                 // ~Ref (of an already-removed transaction). Destroying a TxGraph::Ref has an
     523                 :             :                 // observable effect on the TxGraph it refers to, so this simulation permits doing
     524                 :             :                 // so separately from other actions on TxGraph.
     525                 :             : 
     526                 :             :                 // Pick a Ref of sel_sim.removed to destroy. Note that the same Ref may still occur
     527                 :             :                 // in the other graph, and thus not actually trigger ~Ref yet (which is exactly
     528                 :             :                 // what we want, as destroying Refs is only allowed when it does not refer to an
     529                 :             :                 // existing transaction in either graph).
     530                 :        6841 :                 auto removed_pos = provider.ConsumeIntegralInRange<size_t>(0, sel_sim.removed.size() - 1);
     531   [ -  +  +  + ]:        6841 :                 if (removed_pos != sel_sim.removed.size() - 1) {
     532                 :        4501 :                     std::swap(sel_sim.removed[removed_pos], sel_sim.removed.back());
     533                 :             :                 }
     534                 :        6841 :                 sel_sim.removed.pop_back();
     535                 :        6841 :                 break;
     536   [ +  +  +  + ]:      661115 :             } else if (block_builders.empty() && command-- == 0) {
     537                 :             :                 // ~Ref (of any transaction).
     538                 :       17068 :                 std::vector<TxGraph::Ref*> to_destroy;
     539         [ +  - ]:       17068 :                 to_destroy.push_back(pick_fn());
     540                 :       18400 :                 while (true) {
     541                 :             :                     // Keep adding either the ancestors or descendants the already picked
     542                 :             :                     // transactions have in both graphs (main and staging) combined. Destroying
     543                 :             :                     // will trigger deletions in both, so to have consistent TxGraph behavior, the
     544                 :             :                     // set must be closed under ancestors, or descendants, in both graphs.
     545         [ -  + ]:       18400 :                     auto old_size = to_destroy.size();
     546   [ +  -  +  + ]:       43802 :                     for (auto& sim : sims) sim.IncludeAncDesc(to_destroy, alt);
     547   [ -  +  +  + ]:       18400 :                     if (to_destroy.size() == old_size) break;
     548                 :             :                 }
     549                 :             :                 // The order in which these ancestors/descendants are destroyed should not matter;
     550                 :             :                 // randomly shuffle them.
     551                 :       17068 :                 std::shuffle(to_destroy.begin(), to_destroy.end(), rng);
     552         [ +  + ]:       38259 :                 for (TxGraph::Ref* ptr : to_destroy) {
     553   [ -  +  +  + ]:       50869 :                     for (size_t level = 0; level < sims.size(); ++level) {
     554                 :       29678 :                         sims[level].DestroyTransaction(ptr, level == sims.size() - 1);
     555                 :             :                     }
     556                 :             :                 }
     557                 :       17068 :                 break;
     558   [ +  +  +  + ]:      661115 :             } else if (block_builders.empty() && command-- == 0) {
     559                 :             :                 // SetTransactionFee.
     560                 :       10021 :                 int64_t fee;
     561         [ +  + ]:       10021 :                 if (alt) {
     562                 :        4515 :                     fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     563                 :             :                 } else {
     564                 :        5506 :                     fee = provider.ConsumeIntegral<uint8_t>();
     565                 :             :                 }
     566                 :       10021 :                 auto ref = pick_fn();
     567                 :       10021 :                 real->SetTransactionFee(*ref, fee);
     568         [ +  + ]:       22280 :                 for (auto& sim : sims) {
     569                 :       12259 :                     sim.SetTransactionFee(ref, fee);
     570                 :             :                 }
     571                 :             :                 break;
     572         [ +  + ]:      634026 :             } else if (command-- == 0) {
     573                 :             :                 // GetTransactionCount.
     574   [ -  +  -  + ]:       30581 :                 assert(real->GetTransactionCount(level_select) == sel_sim.GetTransactionCount());
     575                 :             :                 break;
     576         [ +  + ]:      603445 :             } else if (command-- == 0) {
     577                 :             :                 // Exists.
     578                 :       11601 :                 auto ref = pick_fn();
     579                 :       11601 :                 bool exists = real->Exists(*ref, level_select);
     580                 :       11601 :                 bool should_exist = sel_sim.Find(ref) != SimTxGraph::MISSING;
     581         [ -  + ]:       11601 :                 assert(exists == should_exist);
     582                 :             :                 break;
     583         [ +  + ]:      591844 :             } else if (command-- == 0) {
     584                 :             :                 // IsOversized.
     585   [ +  -  -  + ]:       16194 :                 assert(sel_sim.IsOversized() == real->IsOversized(level_select));
     586                 :             :                 break;
     587         [ +  + ]:      575650 :             } else if (command-- == 0) {
     588                 :             :                 // GetIndividualFeerate.
     589                 :        9816 :                 auto ref = pick_fn();
     590                 :        9816 :                 auto feerate = real->GetIndividualFeerate(*ref);
     591                 :        9816 :                 bool found{false};
     592         [ +  + ]:       22130 :                 for (auto& sim : sims) {
     593                 :       12314 :                     auto simpos = sim.Find(ref);
     594         [ +  + ]:       12314 :                     if (simpos != SimTxGraph::MISSING) {
     595                 :        8253 :                         found = true;
     596         [ +  - ]:       20567 :                         assert(feerate == sim.graph.FeeRate(simpos));
     597                 :             :                     }
     598                 :             :                 }
     599   [ +  +  -  + ]:        9816 :                 if (!found) assert(feerate.IsEmpty());
     600                 :             :                 break;
     601   [ +  -  +  +  :      565834 :             } else if (!main_sim.IsOversized() && command-- == 0) {
                   +  + ]
     602                 :             :                 // GetMainChunkFeerate.
     603                 :        8644 :                 auto ref = pick_fn();
     604                 :        8644 :                 auto feerate = real->GetMainChunkFeerate(*ref);
     605                 :        8644 :                 auto simpos = main_sim.Find(ref);
     606         [ +  + ]:        8644 :                 if (simpos == SimTxGraph::MISSING) {
     607         [ -  + ]:        2474 :                     assert(feerate.IsEmpty());
     608                 :             :                 } else {
     609                 :             :                     // Just do some quick checks that the reported value is in range. A full
     610                 :             :                     // recomputation of expected chunk feerates is done at the end.
     611         [ -  + ]:        6170 :                     assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
     612         [ -  + ]:        6170 :                     assert(feerate.size <= main_sim.SumAll().size);
     613                 :             :                 }
     614                 :             :                 break;
     615   [ +  -  +  +  :      557190 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
                   +  + ]
     616                 :             :                 // GetAncestors/GetDescendants.
     617                 :       13904 :                 auto ref = pick_fn();
     618         [ +  + ]:       13904 :                 auto result = alt ? real->GetDescendants(*ref, level_select)
     619                 :       13904 :                                   : real->GetAncestors(*ref, level_select);
     620   [ -  +  -  + ]:       13904 :                 assert(result.size() <= max_cluster_count);
     621                 :       13904 :                 auto result_set = sel_sim.MakeSet(result);
     622   [ -  +  -  + ]:       13904 :                 assert(result.size() == result_set.Count());
     623                 :       13904 :                 auto expect_set = sel_sim.GetAncDesc(ref, alt);
     624         [ -  + ]:       13904 :                 assert(result_set == expect_set);
     625                 :       13904 :                 break;
     626   [ +  -  +  +  :      557190 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
                   +  + ]
     627                 :             :                 // GetAncestorsUnion/GetDescendantsUnion.
     628                 :        9967 :                 std::vector<TxGraph::Ref*> refs;
     629                 :             :                 // Gather a list of up to 15 Ref pointers.
     630                 :        9967 :                 auto count = provider.ConsumeIntegralInRange<size_t>(0, 15);
     631         [ +  - ]:        9967 :                 refs.resize(count);
     632         [ +  + ]:       78335 :                 for (size_t i = 0; i < count; ++i) {
     633                 :       68368 :                     refs[i] = pick_fn();
     634                 :             :                 }
     635                 :             :                 // Their order should not matter, shuffle them.
     636                 :        9967 :                 std::shuffle(refs.begin(), refs.end(), rng);
     637                 :             :                 // Invoke the real function, and convert to SimPos set.
     638         [ +  + ]:        9967 :                 auto result = alt ? real->GetDescendantsUnion(refs, level_select)
     639   [ -  +  -  + ]:        9967 :                                   : real->GetAncestorsUnion(refs, level_select);
     640         [ -  + ]:        9967 :                 auto result_set = sel_sim.MakeSet(result);
     641   [ -  +  -  + ]:        9967 :                 assert(result.size() == result_set.Count());
     642                 :             :                 // Compute the expected result.
     643                 :        9967 :                 SimTxGraph::SetType expect_set;
     644         [ +  + ]:       78335 :                 for (TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
     645                 :             :                 // Compare.
     646         [ -  + ]:        9967 :                 assert(result_set == expect_set);
     647                 :        9967 :                 break;
     648   [ +  -  +  +  :      543286 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
                   +  + ]
     649                 :             :                 // GetCluster.
     650                 :       16877 :                 auto ref = pick_fn();
     651                 :       16877 :                 auto result = real->GetCluster(*ref, level_select);
     652                 :             :                 // Check cluster count limit.
     653   [ -  +  -  + ]:       16877 :                 assert(result.size() <= max_cluster_count);
     654                 :             :                 // Require the result to be topologically valid and not contain duplicates.
     655                 :       16877 :                 auto left = sel_sim.graph.Positions();
     656                 :       16877 :                 uint64_t total_size{0};
     657         [ +  + ]:       69241 :                 for (auto refptr : result) {
     658                 :       52364 :                     auto simpos = sel_sim.Find(refptr);
     659         [ -  + ]:       52364 :                     total_size += sel_sim.graph.FeeRate(simpos).size;
     660         [ -  + ]:       52364 :                     assert(simpos != SimTxGraph::MISSING);
     661         [ -  + ]:       52364 :                     assert(left[simpos]);
     662                 :       52364 :                     left.Reset(simpos);
     663         [ -  + ]:      104728 :                     assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
     664                 :             :                 }
     665                 :             :                 // Check cluster size limit.
     666         [ -  + ]:       16877 :                 assert(total_size <= max_cluster_size);
     667                 :             :                 // Require the set to be connected.
     668         [ -  + ]:       16877 :                 auto result_set = sel_sim.MakeSet(result);
     669         [ -  + ]:       16877 :                 assert(sel_sim.graph.IsConnected(result_set));
     670                 :             :                 // If ref exists, the result must contain it. If not, it must be empty.
     671                 :       16877 :                 auto simpos = sel_sim.Find(ref);
     672         [ +  + ]:       16877 :                 if (simpos != SimTxGraph::MISSING) {
     673         [ -  + ]:        9574 :                     assert(result_set[simpos]);
     674                 :             :                 } else {
     675         [ -  + ]:        7303 :                     assert(result_set.None());
     676                 :             :                 }
     677                 :             :                 // Require the set not to have ancestors or descendants outside of it.
     678         [ +  + ]:       69241 :                 for (auto i : result_set) {
     679         [ -  + ]:      104728 :                     assert(sel_sim.graph.Ancestors(i).IsSubsetOf(result_set));
     680         [ -  + ]:       52364 :                     assert(sel_sim.graph.Descendants(i).IsSubsetOf(result_set));
     681                 :             :                 }
     682                 :       16877 :                 break;
     683         [ +  + ]:      533319 :             } else if (command-- == 0) {
     684                 :             :                 // HaveStaging.
     685   [ -  +  -  + ]:       16352 :                 assert((sims.size() == 2) == real->HaveStaging());
     686                 :             :                 break;
     687   [ -  +  +  +  :      500090 :             } else if (sims.size() < 2 && command-- == 0) {
                   +  + ]
     688                 :             :                 // StartStaging.
     689         [ +  - ]:       11817 :                 sims.emplace_back(sims.back());
     690                 :       11817 :                 sims.back().modified = SimTxGraph::SetType{};
     691                 :       11817 :                 real->StartStaging();
     692                 :       11817 :                 break;
     693   [ +  +  +  +  :      488273 :             } else if (block_builders.empty() && sims.size() > 1 && command-- == 0) {
                   +  + ]
     694                 :             :                 // CommitStaging.
     695                 :        4684 :                 real->CommitStaging();
     696                 :             :                 // Resulting main level is only guaranteed to be optimal if all levels are
     697         [ +  + ]:       10062 :                 const bool main_optimal = std::all_of(sims.cbegin(), sims.cend(), [](const auto &sim) { return sim.real_is_optimal; });
     698                 :        4684 :                 sims.erase(sims.begin());
     699                 :        4684 :                 sims.front().real_is_optimal = main_optimal;
     700                 :        4684 :                 break;
     701   [ +  +  +  + ]:      483589 :             } else if (sims.size() > 1 && command-- == 0) {
     702                 :             :                 // AbortStaging.
     703                 :        5009 :                 real->AbortStaging();
     704                 :        5009 :                 sims.pop_back();
     705                 :             :                 // Reset the cached oversized value (if TxGraph::Ref destructions triggered
     706                 :             :                 // removals of main transactions while staging was active, then aborting will
     707                 :             :                 // cause it to be re-evaluated in TxGraph).
     708         [ +  - ]:      697771 :                 sims.back().oversized = std::nullopt;
     709                 :             :                 break;
     710   [ +  -  +  +  :      478580 :             } else if (!main_sim.IsOversized() && command-- == 0) {
                   +  + ]
     711                 :             :                 // CompareMainOrder.
     712                 :        9206 :                 auto ref_a = pick_fn();
     713                 :        9206 :                 auto ref_b = pick_fn();
     714                 :        9206 :                 auto sim_a = main_sim.Find(ref_a);
     715                 :        9206 :                 auto sim_b = main_sim.Find(ref_b);
     716                 :             :                 // Both transactions must exist in the main graph.
     717         [ +  + ]:        9206 :                 if (sim_a == SimTxGraph::MISSING || sim_b == SimTxGraph::MISSING) break;
     718                 :        5469 :                 auto cmp = real->CompareMainOrder(*ref_a, *ref_b);
     719                 :             :                 // Distinct transactions have distinct places.
     720   [ +  +  -  + ]:        5469 :                 if (sim_a != sim_b) assert(cmp != 0);
     721                 :             :                 // Ancestors go before descendants.
     722   [ +  +  -  + ]:        5469 :                 if (main_sim.graph.Ancestors(sim_a)[sim_b]) assert(cmp >= 0);
     723   [ +  +  -  + ]:        5469 :                 if (main_sim.graph.Descendants(sim_a)[sim_b]) assert(cmp <= 0);
     724                 :             :                 // Do not verify consistency with chunk feerates, as we cannot easily determine
     725                 :             :                 // these here without making more calls to real, which could affect its internal
     726                 :             :                 // state. A full comparison is done at the end.
     727                 :             :                 break;
     728   [ +  -  +  +  :      469374 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
                   +  + ]
     729                 :             :                 // CountDistinctClusters.
     730                 :       10934 :                 std::vector<TxGraph::Ref*> refs;
     731                 :             :                 // Gather a list of up to 15 (or up to 255) Ref pointers.
     732         [ +  + ]:       18638 :                 auto count = provider.ConsumeIntegralInRange<size_t>(0, alt ? 255 : 15);
     733         [ +  - ]:       10934 :                 refs.resize(count);
     734         [ +  + ]:      395233 :                 for (size_t i = 0; i < count; ++i) {
     735                 :      384299 :                     refs[i] = pick_fn();
     736                 :             :                 }
     737                 :             :                 // Their order should not matter, shuffle them.
     738                 :       10934 :                 std::shuffle(refs.begin(), refs.end(), rng);
     739                 :             :                 // Invoke the real function.
     740         [ -  + ]:       10934 :                 auto result = real->CountDistinctClusters(refs, level_select);
     741                 :             :                 // Build a set with representatives of the clusters the Refs occur in the
     742                 :             :                 // simulated graph. For each, remember the lowest-index transaction SimPos in the
     743                 :             :                 // cluster.
     744                 :       10934 :                 SimTxGraph::SetType sim_reps;
     745         [ +  + ]:      395233 :                 for (auto ref : refs) {
     746                 :             :                     // Skip Refs that do not occur in the simulated graph.
     747                 :      384299 :                     auto simpos = sel_sim.Find(ref);
     748         [ +  + ]:      384299 :                     if (simpos == SimTxGraph::MISSING) continue;
     749                 :             :                     // Find the component that includes ref.
     750                 :      302269 :                     auto component = sel_sim.graph.GetConnectedComponent(sel_sim.graph.Positions(), simpos);
     751                 :             :                     // Remember the lowest-index SimPos in component, as a representative for it.
     752         [ -  + ]:      604538 :                     assert(component.Any());
     753                 :      302269 :                     sim_reps.Set(component.First());
     754                 :             :                 }
     755                 :             :                 // Compare the number of deduplicated representatives with the value returned by
     756                 :             :                 // the real function.
     757         [ -  + ]:       10934 :                 assert(result == sim_reps.Count());
     758                 :       10934 :                 break;
     759         [ +  + ]:      469374 :             } else if (command-- == 0) {
     760                 :             :                 // DoWork.
     761         [ +  + ]:       43976 :                 uint64_t iters = provider.ConsumeIntegralInRange<uint64_t>(0, alt ? 10000 : 255);
     762                 :       25938 :                 bool ret = real->DoWork(iters);
     763                 :       25938 :                 uint64_t iters_for_optimal{0};
     764   [ -  +  +  + ]:       63900 :                 for (unsigned level = 0; level < sims.size(); ++level) {
     765                 :             :                     // DoWork() will not optimize oversized levels, or the main level if a builder
     766                 :             :                     // is present. Note that this impacts the DoWork() return value, as true means
     767                 :             :                     // that non-optimal clusters may remain within such oversized or builder-having
     768                 :             :                     // levels.
     769   [ +  -  +  + ]:       37962 :                     if (sims[level].IsOversized()) continue;
     770   [ +  +  +  + ]:       27192 :                     if (level == 0 && !block_builders.empty()) continue;
     771                 :             :                     // If neither of the two above conditions holds, and DoWork() returned true,
     772                 :             :                     // then the level is optimal.
     773         [ +  + ]:       21193 :                     if (ret) {
     774                 :       16171 :                         sims[level].real_is_optimal = true;
     775                 :             :                     }
     776                 :             :                     // Compute how many iterations would be needed to make everything optimal.
     777   [ +  -  +  + ]:      379752 :                     for (auto component : sims[level].GetComponents()) {
     778                 :      358559 :                         auto iters_opt_this_cluster = MaxOptimalLinearizationIters(component.Count());
     779         [ +  + ]:      358559 :                         if (iters_opt_this_cluster > acceptable_iters) {
     780                 :             :                             // If the number of iterations required to linearize this cluster
     781                 :             :                             // optimally exceeds acceptable_iters, DoWork() may process it in two
     782                 :             :                             // stages: once to acceptable, and once to optimal.
     783                 :       13091 :                             iters_for_optimal += iters_opt_this_cluster + acceptable_iters;
     784                 :             :                         } else {
     785                 :      345468 :                             iters_for_optimal += iters_opt_this_cluster;
     786                 :             :                         }
     787                 :       21193 :                     }
     788                 :             :                 }
     789         [ +  + ]:       25938 :                 if (!ret) {
     790                 :             :                     // DoWork can only have more work left if the requested number of iterations
     791                 :             :                     // was insufficient to linearize everything optimally within the levels it is
     792                 :             :                     // allowed to touch.
     793         [ -  + ]:        3483 :                     assert(iters <= iters_for_optimal);
     794                 :             :                 }
     795                 :             :                 break;
     796   [ -  +  +  +  :      432502 :             } else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) {
          +  -  +  +  +  
             -  +  +  +  
                      + ]
     797                 :             :                 // GetMainStagingDiagrams()
     798                 :       13333 :                 auto [real_main_diagram, real_staged_diagram] = real->GetMainStagingDiagrams();
     799                 :       13333 :                 auto real_sum_main = std::accumulate(real_main_diagram.begin(), real_main_diagram.end(), FeeFrac{});
     800                 :       13333 :                 auto real_sum_staged = std::accumulate(real_staged_diagram.begin(), real_staged_diagram.end(), FeeFrac{});
     801                 :       13333 :                 auto real_gain = real_sum_staged - real_sum_main;
     802         [ +  - ]:       13333 :                 auto sim_gain = sims[1].SumAll() - sims[0].SumAll();
     803                 :             :                 // Just check that the total fee gained/lost and size gained/lost according to the
     804                 :             :                 // diagram matches the difference in these values in the simulated graph. A more
     805                 :             :                 // complete check of the GetMainStagingDiagrams result is performed at the end.
     806         [ +  - ]:       13333 :                 assert(sim_gain == real_gain);
     807                 :             :                 // Check that the feerates in each diagram are monotonically decreasing.
     808   [ -  +  +  + ]:      298396 :                 for (size_t i = 1; i < real_main_diagram.size(); ++i) {
     809         [ -  + ]:      285063 :                     assert(FeeRateCompare(real_main_diagram[i], real_main_diagram[i - 1]) <= 0);
     810                 :             :                 }
     811   [ -  +  +  + ]:      294033 :                 for (size_t i = 1; i < real_staged_diagram.size(); ++i) {
     812         [ -  + ]:      280700 :                     assert(FeeRateCompare(real_staged_diagram[i], real_staged_diagram[i - 1]) <= 0);
     813                 :             :                 }
     814                 :       13333 :                 break;
     815   [ -  +  +  +  :      432502 :             } else if (block_builders.size() < 4 && !main_sim.IsOversized() && command-- == 0) {
          +  -  +  +  +  
                      + ]
     816                 :             :                 // GetBlockBuilder.
     817         [ +  - ]:        4659 :                 block_builders.emplace_back(real->GetBlockBuilder());
     818                 :        4659 :                 break;
     819   [ +  +  +  + ]:      414510 :             } else if (!block_builders.empty() && command-- == 0) {
     820                 :             :                 // ~BlockBuilder.
     821                 :        2439 :                 block_builders.erase(block_builders.begin() + builder_idx);
     822                 :             :                 break;
     823   [ +  +  +  + ]:      412071 :             } else if (!block_builders.empty() && command-- == 0) {
     824                 :             :                 // BlockBuilder::GetCurrentChunk, followed by Include/Skip.
     825                 :        5191 :                 auto& builder_data = block_builders[builder_idx];
     826                 :        5191 :                 auto new_included = builder_data.included;
     827                 :        5191 :                 auto new_done = builder_data.done;
     828                 :        5191 :                 auto chunk = builder_data.builder->GetCurrentChunk();
     829         [ +  + ]:        5191 :                 if (chunk) {
     830                 :             :                     // Chunk feerates must be monotonously decreasing.
     831         [ +  + ]:        2888 :                     if (!builder_data.last_feerate.IsEmpty()) {
     832         [ -  + ]:        1909 :                         assert(!(chunk->second >> builder_data.last_feerate));
     833                 :             :                     }
     834                 :        2888 :                     builder_data.last_feerate = chunk->second;
     835                 :             :                     // Verify the contents of GetCurrentChunk.
     836                 :        2888 :                     FeePerWeight sum_feerate;
     837         [ +  + ]:        6272 :                     for (TxGraph::Ref* ref : chunk->first) {
     838                 :             :                         // Each transaction in the chunk must exist in the main graph.
     839                 :        3384 :                         auto simpos = main_sim.Find(ref);
     840         [ -  + ]:        3384 :                         assert(simpos != SimTxGraph::MISSING);
     841                 :             :                         // Verify the claimed chunk feerate.
     842                 :        3384 :                         sum_feerate += main_sim.graph.FeeRate(simpos);
     843                 :             :                         // Make sure no transaction is reported twice.
     844         [ -  + ]:        3384 :                         assert(!new_done[simpos]);
     845                 :        3384 :                         new_done.Set(simpos);
     846                 :             :                         // The concatenation of all included transactions must be topologically valid.
     847                 :        3384 :                         new_included.Set(simpos);
     848         [ -  + ]:        6768 :                         assert(main_sim.graph.Ancestors(simpos).IsSubsetOf(new_included));
     849                 :             :                     }
     850         [ +  - ]:        2888 :                     assert(sum_feerate == chunk->second);
     851                 :             :                 } else {
     852                 :             :                     // When we reach the end, if nothing was skipped, the entire graph should have
     853                 :             :                     // been reported.
     854         [ +  + ]:        2303 :                     if (builder_data.done == builder_data.included) {
     855   [ -  +  -  + ]:        2452 :                         assert(builder_data.done.Count() == main_sim.GetTransactionCount());
     856                 :             :                     }
     857                 :             :                 }
     858                 :             :                 // Possibly invoke GetCurrentChunk() again, which should give the same result.
     859         [ +  + ]:        5191 :                 if ((orig_command % 7) >= 5) {
     860                 :        1576 :                     auto chunk2 = builder_data.builder->GetCurrentChunk();
     861         [ -  + ]:        1576 :                     assert(chunk == chunk2);
     862                 :        1576 :                 }
     863                 :             :                 // Skip or include.
     864         [ +  + ]:        5191 :                 if ((orig_command % 5) >= 3) {
     865                 :             :                     // Skip.
     866                 :        2644 :                     builder_data.builder->Skip();
     867                 :             :                 } else {
     868                 :             :                     // Include.
     869                 :        2547 :                     builder_data.builder->Include();
     870                 :        2547 :                     builder_data.included = new_included;
     871                 :             :                 }
     872                 :        5191 :                 builder_data.done = new_done;
     873                 :        5191 :                 break;
     874   [ +  -  +  +  :      412071 :             } else if (!main_sim.IsOversized() && command-- == 0) {
                   +  + ]
     875                 :             :                 // GetWorstMainChunk.
     876         [ +  + ]:       23775 :                 auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
     877                 :             :                 // Just do some sanity checks here. Consistency with GetBlockBuilder is checked
     878                 :             :                 // below.
     879   [ +  +  +  + ]:       47550 :                 if (main_sim.GetTransactionCount() == 0) {
     880         [ -  + ]:        2233 :                     assert(worst_chunk.empty());
     881         [ -  + ]:        2233 :                     assert(worst_chunk_feerate.IsEmpty());
     882                 :             :                 } else {
     883         [ -  + ]:       21542 :                     assert(!worst_chunk.empty());
     884                 :       21542 :                     SimTxGraph::SetType done;
     885                 :       21542 :                     FeePerWeight sum;
     886         [ +  + ]:       69364 :                     for (TxGraph::Ref* ref : worst_chunk) {
     887                 :             :                         // Each transaction in the chunk must exist in the main graph.
     888                 :       47822 :                         auto simpos = main_sim.Find(ref);
     889         [ -  + ]:       47822 :                         assert(simpos != SimTxGraph::MISSING);
     890                 :       47822 :                         sum += main_sim.graph.FeeRate(simpos);
     891                 :             :                         // Make sure the chunk contains no duplicate transactions.
     892         [ -  + ]:       47822 :                         assert(!done[simpos]);
     893                 :       47822 :                         done.Set(simpos);
     894                 :             :                         // All elements are preceded by all their descendants.
     895         [ -  + ]:       95644 :                         assert(main_sim.graph.Descendants(simpos).IsSubsetOf(done));
     896                 :             :                     }
     897         [ +  - ]:       21542 :                     assert(sum == worst_chunk_feerate);
     898                 :             :                 }
     899                 :       23775 :                 break;
     900   [ +  +  +  +  :      468342 :             } else if ((block_builders.empty() || sims.size() > 1) && command-- == 0) {
                   +  + ]
     901                 :             :                 // Trim.
     902         [ +  - ]:       20407 :                 bool was_oversized = top_sim.IsOversized();
     903                 :       20407 :                 auto removed = real->Trim();
     904                 :             :                 // Verify that something was removed if and only if there was an oversized cluster.
     905         [ -  + ]:       20407 :                 assert(was_oversized == !removed.empty());
     906         [ +  + ]:       20407 :                 if (!was_oversized) break;
     907         [ -  + ]:        1974 :                 auto removed_set = top_sim.MakeSet(removed);
     908                 :             :                 // The removed set must contain all its own descendants.
     909         [ +  + ]:       30676 :                 for (auto simpos : removed_set) {
     910         [ -  + ]:       57404 :                     assert(top_sim.graph.Descendants(simpos).IsSubsetOf(removed_set));
     911                 :             :                 }
     912                 :             :                 // Something from every oversized cluster should have been removed, and nothing
     913                 :             :                 // else.
     914   [ +  -  -  + ]:        1974 :                 assert(top_sim.MatchesOversizedClusters(removed_set));
     915                 :             : 
     916                 :             :                 // Apply all removals to the simulation, and verify the result is no longer
     917                 :             :                 // oversized. Don't query the real graph for oversizedness; it is compared
     918                 :             :                 // against the simulation anyway later.
     919         [ +  + ]:       30676 :                 for (auto simpos : removed_set) {
     920         [ +  - ]:       28702 :                     top_sim.RemoveTransaction(top_sim.GetRef(simpos));
     921                 :             :                 }
     922   [ +  -  -  + ]:        1974 :                 assert(!top_sim.IsOversized());
     923                 :             :                 break;
     924         [ +  + ]:       78305 :             } else if ((block_builders.empty() || sims.size() > 1) &&
     925   [ +  +  +  +  :      392261 :                        top_sim.GetTransactionCount() > max_cluster_count && !top_sim.IsOversized() && command-- == 0) {
          +  -  +  +  +  
                      + ]
     926                 :             :                 // Trim (special case which avoids apparent cycles in the implicit approximate
     927                 :             :                 // dependency graph constructed inside the Trim() implementation). This is worth
     928                 :             :                 // testing separately, because such cycles cannot occur in realistic scenarios,
     929                 :             :                 // but this is hard to replicate in general in this fuzz test.
     930                 :             : 
     931                 :             :                 // First, we need to have dependencies applied and linearizations fixed to avoid
     932                 :             :                 // circular dependencies in implied graph; trigger it via whatever means.
     933                 :        8184 :                 real->CountDistinctClusters({}, TxGraph::Level::TOP);
     934                 :             : 
     935                 :             :                 // Gather the current clusters.
     936         [ +  - ]:        8184 :                 auto clusters = top_sim.GetComponents();
     937                 :             : 
     938                 :             :                 // Merge clusters randomly until at least one oversized one appears.
     939                 :        8184 :                 bool made_oversized = false;
     940         [ -  + ]:        8184 :                 auto merges_left = clusters.size() - 1;
     941         [ +  + ]:      216728 :                 while (merges_left > 0) {
     942                 :      208544 :                     --merges_left;
     943                 :             :                     // Find positions of clusters in the clusters vector to merge together.
     944         [ -  + ]:      208544 :                     auto par_cl = rng.randrange(clusters.size());
     945         [ -  + ]:      208544 :                     auto chl_cl = rng.randrange(clusters.size() - 1);
     946                 :      208544 :                     chl_cl += (chl_cl >= par_cl);
     947         [ -  + ]:      208544 :                     Assume(chl_cl != par_cl);
     948                 :             :                     // Add between 1 and 3 dependencies between them. As all are in the same
     949                 :             :                     // direction (from the child cluster to parent cluster), no cycles are possible,
     950                 :             :                     // regardless of what internal topology Trim() uses as approximation within the
     951                 :             :                     // clusters.
     952                 :      208544 :                     int num_deps = rng.randrange(3) + 1;
     953         [ +  + ]:      625576 :                     for (int i = 0; i < num_deps; ++i) {
     954                 :             :                         // Find a parent transaction in the parent cluster.
     955                 :      417032 :                         auto par_idx = rng.randrange(clusters[par_cl].Count());
     956                 :      417032 :                         SimTxGraph::Pos par_pos = 0;
     957         [ +  - ]:     1018116 :                         for (auto j : clusters[par_cl]) {
     958         [ +  + ]:     1018116 :                             if (par_idx == 0) {
     959                 :             :                                 par_pos = j;
     960                 :             :                                 break;
     961                 :             :                             }
     962                 :      601084 :                             --par_idx;
     963                 :             :                         }
     964                 :             :                         // Find a child transaction in the child cluster.
     965                 :      417032 :                         auto chl_idx = rng.randrange(clusters[chl_cl].Count());
     966                 :      417032 :                         SimTxGraph::Pos chl_pos = 0;
     967         [ +  - ]:      999405 :                         for (auto j : clusters[chl_cl]) {
     968         [ +  + ]:      999405 :                             if (chl_idx == 0) {
     969                 :             :                                 chl_pos = j;
     970                 :             :                                 break;
     971                 :             :                             }
     972                 :      582373 :                             --chl_idx;
     973                 :             :                         }
     974                 :             :                         // Add dependency to both simulation and real TxGraph.
     975                 :      417032 :                         auto par_ref = top_sim.GetRef(par_pos);
     976                 :      417032 :                         auto chl_ref = top_sim.GetRef(chl_pos);
     977                 :      417032 :                         top_sim.AddDependency(par_ref, chl_ref);
     978                 :      417032 :                         real->AddDependency(*par_ref, *chl_ref);
     979                 :             :                     }
     980                 :             :                     // Compute the combined cluster.
     981                 :      208544 :                     auto par_cluster = clusters[par_cl];
     982                 :      208544 :                     auto chl_cluster = clusters[chl_cl];
     983                 :      208544 :                     auto new_cluster = par_cluster | chl_cluster;
     984                 :             :                     // Remove the parent and child cluster from clusters.
     985   [ +  +  +  + ]:     8464115 :                     std::erase_if(clusters, [&](const auto& cl) noexcept { return cl == par_cluster || cl == chl_cluster; });
     986                 :             :                     // Add the combined cluster.
     987         [ +  - ]:      208544 :                     clusters.push_back(new_cluster);
     988                 :             :                     // If this is the first merge that causes an oversized cluster to appear, pick
     989                 :             :                     // a random number of further merges to appear.
     990         [ +  + ]:      208544 :                     if (!made_oversized) {
     991                 :      173580 :                         made_oversized = new_cluster.Count() > max_cluster_count;
     992         [ +  + ]:      173580 :                         if (!made_oversized) {
     993                 :      165549 :                             FeeFrac total;
     994         [ +  + ]:     1180179 :                             for (auto i : new_cluster) total += top_sim.graph.FeeRate(i);
     995         [ +  + ]:      165549 :                             if (uint32_t(total.size) > max_cluster_size) made_oversized = true;
     996                 :             :                         }
     997   [ +  +  -  + ]:      173580 :                         if (made_oversized) merges_left = rng.randrange(clusters.size());
     998                 :             :                     }
     999                 :             :                 }
    1000                 :             : 
    1001                 :             :                 // Determine an upper bound on how many transactions are removed.
    1002                 :        8184 :                 uint32_t max_removed = 0;
    1003         [ +  + ]:       52786 :                 for (auto& cluster : clusters) {
    1004                 :             :                     // Gather all transaction sizes in the to-be-combined cluster.
    1005                 :       44602 :                     std::vector<uint32_t> sizes;
    1006   [ +  -  +  + ]:      459957 :                     for (auto i : cluster) sizes.push_back(top_sim.graph.FeeRate(i).size);
    1007                 :       44602 :                     auto sum_sizes = std::accumulate(sizes.begin(), sizes.end(), uint64_t{0});
    1008                 :             :                     // Sort from large to small.
    1009                 :       44602 :                     std::sort(sizes.begin(), sizes.end(), std::greater{});
    1010                 :             :                     // In the worst case, only the smallest transactions are removed.
    1011   [ -  +  +  +  :      128608 :                     while (sizes.size() > max_cluster_count || sum_sizes > max_cluster_size) {
                   +  + ]
    1012                 :       84006 :                         sum_sizes -= sizes.back();
    1013                 :       84006 :                         sizes.pop_back();
    1014                 :       84006 :                         ++max_removed;
    1015                 :             :                     }
    1016                 :       44602 :                 }
    1017                 :             : 
    1018                 :             :                 // Invoke Trim now on the definitely-oversized txgraph.
    1019                 :        8184 :                 auto removed = real->Trim();
    1020                 :             :                 // Verify that the number of removals is within range.
    1021   [ -  +  -  + ]:        8184 :                 assert(removed.size() >= 1);
    1022         [ -  + ]:        8184 :                 assert(removed.size() <= max_removed);
    1023                 :             :                 // The removed set must contain all its own descendants.
    1024                 :        8184 :                 auto removed_set = top_sim.MakeSet(removed);
    1025         [ +  + ]:       63806 :                 for (auto simpos : removed_set) {
    1026         [ -  + ]:      111244 :                     assert(top_sim.graph.Descendants(simpos).IsSubsetOf(removed_set));
    1027                 :             :                 }
    1028                 :             :                 // Something from every oversized cluster should have been removed, and nothing
    1029                 :             :                 // else.
    1030   [ +  -  -  + ]:        8184 :                 assert(top_sim.MatchesOversizedClusters(removed_set));
    1031                 :             : 
    1032                 :             :                 // Apply all removals to the simulation, and verify the result is no longer
    1033                 :             :                 // oversized. Don't query the real graph for oversizedness; it is compared
    1034                 :             :                 // against the simulation anyway later.
    1035         [ +  + ]:       63806 :                 for (auto simpos : removed_set) {
    1036         [ +  - ]:       55622 :                     top_sim.RemoveTransaction(top_sim.GetRef(simpos));
    1037                 :             :                 }
    1038   [ +  -  -  + ]:        8184 :                 assert(!top_sim.IsOversized());
    1039                 :        8184 :                 break;
    1040         [ +  + ]:      362698 :             } else if (command-- == 0) {
    1041                 :             :                 // GetMainMemoryUsage().
    1042                 :       35120 :                 auto usage = real->GetMainMemoryUsage();
    1043                 :             :                 // Test stability.
    1044         [ +  + ]:       35120 :                 if (alt) {
    1045                 :        6216 :                     auto usage2 = real->GetMainMemoryUsage();
    1046         [ -  + ]:        6216 :                     assert(usage == usage2);
    1047                 :             :                 }
    1048                 :             :                 // Only empty graphs have 0 memory usage.
    1049   [ +  +  +  + ]:       70240 :                 if (main_sim.GetTransactionCount() == 0) {
    1050         [ -  + ]:        1825 :                     assert(usage == 0);
    1051                 :             :                 } else {
    1052         [ -  + ]:       33295 :                     assert(usage > 0);
    1053                 :             :                 }
    1054                 :             :                 break;
    1055                 :             :             }
    1056                 :             :         }
    1057                 :             :     }
    1058                 :             : 
    1059                 :             :     // After running all modifications, perform an internal sanity check (before invoking
    1060                 :             :     // inspectors that may modify the internal state).
    1061         [ +  - ]:        4814 :     real->SanityCheck();
    1062                 :             : 
    1063   [ +  -  +  + ]:        4814 :     if (!sims[0].IsOversized()) {
    1064                 :             :         // If the main graph is not oversized, verify the total ordering implied by
    1065                 :             :         // CompareMainOrder.
    1066                 :             :         // First construct two distinct randomized permutations of the positions in sims[0].
    1067                 :        4089 :         std::vector<SimTxGraph::Pos> vec1;
    1068   [ +  -  +  + ]:      158082 :         for (auto i : sims[0].graph.Positions()) vec1.push_back(i);
    1069                 :        4089 :         std::shuffle(vec1.begin(), vec1.end(), rng);
    1070         [ +  - ]:        4089 :         auto vec2 = vec1;
    1071                 :        4089 :         std::shuffle(vec2.begin(), vec2.end(), rng);
    1072         [ +  + ]:        4089 :         if (vec1 == vec2) std::next_permutation(vec2.begin(), vec2.end());
    1073                 :             :         // Sort both according to CompareMainOrder. By having randomized starting points, the order
    1074                 :             :         // of CompareMainOrder invocations is somewhat randomized as well.
    1075                 :     2147882 :         auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
    1076                 :     2143793 :             return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
    1077                 :        4089 :         };
    1078                 :        4089 :         std::sort(vec1.begin(), vec1.end(), cmp);
    1079                 :        4089 :         std::sort(vec2.begin(), vec2.end(), cmp);
    1080                 :             : 
    1081                 :             :         // Verify the resulting orderings are identical. This could only fail if the ordering was
    1082                 :             :         // not total.
    1083         [ -  + ]:        4089 :         assert(vec1 == vec2);
    1084                 :             : 
    1085                 :             :         // Verify that the ordering is topological.
    1086                 :        4089 :         auto todo = sims[0].graph.Positions();
    1087         [ +  + ]:      158082 :         for (auto i : vec1) {
    1088                 :      153993 :             todo.Reset(i);
    1089         [ -  + ]:      307986 :             assert(!sims[0].graph.Ancestors(i).Overlaps(todo));
    1090                 :             :         }
    1091         [ -  + ]:        4089 :         assert(todo.None());
    1092                 :             : 
    1093                 :             :         // If the real graph claims to be optimal (the last DoWork() call returned true), verify
    1094                 :             :         // that calling Linearize on it does not improve it further.
    1095         [ +  + ]:        4089 :         if (sims[0].real_is_optimal) {
    1096         [ -  + ]:         652 :             auto real_diagram = ChunkLinearization(sims[0].graph, vec1);
    1097                 :       87342 :             auto fallback_order_sim = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
    1098                 :       86690 :                 auto txid_a = sims[0].GetRef(a)->m_txid;
    1099                 :       86690 :                 auto txid_b = sims[0].GetRef(b)->m_txid;
    1100   [ +  -  +  + ]:       86690 :                 return txid_a <=> txid_b;
    1101                 :         652 :             };
    1102   [ -  +  -  + ]:         652 :             auto [sim_lin, sim_optimal, _cost] = Linearize(sims[0].graph, 300000, rng.rand64(), fallback_order_sim, vec1);
    1103   [ -  +  +  - ]:         652 :             PostLinearize(sims[0].graph, sim_lin);
    1104         [ -  + ]:         652 :             auto sim_diagram = ChunkLinearization(sims[0].graph, sim_lin);
    1105   [ -  +  -  +  :         652 :             auto cmp = CompareChunks(real_diagram, sim_diagram);
                   +  - ]
    1106         [ -  + ]:         652 :             assert(cmp == 0);
    1107                 :             : 
    1108                 :             :             // Verify consistency of cross-cluster chunk ordering with tie-break (equal-feerate
    1109                 :             :             // prefix size).
    1110         [ -  + ]:         652 :             auto real_chunking = ChunkLinearizationInfo(sims[0].graph, vec1);
    1111                 :             :             /** Map with one entry per component of the sim main graph. Key is the first Pos of the
    1112                 :             :              *  component. Value is the sum of all chunk sizes from that component seen
    1113                 :             :              *  already, at the current chunk feerate. */
    1114                 :         652 :             std::map<SimTxGraph::Pos, int32_t> comp_prefix_sizes;
    1115                 :             :             /** Current chunk feerate. */
    1116                 :         652 :             FeeFrac last_chunk_feerate;
    1117                 :             :             /** Largest seen (equal-feerate chunk prefix size, max txid).  */
    1118                 :         652 :             std::pair<int32_t, uint64_t> max_chunk_tiebreak{0, 0};
    1119         [ +  + ]:       24297 :             for (const auto& chunk : real_chunking) {
    1120                 :             :                 // If this is the first chunk with a strictly lower feerate, reset.
    1121         [ +  + ]:       23645 :                 if (chunk.feerate << last_chunk_feerate) {
    1122                 :        6225 :                     comp_prefix_sizes.clear();
    1123                 :        6225 :                     max_chunk_tiebreak = {0, 0};
    1124                 :             :                 }
    1125                 :       23645 :                 last_chunk_feerate = chunk.feerate;
    1126                 :             :                 // Find which sim component this chunk belongs to.
    1127                 :       23645 :                 auto component = sims[0].graph.GetConnectedComponent(sims[0].graph.Positions(), chunk.transactions.First());
    1128         [ -  + ]:       47290 :                 assert(chunk.transactions.IsSubsetOf(component));
    1129                 :       23645 :                 auto comp_key = component.First();
    1130         [ +  - ]:       23645 :                 auto& comp_prefix_size = comp_prefix_sizes[comp_key];
    1131                 :       23645 :                 comp_prefix_size += chunk.feerate.size;
    1132                 :             :                 // Determine the chunk's max txid.
    1133                 :       23645 :                 uint64_t chunk_max_txid{0};
    1134         [ +  + ]:       54962 :                 for (auto tx : chunk.transactions) {
    1135                 :       31317 :                     auto txid = sims[0].GetRef(tx)->m_txid;
    1136         [ +  + ]:       56957 :                     chunk_max_txid = std::max(txid, chunk_max_txid);
    1137                 :             :                 }
    1138                 :             :                 // Verify consistency: within each group of equal-feerate chunks, the
    1139                 :             :                 // (equal-feerate chunk prefix size, max txid) must be increasing.
    1140         [ -  + ]:       23645 :                 std::pair<int32_t, uint64_t> chunk_tiebreak{comp_prefix_size, chunk_max_txid};
    1141         [ -  + ]:       23645 :                 assert(chunk_tiebreak > max_chunk_tiebreak);
    1142                 :       23645 :                 max_chunk_tiebreak = chunk_tiebreak;
    1143                 :             :             }
    1144                 :             : 
    1145                 :             :             // Verify that within each cluster, the internal ordering matches that of the
    1146                 :             :             // simulation if that is optimal too, since per-cluster optimal orderings are
    1147                 :             :             // deterministic. Note that both have been PostLinearize()'ed.
    1148         [ +  - ]:         652 :             if (sim_optimal) {
    1149   [ +  -  +  + ]:       17832 :                 for (const auto& component : sims[0].GetComponents()) {
    1150                 :       17180 :                     std::vector<DepGraphIndex> sim_chunk_lin, real_chunk_lin;
    1151         [ +  + ]:     1412247 :                     for (auto i : sim_lin) {
    1152   [ +  +  +  - ]:     1395067 :                         if (component[i]) sim_chunk_lin.push_back(i);
    1153                 :             :                     }
    1154         [ +  + ]:     1412247 :                     for (auto i : vec1) {
    1155   [ +  +  +  - ]:     1395067 :                         if (component[i]) real_chunk_lin.push_back(i);
    1156                 :             :                     }
    1157         [ -  + ]:       17180 :                     assert(sim_chunk_lin == real_chunk_lin);
    1158                 :       17832 :                 }
    1159                 :             :             }
    1160                 :             : 
    1161                 :             :             // Verify that a fresh TxGraph, with the same transactions and txids, but constructed
    1162                 :             :             // in a different order, and with a different RNG state, recreates the exact same
    1163                 :             :             // ordering, showing that for optimal graphs, the full mempool ordering is
    1164                 :             :             // deterministic.
    1165                 :         652 :             auto real_redo = MakeTxGraph(
    1166                 :             :                 /*max_cluster_count=*/max_cluster_count,
    1167                 :             :                 /*max_cluster_size=*/max_cluster_size,
    1168                 :             :                 /*acceptable_iters=*/acceptable_iters,
    1169                 :         652 :                 /*fallback_order=*/fallback_order);
    1170                 :             :             /** Vector (indexed by SimTxGraph::Pos) of TxObjects in real_redo). */
    1171                 :         652 :             std::vector<std::optional<SimTxObject>> txobjects_redo;
    1172   [ -  +  +  - ]:         652 :             txobjects_redo.resize(sims[0].graph.PositionRange());
    1173                 :             :             // Recreate the graph's transactions with same feerate and txid.
    1174                 :         652 :             std::vector<DepGraphIndex> positions;
    1175   [ +  -  +  + ]:       31969 :             for (auto i : sims[0].graph.Positions()) positions.push_back(i);
    1176                 :         652 :             std::shuffle(positions.begin(), positions.end(), rng);
    1177         [ +  + ]:       31969 :             for (auto i : positions) {
    1178                 :       31317 :                 txobjects_redo[i].emplace(sims[0].GetRef(i)->m_txid);
    1179                 :       31317 :                 real_redo->AddTransaction(*txobjects_redo[i], FeePerWeight::FromFeeFrac(sims[0].graph.FeeRate(i)));
    1180                 :             :             }
    1181                 :             :             // Recreate the graph's dependencies.
    1182                 :         652 :             std::vector<std::pair<DepGraphIndex, DepGraphIndex>> deps;
    1183         [ +  + ]:       31969 :             for (auto i : sims[0].graph.Positions()) {
    1184         [ +  + ]:       47291 :                 for (auto j : sims[0].graph.GetReducedParents(i)) {
    1185         [ +  - ]:       15974 :                     deps.emplace_back(j, i);
    1186                 :             :                 }
    1187                 :             :             }
    1188                 :         652 :             std::shuffle(deps.begin(), deps.end(), rng);
    1189         [ +  + ]:       16626 :             for (auto [parent, child] : deps) {
    1190                 :       15974 :                 real_redo->AddDependency(*txobjects_redo[parent], *txobjects_redo[child]);
    1191                 :             :             }
    1192                 :             :             // Do work to reach optimality.
    1193         [ +  - ]:         652 :             if (real_redo->DoWork(300000)) {
    1194                 :             :                 // Start from a random permutation.
    1195         [ +  - ]:         652 :                 auto vec_redo = vec1;
    1196                 :         652 :                 std::shuffle(vec_redo.begin(), vec_redo.end(), rng);
    1197         [ +  + ]:         652 :                 if (vec_redo == vec1) std::next_permutation(vec_redo.begin(), vec_redo.end());
    1198                 :             :                 // Sort it according to the main graph order in real_redo.
    1199                 :      227186 :                 auto cmp_redo = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
    1200                 :      226534 :                     return real_redo->CompareMainOrder(*txobjects_redo[a], *txobjects_redo[b]) < 0;
    1201                 :         652 :                 };
    1202                 :         652 :                 std::sort(vec_redo.begin(), vec_redo.end(), cmp_redo);
    1203                 :             :                 // Compare with the ordering we got from real.
    1204         [ -  + ]:         652 :                 assert(vec1 == vec_redo);
    1205                 :         652 :             }
    1206                 :         652 :         }
    1207                 :             : 
    1208                 :             :         // For every transaction in the total ordering, find a random one before it and after it,
    1209                 :             :         // and compare their chunk feerates, which must be consistent with the ordering.
    1210   [ -  +  +  + ]:      158082 :         for (size_t pos = 0; pos < vec1.size(); ++pos) {
    1211                 :      153993 :             auto pos_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[pos]));
    1212         [ +  + ]:      153993 :             if (pos > 0) {
    1213                 :      150221 :                 size_t before = rng.randrange<size_t>(pos);
    1214                 :      150221 :                 auto before_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[before]));
    1215         [ -  + ]:      150221 :                 assert(FeeRateCompare(before_feerate, pos_feerate) >= 0);
    1216                 :             :             }
    1217   [ -  +  +  + ]:      153993 :             if (pos + 1 < vec1.size()) {
    1218                 :      150221 :                 size_t after = pos + 1 + rng.randrange<size_t>(vec1.size() - 1 - pos);
    1219                 :      150221 :                 auto after_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[after]));
    1220         [ -  + ]:      150221 :                 assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
    1221                 :             :             }
    1222                 :             :         }
    1223                 :             : 
    1224                 :             :         // The same order should be obtained through a BlockBuilder as implied by CompareMainOrder,
    1225                 :             :         // if nothing is skipped.
    1226                 :        4089 :         auto builder = real->GetBlockBuilder();
    1227                 :        4089 :         std::vector<SimTxGraph::Pos> vec_builder;
    1228                 :        4089 :         std::vector<TxGraph::Ref*> last_chunk;
    1229                 :        4089 :         FeePerWeight last_chunk_feerate;
    1230         [ +  + ]:      269887 :         while (auto chunk = builder->GetCurrentChunk()) {
    1231                 :      132899 :             FeePerWeight sum;
    1232         [ +  + ]:      286892 :             for (TxGraph::Ref* ref : chunk->first) {
    1233                 :             :                 // The reported chunk feerate must match the chunk feerate obtained by asking
    1234                 :             :                 // it for each of the chunk's transactions individually.
    1235         [ +  - ]:      153993 :                 assert(real->GetMainChunkFeerate(*ref) == chunk->second);
    1236                 :             :                 // Verify the chunk feerate matches the sum of the reported individual feerates.
    1237                 :      153993 :                 sum += real->GetIndividualFeerate(*ref);
    1238                 :             :                 // Chunks must contain transactions that exist in the graph.
    1239                 :      153993 :                 auto simpos = sims[0].Find(ref);
    1240         [ -  + ]:      153993 :                 assert(simpos != SimTxGraph::MISSING);
    1241         [ +  - ]:      153993 :                 vec_builder.push_back(simpos);
    1242                 :             :             }
    1243         [ +  - ]:      132899 :             assert(sum == chunk->second);
    1244                 :      132899 :             last_chunk = std::move(chunk->first);
    1245                 :      132899 :             last_chunk_feerate = chunk->second;
    1246                 :      132899 :             builder->Include();
    1247                 :      132899 :         }
    1248         [ -  + ]:        4089 :         assert(vec_builder == vec1);
    1249                 :             : 
    1250                 :             :         // The last chunk returned by the BlockBuilder must match GetWorstMainChunk, in reverse.
    1251                 :        4089 :         std::reverse(last_chunk.begin(), last_chunk.end());
    1252         [ -  + ]:        4089 :         auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
    1253         [ -  + ]:        4089 :         assert(last_chunk == worst_chunk);
    1254         [ +  - ]:        4089 :         assert(last_chunk_feerate == worst_chunk_feerate);
    1255                 :             : 
    1256                 :             :         // Check that the implied ordering gives rise to a combined diagram that matches the
    1257                 :             :         // diagram constructed from the individual cluster linearization chunkings.
    1258         [ +  - ]:        4089 :         auto main_real_diagram = get_diagram_fn(TxGraph::Level::MAIN);
    1259         [ -  + ]:        4089 :         auto main_implied_diagram = ChunkLinearization(sims[0].graph, vec1);
    1260   [ -  +  -  +  :        4089 :         assert(CompareChunks(main_real_diagram, main_implied_diagram) == 0);
             +  -  -  + ]
    1261                 :             : 
    1262   [ -  +  +  +  :        4089 :         if (sims.size() >= 2 && !sims[1].IsOversized()) {
             +  -  +  + ]
    1263                 :             :             // When the staging graph is not oversized as well, call GetMainStagingDiagrams, and
    1264                 :             :             // fully verify the result.
    1265                 :        1720 :             auto [main_cmp_diagram, stage_cmp_diagram] = real->GetMainStagingDiagrams();
    1266                 :             :             // Check that the feerates in each diagram are monotonically decreasing.
    1267   [ -  +  +  + ]:       24847 :             for (size_t i = 1; i < main_cmp_diagram.size(); ++i) {
    1268         [ -  + ]:       23127 :                 assert(FeeRateCompare(main_cmp_diagram[i], main_cmp_diagram[i - 1]) <= 0);
    1269                 :             :             }
    1270   [ -  +  +  + ]:       29988 :             for (size_t i = 1; i < stage_cmp_diagram.size(); ++i) {
    1271         [ -  + ]:       28268 :                 assert(FeeRateCompare(stage_cmp_diagram[i], stage_cmp_diagram[i - 1]) <= 0);
    1272                 :             :             }
    1273                 :             :             // Treat the diagrams as sets of chunk feerates, and sort them in the same way so that
    1274                 :             :             // std::set_difference can be used on them below. The exact ordering does not matter
    1275                 :             :             // here, but it has to be consistent with the one used in main_real_diagram and
    1276                 :             :             // stage_real_diagram).
    1277                 :        1720 :             std::sort(main_cmp_diagram.begin(), main_cmp_diagram.end(), std::greater{});
    1278                 :        1720 :             std::sort(stage_cmp_diagram.begin(), stage_cmp_diagram.end(), std::greater{});
    1279                 :             :             // Find the chunks that appear in main_diagram but are missing from main_cmp_diagram.
    1280                 :             :             // This is allowed, because GetMainStagingDiagrams omits clusters in main unaffected
    1281                 :             :             // by staging.
    1282                 :        1720 :             std::vector<FeeFrac> missing_main_cmp;
    1283         [ +  - ]:        1720 :             std::set_difference(main_real_diagram.begin(), main_real_diagram.end(),
    1284                 :             :                                 main_cmp_diagram.begin(), main_cmp_diagram.end(),
    1285                 :             :                                 std::inserter(missing_main_cmp, missing_main_cmp.end()),
    1286                 :             :                                 std::greater{});
    1287   [ -  +  -  +  :        1720 :             assert(main_cmp_diagram.size() + missing_main_cmp.size() == main_real_diagram.size());
             -  +  -  + ]
    1288                 :             :             // Do the same for chunks in stage_diagram missing from stage_cmp_diagram.
    1289         [ +  - ]:        1720 :             auto stage_real_diagram = get_diagram_fn(TxGraph::Level::TOP);
    1290                 :        1720 :             std::vector<FeeFrac> missing_stage_cmp;
    1291         [ +  - ]:        1720 :             std::set_difference(stage_real_diagram.begin(), stage_real_diagram.end(),
    1292                 :             :                                 stage_cmp_diagram.begin(), stage_cmp_diagram.end(),
    1293                 :             :                                 std::inserter(missing_stage_cmp, missing_stage_cmp.end()),
    1294                 :             :                                 std::greater{});
    1295   [ -  +  -  +  :        1720 :             assert(stage_cmp_diagram.size() + missing_stage_cmp.size() == stage_real_diagram.size());
             -  +  -  + ]
    1296                 :             :             // The missing chunks must be equal across main & staging (otherwise they couldn't have
    1297                 :             :             // been omitted).
    1298         [ -  + ]:        1720 :             assert(missing_main_cmp == missing_stage_cmp);
    1299                 :             : 
    1300                 :             :             // The missing part must include at least all transactions in staging which have not been
    1301                 :             :             // modified, or been in a cluster together with modified transactions, since they were
    1302                 :             :             // copied from main. Note that due to the reordering of removals w.r.t. dependency
    1303                 :             :             // additions, it is possible that the real implementation found more unaffected things.
    1304                 :        1720 :             FeeFrac missing_real;
    1305         [ +  + ]:       36472 :             for (const auto& feerate : missing_main_cmp) missing_real += feerate;
    1306                 :        1720 :             FeeFrac missing_expected = sims[1].graph.FeeRate(sims[1].graph.Positions() - sims[1].modified);
    1307                 :             :             // Note that missing_real.fee < missing_expected.fee is possible to due the presence of
    1308                 :             :             // negative-fee transactions.
    1309         [ -  + ]:        1720 :             assert(missing_real.size >= missing_expected.size);
    1310                 :        3440 :         }
    1311                 :        4089 :     }
    1312                 :             : 
    1313   [ -  +  -  + ]:        4814 :     assert(real->HaveStaging() == (sims.size() > 1));
    1314                 :             : 
    1315                 :             :     // Try to run a full comparison, for both TxGraph::Level::MAIN and TxGraph::Level::TOP in
    1316                 :             :     // TxGraph inspector functions that support both.
    1317         [ +  + ]:       14442 :     for (auto level : {TxGraph::Level::TOP, TxGraph::Level::MAIN}) {
    1318         [ +  + ]:        9628 :         auto& sim = level == TxGraph::Level::TOP ? sims.back() : sims.front();
    1319                 :             :         // Compare simple properties of the graph with the simulation.
    1320   [ +  -  -  + ]:        9628 :         assert(real->IsOversized(level) == sim.IsOversized());
    1321   [ -  +  -  + ]:        9628 :         assert(real->GetTransactionCount(level) == sim.GetTransactionCount());
    1322                 :             :         // If the graph (and the simulation) are not oversized, perform a full comparison.
    1323   [ +  -  +  + ]:        9628 :         if (!sim.IsOversized()) {
    1324                 :        8100 :             auto todo = sim.graph.Positions();
    1325                 :             :             // Iterate over all connected components of the resulting (simulated) graph, each of which
    1326                 :             :             // should correspond to a cluster in the real one.
    1327         [ +  + ]:      432286 :             while (todo.Any()) {
    1328                 :      208043 :                 auto component = sim.graph.FindConnectedComponent(todo);
    1329                 :      208043 :                 todo -= component;
    1330                 :             :                 // Iterate over the transactions in that component.
    1331         [ +  + ]:      522590 :                 for (auto i : component) {
    1332                 :             :                     // Check its individual feerate against simulation.
    1333         [ +  - ]:      314547 :                     assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
    1334                 :             :                     // Check its ancestors against simulation.
    1335                 :      314547 :                     auto expect_anc = sim.graph.Ancestors(i);
    1336         [ -  + ]:      314547 :                     auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), level));
    1337         [ -  + ]:      314547 :                     assert(anc.Count() <= max_cluster_count);
    1338         [ -  + ]:      314547 :                     assert(anc == expect_anc);
    1339                 :             :                     // Check its descendants against simulation.
    1340                 :      314547 :                     auto expect_desc = sim.graph.Descendants(i);
    1341         [ -  + ]:      314547 :                     auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), level));
    1342         [ -  + ]:      314547 :                     assert(desc.Count() <= max_cluster_count);
    1343         [ -  + ]:      314547 :                     assert(desc == expect_desc);
    1344                 :             :                     // Check the cluster the transaction is part of.
    1345                 :      314547 :                     auto cluster = real->GetCluster(*sim.GetRef(i), level);
    1346   [ -  +  -  + ]:      314547 :                     assert(cluster.size() <= max_cluster_count);
    1347         [ -  + ]:      314547 :                     assert(sim.MakeSet(cluster) == component);
    1348                 :             :                     // Check that the cluster is reported in a valid topological order (its
    1349                 :             :                     // linearization).
    1350                 :      314547 :                     std::vector<DepGraphIndex> simlin;
    1351                 :      314547 :                     SimTxGraph::SetType done;
    1352                 :      314547 :                     uint64_t total_size{0};
    1353         [ +  + ]:     5172578 :                     for (TxGraph::Ref* ptr : cluster) {
    1354                 :     4858031 :                         auto simpos = sim.Find(ptr);
    1355         [ -  + ]:     9716062 :                         assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
    1356                 :     4858031 :                         done.Set(simpos);
    1357         [ -  + ]:     9716062 :                         assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
    1358         [ +  - ]:     4858031 :                         simlin.push_back(simpos);
    1359                 :     4858031 :                         total_size += sim.graph.FeeRate(simpos).size;
    1360                 :             :                     }
    1361                 :             :                     // Check cluster size.
    1362         [ -  + ]:      314547 :                     assert(total_size <= max_cluster_size);
    1363                 :             :                     // Construct a chunking object for the simulated graph, using the reported cluster
    1364                 :             :                     // linearization as ordering, and compare it against the reported chunk feerates.
    1365   [ -  +  +  +  :      314547 :                     if (sims.size() == 1 || level == TxGraph::Level::MAIN) {
                   +  + ]
    1366         [ -  + ]:      235282 :                         auto simlinchunk = ChunkLinearizationInfo(sim.graph, simlin);
    1367                 :      235282 :                         DepGraphIndex idx{0};
    1368         [ +  + ]:     2059734 :                         for (auto& chunk : simlinchunk) {
    1369                 :             :                             // Require that the chunks of cluster linearizations are connected (this must
    1370                 :             :                             // be the case as all linearizations inside are PostLinearized).
    1371         [ -  + ]:     1824452 :                             assert(sim.graph.IsConnected(chunk.transactions));
    1372                 :             :                             // Check the chunk feerates of all transactions in the cluster.
    1373         [ +  + ]:    10477988 :                             while (chunk.transactions.Any()) {
    1374         [ -  + ]:     3414542 :                                 assert(chunk.transactions[simlin[idx]]);
    1375                 :     3414542 :                                 chunk.transactions.Reset(simlin[idx]);
    1376         [ +  - ]:     3414542 :                                 assert(chunk.feerate == real->GetMainChunkFeerate(*cluster[idx]));
    1377                 :     3414542 :                                 ++idx;
    1378                 :             :                             }
    1379                 :             :                         }
    1380                 :      235282 :                     }
    1381                 :      314547 :                 }
    1382                 :             :             }
    1383                 :             :         }
    1384                 :             :     }
    1385                 :             : 
    1386                 :             :     // Sanity check again (because invoking inspectors may modify internal unobservable state).
    1387         [ +  - ]:        4814 :     real->SanityCheck();
    1388                 :             : 
    1389                 :             :     // Kill the block builders.
    1390                 :        4814 :     block_builders.clear();
    1391                 :             :     // Kill the TxGraph object.
    1392         [ +  - ]:        4814 :     real.reset();
    1393                 :             :     // Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
    1394                 :             :     // can outlive the TxGraph that created them.
    1395                 :        4814 :     sims.clear();
    1396                 :        4814 : }
        

Generated by: LCOV version 2.0-1