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 % 529 527
Test Date: 2025-06-01 05:27:21 Functions: 95.5 % 22 21
Branches: 78.4 % 592 464

             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 <txgraph.h>
       6                 :             : #include <cluster_linearize.h>
       7                 :             : #include <test/fuzz/fuzz.h>
       8                 :             : #include <test/fuzz/FuzzedDataProvider.h>
       9                 :             : #include <test/util/random.h>
      10                 :             : #include <util/bitset.h>
      11                 :             : #include <util/feefrac.h>
      12                 :             : 
      13                 :             : #include <algorithm>
      14                 :             : #include <iterator>
      15                 :             : #include <map>
      16                 :             : #include <memory>
      17                 :             : #include <set>
      18                 :             : #include <stdint.h>
      19                 :             : #include <utility>
      20                 :             : 
      21                 :             : using namespace cluster_linearize;
      22                 :             : 
      23                 :             : namespace {
      24                 :             : 
      25                 :             : /** Data type representing a naive simulated TxGraph, keeping all transactions (even from
      26                 :             :  *  disconnected components) in a single DepGraph. Unlike the real TxGraph, this only models
      27                 :             :  *  a single graph, and multiple instances are used to simulate main/staging. */
      28                 :             : struct SimTxGraph
      29                 :             : {
      30                 :             :     /** Maximum number of transactions to support simultaneously. Set this higher than txgraph's
      31                 :             :      *  cluster count, so we can exercise situations with more transactions than fit in one
      32                 :             :      *  cluster. */
      33                 :             :     static constexpr unsigned MAX_TRANSACTIONS = MAX_CLUSTER_COUNT_LIMIT * 2;
      34                 :             :     /** Set type to use in the simulation. */
      35                 :             :     using SetType = BitSet<MAX_TRANSACTIONS>;
      36                 :             :     /** Data type for representing positions within SimTxGraph::graph. */
      37                 :             :     using Pos = DepGraphIndex;
      38                 :             :     /** Constant to mean "missing in this graph". */
      39                 :             :     static constexpr auto MISSING = Pos(-1);
      40                 :             : 
      41                 :             :     /** The dependency graph (for all transactions in the simulation, regardless of
      42                 :             :      *  connectivity/clustering). */
      43                 :             :     DepGraph<SetType> graph;
      44                 :             :     /** For each position in graph, which TxGraph::Ref it corresponds with (if any). Use shared_ptr
      45                 :             :      *  so that a SimTxGraph can be copied to create a staging one, while sharing Refs with
      46                 :             :      *  the main graph. */
      47                 :             :     std::array<std::shared_ptr<TxGraph::Ref>, MAX_TRANSACTIONS> simmap;
      48                 :             :     /** For each TxGraph::Ref in graph, the position it corresponds with. */
      49                 :             :     std::map<const TxGraph::Ref*, Pos> simrevmap;
      50                 :             :     /** The set of TxGraph::Ref entries that have been removed, but not yet destroyed. */
      51                 :             :     std::vector<std::shared_ptr<TxGraph::Ref>> removed;
      52                 :             :     /** Whether the graph is oversized (true = yes, false = no, std::nullopt = unknown). */
      53                 :             :     std::optional<bool> oversized;
      54                 :             :     /** The configured maximum number of transactions per cluster. */
      55                 :             :     DepGraphIndex max_cluster_count;
      56                 :             :     /** Which transactions have been modified in the graph since creation, either directly or by
      57                 :             :      *  being in a cluster which includes modifications. Only relevant for the staging graph. */
      58                 :             :     SetType modified;
      59                 :             : 
      60                 :             :     /** Construct a new SimTxGraph with the specified maximum cluster count. */
      61                 :        2669 :     explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
      62                 :             : 
      63                 :             :     // Permit copying and moving.
      64   [ +  -  +  - ]:        5080 :     SimTxGraph(const SimTxGraph&) noexcept = default;
      65                 :             :     SimTxGraph& operator=(const SimTxGraph&) noexcept = default;
      66                 :           0 :     SimTxGraph(SimTxGraph&&) noexcept = default;
      67                 :        2191 :     SimTxGraph& operator=(SimTxGraph&&) noexcept = default;
      68                 :             : 
      69                 :             :     /** Check whether this graph is oversized (contains a connected component whose number of
      70                 :             :      *  transactions exceeds max_cluster_count. */
      71                 :     2579185 :     bool IsOversized()
      72                 :             :     {
      73         [ +  + ]:     2579185 :         if (!oversized.has_value()) {
      74                 :             :             // Only recompute when oversized isn't already known.
      75                 :       23367 :             oversized = false;
      76                 :       23367 :             auto todo = graph.Positions();
      77                 :             :             // Iterate over all connected components of the graph.
      78         [ +  + ]:      712404 :             while (todo.Any()) {
      79                 :      332835 :                 auto component = graph.FindConnectedComponent(todo);
      80         [ +  + ]:      332835 :                 if (component.Count() > max_cluster_count) oversized = true;
      81                 :      332835 :                 todo -= component;
      82                 :             :             }
      83                 :             :         }
      84                 :     2579185 :         return *oversized;
      85                 :             :     }
      86                 :             : 
      87                 :      126435 :     void MakeModified(DepGraphIndex index)
      88                 :             :     {
      89                 :      126435 :         modified |= graph.GetConnectedComponent(graph.Positions(), index);
      90                 :      126435 :     }
      91                 :             : 
      92                 :             :     /** Determine the number of (non-removed) transactions in the graph. */
      93   [ +  +  +  +  :     1164327 :     DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
                   +  + ]
      94                 :             : 
      95                 :             :     /** Get the sum of all fees/sizes in the graph. */
      96                 :       15362 :     FeePerWeight SumAll() const
      97                 :             :     {
      98                 :       15362 :         FeePerWeight ret;
      99         [ +  + ]:      142147 :         for (auto i : graph.Positions()) {
     100                 :      126785 :             ret += graph.FeeRate(i);
     101                 :             :         }
     102                 :       15362 :         return ret;
     103                 :             :     }
     104                 :             : 
     105                 :             :     /** Get the position where ref occurs in this simulated graph, or -1 if it does not. */
     106                 :      954862 :     Pos Find(const TxGraph::Ref* ref) const
     107                 :             :     {
     108                 :      954862 :         auto it = simrevmap.find(ref);
     109         [ +  + ]:      954862 :         if (it != simrevmap.end()) return it->second;
     110                 :             :         return MISSING;
     111                 :             :     }
     112                 :             : 
     113                 :             :     /** Given a position in this simulated graph, get the corresponding TxGraph::Ref. */
     114                 :     1206983 :     TxGraph::Ref* GetRef(Pos pos)
     115                 :             :     {
     116         [ -  + ]:     1206983 :         assert(graph.Positions()[pos]);
     117         [ -  + ]:     1206983 :         assert(simmap[pos]);
     118                 :     1206983 :         return simmap[pos].get();
     119                 :             :     }
     120                 :             : 
     121                 :             :     /** Add a new transaction to the simulation. */
     122                 :       84603 :     TxGraph::Ref* AddTransaction(const FeePerWeight& feerate)
     123                 :             :     {
     124         [ -  + ]:       84603 :         assert(graph.TxCount() < MAX_TRANSACTIONS);
     125                 :       84603 :         auto simpos = graph.AddTransaction(feerate);
     126                 :       84603 :         MakeModified(simpos);
     127         [ -  + ]:       84603 :         assert(graph.Positions()[simpos]);
     128         [ -  + ]:       84603 :         simmap[simpos] = std::make_shared<TxGraph::Ref>();
     129                 :       84603 :         auto ptr = simmap[simpos].get();
     130                 :       84603 :         simrevmap[ptr] = simpos;
     131                 :       84603 :         return ptr;
     132                 :             :     }
     133                 :             : 
     134                 :             :     /** Add a dependency between two positions in this graph. */
     135                 :       34492 :     void AddDependency(TxGraph::Ref* parent, TxGraph::Ref* child)
     136                 :             :     {
     137                 :       34492 :         auto par_pos = Find(parent);
     138         [ +  + ]:       34492 :         if (par_pos == MISSING) return;
     139                 :       29383 :         auto chl_pos = Find(child);
     140         [ +  + ]:       29383 :         if (chl_pos == MISSING) return;
     141                 :       25227 :         graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
     142                 :       25227 :         MakeModified(par_pos);
     143                 :             :         // This may invalidate our cached oversized value.
     144   [ +  +  +  + ]:       25227 :         if (oversized.has_value() && !*oversized) oversized = std::nullopt;
     145                 :             :     }
     146                 :             : 
     147                 :             :     /** Modify the transaction fee of a ref, if it exists. */
     148                 :        4357 :     void SetTransactionFee(TxGraph::Ref* ref, int64_t fee)
     149                 :             :     {
     150                 :        4357 :         auto pos = Find(ref);
     151         [ +  + ]:        4357 :         if (pos == MISSING) return;
     152                 :             :         // No need to invoke MakeModified, because this equally affects main and staging.
     153                 :        3568 :         graph.FeeRate(pos).fee = fee;
     154                 :             :     }
     155                 :             : 
     156                 :             :     /** Remove the transaction in the specified position from the graph. */
     157                 :       10190 :     void RemoveTransaction(TxGraph::Ref* ref)
     158                 :             :     {
     159                 :       10190 :         auto pos = Find(ref);
     160         [ +  + ]:       10190 :         if (pos == MISSING) return;
     161                 :        6686 :         MakeModified(pos);
     162                 :        6686 :         graph.RemoveTransactions(SetType::Singleton(pos));
     163                 :        6686 :         simrevmap.erase(simmap[pos].get());
     164                 :             :         // Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
     165                 :             :         // invoked until the simulation explicitly decided to do so.
     166                 :        6686 :         removed.push_back(std::move(simmap[pos]));
     167                 :        6686 :         simmap[pos].reset();
     168                 :             :         // This may invalidate our cached oversized value.
     169   [ +  +  +  + ]:        6686 :         if (oversized.has_value() && *oversized) oversized = std::nullopt;
     170                 :             :     }
     171                 :             : 
     172                 :             :     /** Destroy the transaction from the graph, including from the removed set. This will
     173                 :             :      *  trigger TxGraph::Ref::~Ref. reset_oversize controls whether the cached oversized
     174                 :             :      *  value is cleared (destroying does not clear oversizedness in TxGraph of the main
     175                 :             :      *  graph while staging exists). */
     176                 :       13037 :     void DestroyTransaction(TxGraph::Ref* ref, bool reset_oversize)
     177                 :             :     {
     178                 :       13037 :         auto pos = Find(ref);
     179         [ +  + ]:       13037 :         if (pos == MISSING) {
     180                 :             :             // Wipe the ref, if it exists, from the removed vector. Use std::partition rather
     181                 :             :             // than std::erase because we don't care about the order of the entries that
     182                 :             :             // remain.
     183   [ +  +  -  + ]:        4571 :             auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
     184                 :        3118 :             removed.erase(remove, removed.end());
     185                 :             :         } else {
     186                 :        9919 :             MakeModified(pos);
     187                 :        9919 :             graph.RemoveTransactions(SetType::Singleton(pos));
     188                 :        9919 :             simrevmap.erase(simmap[pos].get());
     189                 :        9919 :             simmap[pos].reset();
     190                 :             :             // This may invalidate our cached oversized value.
     191   [ +  +  +  +  :        9919 :             if (reset_oversize && oversized.has_value() && *oversized) {
                   +  + ]
     192                 :        2651 :                 oversized = std::nullopt;
     193                 :             :             }
     194                 :             :         }
     195                 :       13037 :     }
     196                 :             : 
     197                 :             :     /** Construct the set with all positions in this graph corresponding to the specified
     198                 :             :      *  TxGraph::Refs. All of them must occur in this graph and not be removed. */
     199                 :      215019 :     SetType MakeSet(std::span<TxGraph::Ref* const> arg)
     200                 :             :     {
     201                 :      215019 :         SetType ret;
     202         [ +  + ]:      510759 :         for (TxGraph::Ref* ptr : arg) {
     203                 :      295740 :             auto pos = Find(ptr);
     204         [ -  + ]:      295740 :             assert(pos != Pos(-1));
     205                 :      295740 :             ret.Set(pos);
     206                 :             :         }
     207                 :      215019 :         return ret;
     208                 :             :     }
     209                 :             : 
     210                 :             :     /** Get the set of ancestors (desc=false) or descendants (desc=true) in this graph. */
     211                 :       31058 :     SetType GetAncDesc(TxGraph::Ref* arg, bool desc)
     212                 :             :     {
     213                 :       31058 :         auto pos = Find(arg);
     214         [ +  + ]:       31058 :         if (pos == MISSING) return {};
     215         [ +  + ]:       19560 :         return desc ? graph.Descendants(pos) : graph.Ancestors(pos);
     216                 :             :     }
     217                 :             : 
     218                 :             :     /** Given a set of Refs (given as a vector of pointers), expand the set to include all its
     219                 :             :      *  ancestors (desc=false) or all its descendants (desc=true) in this graph. */
     220                 :       19258 :     void IncludeAncDesc(std::vector<TxGraph::Ref*>& arg, bool desc)
     221                 :             :     {
     222                 :       19258 :         std::vector<TxGraph::Ref*> ret;
     223         [ +  + ]:       41628 :         for (auto ptr : arg) {
     224                 :       22370 :             auto simpos = Find(ptr);
     225         [ +  + ]:       22370 :             if (simpos != MISSING) {
     226   [ +  +  +  + ]:       39849 :                 for (auto i : desc ? graph.Descendants(simpos) : graph.Ancestors(simpos)) {
     227         [ +  - ]:       24137 :                     ret.push_back(simmap[i].get());
     228                 :             :                 }
     229                 :             :             } else {
     230         [ +  - ]:        6658 :                 ret.push_back(ptr);
     231                 :             :             }
     232                 :             :         }
     233                 :             :         // Construct deduplicated version in input (do not use std::sort/std::unique for
     234                 :             :         // deduplication as it'd rely on non-deterministic pointer comparison).
     235         [ +  - ]:       19258 :         arg.clear();
     236         [ +  + ]:       50053 :         for (auto ptr : ret) {
     237         [ +  + ]:       30795 :             if (std::find(arg.begin(), arg.end(), ptr) == arg.end()) {
     238         [ +  - ]:       27058 :                 arg.push_back(ptr);
     239                 :             :             }
     240                 :             :         }
     241                 :       19258 :     }
     242                 :             : };
     243                 :             : 
     244                 :             : } // namespace
     245                 :             : 
     246         [ +  - ]:        3111 : FUZZ_TARGET(txgraph)
     247                 :             : {
     248                 :             :     // This is a big simulation test for TxGraph, which performs a fuzz-derived sequence of valid
     249                 :             :     // operations on a TxGraph instance, as well as on a simpler (mostly) reimplementation (see
     250                 :             :     // SimTxGraph above), comparing the outcome of functions that return a result, and finally
     251                 :             :     // performing a full comparison between the two.
     252                 :             : 
     253                 :        2669 :     SeedRandomStateForTest(SeedRand::ZEROS);
     254                 :        2669 :     FuzzedDataProvider provider(buffer.data(), buffer.size());
     255                 :             : 
     256                 :             :     /** Internal test RNG, used only for decisions which would require significant amount of data
     257                 :             :      *  to be read from the provider, without realistically impacting test sensitivity. */
     258                 :        2669 :     InsecureRandomContext rng(0xdecade2009added + buffer.size());
     259                 :             : 
     260                 :             :     /** Variable used whenever an empty TxGraph::Ref is needed. */
     261                 :        2669 :     TxGraph::Ref empty_ref;
     262                 :             : 
     263                 :             :     // Decide the maximum number of transactions per cluster we will use in this simulation.
     264                 :        2669 :     auto max_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
     265                 :             : 
     266                 :             :     // Construct a real graph, and a vector of simulated graphs (main, and possibly staging).
     267                 :        2669 :     auto real = MakeTxGraph(max_count);
     268                 :        2669 :     std::vector<SimTxGraph> sims;
     269         [ +  - ]:        2669 :     sims.reserve(2);
     270         [ +  - ]:        2669 :     sims.emplace_back(max_count);
     271                 :             : 
     272                 :             :     /** Struct encapsulating information about a BlockBuilder that's currently live. */
     273                 :        9483 :     struct BlockBuilderData
     274                 :             :     {
     275                 :             :         /** BlockBuilder object from real. */
     276                 :             :         std::unique_ptr<TxGraph::BlockBuilder> builder;
     277                 :             :         /** The set of transactions marked as included in *builder. */
     278                 :             :         SimTxGraph::SetType included;
     279                 :             :         /** The set of transactions marked as included or skipped in *builder. */
     280                 :             :         SimTxGraph::SetType done;
     281                 :             :         /** The last chunk feerate returned by *builder. IsEmpty() if none yet. */
     282                 :             :         FeePerWeight last_feerate;
     283                 :             : 
     284                 :        3999 :         BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
     285                 :             :     };
     286                 :             : 
     287                 :             :     /** Currently active block builders. */
     288                 :        2669 :     std::vector<BlockBuilderData> block_builders;
     289                 :             : 
     290                 :             :     /** Function to pick any Ref (for either sim in sims: from sim.simmap or sim.removed, or the
     291                 :             :      *  empty Ref). */
     292                 :      353647 :     auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
     293         [ +  + ]:      350978 :         size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
     294                 :             :         /** The number of possible choices. */
     295         [ +  + ]:      350978 :         size_t choices = tx_count[0] + sims[0].removed.size() + 1;
     296         [ +  + ]:      350978 :         if (sims.size() == 2) {
     297                 :      139183 :             tx_count[1] = sims[1].GetTransactionCount();
     298                 :      139183 :             choices += tx_count[1] + sims[1].removed.size();
     299                 :             :         }
     300                 :             :         /** Pick one of them. */
     301                 :      350978 :         auto choice = provider.ConsumeIntegralInRange<size_t>(0, choices - 1);
     302                 :             :         // Consider both main and (if it exists) staging.
     303         [ +  + ]:      513623 :         for (size_t level = 0; level < sims.size(); ++level) {
     304         [ +  + ]:      426906 :             auto& sim = sims[level];
     305         [ +  + ]:      426906 :             if (choice < tx_count[level]) {
     306                 :             :                 // Return from graph.
     307         [ +  - ]:     1333360 :                 for (auto i : sim.graph.Positions()) {
     308         [ +  + ]:     1333360 :                     if (choice == 0) return sim.GetRef(i);
     309                 :     1081752 :                     --choice;
     310                 :             :                 }
     311                 :           0 :                 assert(false);
     312                 :             :             } else {
     313                 :      175298 :                 choice -= tx_count[level];
     314                 :             :             }
     315         [ +  + ]:      175298 :             if (choice < sim.removed.size()) {
     316                 :             :                 // Return from removed.
     317                 :       12653 :                 return sim.removed[choice].get();
     318                 :             :             } else {
     319                 :      162645 :                 choice -= sim.removed.size();
     320                 :             :             }
     321                 :             :         }
     322                 :             :         // Return empty.
     323         [ -  + ]:       86717 :         assert(choice == 0);
     324                 :       86717 :         return &empty_ref;
     325                 :        2669 :     };
     326                 :             : 
     327                 :             :     /** Function to construct the correct fee-size diagram a real graph has based on its graph
     328                 :             :      *  order (as reported by GetCluster(), so it works for both main and staging). */
     329                 :        6082 :     auto get_diagram_fn = [&](bool main_only) -> std::vector<FeeFrac> {
     330         [ +  + ]:        3413 :         int level = main_only ? 0 : sims.size() - 1;
     331                 :        3413 :         auto& sim = sims[level];
     332                 :             :         // For every transaction in the graph, request its cluster, and throw them into a set.
     333                 :        3413 :         std::set<std::vector<TxGraph::Ref*>> clusters;
     334         [ +  + ]:       52628 :         for (auto i : sim.graph.Positions()) {
     335                 :       49215 :             auto ref = sim.GetRef(i);
     336         [ +  - ]:       98430 :             clusters.insert(real->GetCluster(*ref, main_only));
     337                 :             :         }
     338                 :             :         // Compute the chunkings of each (deduplicated) cluster.
     339                 :        3413 :         size_t num_tx{0};
     340                 :        3413 :         std::vector<FeeFrac> chunk_feerates;
     341         [ +  + ]:       46927 :         for (const auto& cluster : clusters) {
     342         [ +  - ]:       43514 :             num_tx += cluster.size();
     343                 :       43514 :             std::vector<SimTxGraph::Pos> linearization;
     344         [ +  - ]:       43514 :             linearization.reserve(cluster.size());
     345   [ +  -  +  + ]:       92729 :             for (auto refptr : cluster) linearization.push_back(sim.Find(refptr));
     346         [ +  + ]:       90759 :             for (const FeeFrac& chunk_feerate : ChunkLinearization(sim.graph, linearization)) {
     347         [ +  - ]:       47245 :                 chunk_feerates.push_back(chunk_feerate);
     348                 :       43514 :             }
     349                 :       43514 :         }
     350                 :             :         // Verify the number of transactions after deduplicating clusters. This implicitly verifies
     351                 :             :         // that GetCluster on each element of a cluster reports the cluster transactions in the same
     352                 :             :         // order.
     353         [ -  + ]:        3413 :         assert(num_tx == sim.GetTransactionCount());
     354                 :             :         // Sort by feerate only, since violating topological constraints within same-feerate
     355                 :             :         // chunks won't affect diagram comparisons.
     356                 :        3413 :         std::sort(chunk_feerates.begin(), chunk_feerates.end(), std::greater{});
     357                 :        3413 :         return chunk_feerates;
     358                 :        6082 :     };
     359                 :             : 
     360   [ +  +  +  + ]:      340175 :     LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
     361                 :             :         // Read a one-byte command.
     362                 :      337506 :         int command = provider.ConsumeIntegral<uint8_t>();
     363                 :      337506 :         int orig_command = command;
     364                 :             : 
     365                 :             :         // Treat the lowest bit of a command as a flag (which selects a variant of some of the
     366                 :             :         // operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
     367                 :             :         // the rest of the bits in command.
     368                 :      337506 :         bool alt = command & 1;
     369                 :      337506 :         bool use_main = command & 2;
     370                 :      337506 :         command >>= 2;
     371                 :             : 
     372                 :             :         /** Use the bottom 2 bits of command to select an entry in the block_builders vector (if
     373                 :             :          *  any). These use the same bits as alt/use_main, so don't use those in actions below
     374                 :             :          *  where builder_idx is used as well. */
     375         [ +  + ]:      337506 :         int builder_idx = block_builders.empty() ? -1 : int((orig_command & 3) % block_builders.size());
     376                 :             : 
     377                 :             :         // Provide convenient aliases for the top simulated graph (main, or staging if it exists),
     378                 :             :         // one for the simulated graph selected based on use_main (for operations that can operate
     379                 :             :         // on both graphs), and one that always refers to the main graph.
     380                 :      337506 :         auto& top_sim = sims.back();
     381         [ +  + ]:      337506 :         auto& sel_sim = use_main ? sims[0] : top_sim;
     382                 :      337506 :         auto& main_sim = sims[0];
     383                 :             : 
     384                 :             :         // Keep decrementing command for each applicable operation, until one is hit. Multiple
     385                 :             :         // iterations may be necessary.
     386                 :      572100 :         while (true) {
     387   [ +  +  +  +  :      572100 :             if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
             +  +  +  + ]
     388                 :             :                 // AddTransaction.
     389                 :       84603 :                 int64_t fee;
     390                 :       84603 :                 int32_t size;
     391         [ +  + ]:       84603 :                 if (alt) {
     392                 :             :                     // If alt is true, pick fee and size from the entire range.
     393                 :        6405 :                     fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     394                 :        6405 :                     size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
     395                 :             :                 } else {
     396                 :             :                     // Otherwise, use smaller range which consume fewer fuzz input bytes, as just
     397                 :             :                     // these are likely sufficient to trigger all interesting code paths already.
     398                 :       78198 :                     fee = provider.ConsumeIntegral<uint8_t>();
     399                 :       78198 :                     size = provider.ConsumeIntegral<uint8_t>() + 1;
     400                 :             :                 }
     401                 :       84603 :                 FeePerWeight feerate{fee, size};
     402                 :             :                 // Create a real TxGraph::Ref.
     403                 :       84603 :                 auto ref = real->AddTransaction(feerate);
     404                 :             :                 // Create a shared_ptr place in the simulation to put the Ref in.
     405         [ +  - ]:       84603 :                 auto ref_loc = top_sim.AddTransaction(feerate);
     406                 :             :                 // Move it in place.
     407                 :       84603 :                 *ref_loc = std::move(ref);
     408                 :       84603 :                 break;
     409   [ +  +  +  +  :      572100 :             } else if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
             +  +  +  + ]
     410                 :             :                 // AddDependency.
     411                 :       42583 :                 auto par = pick_fn();
     412                 :       42583 :                 auto chl = pick_fn();
     413                 :       42583 :                 auto pos_par = top_sim.Find(par);
     414                 :       42583 :                 auto pos_chl = top_sim.Find(chl);
     415         [ +  + ]:       42583 :                 if (pos_par != SimTxGraph::MISSING && pos_chl != SimTxGraph::MISSING) {
     416                 :             :                     // Determine if adding this would introduce a cycle (not allowed by TxGraph),
     417                 :             :                     // and if so, skip.
     418         [ +  + ]:       33318 :                     if (top_sim.graph.Ancestors(pos_par)[pos_chl]) break;
     419                 :             :                 }
     420                 :       34492 :                 top_sim.AddDependency(par, chl);
     421                 :       34492 :                 real->AddDependency(*par, *chl);
     422                 :       34492 :                 break;
     423   [ +  +  +  +  :      444914 :             } else if ((block_builders.empty() || sims.size() > 1) && top_sim.removed.size() < 100 && command-- == 0) {
             +  -  +  + ]
     424                 :             :                 // RemoveTransaction. Either all its ancestors or all its descendants are also
     425                 :             :                 // removed (if any), to make sure TxGraph's reordering of removals and dependencies
     426                 :             :                 // has no effect.
     427                 :        7850 :                 std::vector<TxGraph::Ref*> to_remove;
     428         [ +  - ]:        7850 :                 to_remove.push_back(pick_fn());
     429         [ +  - ]:        7850 :                 top_sim.IncludeAncDesc(to_remove, alt);
     430                 :             :                 // The order in which these ancestors/descendants are removed should not matter;
     431                 :             :                 // randomly shuffle them.
     432                 :        7850 :                 std::shuffle(to_remove.begin(), to_remove.end(), rng);
     433         [ +  + ]:       18040 :                 for (TxGraph::Ref* ptr : to_remove) {
     434                 :       10190 :                     real->RemoveTransaction(*ptr);
     435         [ +  - ]:       10190 :                     top_sim.RemoveTransaction(ptr);
     436                 :             :                 }
     437                 :        7850 :                 break;
     438   [ +  +  +  + ]:      444914 :             } else if (sel_sim.removed.size() > 0 && command-- == 0) {
     439                 :             :                 // ~Ref (of an already-removed transaction). Destroying a TxGraph::Ref has an
     440                 :             :                 // observable effect on the TxGraph it refers to, so this simulation permits doing
     441                 :             :                 // so separately from other actions on TxGraph.
     442                 :             : 
     443                 :             :                 // Pick a Ref of sel_sim.removed to destroy. Note that the same Ref may still occur
     444                 :             :                 // in the other graph, and thus not actually trigger ~Ref yet (which is exactly
     445                 :             :                 // what we want, as destroying Refs is only allowed when it does not refer to an
     446                 :             :                 // existing transaction in either graph).
     447                 :        2055 :                 auto removed_pos = provider.ConsumeIntegralInRange<size_t>(0, sel_sim.removed.size() - 1);
     448         [ +  + ]:        2055 :                 if (removed_pos != sel_sim.removed.size() - 1) {
     449                 :         688 :                     std::swap(sel_sim.removed[removed_pos], sel_sim.removed.back());
     450                 :             :                 }
     451                 :        2055 :                 sel_sim.removed.pop_back();
     452                 :        2055 :                 break;
     453   [ +  +  +  + ]:      435009 :             } else if (block_builders.empty() && command-- == 0) {
     454                 :             :                 // ~Ref (of any transaction).
     455                 :        8924 :                 std::vector<TxGraph::Ref*> to_destroy;
     456         [ +  - ]:        8924 :                 to_destroy.push_back(pick_fn());
     457                 :        9965 :                 while (true) {
     458                 :             :                     // Keep adding either the ancestors or descendants the already picked
     459                 :             :                     // transactions have in both graphs (main and staging) combined. Destroying
     460                 :             :                     // will trigger deletions in both, so to have consistent TxGraph behavior, the
     461                 :             :                     // set must be closed under ancestors, or descendants, in both graphs.
     462                 :        9965 :                     auto old_size = to_destroy.size();
     463   [ +  -  +  + ]:       21373 :                     for (auto& sim : sims) sim.IncludeAncDesc(to_destroy, alt);
     464         [ +  + ]:        9965 :                     if (to_destroy.size() == old_size) break;
     465                 :             :                 }
     466                 :             :                 // The order in which these ancestors/descendants are destroyed should not matter;
     467                 :             :                 // randomly shuffle them.
     468                 :        8924 :                 std::shuffle(to_destroy.begin(), to_destroy.end(), rng);
     469         [ +  + ]:       20196 :                 for (TxGraph::Ref* ptr : to_destroy) {
     470         [ +  + ]:       24309 :                     for (size_t level = 0; level < sims.size(); ++level) {
     471                 :       13037 :                         sims[level].DestroyTransaction(ptr, level == sims.size() - 1);
     472                 :             :                     }
     473                 :             :                 }
     474                 :        8924 :                 break;
     475   [ +  +  +  + ]:      435009 :             } else if (block_builders.empty() && command-- == 0) {
     476                 :             :                 // SetTransactionFee.
     477                 :        3110 :                 int64_t fee;
     478         [ +  + ]:        3110 :                 if (alt) {
     479                 :         965 :                     fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     480                 :             :                 } else {
     481                 :        2145 :                     fee = provider.ConsumeIntegral<uint8_t>();
     482                 :             :                 }
     483                 :        3110 :                 auto ref = pick_fn();
     484                 :        3110 :                 real->SetTransactionFee(*ref, fee);
     485         [ +  + ]:        7467 :                 for (auto& sim : sims) {
     486                 :        4357 :                     sim.SetTransactionFee(ref, fee);
     487                 :             :                 }
     488                 :             :                 break;
     489         [ +  + ]:      422975 :             } else if (command-- == 0) {
     490                 :             :                 // GetTransactionCount.
     491         [ -  + ]:       53457 :                 assert(real->GetTransactionCount(use_main) == sel_sim.GetTransactionCount());
     492                 :             :                 break;
     493         [ +  + ]:      369518 :             } else if (command-- == 0) {
     494                 :             :                 // Exists.
     495                 :       11257 :                 auto ref = pick_fn();
     496                 :       11257 :                 bool exists = real->Exists(*ref, use_main);
     497                 :       11257 :                 bool should_exist = sel_sim.Find(ref) != SimTxGraph::MISSING;
     498         [ -  + ]:       11257 :                 assert(exists == should_exist);
     499                 :             :                 break;
     500         [ +  + ]:      358261 :             } else if (command-- == 0) {
     501                 :             :                 // IsOversized.
     502         [ -  + ]:       18849 :                 assert(sel_sim.IsOversized() == real->IsOversized(use_main));
     503                 :             :                 break;
     504         [ +  + ]:      339412 :             } else if (command-- == 0) {
     505                 :             :                 // GetIndividualFeerate.
     506                 :        5903 :                 auto ref = pick_fn();
     507                 :        5903 :                 auto feerate = real->GetIndividualFeerate(*ref);
     508                 :        5903 :                 bool found{false};
     509         [ +  + ]:       13511 :                 for (auto& sim : sims) {
     510                 :        7608 :                     auto simpos = sim.Find(ref);
     511         [ +  + ]:        7608 :                     if (simpos != SimTxGraph::MISSING) {
     512                 :        5134 :                         found = true;
     513         [ +  - ]:       12742 :                         assert(feerate == sim.graph.FeeRate(simpos));
     514                 :             :                     }
     515                 :             :                 }
     516   [ +  +  -  + ]:        5903 :                 if (!found) assert(feerate.IsEmpty());
     517                 :             :                 break;
     518   [ +  +  +  + ]:      333509 :             } else if (!main_sim.IsOversized() && command-- == 0) {
     519                 :             :                 // GetMainChunkFeerate.
     520                 :        8024 :                 auto ref = pick_fn();
     521                 :        8024 :                 auto feerate = real->GetMainChunkFeerate(*ref);
     522                 :        8024 :                 auto simpos = main_sim.Find(ref);
     523         [ +  + ]:        8024 :                 if (simpos == SimTxGraph::MISSING) {
     524         [ -  + ]:        2956 :                     assert(feerate.IsEmpty());
     525                 :             :                 } else {
     526                 :             :                     // Just do some quick checks that the reported value is in range. A full
     527                 :             :                     // recomputation of expected chunk feerates is done at the end.
     528         [ -  + ]:        5068 :                     assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
     529         [ -  + ]:        5068 :                     assert(feerate.size <= main_sim.SumAll().size);
     530                 :             :                 }
     531                 :             :                 break;
     532   [ +  +  +  + ]:      325485 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
     533                 :             :                 // GetAncestors/GetDescendants.
     534                 :        5066 :                 auto ref = pick_fn();
     535         [ +  + ]:        5066 :                 auto result = alt ? real->GetDescendants(*ref, use_main)
     536                 :        5066 :                                   : real->GetAncestors(*ref, use_main);
     537         [ -  + ]:        5066 :                 assert(result.size() <= max_count);
     538                 :        5066 :                 auto result_set = sel_sim.MakeSet(result);
     539         [ -  + ]:        5066 :                 assert(result.size() == result_set.Count());
     540                 :        5066 :                 auto expect_set = sel_sim.GetAncDesc(ref, alt);
     541         [ -  + ]:        5066 :                 assert(result_set == expect_set);
     542                 :        5066 :                 break;
     543   [ +  +  +  + ]:      325485 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
     544                 :             :                 // GetAncestorsUnion/GetDescendantsUnion.
     545                 :        4314 :                 std::vector<TxGraph::Ref*> refs;
     546                 :             :                 // Gather a list of up to 15 Ref pointers.
     547                 :        4314 :                 auto count = provider.ConsumeIntegralInRange<size_t>(0, 15);
     548         [ +  - ]:        4314 :                 refs.resize(count);
     549         [ +  + ]:       30306 :                 for (size_t i = 0; i < count; ++i) {
     550                 :       25992 :                     refs[i] = pick_fn();
     551                 :             :                 }
     552                 :             :                 // Their order should not matter, shuffle them.
     553                 :        4314 :                 std::shuffle(refs.begin(), refs.end(), rng);
     554                 :             :                 // Invoke the real function, and convert to SimPos set.
     555         [ +  + ]:        4314 :                 auto result = alt ? real->GetDescendantsUnion(refs, use_main)
     556                 :        4314 :                                   : real->GetAncestorsUnion(refs, use_main);
     557                 :        4314 :                 auto result_set = sel_sim.MakeSet(result);
     558         [ -  + ]:        4314 :                 assert(result.size() == result_set.Count());
     559                 :             :                 // Compute the expected result.
     560                 :        4314 :                 SimTxGraph::SetType expect_set;
     561         [ +  + ]:       30306 :                 for (TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
     562                 :             :                 // Compare.
     563         [ -  + ]:        4314 :                 assert(result_set == expect_set);
     564                 :        4314 :                 break;
     565   [ +  +  +  + ]:      320419 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
     566                 :             :                 // GetCluster.
     567                 :        7726 :                 auto ref = pick_fn();
     568                 :        7726 :                 auto result = real->GetCluster(*ref, use_main);
     569                 :             :                 // Check cluster count limit.
     570         [ -  + ]:        7726 :                 assert(result.size() <= max_count);
     571                 :             :                 // Require the result to be topologically valid and not contain duplicates.
     572                 :        7726 :                 auto left = sel_sim.graph.Positions();
     573         [ +  + ]:       14371 :                 for (auto refptr : result) {
     574                 :        6645 :                     auto simpos = sel_sim.Find(refptr);
     575         [ -  + ]:        6645 :                     assert(simpos != SimTxGraph::MISSING);
     576         [ -  + ]:        6645 :                     assert(left[simpos]);
     577                 :        6645 :                     left.Reset(simpos);
     578         [ -  + ]:       13290 :                     assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
     579                 :             :                 }
     580                 :             :                 // Require the set to be connected.
     581                 :        7726 :                 auto result_set = sel_sim.MakeSet(result);
     582         [ -  + ]:        7726 :                 assert(sel_sim.graph.IsConnected(result_set));
     583                 :             :                 // If ref exists, the result must contain it. If not, it must be empty.
     584                 :        7726 :                 auto simpos = sel_sim.Find(ref);
     585         [ +  + ]:        7726 :                 if (simpos != SimTxGraph::MISSING) {
     586         [ -  + ]:        3809 :                     assert(result_set[simpos]);
     587                 :             :                 } else {
     588         [ -  + ]:        3917 :                     assert(result_set.None());
     589                 :             :                 }
     590                 :             :                 // Require the set not to have ancestors or descendants outside of it.
     591         [ +  + ]:       14371 :                 for (auto i : result_set) {
     592         [ -  + ]:       13290 :                     assert(sel_sim.graph.Ancestors(i).IsSubsetOf(result_set));
     593         [ -  + ]:        6645 :                     assert(sel_sim.graph.Descendants(i).IsSubsetOf(result_set));
     594                 :             :                 }
     595                 :        7726 :                 break;
     596         [ +  + ]:      316105 :             } else if (command-- == 0) {
     597                 :             :                 // HaveStaging.
     598         [ -  + ]:       10398 :                 assert((sims.size() == 2) == real->HaveStaging());
     599                 :             :                 break;
     600   [ +  +  +  + ]:      297981 :             } else if (sims.size() < 2 && command-- == 0) {
     601                 :             :                 // StartStaging.
     602         [ +  - ]:        5080 :                 sims.emplace_back(sims.back());
     603                 :        5080 :                 sims.back().modified = SimTxGraph::SetType{};
     604                 :        5080 :                 real->StartStaging();
     605                 :        5080 :                 break;
     606   [ +  +  +  +  :      292901 :             } else if (block_builders.empty() && sims.size() > 1 && command-- == 0) {
                   +  + ]
     607                 :             :                 // CommitStaging.
     608                 :        2191 :                 real->CommitStaging();
     609                 :        2191 :                 sims.erase(sims.begin());
     610                 :             :                 break;
     611   [ +  +  +  + ]:      290710 :             } else if (sims.size() > 1 && command-- == 0) {
     612                 :             :                 // AbortStaging.
     613                 :        1597 :                 real->AbortStaging();
     614                 :        1597 :                 sims.pop_back();
     615                 :             :                 // Reset the cached oversized value (if TxGraph::Ref destructions triggered
     616                 :             :                 // removals of main transactions while staging was active, then aborting will
     617                 :             :                 // cause it to be re-evaluated in TxGraph).
     618         [ +  - ]:      339103 :                 sims.back().oversized = std::nullopt;
     619                 :             :                 break;
     620   [ +  +  +  + ]:      289113 :             } else if (!main_sim.IsOversized() && command-- == 0) {
     621                 :             :                 // CompareMainOrder.
     622                 :        7556 :                 auto ref_a = pick_fn();
     623                 :        7556 :                 auto ref_b = pick_fn();
     624                 :        7556 :                 auto sim_a = main_sim.Find(ref_a);
     625                 :        7556 :                 auto sim_b = main_sim.Find(ref_b);
     626                 :             :                 // Both transactions must exist in the main graph.
     627         [ +  + ]:        7556 :                 if (sim_a == SimTxGraph::MISSING || sim_b == SimTxGraph::MISSING) break;
     628                 :        2619 :                 auto cmp = real->CompareMainOrder(*ref_a, *ref_b);
     629                 :             :                 // Distinct transactions have distinct places.
     630   [ +  +  -  + ]:        2619 :                 if (sim_a != sim_b) assert(cmp != 0);
     631                 :             :                 // Ancestors go before descendants.
     632   [ +  +  -  + ]:        2619 :                 if (main_sim.graph.Ancestors(sim_a)[sim_b]) assert(cmp >= 0);
     633   [ +  +  -  + ]:        2619 :                 if (main_sim.graph.Descendants(sim_a)[sim_b]) assert(cmp <= 0);
     634                 :             :                 // Do not verify consistency with chunk feerates, as we cannot easily determine
     635                 :             :                 // these here without making more calls to real, which could affect its internal
     636                 :             :                 // state. A full comparison is done at the end.
     637                 :             :                 break;
     638   [ +  +  +  + ]:      281557 :             } else if (!sel_sim.IsOversized() && command-- == 0) {
     639                 :             :                 // CountDistinctClusters.
     640                 :        7382 :                 std::vector<TxGraph::Ref*> refs;
     641                 :             :                 // Gather a list of up to 15 (or up to 255) Ref pointers.
     642         [ +  + ]:       11547 :                 auto count = provider.ConsumeIntegralInRange<size_t>(0, alt ? 255 : 15);
     643         [ +  - ]:        7382 :                 refs.resize(count);
     644         [ +  + ]:      174230 :                 for (size_t i = 0; i < count; ++i) {
     645                 :      166848 :                     refs[i] = pick_fn();
     646                 :             :                 }
     647                 :             :                 // Their order should not matter, shuffle them.
     648                 :        7382 :                 std::shuffle(refs.begin(), refs.end(), rng);
     649                 :             :                 // Invoke the real function.
     650                 :        7382 :                 auto result = real->CountDistinctClusters(refs, use_main);
     651                 :             :                 // Build a set with representatives of the clusters the Refs occur in in the
     652                 :             :                 // simulated graph. For each, remember the lowest-index transaction SimPos in the
     653                 :             :                 // cluster.
     654                 :        7382 :                 SimTxGraph::SetType sim_reps;
     655         [ +  + ]:      174230 :                 for (auto ref : refs) {
     656                 :             :                     // Skip Refs that do not occur in the simulated graph.
     657                 :      166848 :                     auto simpos = sel_sim.Find(ref);
     658         [ +  + ]:      166848 :                     if (simpos == SimTxGraph::MISSING) continue;
     659                 :             :                     // Find the component that includes ref.
     660                 :       94744 :                     auto component = sel_sim.graph.GetConnectedComponent(sel_sim.graph.Positions(), simpos);
     661                 :             :                     // Remember the lowest-index SimPos in component, as a representative for it.
     662         [ -  + ]:      189488 :                     assert(component.Any());
     663                 :       94744 :                     sim_reps.Set(component.First());
     664                 :             :                 }
     665                 :             :                 // Compare the number of deduplicated representatives with the value returned by
     666                 :             :                 // the real function.
     667         [ -  + ]:        7382 :                 assert(result == sim_reps.Count());
     668                 :        7382 :                 break;
     669         [ +  + ]:      281557 :             } else if (command-- == 0) {
     670                 :             :                 // DoWork.
     671                 :       13461 :                 real->DoWork();
     672                 :       13461 :                 break;
     673   [ +  +  +  +  :      260714 :             } else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) {
             +  +  +  + ]
     674                 :             :                 // GetMainStagingDiagrams()
     675                 :        5147 :                 auto [real_main_diagram, real_staged_diagram] = real->GetMainStagingDiagrams();
     676                 :        5147 :                 auto real_sum_main = std::accumulate(real_main_diagram.begin(), real_main_diagram.end(), FeeFrac{});
     677                 :        5147 :                 auto real_sum_staged = std::accumulate(real_staged_diagram.begin(), real_staged_diagram.end(), FeeFrac{});
     678                 :        5147 :                 auto real_gain = real_sum_staged - real_sum_main;
     679         [ +  - ]:        5147 :                 auto sim_gain = sims[1].SumAll() - sims[0].SumAll();
     680                 :             :                 // Just check that the total fee gained/lost and size gained/lost according to the
     681                 :             :                 // diagram matches the difference in these values in the simulated graph. A more
     682                 :             :                 // complete check of the GetMainStagingDiagrams result is performed at the end.
     683         [ +  - ]:        5147 :                 assert(sim_gain == real_gain);
     684                 :             :                 // Check that the feerates in each diagram are monotonically decreasing.
     685         [ +  + ]:       12012 :                 for (size_t i = 1; i < real_main_diagram.size(); ++i) {
     686         [ -  + ]:        6865 :                     assert(FeeRateCompare(real_main_diagram[i], real_main_diagram[i - 1]) <= 0);
     687                 :             :                 }
     688         [ +  + ]:       44468 :                 for (size_t i = 1; i < real_staged_diagram.size(); ++i) {
     689         [ -  + ]:       39321 :                     assert(FeeRateCompare(real_staged_diagram[i], real_staged_diagram[i - 1]) <= 0);
     690                 :             :                 }
     691                 :        5147 :                 break;
     692   [ +  +  +  +  :      260714 :             } else if (block_builders.size() < 4 && !main_sim.IsOversized() && command-- == 0) {
                   +  + ]
     693                 :             :                 // GetBlockBuilder.
     694         [ +  - ]:        3999 :                 block_builders.emplace_back(real->GetBlockBuilder());
     695                 :        3999 :                 break;
     696   [ +  +  +  + ]:      251568 :             } else if (!block_builders.empty() && command-- == 0) {
     697                 :             :                 // ~BlockBuilder.
     698                 :        1850 :                 block_builders.erase(block_builders.begin() + builder_idx);
     699                 :             :                 break;
     700   [ +  +  +  + ]:      249718 :             } else if (!block_builders.empty() && command-- == 0) {
     701                 :             :                 // BlockBuilder::GetCurrentChunk, followed by Include/Skip.
     702                 :        3593 :                 auto& builder_data = block_builders[builder_idx];
     703                 :        3593 :                 auto new_included = builder_data.included;
     704                 :        3593 :                 auto new_done = builder_data.done;
     705                 :        3593 :                 auto chunk = builder_data.builder->GetCurrentChunk();
     706         [ +  + ]:        3593 :                 if (chunk) {
     707                 :             :                     // Chunk feerates must be monotonously decreasing.
     708         [ +  + ]:         877 :                     if (!builder_data.last_feerate.IsEmpty()) {
     709         [ -  + ]:         307 :                         assert(!(chunk->second >> builder_data.last_feerate));
     710                 :             :                     }
     711                 :         877 :                     builder_data.last_feerate = chunk->second;
     712                 :             :                     // Verify the contents of GetCurrentChunk.
     713                 :         877 :                     FeePerWeight sum_feerate;
     714         [ +  + ]:        1784 :                     for (TxGraph::Ref* ref : chunk->first) {
     715                 :             :                         // Each transaction in the chunk must exist in the main graph.
     716                 :         907 :                         auto simpos = main_sim.Find(ref);
     717         [ -  + ]:         907 :                         assert(simpos != SimTxGraph::MISSING);
     718                 :             :                         // Verify the claimed chunk feerate.
     719                 :         907 :                         sum_feerate += main_sim.graph.FeeRate(simpos);
     720                 :             :                         // Make sure no transaction is reported twice.
     721         [ -  + ]:         907 :                         assert(!new_done[simpos]);
     722                 :         907 :                         new_done.Set(simpos);
     723                 :             :                         // The concatenation of all included transactions must be topologically valid.
     724                 :         907 :                         new_included.Set(simpos);
     725         [ -  + ]:        1814 :                         assert(main_sim.graph.Ancestors(simpos).IsSubsetOf(new_included));
     726                 :             :                     }
     727         [ +  - ]:         877 :                     assert(sum_feerate == chunk->second);
     728                 :             :                 } else {
     729                 :             :                     // When we reach the end, if nothing was skipped, the entire graph should have
     730                 :             :                     // been reported.
     731         [ +  + ]:        2716 :                     if (builder_data.done == builder_data.included) {
     732         [ -  + ]:        2136 :                         assert(builder_data.done.Count() == main_sim.GetTransactionCount());
     733                 :             :                     }
     734                 :             :                 }
     735                 :             :                 // Possibly invoke GetCurrentChunk() again, which should give the same result.
     736         [ +  + ]:        3593 :                 if ((orig_command % 7) >= 5) {
     737                 :         502 :                     auto chunk2 = builder_data.builder->GetCurrentChunk();
     738         [ -  + ]:         502 :                     assert(chunk == chunk2);
     739                 :         502 :                 }
     740                 :             :                 // Skip or include.
     741         [ +  + ]:        3593 :                 if ((orig_command % 5) >= 3) {
     742                 :             :                     // Skip.
     743                 :         753 :                     builder_data.builder->Skip();
     744                 :             :                 } else {
     745                 :             :                     // Include.
     746                 :        2840 :                     builder_data.builder->Include();
     747                 :        2840 :                     builder_data.included = new_included;
     748                 :             :                 }
     749                 :        3593 :                 builder_data.done = new_done;
     750                 :        3593 :                 break;
     751   [ +  +  +  +  :      434205 :             } else if (!main_sim.IsOversized() && command-- == 0) {
                   +  + ]
     752                 :             :                 // GetWorstMainChunk.
     753         [ +  + ]:       11531 :                 auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
     754                 :             :                 // Just do some sanity checks here. Consistency with GetBlockBuilder is checked
     755                 :             :                 // below.
     756         [ +  + ]:       11531 :                 if (main_sim.GetTransactionCount() == 0) {
     757         [ -  + ]:        5773 :                     assert(worst_chunk.empty());
     758         [ -  + ]:        5773 :                     assert(worst_chunk_feerate.IsEmpty());
     759                 :             :                 } else {
     760         [ -  + ]:        5758 :                     assert(!worst_chunk.empty());
     761                 :        5758 :                     SimTxGraph::SetType done;
     762                 :        5758 :                     FeePerWeight sum;
     763         [ +  + ]:       11720 :                     for (TxGraph::Ref* ref : worst_chunk) {
     764                 :             :                         // Each transaction in the chunk must exist in the main graph.
     765                 :        5962 :                         auto simpos = main_sim.Find(ref);
     766         [ -  + ]:        5962 :                         assert(simpos != SimTxGraph::MISSING);
     767                 :        5962 :                         sum += main_sim.graph.FeeRate(simpos);
     768                 :             :                         // Make sure the chunk contains no duplicate transactions.
     769         [ -  + ]:        5962 :                         assert(!done[simpos]);
     770                 :        5962 :                         done.Set(simpos);
     771                 :             :                         // All elements are preceded by all their descendants.
     772         [ -  + ]:       11924 :                         assert(main_sim.graph.Descendants(simpos).IsSubsetOf(done));
     773                 :             :                     }
     774         [ +  - ]:        5758 :                     assert(sum == worst_chunk_feerate);
     775                 :             :                 }
     776                 :       11531 :                 break;
     777                 :       11531 :             }
     778                 :             :         }
     779                 :             :     }
     780                 :             : 
     781                 :             :     // After running all modifications, perform an internal sanity check (before invoking
     782                 :             :     // inspectors that may modify the internal state).
     783         [ +  - ]:        2669 :     real->SanityCheck();
     784                 :             : 
     785         [ +  + ]:        2669 :     if (!sims[0].IsOversized()) {
     786                 :             :         // If the main graph is not oversized, verify the total ordering implied by
     787                 :             :         // CompareMainOrder.
     788                 :             :         // First construct two distinct randomized permutations of the positions in sims[0].
     789                 :        2316 :         std::vector<SimTxGraph::Pos> vec1;
     790   [ +  -  +  + ]:       27746 :         for (auto i : sims[0].graph.Positions()) vec1.push_back(i);
     791                 :        2316 :         std::shuffle(vec1.begin(), vec1.end(), rng);
     792         [ +  - ]:        2316 :         auto vec2 = vec1;
     793                 :        2316 :         std::shuffle(vec2.begin(), vec2.end(), rng);
     794         [ +  + ]:        2316 :         if (vec1 == vec2) std::next_permutation(vec2.begin(), vec2.end());
     795                 :             :         // Sort both according to CompareMainOrder. By having randomized starting points, the order
     796                 :             :         // of CompareMainOrder invocations is somewhat randomized as well.
     797                 :      287286 :         auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
     798                 :      284970 :             return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
     799                 :        2316 :         };
     800                 :        2316 :         std::sort(vec1.begin(), vec1.end(), cmp);
     801                 :        2316 :         std::sort(vec2.begin(), vec2.end(), cmp);
     802                 :             : 
     803                 :             :         // Verify the resulting orderings are identical. This could only fail if the ordering was
     804                 :             :         // not total.
     805         [ -  + ]:        2316 :         assert(vec1 == vec2);
     806                 :             : 
     807                 :             :         // Verify that the ordering is topological.
     808                 :        2316 :         auto todo = sims[0].graph.Positions();
     809         [ +  + ]:       27746 :         for (auto i : vec1) {
     810                 :       25430 :             todo.Reset(i);
     811         [ -  + ]:       50860 :             assert(!sims[0].graph.Ancestors(i).Overlaps(todo));
     812                 :             :         }
     813         [ -  + ]:        2316 :         assert(todo.None());
     814                 :             : 
     815                 :             :         // For every transaction in the total ordering, find a random one before it and after it,
     816                 :             :         // and compare their chunk feerates, which must be consistent with the ordering.
     817         [ +  + ]:       27746 :         for (size_t pos = 0; pos < vec1.size(); ++pos) {
     818                 :       25430 :             auto pos_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[pos]));
     819         [ +  + ]:       25430 :             if (pos > 0) {
     820                 :       23453 :                 size_t before = rng.randrange<size_t>(pos);
     821                 :       23453 :                 auto before_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[before]));
     822         [ -  + ]:       23453 :                 assert(FeeRateCompare(before_feerate, pos_feerate) >= 0);
     823                 :             :             }
     824         [ +  + ]:       25430 :             if (pos + 1 < vec1.size()) {
     825                 :       23453 :                 size_t after = pos + 1 + rng.randrange<size_t>(vec1.size() - 1 - pos);
     826                 :       23453 :                 auto after_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[after]));
     827         [ -  + ]:       23453 :                 assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
     828                 :             :             }
     829                 :             :         }
     830                 :             : 
     831                 :             :         // The same order should be obtained through a BlockBuilder as implied by CompareMainOrder,
     832                 :             :         // if nothing is skipped.
     833                 :        2316 :         auto builder = real->GetBlockBuilder();
     834                 :        2316 :         std::vector<SimTxGraph::Pos> vec_builder;
     835                 :        2316 :         std::vector<TxGraph::Ref*> last_chunk;
     836                 :        2316 :         FeePerWeight last_chunk_feerate;
     837         [ +  + ]:       51862 :         while (auto chunk = builder->GetCurrentChunk()) {
     838                 :       24773 :             FeePerWeight sum;
     839         [ +  + ]:       50203 :             for (TxGraph::Ref* ref : chunk->first) {
     840                 :             :                 // The reported chunk feerate must match the chunk feerate obtained by asking
     841                 :             :                 // it for each of the chunk's transactions individually.
     842         [ +  - ]:       25430 :                 assert(real->GetMainChunkFeerate(*ref) == chunk->second);
     843                 :             :                 // Verify the chunk feerate matches the sum of the reported individual feerates.
     844                 :       25430 :                 sum += real->GetIndividualFeerate(*ref);
     845                 :             :                 // Chunks must contain transactions that exist in the graph.
     846                 :       25430 :                 auto simpos = sims[0].Find(ref);
     847         [ -  + ]:       25430 :                 assert(simpos != SimTxGraph::MISSING);
     848         [ +  - ]:       25430 :                 vec_builder.push_back(simpos);
     849                 :             :             }
     850         [ +  - ]:       24773 :             assert(sum == chunk->second);
     851                 :       24773 :             last_chunk = std::move(chunk->first);
     852                 :       24773 :             last_chunk_feerate = chunk->second;
     853                 :       24773 :             builder->Include();
     854                 :       24773 :         }
     855         [ -  + ]:        2316 :         assert(vec_builder == vec1);
     856                 :             : 
     857                 :             :         // The last chunk returned by the BlockBuilder must match GetWorstMainChunk, in reverse.
     858                 :        2316 :         std::reverse(last_chunk.begin(), last_chunk.end());
     859         [ -  + ]:        2316 :         auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
     860         [ -  + ]:        2316 :         assert(last_chunk == worst_chunk);
     861         [ +  - ]:        2316 :         assert(last_chunk_feerate == worst_chunk_feerate);
     862                 :             : 
     863                 :             :         // Check that the implied ordering gives rise to a combined diagram that matches the
     864                 :             :         // diagram constructed from the individual cluster linearization chunkings.
     865         [ +  - ]:        2316 :         auto main_real_diagram = get_diagram_fn(/*main_only=*/true);
     866                 :        2316 :         auto main_implied_diagram = ChunkLinearization(sims[0].graph, vec1);
     867   [ +  -  -  + ]:        2316 :         assert(CompareChunks(main_real_diagram, main_implied_diagram) == 0);
     868                 :             : 
     869   [ +  +  +  + ]:        2316 :         if (sims.size() >= 2 && !sims[1].IsOversized()) {
     870                 :             :             // When the staging graph is not oversized as well, call GetMainStagingDiagrams, and
     871                 :             :             // fully verify the result.
     872                 :        1097 :             auto [main_cmp_diagram, stage_cmp_diagram] = real->GetMainStagingDiagrams();
     873                 :             :             // Check that the feerates in each diagram are monotonically decreasing.
     874         [ +  + ]:        2767 :             for (size_t i = 1; i < main_cmp_diagram.size(); ++i) {
     875         [ -  + ]:        1670 :                 assert(FeeRateCompare(main_cmp_diagram[i], main_cmp_diagram[i - 1]) <= 0);
     876                 :             :             }
     877         [ +  + ]:       16629 :             for (size_t i = 1; i < stage_cmp_diagram.size(); ++i) {
     878         [ -  + ]:       15532 :                 assert(FeeRateCompare(stage_cmp_diagram[i], stage_cmp_diagram[i - 1]) <= 0);
     879                 :             :             }
     880                 :             :             // Treat the diagrams as sets of chunk feerates, and sort them in the same way so that
     881                 :             :             // std::set_difference can be used on them below. The exact ordering does not matter
     882                 :             :             // here, but it has to be consistent with the one used in main_real_diagram and
     883                 :             :             // stage_real_diagram).
     884                 :        1097 :             std::sort(main_cmp_diagram.begin(), main_cmp_diagram.end(), std::greater{});
     885                 :        1097 :             std::sort(stage_cmp_diagram.begin(), stage_cmp_diagram.end(), std::greater{});
     886                 :             :             // Find the chunks that appear in main_diagram but are missing from main_cmp_diagram.
     887                 :             :             // This is allowed, because GetMainStagingDiagrams omits clusters in main unaffected
     888                 :             :             // by staging.
     889                 :        1097 :             std::vector<FeeFrac> missing_main_cmp;
     890         [ +  - ]:        1097 :             std::set_difference(main_real_diagram.begin(), main_real_diagram.end(),
     891                 :             :                                 main_cmp_diagram.begin(), main_cmp_diagram.end(),
     892                 :             :                                 std::inserter(missing_main_cmp, missing_main_cmp.end()),
     893                 :             :                                 std::greater{});
     894         [ -  + ]:        1097 :             assert(main_cmp_diagram.size() + missing_main_cmp.size() == main_real_diagram.size());
     895                 :             :             // Do the same for chunks in stage_diagram missing from stage_cmp_diagram.
     896         [ +  - ]:        1097 :             auto stage_real_diagram = get_diagram_fn(/*main_only=*/false);
     897                 :        1097 :             std::vector<FeeFrac> missing_stage_cmp;
     898         [ +  - ]:        1097 :             std::set_difference(stage_real_diagram.begin(), stage_real_diagram.end(),
     899                 :             :                                 stage_cmp_diagram.begin(), stage_cmp_diagram.end(),
     900                 :             :                                 std::inserter(missing_stage_cmp, missing_stage_cmp.end()),
     901                 :             :                                 std::greater{});
     902         [ -  + ]:        1097 :             assert(stage_cmp_diagram.size() + missing_stage_cmp.size() == stage_real_diagram.size());
     903                 :             :             // The missing chunks must be equal across main & staging (otherwise they couldn't have
     904                 :             :             // been omitted).
     905         [ -  + ]:        1097 :             assert(missing_main_cmp == missing_stage_cmp);
     906                 :             : 
     907                 :             :             // The missing part must include at least all transactions in staging which have not been
     908                 :             :             // modified, or been in a cluster together with modified transactions, since they were
     909                 :             :             // copied from main. Note that due to the reordering of removals w.r.t. dependency
     910                 :             :             // additions, it is possible that the real implementation found more unaffected things.
     911                 :        1097 :             FeeFrac missing_real;
     912         [ +  + ]:        6997 :             for (const auto& feerate : missing_main_cmp) missing_real += feerate;
     913                 :        1097 :             FeeFrac missing_expected = sims[1].graph.FeeRate(sims[1].graph.Positions() - sims[1].modified);
     914                 :             :             // Note that missing_real.fee < missing_expected.fee is possible to due the presence of
     915                 :             :             // negative-fee transactions.
     916         [ -  + ]:        1097 :             assert(missing_real.size >= missing_expected.size);
     917                 :        2194 :         }
     918                 :        2316 :     }
     919                 :             : 
     920         [ -  + ]:        2669 :     assert(real->HaveStaging() == (sims.size() > 1));
     921                 :             : 
     922                 :             :     // Try to run a full comparison, for both main_only=false and main_only=true in TxGraph
     923                 :             :     // inspector functions that support both.
     924         [ +  + ]:        8007 :     for (int main_only = 0; main_only < 2; ++main_only) {
     925         [ +  + ]:        5338 :         auto& sim = main_only ? sims[0] : sims.back();
     926                 :             :         // Compare simple properties of the graph with the simulation.
     927         [ -  + ]:        5338 :         assert(real->IsOversized(main_only) == sim.IsOversized());
     928         [ -  + ]:        5338 :         assert(real->GetTransactionCount(main_only) == sim.GetTransactionCount());
     929                 :             :         // If the graph (and the simulation) are not oversized, perform a full comparison.
     930         [ +  + ]:        5338 :         if (!sim.IsOversized()) {
     931                 :        4571 :             auto todo = sim.graph.Positions();
     932                 :             :             // Iterate over all connected components of the resulting (simulated) graph, each of which
     933                 :             :             // should correspond to a cluster in the real one.
     934         [ +  + ]:      127688 :             while (todo.Any()) {
     935                 :       59273 :                 auto component = sim.graph.FindConnectedComponent(todo);
     936                 :       59273 :                 todo -= component;
     937                 :             :                 // Iterate over the transactions in that component.
     938         [ +  + ]:      125244 :                 for (auto i : component) {
     939                 :             :                     // Check its individual feerate against simulation.
     940         [ +  - ]:       65971 :                     assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
     941                 :             :                     // Check its ancestors against simulation.
     942                 :       65971 :                     auto expect_anc = sim.graph.Ancestors(i);
     943                 :       65971 :                     auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), main_only));
     944         [ -  + ]:       65971 :                     assert(anc.Count() <= max_count);
     945         [ -  + ]:       65971 :                     assert(anc == expect_anc);
     946                 :             :                     // Check its descendants against simulation.
     947                 :       65971 :                     auto expect_desc = sim.graph.Descendants(i);
     948                 :       65971 :                     auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), main_only));
     949         [ -  + ]:       65971 :                     assert(desc.Count() <= max_count);
     950         [ -  + ]:       65971 :                     assert(desc == expect_desc);
     951                 :             :                     // Check the cluster the transaction is part of.
     952                 :       65971 :                     auto cluster = real->GetCluster(*sim.GetRef(i), main_only);
     953         [ -  + ]:       65971 :                     assert(cluster.size() <= max_count);
     954         [ -  + ]:       65971 :                     assert(sim.MakeSet(cluster) == component);
     955                 :             :                     // Check that the cluster is reported in a valid topological order (its
     956                 :             :                     // linearization).
     957                 :       65971 :                     std::vector<DepGraphIndex> simlin;
     958                 :       65971 :                     SimTxGraph::SetType done;
     959         [ +  + ]:      190306 :                     for (TxGraph::Ref* ptr : cluster) {
     960                 :      124335 :                         auto simpos = sim.Find(ptr);
     961         [ -  + ]:      248670 :                         assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
     962                 :      124335 :                         done.Set(simpos);
     963         [ -  + ]:      248670 :                         assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
     964         [ +  - ]:      124335 :                         simlin.push_back(simpos);
     965                 :             :                     }
     966                 :             :                     // Construct a chunking object for the simulated graph, using the reported cluster
     967                 :             :                     // linearization as ordering, and compare it against the reported chunk feerates.
     968   [ +  +  +  + ]:       65971 :                     if (sims.size() == 1 || main_only) {
     969                 :       41843 :                         cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin);
     970                 :       41843 :                         DepGraphIndex idx{0};
     971         [ +  + ]:      100922 :                         for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) {
     972                 :       59079 :                             auto chunk = simlinchunk.GetChunk(chunknum);
     973                 :             :                             // Require that the chunks of cluster linearizations are connected (this must
     974                 :             :                             // be the case as all linearizations inside are PostLinearized).
     975         [ -  + ]:       59079 :                             assert(sim.graph.IsConnected(chunk.transactions));
     976                 :             :                             // Check the chunk feerates of all transactions in the cluster.
     977         [ +  + ]:      249384 :                             while (chunk.transactions.Any()) {
     978         [ -  + ]:       65613 :                                 assert(chunk.transactions[simlin[idx]]);
     979                 :       65613 :                                 chunk.transactions.Reset(simlin[idx]);
     980         [ +  - ]:       65613 :                                 assert(chunk.feerate == real->GetMainChunkFeerate(*cluster[idx]));
     981                 :       65613 :                                 ++idx;
     982                 :             :                             }
     983                 :             :                         }
     984                 :       41843 :                     }
     985                 :       65971 :                 }
     986                 :             :             }
     987                 :             :         }
     988                 :             :     }
     989                 :             : 
     990                 :             :     // Sanity check again (because invoking inspectors may modify internal unobservable state).
     991         [ +  - ]:        2669 :     real->SanityCheck();
     992                 :             : 
     993                 :             :     // Kill the block builders.
     994                 :        2669 :     block_builders.clear();
     995                 :             :     // Kill the TxGraph object.
     996         [ +  - ]:        2669 :     real.reset();
     997                 :             :     // Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
     998                 :             :     // can outlive the TxGraph that created them.
     999                 :        2669 :     sims.clear();
    1000                 :        2669 : }
        

Generated by: LCOV version 2.0-1