LCOV - code coverage report
Current view: top level - src/test/fuzz - cluster_linearize.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 98.5 % 745 734
Test Date: 2025-10-31 04:12:16 Functions: 100.0 % 44 44
Branches: 65.2 % 1082 706

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <cluster_linearize.h>
       6                 :             : #include <random.h>
       7                 :             : #include <serialize.h>
       8                 :             : #include <streams.h>
       9                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      10                 :             : #include <test/fuzz/fuzz.h>
      11                 :             : #include <test/util/cluster_linearize.h>
      12                 :             : #include <util/bitset.h>
      13                 :             : #include <util/feefrac.h>
      14                 :             : 
      15                 :             : #include <algorithm>
      16                 :             : #include <cstdint>
      17                 :             : #include <utility>
      18                 :             : #include <vector>
      19                 :             : 
      20                 :             : /*
      21                 :             :  * The tests in this file primarily cover the candidate finder classes and linearization algorithms.
      22                 :             :  *
      23                 :             :  *   <----: An implementation (at the start of the line --) is tested in the test marked with *,
      24                 :             :  *          possibly by comparison with other implementations (at the end of the line ->).
      25                 :             :  *   <<---: The right side is implemented using the left side.
      26                 :             :  *
      27                 :             :  *   +-----------------------+
      28                 :             :  *   | SearchCandidateFinder | <<---------------------\
      29                 :             :  *   +-----------------------+                        |
      30                 :             :  *     |                                            +-----------+
      31                 :             :  *     |                                            | Linearize |
      32                 :             :  *     |                                            +-----------+
      33                 :             :  *     |        +-------------------------+           |  |
      34                 :             :  *     |        | AncestorCandidateFinder | <<--------/  |
      35                 :             :  *     |        +-------------------------+              |
      36                 :             :  *     |          |                     ^                |        ^^  PRODUCTION CODE
      37                 :             :  *     |          |                     |                |        ||
      38                 :             :  *  ==============================================================================================
      39                 :             :  *     |          |                     |                |        ||
      40                 :             :  *     | clusterlin_ancestor_finder*    |                |        vv  TEST CODE
      41                 :             :  *     |                                |                |
      42                 :             :  *     |-clusterlin_search_finder*      |                |-clusterlin_linearize*
      43                 :             :  *     |                                |                |
      44                 :             :  *     v                                |                v
      45                 :             :  *   +-----------------------+          |           +-----------------+
      46                 :             :  *   | SimpleCandidateFinder | <<-------------------| SimpleLinearize |
      47                 :             :  *   +-----------------------+          |           +-----------------+
      48                 :             :  *                  |                   |                |
      49                 :             :  *                  +-------------------/                |
      50                 :             :  *                  |                                    |
      51                 :             :  *                  |-clusterlin_simple_finder*          |-clusterlin_simple_linearize*
      52                 :             :  *                  v                                    v
      53                 :             :  *   +---------------------------+                  +---------------------+
      54                 :             :  *   | ExhaustiveCandidateFinder |                  | ExhaustiveLinearize |
      55                 :             :  *   +---------------------------+                  +---------------------+
      56                 :             :  *
      57                 :             :  * More tests are included for lower-level and related functions and classes:
      58                 :             :  * - DepGraph tests:
      59                 :             :  *   - clusterlin_depgraph_sim
      60                 :             :  *   - clusterlin_depgraph_serialization
      61                 :             :  *   - clusterlin_components
      62                 :             :  * - ChunkLinearization and LinearizationChunking tests:
      63                 :             :  *   - clusterlin_chunking
      64                 :             :  *   - clusterlin_linearization_chunking
      65                 :             :  * - PostLinearize tests:
      66                 :             :  *   - clusterlin_postlinearize
      67                 :             :  *   - clusterlin_postlinearize_tree
      68                 :             :  *   - clusterlin_postlinearize_moved_leaf
      69                 :             :  * - MergeLinearization tests:
      70                 :             :  *   - clusterlin_merge
      71                 :             :  * - FixLinearization tests:
      72                 :             :  *   - clusterlin_fix_linearization
      73                 :             :  * - MakeConnected tests (a test-only function):
      74                 :             :  *   - clusterlin_make_connected
      75                 :             :  */
      76                 :             : 
      77                 :             : using namespace cluster_linearize;
      78                 :             : 
      79                 :             : namespace {
      80                 :             : 
      81                 :             : /** A simple finder class for candidate sets.
      82                 :             :  *
      83                 :             :  * This class matches SearchCandidateFinder in interface and behavior, though with fewer
      84                 :             :  * optimizations.
      85                 :             :  */
      86                 :             : template<typename SetType>
      87                 :             : class SimpleCandidateFinder
      88                 :             : {
      89                 :             :     /** Internal dependency graph. */
      90                 :             :     const DepGraph<SetType>& m_depgraph;
      91                 :             :     /** Which transaction are left to include. */
      92                 :             :     SetType m_todo;
      93                 :             : 
      94                 :             : public:
      95                 :             :     /** Construct an SimpleCandidateFinder for a given graph. */
      96                 :        1064 :     SimpleCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
      97                 :        1064 :         m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
      98                 :             : 
      99                 :             :     /** Remove a set of transactions from the set of to-be-linearized ones. */
     100                 :       10784 :     void MarkDone(SetType select) noexcept { m_todo -= select; }
     101                 :             : 
     102                 :             :     /** Determine whether unlinearized transactions remain. */
     103                 :        7809 :     bool AllDone() const noexcept { return m_todo.None(); }
     104                 :             : 
     105                 :             :     /** Find a candidate set using at most max_iterations iterations, and the number of iterations
     106                 :             :      *  actually performed. If that number is less than max_iterations, then the result is optimal.
     107                 :             :      *
     108                 :             :      * Always returns a connected set of transactions.
     109                 :             :      *
     110                 :             :      * Complexity: O(N * M), where M is the number of connected topological subsets of the cluster.
     111                 :             :      *             That number is bounded by M <= 2^(N-1).
     112                 :             :      */
     113                 :        6465 :     std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations) const noexcept
     114                 :             :     {
     115                 :        6465 :         uint64_t iterations_left = max_iterations;
     116                 :             :         // Queue of work units. Each consists of:
     117                 :             :         // - inc: set of transactions definitely included
     118                 :             :         // - und: set of transactions that can be added to inc still
     119                 :        6465 :         std::vector<std::pair<SetType, SetType>> queue;
     120                 :             :         // Initially we have just one queue element, with the entire graph in und.
     121                 :        6465 :         queue.emplace_back(SetType{}, m_todo);
     122                 :             :         // Best solution so far. Initialize with the remaining ancestors of the first remaining
     123                 :             :         // transaction.
     124                 :        6465 :         SetInfo best(m_depgraph, m_depgraph.Ancestors(m_todo.First()) & m_todo);
     125                 :             :         // Process the queue.
     126   [ +  +  +  + ]:    51849965 :         while (!queue.empty() && iterations_left) {
     127                 :             :             // Pop top element of the queue.
     128                 :    51843500 :             auto [inc, und] = queue.back();
     129                 :    51843500 :             queue.pop_back();
     130                 :             :             // Look for a transaction to consider adding/removing.
     131                 :    51843500 :             bool inc_none = inc.None();
     132         [ +  + ]:    88089276 :             for (auto split : und) {
     133                 :             :                 // If inc is empty, consider any split transaction. Otherwise only consider
     134                 :             :                 // transactions that share ancestry with inc so far (which means only connected
     135                 :             :                 // sets will be considered).
     136   [ +  +  +  + ]:    62164969 :                 if (inc_none || inc.Overlaps(m_depgraph.Ancestors(split))) {
     137                 :    25919193 :                     --iterations_left;
     138                 :             :                     // Add a queue entry with split included.
     139                 :    25919193 :                     SetInfo new_inc(m_depgraph, inc | (m_todo & m_depgraph.Ancestors(split)));
     140                 :    25919193 :                     queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
     141                 :             :                     // Add a queue entry with split excluded.
     142                 :    25919193 :                     queue.emplace_back(inc, und - m_depgraph.Descendants(split));
     143                 :             :                     // Update statistics to account for the candidate new_inc.
     144         [ +  + ]:    25919193 :                     if (new_inc.feerate > best.feerate) best = new_inc;
     145                 :             :                     break;
     146                 :             :                 }
     147                 :             :             }
     148                 :             :         }
     149                 :        6465 :         return {std::move(best), max_iterations - iterations_left};
     150                 :        6465 :     }
     151                 :             : };
     152                 :             : 
     153                 :             : /** A very simple finder class for optimal candidate sets, which tries every subset.
     154                 :             :  *
     155                 :             :  * It is even simpler than SimpleCandidateFinder, and exists just to help test the correctness of
     156                 :             :  * SimpleCandidateFinder, which is then used to test the correctness of SearchCandidateFinder.
     157                 :             :  */
     158                 :             : template<typename SetType>
     159                 :             : class ExhaustiveCandidateFinder
     160                 :             : {
     161                 :             :     /** Internal dependency graph. */
     162                 :             :     const DepGraph<SetType>& m_depgraph;
     163                 :             :     /** Which transaction are left to include. */
     164                 :             :     SetType m_todo;
     165                 :             : 
     166                 :             : public:
     167                 :             :     /** Construct an ExhaustiveCandidateFinder for a given graph. */
     168                 :         131 :     ExhaustiveCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
     169                 :         131 :         m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
     170                 :             : 
     171                 :             :     /** Remove a set of transactions from the set of to-be-linearized ones. */
     172                 :        1210 :     void MarkDone(SetType select) noexcept { m_todo -= select; }
     173                 :             : 
     174                 :             :     /** Determine whether unlinearized transactions remain. */
     175                 :        1341 :     bool AllDone() const noexcept { return m_todo.None(); }
     176                 :             : 
     177                 :             :     /** Find the optimal remaining candidate set.
     178                 :             :      *
     179                 :             :      * Complexity: O(N * 2^N).
     180                 :             :      */
     181                 :         761 :     SetInfo<SetType> FindCandidateSet() const noexcept
     182                 :             :     {
     183                 :             :         // Best solution so far.
     184                 :         761 :         SetInfo<SetType> best{m_todo, m_depgraph.FeeRate(m_todo)};
     185                 :             :         // The number of combinations to try.
     186                 :         761 :         uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
     187                 :             :         // Try the transitive closure of every non-empty subset of m_todo.
     188         [ +  + ]:      351073 :         for (uint64_t x = 1; x < limit; ++x) {
     189                 :             :             // If bit number b is set in x, then the remaining ancestors of the b'th remaining
     190                 :             :             // transaction in m_todo are included.
     191                 :      350312 :             SetType txn;
     192                 :      350312 :             auto x_shifted{x};
     193   [ +  -  +  + ]:     4511682 :             for (auto i : m_todo) {
     194         [ +  + ]:     3811058 :                 if (x_shifted & 1) txn |= m_depgraph.Ancestors(i);
     195                 :     3811058 :                 x_shifted >>= 1;
     196                 :             :             }
     197                 :      350312 :             SetInfo cur(m_depgraph, txn & m_todo);
     198         [ +  + ]:      350312 :             if (cur.feerate > best.feerate) best = cur;
     199                 :             :         }
     200                 :         761 :         return best;
     201                 :             :     }
     202                 :             : };
     203                 :             : 
     204                 :             : /** A simple linearization algorithm.
     205                 :             :  *
     206                 :             :  * This matches Linearize() in interface and behavior, though with fewer optimizations, lacking
     207                 :             :  * the ability to pass in an existing linearization, and using just SimpleCandidateFinder rather
     208                 :             :  * than AncestorCandidateFinder and SearchCandidateFinder.
     209                 :             :  */
     210                 :             : template<typename SetType>
     211                 :         575 : std::pair<std::vector<DepGraphIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations)
     212                 :             : {
     213                 :         575 :     std::vector<DepGraphIndex> linearization;
     214                 :         575 :     SimpleCandidateFinder finder(depgraph);
     215                 :         575 :     SetType todo = depgraph.Positions();
     216                 :         575 :     bool optimal = true;
     217         [ +  + ]:        4039 :     while (todo.Any()) {
     218         [ +  + ]:        3464 :         auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
     219         [ +  + ]:        3464 :         if (iterations_done == max_iterations) optimal = false;
     220                 :        3464 :         depgraph.AppendTopo(linearization, candidate.transactions);
     221                 :        3464 :         todo -= candidate.transactions;
     222                 :        3464 :         finder.MarkDone(candidate.transactions);
     223                 :        3464 :         max_iterations -= iterations_done;
     224                 :             :     }
     225                 :         575 :     return {std::move(linearization), optimal};
     226                 :         575 : }
     227                 :             : 
     228                 :             : /** An even simpler linearization algorithm that tries all permutations.
     229                 :             :  *
     230                 :             :  * This roughly matches SimpleLinearize() (and Linearize) in interface and behavior, but always
     231                 :             :  * tries all topologically-valid transaction orderings, has no way to bound how much work it does,
     232                 :             :  * and always finds the optimal. With an O(n!) complexity, it should only be used for small
     233                 :             :  * clusters.
     234                 :             :  */
     235                 :             : template<typename SetType>
     236                 :         108 : std::vector<DepGraphIndex> ExhaustiveLinearize(const DepGraph<SetType>& depgraph)
     237                 :             : {
     238                 :             :     // The best linearization so far, and its chunking.
     239                 :         108 :     std::vector<DepGraphIndex> linearization;
     240                 :         108 :     std::vector<FeeFrac> chunking;
     241                 :             : 
     242         [ +  + ]:         108 :     std::vector<DepGraphIndex> perm_linearization;
     243                 :             :     // Initialize with the lexicographically-first linearization.
     244   [ +  +  +  -  :         737 :     for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i);
                   +  + ]
     245                 :             :     // Iterate over all valid permutations.
     246                 :             :     do {
     247                 :             :         /** What prefix of perm_linearization is topological. */
     248                 :      500425 :         DepGraphIndex topo_length{0};
     249                 :      500425 :         TestBitSet perm_done;
     250   [ -  +  +  + ]:     4311001 :         while (topo_length < perm_linearization.size()) {
     251                 :     3863971 :             auto i = perm_linearization[topo_length];
     252         [ +  + ]:     3863971 :             perm_done.Set(i);
     253         [ +  + ]:     3863971 :             if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break;
     254                 :     3810576 :             ++topo_length;
     255                 :             :         }
     256   [ -  +  +  + ]:      500425 :         if (topo_length == perm_linearization.size()) {
     257                 :             :             // If all of perm_linearization is topological, check if it is perhaps our best
     258                 :             :             // linearization so far.
     259         [ -  + ]:      447030 :             auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
     260   [ -  +  +  - ]:      447030 :             auto cmp = CompareChunks(perm_chunking, chunking);
     261                 :             :             // If the diagram is better, or if it is equal but with more chunks (because we
     262                 :             :             // prefer minimal chunks), consider this better.
     263   [ +  +  +  +  :      500366 :             if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) {
          +  +  -  +  +  
                      + ]
     264         [ +  - ]:        1243 :                 linearization = perm_linearization;
     265         [ +  - ]:        1243 :                 chunking = perm_chunking;
     266                 :             :             }
     267                 :      447030 :         } else {
     268                 :             :             // Otherwise, fast forward to the last permutation with the same non-topological
     269                 :             :             // prefix.
     270                 :       53395 :             auto first_non_topo = perm_linearization.begin() + topo_length;
     271         [ -  + ]:       53395 :             assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
     272                 :       53395 :             std::reverse(first_non_topo + 1, perm_linearization.end());
     273                 :             :         }
     274         [ +  + ]:      500425 :     } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
     275                 :             : 
     276                 :         108 :     return linearization;
     277                 :         108 : }
     278                 :             : 
     279                 :             : 
     280                 :             : /** Stitch connected components together in a DepGraph, guaranteeing its corresponding cluster is connected. */
     281                 :             : template<typename BS>
     282                 :         916 : void MakeConnected(DepGraph<BS>& depgraph)
     283                 :             : {
     284                 :         916 :     auto todo = depgraph.Positions();
     285                 :         916 :     auto comp = depgraph.FindConnectedComponent(todo);
     286                 :         916 :     Assume(depgraph.IsConnected(comp));
     287                 :         916 :     todo -= comp;
     288         [ +  + ]:       11870 :     while (todo.Any()) {
     289                 :       10954 :         auto nextcomp = depgraph.FindConnectedComponent(todo);
     290                 :       10954 :         Assume(depgraph.IsConnected(nextcomp));
     291                 :       10954 :         depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
     292                 :       10954 :         todo -= nextcomp;
     293                 :       10954 :         comp = nextcomp;
     294                 :             :     }
     295                 :         916 : }
     296                 :             : 
     297                 :             : /** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
     298                 :             : template<typename SetType>
     299                 :       18785 : SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader, bool non_empty)
     300                 :             : {
     301                 :             :     // Read a bitmask from the fuzzing input. Add 1 if non_empty, so the mask is definitely not
     302                 :             :     // zero in that case.
     303         [ +  + ]:       18785 :     uint64_t mask{0};
     304                 :             :     try {
     305         [ +  + ]:       18785 :         reader >> VARINT(mask);
     306         [ -  + ]:       15054 :     } catch(const std::ios_base::failure&) {}
     307         [ +  + ]:       18785 :     if (mask != uint64_t(-1)) mask += non_empty;
     308                 :             : 
     309                 :       18785 :     SetType ret;
     310   [ +  +  +  + ]:      281407 :     for (auto i : todo) {
     311         [ +  + ]:      243843 :         if (!ret[i]) {
     312         [ +  + ]:      238634 :             if (mask & 1) ret |= depgraph.Ancestors(i);
     313                 :      238634 :             mask >>= 1;
     314                 :             :         }
     315                 :             :     }
     316                 :       18785 :     ret &= todo;
     317                 :             : 
     318                 :             :     // While mask starts off non-zero if non_empty is true, it is still possible that all its low
     319                 :             :     // bits are 0, and ret ends up being empty. As a last resort, use the in-todo ancestry of the
     320                 :             :     // first todo position.
     321   [ +  +  +  + ]:       18785 :     if (non_empty && ret.None()) {
     322                 :         331 :         Assume(todo.Any());
     323                 :         331 :         ret = depgraph.Ancestors(todo.First()) & todo;
     324                 :         331 :         Assume(ret.Any());
     325                 :             :     }
     326                 :       18785 :     return ret;
     327                 :             : }
     328                 :             : 
     329                 :             : /** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
     330                 :             : template<typename BS>
     331                 :        1920 : std::vector<DepGraphIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
     332                 :             : {
     333                 :        1920 :     std::vector<DepGraphIndex> linearization;
     334                 :        1920 :     TestBitSet todo = depgraph.Positions();
     335                 :             :     // In every iteration one topologically-valid transaction is appended to linearization.
     336         [ +  + ]:       38412 :     while (todo.Any()) {
     337                 :             :         // Compute the set of transactions with no not-yet-included ancestors.
     338                 :       34572 :         TestBitSet potential_next;
     339         [ +  + ]:      496494 :         for (auto j : todo) {
     340         [ +  + ]:      709888 :             if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
     341                 :      247966 :                 potential_next.Set(j);
     342                 :             :             }
     343                 :             :         }
     344                 :             :         // There must always be one (otherwise there is a cycle in the graph).
     345         [ -  + ]:       34572 :         assert(potential_next.Any());
     346                 :             :         // Read a number from reader, and interpret it as index into potential_next.
     347         [ +  + ]:       34572 :         uint64_t idx{0};
     348                 :             :         try {
     349   [ +  +  +  - ]:       69144 :             reader >> VARINT(idx);
     350         [ -  + ]:       29595 :         } catch (const std::ios_base::failure&) {}
     351                 :       34572 :         idx %= potential_next.Count();
     352                 :             :         // Find out which transaction that corresponds to.
     353   [ +  -  +  - ]:       84609 :         for (auto j : potential_next) {
     354         [ +  + ]:       50037 :             if (idx == 0) {
     355                 :             :                 // When found, add it to linearization and remove it from todo.
     356         [ +  - ]:       34572 :                 linearization.push_back(j);
     357         [ -  + ]:       34572 :                 assert(todo[j]);
     358                 :       34572 :                 todo.Reset(j);
     359                 :       34572 :                 break;
     360                 :             :             }
     361                 :       15465 :             --idx;
     362                 :             :         }
     363                 :             :     }
     364                 :        1920 :     return linearization;
     365                 :           0 : }
     366                 :             : 
     367                 :             : } // namespace
     368                 :             : 
     369         [ +  - ]:         709 : FUZZ_TARGET(clusterlin_depgraph_sim)
     370                 :             : {
     371                 :             :     // Simulation test to verify the full behavior of DepGraph.
     372                 :             : 
     373                 :         251 :     FuzzedDataProvider provider(buffer.data(), buffer.size());
     374                 :             : 
     375                 :             :     /** Real DepGraph being tested. */
     376                 :         251 :     DepGraph<TestBitSet> real;
     377                 :             :     /** Simulated DepGraph (sim[i] is std::nullopt if position i does not exist; otherwise,
     378                 :             :      *  sim[i]->first is its individual feerate, and sim[i]->second is its set of ancestors. */
     379                 :         251 :     std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
     380                 :             :     /** The number of non-nullopt position in sim. */
     381                 :         251 :     DepGraphIndex num_tx_sim{0};
     382                 :             : 
     383                 :             :     /** Read a valid index of a transaction from the provider. */
     384                 :       14648 :     auto idx_fn = [&]() {
     385                 :       14397 :         auto offset = provider.ConsumeIntegralInRange<DepGraphIndex>(0, num_tx_sim - 1);
     386         [ +  - ]:       75610 :         for (DepGraphIndex i = 0; i < sim.size(); ++i) {
     387         [ +  + ]:       75610 :             if (!sim[i].has_value()) continue;
     388         [ +  + ]:       67311 :             if (offset == 0) return i;
     389                 :       52914 :             --offset;
     390                 :             :         }
     391                 :           0 :         assert(false);
     392                 :             :         return DepGraphIndex(-1);
     393                 :         251 :     };
     394                 :             : 
     395                 :             :     /** Read a valid subset of the transactions from the provider. */
     396                 :       14648 :     auto subset_fn = [&]() {
     397                 :       14397 :         auto range = (uint64_t{1} << num_tx_sim) - 1;
     398                 :       14397 :         const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
     399                 :       14397 :         auto mask_shifted = mask;
     400                 :       14397 :         TestBitSet subset;
     401         [ +  + ]:      475101 :         for (DepGraphIndex i = 0; i < sim.size(); ++i) {
     402         [ +  + ]:      460704 :             if (!sim[i].has_value()) continue;
     403         [ +  + ]:      279172 :             if (mask_shifted & 1) {
     404                 :       66178 :                 subset.Set(i);
     405                 :             :             }
     406                 :      279172 :             mask_shifted >>= 1;
     407                 :             :         }
     408         [ -  + ]:       14397 :         assert(mask_shifted == 0);
     409                 :       14397 :         return subset;
     410                 :         251 :     };
     411                 :             : 
     412                 :             :     /** Read any set of transactions from the provider (including unused positions). */
     413                 :        5228 :     auto set_fn = [&]() {
     414                 :        4977 :         auto range = (uint64_t{1} << sim.size()) - 1;
     415                 :        4977 :         const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
     416                 :        4977 :         TestBitSet set;
     417         [ +  + ]:      164241 :         for (DepGraphIndex i = 0; i < sim.size(); ++i) {
     418         [ +  + ]:      159264 :             if ((mask >> i) & 1) {
     419                 :       60677 :                 set.Set(i);
     420                 :             :             }
     421                 :             :         }
     422                 :        4977 :         return set;
     423                 :         251 :     };
     424                 :             : 
     425                 :             :     /** Propagate ancestor information in sim. */
     426                 :        5479 :     auto anc_update_fn = [&]() {
     427                 :        5981 :         while (true) {
     428                 :        5981 :             bool updates{false};
     429         [ +  + ]:      197373 :             for (DepGraphIndex chl = 0; chl < sim.size(); ++chl) {
     430         [ +  + ]:      191392 :                 if (!sim[chl].has_value()) continue;
     431   [ +  -  +  + ]:      365227 :                 for (auto par : sim[chl]->second) {
     432         [ +  + ]:      213253 :                     if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
     433                 :        2557 :                         sim[chl]->second |= sim[par]->second;
     434                 :        2557 :                         updates = true;
     435                 :             :                     }
     436                 :             :                 }
     437                 :             :             }
     438         [ +  + ]:        5981 :             if (!updates) break;
     439                 :             :         }
     440                 :        5479 :     };
     441                 :             : 
     442                 :             :     /** Compare the state of transaction i in the simulation with the real one. */
     443                 :       68960 :     auto check_fn = [&](DepGraphIndex i) {
     444                 :             :         // Compare used positions.
     445         [ -  + ]:       68709 :         assert(real.Positions()[i] == sim[i].has_value());
     446         [ +  + ]:       68709 :         if (sim[i].has_value()) {
     447                 :             :             // Compare feerate.
     448         [ +  - ]:       12160 :             assert(real.FeeRate(i) == sim[i]->first);
     449                 :             :             // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
     450                 :             :             // and descendants, so we can restrict ourselves to ancestors here).
     451         [ -  + ]:       12160 :             assert(real.Ancestors(i) == sim[i]->second);
     452                 :             :         }
     453                 :       68960 :     };
     454                 :             : 
     455                 :         251 :     auto last_compaction_pos{real.PositionRange()};
     456                 :             : 
     457   [ +  +  +  + ]:       55476 :     LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
     458                 :       55225 :         int command = provider.ConsumeIntegral<uint8_t>() % 4;
     459                 :       58629 :         while (true) {
     460                 :             :             // Iterate decreasing command until an applicable branch is found.
     461   [ +  +  +  + ]:       58629 :             if (num_tx_sim < TestBitSet::Size() && command-- == 0) {
     462                 :             :                 // AddTransaction.
     463                 :       12160 :                 auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     464                 :       12160 :                 auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
     465                 :       12160 :                 FeeFrac feerate{fee, size};
     466                 :             :                 // Apply to DepGraph.
     467                 :       12160 :                 auto idx = real.AddTransaction(feerate);
     468                 :             :                 // Verify that the returned index is correct.
     469         [ -  + ]:       12160 :                 assert(!sim[idx].has_value());
     470         [ +  - ]:      145992 :                 for (DepGraphIndex i = 0; i < TestBitSet::Size(); ++i) {
     471         [ +  + ]:      145992 :                     if (!sim[i].has_value()) {
     472         [ -  + ]:       12160 :                         assert(idx == i);
     473                 :             :                         break;
     474                 :             :                     }
     475                 :             :                 }
     476                 :             :                 // Update sim.
     477         [ -  + ]:       12160 :                 sim[idx] = {feerate, TestBitSet::Singleton(idx)};
     478                 :       12160 :                 ++num_tx_sim;
     479                 :       12160 :                 break;
     480   [ +  +  +  + ]:       46469 :             } else if (num_tx_sim > 0 && command-- == 0) {
     481                 :             :                 // AddDependencies.
     482                 :       14397 :                 DepGraphIndex child = idx_fn();
     483                 :       14397 :                 auto parents = subset_fn();
     484                 :             :                 // Apply to DepGraph.
     485                 :       14397 :                 real.AddDependencies(parents, child);
     486                 :             :                 // Apply to sim.
     487                 :       14397 :                 sim[child]->second |= parents;
     488                 :       14397 :                 break;
     489   [ +  +  +  + ]:       32072 :             } else if (num_tx_sim > 0 && command-- == 0) {
     490                 :             :                 // Remove transactions.
     491                 :        4977 :                 auto del = set_fn();
     492                 :             :                 // Propagate all ancestry information before deleting anything in the simulation (as
     493                 :             :                 // intermediary transactions may be deleted which impact connectivity).
     494                 :        4977 :                 anc_update_fn();
     495                 :             :                 // Compare the state of the transactions being deleted.
     496   [ +  +  +  + ]:       70010 :                 for (auto i : del) check_fn(i);
     497                 :             :                 // Apply to DepGraph.
     498                 :        4977 :                 real.RemoveTransactions(del);
     499                 :             :                 // Apply to sim.
     500         [ +  + ]:      164241 :                 for (DepGraphIndex i = 0; i < sim.size(); ++i) {
     501         [ +  + ]:      159264 :                     if (sim[i].has_value()) {
     502         [ +  + ]:       54961 :                         if (del[i]) {
     503                 :        9536 :                             --num_tx_sim;
     504         [ +  - ]:      168800 :                             sim[i] = std::nullopt;
     505                 :             :                         } else {
     506                 :       45425 :                             sim[i]->second -= del;
     507                 :             :                         }
     508                 :             :                     }
     509                 :             :                 }
     510                 :             :                 break;
     511         [ +  + ]:       27095 :             } else if (command-- == 0) {
     512                 :             :                 // Compact.
     513         [ -  + ]:       23691 :                 const size_t mem_before{real.DynamicMemoryUsage()};
     514                 :       23691 :                 real.Compact();
     515         [ -  + ]:       23691 :                 const size_t mem_after{real.DynamicMemoryUsage()};
     516   [ -  +  +  +  :       23691 :                 assert(real.PositionRange() < last_compaction_pos ? mem_after < mem_before : mem_after <= mem_before);
                   -  + ]
     517                 :             :                 last_compaction_pos = real.PositionRange();
     518                 :             :                 break;
     519                 :             :             }
     520                 :             :         }
     521                 :             :     }
     522                 :             : 
     523                 :             :     // Compare the real obtained depgraph against the simulation.
     524                 :         251 :     anc_update_fn();
     525         [ +  + ]:        8283 :     for (DepGraphIndex i = 0; i < sim.size(); ++i) check_fn(i);
     526         [ -  + ]:         251 :     assert(real.TxCount() == num_tx_sim);
     527                 :             :     // Sanity check the result (which includes round-tripping serialization, if applicable).
     528         [ +  - ]:         251 :     SanityCheck(real);
     529                 :         251 : }
     530                 :             : 
     531         [ +  - ]:         651 : FUZZ_TARGET(clusterlin_depgraph_serialization)
     532                 :             : {
     533                 :             :     // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
     534                 :             : 
     535                 :             :     // Construct a graph by deserializing.
     536                 :         193 :     SpanReader reader(buffer);
     537                 :         193 :     DepGraph<TestBitSet> depgraph;
     538                 :         193 :     DepGraphIndex par_code{0}, chl_code{0};
     539                 :         193 :     try {
     540   [ +  -  +  +  :         193 :         reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(par_code) >> VARINT(chl_code);
                   +  + ]
     541         [ -  + ]:         165 :     } catch (const std::ios_base::failure&) {}
     542         [ +  - ]:         193 :     SanityCheck(depgraph);
     543                 :             : 
     544                 :             :     // Verify the graph is a DAG.
     545         [ -  + ]:         193 :     assert(depgraph.IsAcyclic());
     546                 :             : 
     547                 :             :     // Introduce a cycle, and then test that IsAcyclic returns false.
     548         [ +  + ]:         193 :     if (depgraph.TxCount() < 2) return;
     549                 :         178 :     DepGraphIndex par(0), chl(0);
     550                 :             :     // Pick any transaction of depgraph as parent.
     551         [ +  - ]:         178 :     par_code %= depgraph.TxCount();
     552   [ +  -  +  - ]:         612 :     for (auto i : depgraph.Positions()) {
     553         [ +  + ]:         434 :         if (par_code == 0) {
     554                 :             :             par = i;
     555                 :             :             break;
     556                 :             :         }
     557                 :         256 :         --par_code;
     558                 :             :     }
     559                 :             :     // Pick any ancestor of par (excluding itself) as child, if any.
     560         [ +  + ]:         178 :     auto ancestors = depgraph.Ancestors(par) - TestBitSet::Singleton(par);
     561         [ +  + ]:         178 :     if (ancestors.None()) return;
     562                 :          91 :     chl_code %= ancestors.Count();
     563         [ +  - ]:         177 :     for (auto i : ancestors) {
     564         [ +  + ]:         177 :         if (chl_code == 0) {
     565                 :             :             chl = i;
     566                 :             :             break;
     567                 :             :         }
     568                 :          86 :         --chl_code;
     569                 :             :     }
     570                 :             :     // Add the cycle-introducing dependency.
     571                 :          91 :     depgraph.AddDependencies(TestBitSet::Singleton(par), chl);
     572                 :             :     // Check that we now detect a cycle.
     573         [ -  + ]:          91 :     assert(!depgraph.IsAcyclic());
     574                 :         193 : }
     575                 :             : 
     576         [ +  - ]:         563 : FUZZ_TARGET(clusterlin_components)
     577                 :             : {
     578                 :             :     // Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
     579                 :             : 
     580                 :             :     // Construct a depgraph.
     581                 :         105 :     SpanReader reader(buffer);
     582                 :         105 :     DepGraph<TestBitSet> depgraph;
     583                 :         105 :     std::vector<DepGraphIndex> linearization;
     584                 :         105 :     try {
     585         [ +  - ]:         105 :         reader >> Using<DepGraphFormatter>(depgraph);
     586         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     587                 :             : 
     588                 :         105 :     TestBitSet todo = depgraph.Positions();
     589         [ +  + ]:        1222 :     while (todo.Any()) {
     590                 :             :         // Pick a transaction in todo, or nothing.
     591                 :        1117 :         std::optional<DepGraphIndex> picked;
     592                 :        1117 :         {
     593                 :        1117 :             uint64_t picked_num{0};
     594                 :        1117 :             try {
     595         [ +  + ]:        1117 :                 reader >> VARINT(picked_num);
     596         [ -  + ]:         793 :             } catch (const std::ios_base::failure&) {}
     597   [ +  +  +  + ]:        1117 :             if (picked_num < todo.Size() && todo[picked_num]) {
     598                 :         161 :                 picked = picked_num;
     599                 :             :             }
     600                 :             :         }
     601                 :             : 
     602                 :             :         // Find a connected component inside todo, including picked if any.
     603         [ +  + ]:        1117 :         auto component = picked ? depgraph.GetConnectedComponent(todo, *picked)
     604                 :         956 :                                 : depgraph.FindConnectedComponent(todo);
     605                 :             : 
     606                 :             :         // The component must be a subset of todo and non-empty.
     607         [ -  + ]:        1117 :         assert(component.IsSubsetOf(todo));
     608         [ -  + ]:        1117 :         assert(component.Any());
     609                 :             : 
     610                 :             :         // If picked was provided, the component must include it.
     611   [ +  +  -  + ]:        1117 :         if (picked) assert(component[*picked]);
     612                 :             : 
     613                 :             :         // If todo is the entire graph, and the entire graph is connected, then the component must
     614                 :             :         // be the entire graph.
     615         [ +  + ]:        1117 :         if (todo == depgraph.Positions()) {
     616   [ +  +  -  + ]:         165 :             assert((component == todo) == depgraph.IsConnected());
     617                 :             :         }
     618                 :             : 
     619                 :             :         // If subset is connected, then component must match subset.
     620   [ +  +  -  + ]:        1908 :         assert((component == todo) == depgraph.IsConnected(todo));
     621                 :             : 
     622                 :             :         // The component cannot have any ancestors or descendants outside of component but in todo.
     623   [ +  -  +  + ]:        8141 :         for (auto i : component) {
     624         [ -  + ]:        5907 :             assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
     625         [ -  + ]:        5907 :             assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
     626                 :             :         }
     627                 :             : 
     628                 :             :         // Starting from any component element, we must be able to reach every element.
     629   [ +  -  +  + ]:        8141 :         for (auto i : component) {
     630                 :             :             // Start with just i as reachable.
     631                 :        5907 :             TestBitSet reachable = TestBitSet::Singleton(i);
     632                 :             :             // Add in-todo descendants and ancestors to reachable until it does not change anymore.
     633                 :       36803 :             while (true) {
     634                 :       21355 :                 TestBitSet new_reachable = reachable;
     635   [ +  -  +  + ]:      249773 :                 for (auto j : new_reachable) {
     636                 :      207063 :                     new_reachable |= depgraph.Ancestors(j) & todo;
     637                 :      207063 :                     new_reachable |= depgraph.Descendants(j) & todo;
     638                 :             :                 }
     639         [ +  + ]:       21355 :                 if (new_reachable == reachable) break;
     640                 :       15448 :                 reachable = new_reachable;
     641                 :       15448 :             }
     642                 :             :             // Verify that the result is the entire component.
     643         [ -  + ]:        5907 :             assert(component == reachable);
     644                 :             :         }
     645                 :             : 
     646                 :             :         // Construct an arbitrary subset of todo.
     647                 :        1117 :         uint64_t subset_bits{0};
     648                 :        1117 :         try {
     649         [ +  + ]:        1117 :             reader >> VARINT(subset_bits);
     650         [ -  + ]:         807 :         } catch (const std::ios_base::failure&) {}
     651                 :        1117 :         TestBitSet subset;
     652   [ +  -  +  + ]:       26259 :         for (DepGraphIndex i : depgraph.Positions()) {
     653         [ +  + ]:       24025 :             if (todo[i]) {
     654         [ +  + ]:       12658 :                 if (subset_bits & 1) subset.Set(i);
     655                 :       12658 :                 subset_bits >>= 1;
     656                 :             :             }
     657                 :             :         }
     658                 :             :         // Which must be non-empty.
     659         [ +  + ]:        1117 :         if (subset.None()) subset = TestBitSet::Singleton(todo.First());
     660                 :             :         // Remove it from todo.
     661                 :        1117 :         todo -= subset;
     662                 :             :     }
     663                 :             : 
     664                 :             :     // No components can be found in an empty subset.
     665         [ -  + ]:         105 :     assert(depgraph.FindConnectedComponent(todo).None());
     666                 :         105 : }
     667                 :             : 
     668         [ +  - ]:         616 : FUZZ_TARGET(clusterlin_make_connected)
     669                 :             : {
     670                 :             :     // Verify that MakeConnected makes graphs connected.
     671                 :             : 
     672                 :         158 :     SpanReader reader(buffer);
     673                 :         158 :     DepGraph<TestBitSet> depgraph;
     674                 :         158 :     try {
     675         [ +  - ]:         158 :         reader >> Using<DepGraphFormatter>(depgraph);
     676         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     677         [ +  - ]:         158 :     MakeConnected(depgraph);
     678         [ +  - ]:         158 :     SanityCheck(depgraph);
     679         [ -  + ]:         158 :     assert(depgraph.IsConnected());
     680                 :         158 : }
     681                 :             : 
     682         [ +  - ]:         552 : FUZZ_TARGET(clusterlin_chunking)
     683                 :             : {
     684                 :             :     // Verify the correctness of the ChunkLinearization function.
     685                 :             : 
     686                 :             :     // Construct a graph by deserializing.
     687                 :          94 :     SpanReader reader(buffer);
     688                 :          94 :     DepGraph<TestBitSet> depgraph;
     689                 :          94 :     try {
     690         [ +  - ]:          94 :         reader >> Using<DepGraphFormatter>(depgraph);
     691         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     692                 :             : 
     693                 :             :     // Read a valid linearization for depgraph.
     694         [ +  - ]:          94 :     auto linearization = ReadLinearization(depgraph, reader);
     695                 :             : 
     696                 :             :     // Invoke the chunking function.
     697         [ -  + ]:          94 :     auto chunking = ChunkLinearization(depgraph, linearization);
     698                 :             : 
     699                 :             :     // Verify that chunk feerates are monotonically non-increasing.
     700   [ -  +  +  + ]:         349 :     for (size_t i = 1; i < chunking.size(); ++i) {
     701         [ -  + ]:         255 :         assert(!(chunking[i] >> chunking[i - 1]));
     702                 :             :     }
     703                 :             : 
     704                 :             :     // Naively recompute the chunks (each is the highest-feerate prefix of what remains).
     705                 :          94 :     auto todo = depgraph.Positions();
     706         [ +  + ]:         437 :     for (const auto& chunk_feerate : chunking) {
     707         [ -  + ]:         343 :         assert(todo.Any());
     708                 :         343 :         SetInfo<TestBitSet> accumulator, best;
     709         [ +  + ]:        7090 :         for (DepGraphIndex idx : linearization) {
     710         [ +  + ]:        6747 :             if (todo[idx]) {
     711                 :        3672 :                 accumulator.Set(depgraph, idx);
     712   [ +  +  +  + ]:        3672 :                 if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
     713                 :         760 :                     best = accumulator;
     714                 :             :                 }
     715                 :             :             }
     716                 :             :         }
     717         [ +  - ]:         343 :         assert(chunk_feerate == best.feerate);
     718         [ -  + ]:         343 :         assert(best.transactions.IsSubsetOf(todo));
     719                 :         343 :         todo -= best.transactions;
     720                 :             :     }
     721         [ -  + ]:          94 :     assert(todo.None());
     722                 :          94 : }
     723                 :             : 
     724         [ +  - ]:         568 : FUZZ_TARGET(clusterlin_ancestor_finder)
     725                 :             : {
     726                 :             :     // Verify that AncestorCandidateFinder works as expected.
     727                 :             : 
     728                 :             :     // Retrieve a depgraph from the fuzz input.
     729                 :         110 :     SpanReader reader(buffer);
     730                 :         110 :     DepGraph<TestBitSet> depgraph;
     731                 :         110 :     try {
     732         [ +  - ]:         110 :         reader >> Using<DepGraphFormatter>(depgraph);
     733         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     734                 :             : 
     735                 :         110 :     AncestorCandidateFinder anc_finder(depgraph);
     736                 :         110 :     auto todo = depgraph.Positions();
     737         [ +  + ]:        1090 :     while (todo.Any()) {
     738                 :             :         // Call the ancestor finder's FindCandidateSet for what remains of the graph.
     739         [ -  + ]:         980 :         assert(!anc_finder.AllDone());
     740         [ -  + ]:         980 :         assert(todo.Count() == anc_finder.NumRemaining());
     741                 :         980 :         auto best_anc = anc_finder.FindCandidateSet();
     742                 :             :         // Sanity check the result.
     743         [ -  + ]:         980 :         assert(best_anc.transactions.Any());
     744         [ -  + ]:         980 :         assert(best_anc.transactions.IsSubsetOf(todo));
     745         [ +  - ]:         980 :         assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
     746         [ -  + ]:         980 :         assert(depgraph.IsConnected(best_anc.transactions));
     747                 :             :         // Check that it is topologically valid.
     748   [ +  -  +  + ]:        3972 :         for (auto i : best_anc.transactions) {
     749         [ -  + ]:        2012 :             assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
     750                 :             :         }
     751                 :             : 
     752                 :             :         // Compute all remaining ancestor sets.
     753                 :         980 :         std::optional<SetInfo<TestBitSet>> real_best_anc;
     754   [ +  -  +  + ]:       13336 :         for (auto i : todo) {
     755                 :       11376 :             SetInfo info(depgraph, todo & depgraph.Ancestors(i));
     756   [ +  +  +  + ]:       11376 :             if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
     757         [ +  + ]:       13963 :                 real_best_anc = info;
     758                 :             :             }
     759                 :             :         }
     760                 :             :         // The set returned by anc_finder must equal the real best ancestor sets.
     761         [ -  + ]:         980 :         assert(real_best_anc.has_value());
     762         [ +  - ]:         980 :         assert(*real_best_anc == best_anc);
     763                 :             : 
     764                 :             :         // Find a non-empty topologically valid subset of transactions to remove from the graph.
     765                 :             :         // Using an empty set would mean the next iteration is identical to the current one, and
     766                 :             :         // could cause an infinite loop.
     767         [ +  - ]:         980 :         auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
     768                 :         980 :         todo -= del_set;
     769                 :         980 :         anc_finder.MarkDone(del_set);
     770                 :             :     }
     771         [ -  + ]:         110 :     assert(anc_finder.AllDone());
     772         [ -  + ]:         110 :     assert(anc_finder.NumRemaining() == 0);
     773                 :         110 : }
     774                 :             : 
     775                 :             : static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
     776                 :             : 
     777         [ +  - ]:         589 : FUZZ_TARGET(clusterlin_simple_finder)
     778                 :             : {
     779                 :             :     // Verify that SimpleCandidateFinder works as expected by sanity checking the results
     780                 :             :     // and comparing them (if claimed to be optimal) against the sets found by
     781                 :             :     // ExhaustiveCandidateFinder and AncestorCandidateFinder.
     782                 :             :     //
     783                 :             :     // Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to
     784                 :             :     // establish confidence in SimpleCandidateFinder, so that it can be used to test
     785                 :             :     // SearchCandidateFinder below.
     786                 :             : 
     787                 :             :     // Retrieve a depgraph from the fuzz input.
     788                 :         131 :     SpanReader reader(buffer);
     789                 :         131 :     DepGraph<TestBitSet> depgraph;
     790                 :         131 :     try {
     791         [ +  - ]:         131 :         reader >> Using<DepGraphFormatter>(depgraph);
     792         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     793                 :             : 
     794                 :             :     // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder and
     795                 :             :     // AncestorCandidateFinder it is being tested against.
     796                 :         131 :     SimpleCandidateFinder smp_finder(depgraph);
     797                 :         131 :     ExhaustiveCandidateFinder exh_finder(depgraph);
     798                 :         131 :     AncestorCandidateFinder anc_finder(depgraph);
     799                 :             : 
     800                 :         131 :     auto todo = depgraph.Positions();
     801         [ +  + ]:        1341 :     while (todo.Any()) {
     802         [ -  + ]:        1210 :         assert(!smp_finder.AllDone());
     803         [ -  + ]:        1210 :         assert(!exh_finder.AllDone());
     804         [ -  + ]:        1210 :         assert(!anc_finder.AllDone());
     805         [ -  + ]:        1210 :         assert(anc_finder.NumRemaining() == todo.Count());
     806                 :             : 
     807                 :             :         // Call SimpleCandidateFinder.
     808         [ -  + ]:        1210 :         auto [found, iterations_done] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
     809                 :        1210 :         bool optimal = (iterations_done != MAX_SIMPLE_ITERATIONS);
     810                 :             : 
     811                 :             :         // Sanity check the result.
     812         [ -  + ]:        1210 :         assert(iterations_done <= MAX_SIMPLE_ITERATIONS);
     813         [ -  + ]:        1210 :         assert(found.transactions.Any());
     814         [ -  + ]:        1210 :         assert(found.transactions.IsSubsetOf(todo));
     815         [ +  - ]:        1210 :         assert(depgraph.FeeRate(found.transactions) == found.feerate);
     816                 :             :         // Check that it is topologically valid.
     817   [ +  -  +  + ]:        4575 :         for (auto i : found.transactions) {
     818         [ -  + ]:        2155 :             assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
     819                 :             :         }
     820                 :             : 
     821                 :             :         // At most 2^(N-1) iterations can be required: the number of non-empty connected subsets a
     822                 :             :         // graph with N transactions can have. If MAX_SIMPLE_ITERATIONS exceeds this number, the
     823                 :             :         // result is necessarily optimal.
     824         [ -  + ]:        1210 :         assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
     825   [ +  +  -  + ]:        1210 :         if (MAX_SIMPLE_ITERATIONS > (uint64_t{1} << (todo.Count() - 1))) assert(optimal);
     826                 :             : 
     827                 :             :         // SimpleCandidateFinder only finds connected sets.
     828         [ -  + ]:        1210 :         assert(depgraph.IsConnected(found.transactions));
     829                 :             : 
     830                 :             :         // Perform further quality checks only if SimpleCandidateFinder claims an optimal result.
     831         [ +  + ]:        1210 :         if (optimal) {
     832                 :             :             // Compare with AncestorCandidateFinder.
     833                 :        1193 :             auto anc = anc_finder.FindCandidateSet();
     834         [ -  + ]:        1193 :             assert(anc.feerate <= found.feerate);
     835                 :             : 
     836         [ +  + ]:        1193 :             if (todo.Count() <= 12) {
     837                 :             :                 // Compare with ExhaustiveCandidateFinder. This quickly gets computationally
     838                 :             :                 // expensive for large clusters (O(2^n)), so only do it for sufficiently small ones.
     839                 :         761 :                 auto exhaustive = exh_finder.FindCandidateSet();
     840         [ +  - ]:         761 :                 assert(exhaustive.feerate == found.feerate);
     841                 :             :             }
     842                 :             : 
     843                 :             :             // Compare with a non-empty topological set read from the fuzz input (comparing with an
     844                 :             :             // empty set is not interesting).
     845         [ +  - ]:        1193 :             auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
     846         [ -  + ]:        1193 :             assert(found.feerate >= depgraph.FeeRate(read_topo));
     847                 :             :         }
     848                 :             : 
     849                 :             :         // Find a non-empty topologically valid subset of transactions to remove from the graph.
     850                 :             :         // Using an empty set would mean the next iteration is identical to the current one, and
     851                 :             :         // could cause an infinite loop.
     852         [ +  - ]:        1210 :         auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
     853                 :        1210 :         todo -= del_set;
     854                 :        1210 :         smp_finder.MarkDone(del_set);
     855                 :        1210 :         exh_finder.MarkDone(del_set);
     856                 :        1210 :         anc_finder.MarkDone(del_set);
     857                 :             :     }
     858                 :             : 
     859         [ -  + ]:         131 :     assert(smp_finder.AllDone());
     860         [ -  + ]:         131 :     assert(exh_finder.AllDone());
     861         [ -  + ]:         131 :     assert(anc_finder.AllDone());
     862         [ -  + ]:         131 :     assert(anc_finder.NumRemaining() == 0);
     863                 :         131 : }
     864                 :             : 
     865         [ +  - ]:         816 : FUZZ_TARGET(clusterlin_search_finder)
     866                 :             : {
     867                 :             :     // Verify that SearchCandidateFinder works as expected by sanity checking the results
     868                 :             :     // and comparing with the results from SimpleCandidateFinder and AncestorCandidateFinder,
     869                 :             :     // if the result is claimed to be optimal.
     870                 :             : 
     871                 :             :     // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input.
     872                 :         358 :     SpanReader reader(buffer);
     873                 :         358 :     DepGraph<TestBitSet> depgraph;
     874                 :         358 :     uint64_t rng_seed{0};
     875                 :         358 :     uint8_t make_connected{1};
     876                 :         358 :     try {
     877   [ +  -  +  +  :         358 :         reader >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
                   +  + ]
     878         [ -  + ]:         198 :     } catch (const std::ios_base::failure&) {}
     879                 :             :     // The most complicated graphs are connected ones (other ones just split up). Optionally force
     880                 :             :     // the graph to be connected.
     881   [ +  +  +  - ]:         358 :     if (make_connected) MakeConnected(depgraph);
     882                 :             : 
     883                 :             :     // Instantiate the candidate finders.
     884                 :         358 :     SearchCandidateFinder src_finder(depgraph, rng_seed);
     885                 :         358 :     SimpleCandidateFinder smp_finder(depgraph);
     886                 :         358 :     AncestorCandidateFinder anc_finder(depgraph);
     887                 :             : 
     888                 :         358 :     auto todo = depgraph.Positions();
     889         [ +  + ]:        6468 :     while (todo.Any()) {
     890         [ -  + ]:        6110 :         assert(!src_finder.AllDone());
     891         [ -  + ]:        6110 :         assert(!smp_finder.AllDone());
     892         [ -  + ]:        6110 :         assert(!anc_finder.AllDone());
     893         [ -  + ]:        6110 :         assert(anc_finder.NumRemaining() == todo.Count());
     894                 :             : 
     895                 :             :         // For each iteration, read an iteration count limit from the fuzz input.
     896                 :        6110 :         uint64_t max_iterations = 1;
     897                 :        6110 :         try {
     898         [ +  + ]:        6110 :             reader >> VARINT(max_iterations);
     899         [ -  + ]:        5022 :         } catch (const std::ios_base::failure&) {}
     900                 :        6110 :         max_iterations &= 0xfffff;
     901                 :             : 
     902                 :             :         // Read an initial subset from the fuzz input (allowed to be empty).
     903         [ +  - ]:        6110 :         auto init_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false);
     904                 :        6110 :         SetInfo init_best(depgraph, init_set);
     905                 :             : 
     906                 :             :         // Call the search finder's FindCandidateSet for what remains of the graph.
     907         [ -  + ]:        6110 :         auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best);
     908                 :        6110 :         bool optimal = iterations_done < max_iterations;
     909                 :             : 
     910                 :             :         // Sanity check the result.
     911         [ -  + ]:        6110 :         assert(iterations_done <= max_iterations);
     912         [ -  + ]:        6110 :         assert(found.transactions.Any());
     913         [ -  + ]:        6110 :         assert(found.transactions.IsSubsetOf(todo));
     914         [ +  - ]:        6110 :         assert(depgraph.FeeRate(found.transactions) == found.feerate);
     915   [ +  +  -  + ]:        6110 :         if (!init_best.feerate.IsEmpty()) assert(found.feerate >= init_best.feerate);
     916                 :             :         // Check that it is topologically valid.
     917   [ +  -  +  + ]:       46563 :         for (auto i : found.transactions) {
     918         [ -  + ]:       34343 :             assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
     919                 :             :         }
     920                 :             : 
     921                 :             :         // At most 2^(N-1) iterations can be required: the maximum number of non-empty topological
     922                 :             :         // subsets a (connected) cluster with N transactions can have. Even when the cluster is no
     923                 :             :         // longer connected after removing certain transactions, this holds, because the connected
     924                 :             :         // components are searched separately.
     925         [ -  + ]:        6110 :         assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
     926                 :             :         // Additionally, test that no more than sqrt(2^N)+1 iterations are required. This is just
     927                 :             :         // an empirical bound that seems to hold, without proof. Still, add a test for it so we
     928                 :             :         // can learn about counterexamples if they exist.
     929   [ +  +  +  - ]:        6110 :         if (iterations_done >= 1 && todo.Count() <= 63) {
     930         [ +  - ]:        4808 :             Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count());
     931                 :             :         }
     932                 :             : 
     933                 :             :         // Perform quality checks only if SearchCandidateFinder claims an optimal result.
     934         [ +  + ]:        6110 :         if (optimal) {
     935                 :             :             // Optimal sets are always connected.
     936         [ -  + ]:        1791 :             assert(depgraph.IsConnected(found.transactions));
     937                 :             : 
     938                 :             :             // Compare with SimpleCandidateFinder.
     939         [ -  + ]:        1791 :             auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
     940         [ -  + ]:        1791 :             assert(found.feerate >= simple.feerate);
     941         [ +  + ]:        1791 :             if (simple_iters < MAX_SIMPLE_ITERATIONS) {
     942         [ +  - ]:        1753 :                 assert(found.feerate == simple.feerate);
     943                 :             :             }
     944                 :             : 
     945                 :             :             // Compare with AncestorCandidateFinder;
     946                 :        1791 :             auto anc = anc_finder.FindCandidateSet();
     947         [ -  + ]:        1791 :             assert(found.feerate >= anc.feerate);
     948                 :             : 
     949                 :             :             // Compare with a non-empty topological set read from the fuzz input (comparing with an
     950                 :             :             // empty set is not interesting).
     951         [ +  - ]:        1791 :             auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
     952         [ -  + ]:        1791 :             assert(found.feerate >= depgraph.FeeRate(read_topo));
     953                 :             :         }
     954                 :             : 
     955                 :             :         // Find a non-empty topologically valid subset of transactions to remove from the graph.
     956                 :             :         // Using an empty set would mean the next iteration is identical to the current one, and
     957                 :             :         // could cause an infinite loop.
     958         [ +  - ]:        6110 :         auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
     959                 :        6110 :         todo -= del_set;
     960                 :        6110 :         src_finder.MarkDone(del_set);
     961                 :        6110 :         smp_finder.MarkDone(del_set);
     962                 :        6110 :         anc_finder.MarkDone(del_set);
     963                 :             :     }
     964                 :             : 
     965         [ -  + ]:         358 :     assert(src_finder.AllDone());
     966         [ -  + ]:         358 :     assert(smp_finder.AllDone());
     967         [ -  + ]:         358 :     assert(anc_finder.AllDone());
     968         [ -  + ]:         358 :     assert(anc_finder.NumRemaining() == 0);
     969                 :         358 : }
     970                 :             : 
     971         [ +  - ]:         588 : FUZZ_TARGET(clusterlin_linearization_chunking)
     972                 :             : {
     973                 :             :     // Verify the behavior of LinearizationChunking.
     974                 :             : 
     975                 :             :     // Retrieve a depgraph from the fuzz input.
     976                 :         130 :     SpanReader reader(buffer);
     977                 :         130 :     DepGraph<TestBitSet> depgraph;
     978                 :         130 :     try {
     979         [ +  - ]:         130 :         reader >> Using<DepGraphFormatter>(depgraph);
     980         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     981                 :             : 
     982                 :             :     // Retrieve a topologically-valid subset of depgraph (allowed to be empty, because the argument
     983                 :             :     // to LinearizationChunking::Intersect is allowed to be empty).
     984                 :         130 :     auto todo = depgraph.Positions();
     985         [ +  - ]:         130 :     auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false));
     986                 :             : 
     987                 :             :     // Retrieve a valid linearization for depgraph.
     988         [ +  - ]:         130 :     auto linearization = ReadLinearization(depgraph, reader);
     989                 :             : 
     990                 :             :     // Construct a LinearizationChunking object, initially for the whole linearization.
     991         [ -  + ]:         130 :     LinearizationChunking chunking(depgraph, linearization);
     992                 :             : 
     993                 :             :     // Incrementally remove transactions from the chunking object, and check various properties at
     994                 :             :     // every step.
     995         [ +  + ]:        1521 :     while (todo.Any()) {
     996   [ -  +  -  + ]:        1261 :         assert(chunking.NumChunksLeft() > 0);
     997                 :             : 
     998                 :             :         // Construct linearization with just todo.
     999                 :        1261 :         std::vector<DepGraphIndex> linearization_left;
    1000         [ +  + ]:       29803 :         for (auto i : linearization) {
    1001   [ +  +  +  - ]:       28542 :             if (todo[i]) linearization_left.push_back(i);
    1002                 :             :         }
    1003                 :             : 
    1004                 :             :         // Compute the chunking for linearization_left.
    1005         [ -  + ]:        1261 :         auto chunking_left = ChunkLinearization(depgraph, linearization_left);
    1006                 :             : 
    1007                 :             :         // Verify that it matches the feerates of the chunks of chunking.
    1008   [ -  +  -  +  :        1261 :         assert(chunking.NumChunksLeft() == chunking_left.size());
                   -  + ]
    1009   [ -  +  +  + ]:        7981 :         for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
    1010         [ +  - ]:       13440 :             assert(chunking.GetChunk(i).feerate == chunking_left[i]);
    1011                 :             :         }
    1012                 :             : 
    1013                 :             :         // Check consistency of chunking.
    1014                 :        1261 :         TestBitSet combined;
    1015   [ -  +  +  + ]:        7981 :         for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
    1016                 :        6720 :             const auto& chunk_info = chunking.GetChunk(i);
    1017                 :             :             // Chunks must be non-empty.
    1018         [ -  + ]:        6720 :             assert(chunk_info.transactions.Any());
    1019                 :             :             // Chunk feerates must be monotonically non-increasing.
    1020   [ +  +  -  + ]:        6720 :             if (i > 0) assert(!(chunk_info.feerate >> chunking.GetChunk(i - 1).feerate));
    1021                 :             :             // Chunks must be a subset of what is left of the linearization.
    1022         [ -  + ]:        6720 :             assert(chunk_info.transactions.IsSubsetOf(todo));
    1023                 :             :             // Chunks' claimed feerates must match their transactions' aggregate feerate.
    1024         [ +  - ]:        6720 :             assert(depgraph.FeeRate(chunk_info.transactions) == chunk_info.feerate);
    1025                 :             :             // Chunks must be the highest-feerate remaining prefix.
    1026                 :        6720 :             SetInfo<TestBitSet> accumulator, best;
    1027         [ +  + ]:      187570 :             for (auto j : linearization) {
    1028   [ +  +  +  + ]:      180850 :                 if (todo[j] && !combined[j]) {
    1029                 :       64623 :                     accumulator.Set(depgraph, j);
    1030   [ +  +  +  + ]:       64623 :                     if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) {
    1031                 :       12196 :                         best = accumulator;
    1032                 :             :                     }
    1033                 :             :                 }
    1034                 :             :             }
    1035         [ -  + ]:        6720 :             assert(best.transactions == chunk_info.transactions);
    1036         [ +  - ]:        6720 :             assert(best.feerate == chunk_info.feerate);
    1037                 :             :             // Chunks cannot overlap.
    1038         [ -  + ]:        6720 :             assert(!chunk_info.transactions.Overlaps(combined));
    1039         [ +  - ]:        6720 :             combined |= chunk_info.transactions;
    1040                 :             :             // Chunks must be topological.
    1041   [ +  -  +  + ]:       27996 :             for (auto idx : chunk_info.transactions) {
    1042         [ -  + ]:       14556 :                 assert((depgraph.Ancestors(idx) & todo).IsSubsetOf(combined));
    1043                 :             :             }
    1044                 :             :         }
    1045         [ -  + ]:        1261 :         assert(combined == todo);
    1046                 :             : 
    1047                 :             :         // Verify the expected properties of LinearizationChunking::IntersectPrefixes:
    1048                 :        1261 :         auto intersect = chunking.IntersectPrefixes(subset);
    1049                 :             :         // - Intersecting again doesn't change the result.
    1050         [ +  - ]:        1261 :         assert(chunking.IntersectPrefixes(intersect) == intersect);
    1051                 :             :         // - The intersection is topological.
    1052                 :        1261 :         TestBitSet intersect_anc;
    1053   [ +  +  +  + ]:        4463 :         for (auto idx : intersect.transactions) {
    1054                 :        2572 :             intersect_anc |= (depgraph.Ancestors(idx) & todo);
    1055                 :             :         }
    1056         [ -  + ]:        1261 :         assert(intersect.transactions == intersect_anc);
    1057                 :             :         // - The claimed intersection feerate matches its transactions.
    1058         [ +  - ]:        1261 :         assert(intersect.feerate == depgraph.FeeRate(intersect.transactions));
    1059                 :             :         // - The intersection may only be empty if its input is empty.
    1060         [ -  + ]:        1261 :         assert(intersect.transactions.Any() == subset.transactions.Any());
    1061                 :             :         // - The intersection feerate must be as high as the input.
    1062         [ -  + ]:        1261 :         assert(intersect.feerate >= subset.feerate);
    1063                 :             :         // - No non-empty intersection between the intersection and a prefix of the chunks of the
    1064                 :             :         //   remainder of the linearization may be better than the intersection.
    1065                 :        1261 :         TestBitSet prefix;
    1066   [ -  +  +  + ]:        7981 :         for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
    1067                 :        6720 :             prefix |= chunking.GetChunk(i).transactions;
    1068                 :        6720 :             auto reintersect = SetInfo(depgraph, prefix & intersect.transactions);
    1069         [ +  + ]:        6720 :             if (!reintersect.feerate.IsEmpty()) {
    1070         [ -  + ]:        3478 :                 assert(reintersect.feerate <= intersect.feerate);
    1071                 :             :             }
    1072                 :             :         }
    1073                 :             : 
    1074                 :             :         // Find a non-empty topologically valid subset of transactions to remove from the graph.
    1075                 :             :         // Using an empty set would mean the next iteration is identical to the current one, and
    1076                 :             :         // could cause an infinite loop.
    1077         [ +  - ]:        1261 :         auto done = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
    1078                 :        1261 :         todo -= done;
    1079                 :        1261 :         chunking.MarkDone(done);
    1080                 :        1261 :         subset = SetInfo(depgraph, subset.transactions - done);
    1081                 :        1261 :     }
    1082                 :             : 
    1083   [ -  +  -  + ]:         130 :     assert(chunking.NumChunksLeft() == 0);
    1084                 :         130 : }
    1085                 :             : 
    1086         [ +  - ]:         703 : FUZZ_TARGET(clusterlin_simple_linearize)
    1087                 :             : {
    1088                 :             :     // Verify the behavior of SimpleLinearize(). Note that SimpleLinearize is only used in tests;
    1089                 :             :     // the purpose of this fuzz test is to establish confidence in SimpleLinearize, so that it can
    1090                 :             :     // be used to test the real Linearize function in the fuzz test below.
    1091                 :             : 
    1092                 :             :     // Retrieve an iteration count and a depgraph from the fuzz input.
    1093                 :         245 :     SpanReader reader(buffer);
    1094                 :         245 :     uint64_t iter_count{0};
    1095                 :         245 :     DepGraph<TestBitSet> depgraph;
    1096                 :         245 :     try {
    1097   [ +  +  +  - ]:         245 :         reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph);
    1098         [ -  + ]:           4 :     } catch (const std::ios_base::failure&) {}
    1099                 :         245 :     iter_count %= MAX_SIMPLE_ITERATIONS;
    1100                 :             : 
    1101                 :             :     // Invoke SimpleLinearize().
    1102         [ -  + ]:         245 :     auto [linearization, optimal] = SimpleLinearize(depgraph, iter_count);
    1103         [ -  + ]:         245 :     SanityCheck(depgraph, linearization);
    1104         [ -  + ]:         245 :     auto simple_chunking = ChunkLinearization(depgraph, linearization);
    1105                 :             : 
    1106                 :             :     // If the iteration count is sufficiently high, an optimal linearization must be found.
    1107                 :             :     // SimpleLinearize on k transactions can take up to 2^(k-1) iterations (one per non-empty
    1108                 :             :     // connected topologically valid subset), which sums over k=1..n to (2^n)-1.
    1109         [ +  - ]:         245 :     const uint64_t n = depgraph.TxCount();
    1110   [ +  -  +  + ]:         245 :     if (n <= 63 && (iter_count >> n)) {
    1111         [ -  + ]:          57 :         assert(optimal);
    1112                 :             :     }
    1113                 :             : 
    1114                 :             :     // If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are
    1115                 :             :     // n! linearizations), test that the result is as good as every valid linearization.
    1116   [ +  +  +  + ]:         245 :     if (optimal && depgraph.TxCount() <= 8) {
    1117         [ +  - ]:         108 :         auto exh_linearization = ExhaustiveLinearize(depgraph);
    1118         [ -  + ]:         108 :         auto exh_chunking = ChunkLinearization(depgraph, exh_linearization);
    1119   [ -  +  -  +  :         108 :         auto cmp = CompareChunks(simple_chunking, exh_chunking);
                   +  - ]
    1120         [ -  + ]:         108 :         assert(cmp == 0);
    1121   [ -  +  -  +  :         108 :         assert(simple_chunking.size() == exh_chunking.size());
                   -  + ]
    1122                 :         108 :     }
    1123                 :             : 
    1124         [ +  + ]:         245 :     if (optimal) {
    1125                 :             :         // Compare with a linearization read from the fuzz input.
    1126         [ +  - ]:         186 :         auto read = ReadLinearization(depgraph, reader);
    1127         [ -  + ]:         186 :         auto read_chunking = ChunkLinearization(depgraph, read);
    1128   [ -  +  -  +  :         186 :         auto cmp = CompareChunks(simple_chunking, read_chunking);
                   +  - ]
    1129         [ -  + ]:         186 :         assert(cmp >= 0);
    1130                 :         186 :     }
    1131                 :         245 : }
    1132                 :             : 
    1133         [ +  - ]:         931 : FUZZ_TARGET(clusterlin_linearize)
    1134                 :             : {
    1135                 :             :     // Verify the behavior of Linearize().
    1136                 :             : 
    1137                 :             :     // Retrieve an RNG seed, an iteration count, a depgraph, and whether to make it connected from
    1138                 :             :     // the fuzz input.
    1139                 :         473 :     SpanReader reader(buffer);
    1140                 :         473 :     DepGraph<TestBitSet> depgraph;
    1141                 :         473 :     uint64_t rng_seed{0};
    1142                 :         473 :     uint64_t iter_count{0};
    1143                 :         473 :     uint8_t make_connected{1};
    1144                 :         473 :     try {
    1145   [ +  +  +  -  :         473 :         reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
             +  +  +  + ]
    1146         [ -  + ]:         368 :     } catch (const std::ios_base::failure&) {}
    1147                 :             :     // The most complicated graphs are connected ones (other ones just split up). Optionally force
    1148                 :             :     // the graph to be connected.
    1149   [ +  +  +  - ]:         473 :     if (make_connected) MakeConnected(depgraph);
    1150                 :             : 
    1151                 :             :     // Optionally construct an old linearization for it.
    1152                 :         473 :     std::vector<DepGraphIndex> old_linearization;
    1153                 :         473 :     {
    1154                 :         473 :         uint8_t have_old_linearization{0};
    1155                 :         473 :         try {
    1156         [ +  + ]:         473 :             reader >> have_old_linearization;
    1157         [ -  + ]:         313 :         } catch(const std::ios_base::failure&) {}
    1158         [ +  + ]:         473 :         if (have_old_linearization & 1) {
    1159         [ +  - ]:         264 :             old_linearization = ReadLinearization(depgraph, reader);
    1160         [ -  + ]:         132 :             SanityCheck(depgraph, old_linearization);
    1161                 :             :         }
    1162                 :             :     }
    1163                 :             : 
    1164                 :             :     // Invoke Linearize().
    1165                 :         473 :     iter_count &= 0x7ffff;
    1166   [ -  +  -  + ]:         473 :     auto [linearization, optimal, cost] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
    1167         [ -  + ]:         473 :     assert(cost <= iter_count);
    1168         [ -  + ]:         473 :     SanityCheck(depgraph, linearization);
    1169         [ -  + ]:         473 :     auto chunking = ChunkLinearization(depgraph, linearization);
    1170                 :             : 
    1171                 :             :     // Linearization must always be as good as the old one, if provided.
    1172         [ +  + ]:         473 :     if (!old_linearization.empty()) {
    1173         [ -  + ]:         128 :         auto old_chunking = ChunkLinearization(depgraph, old_linearization);
    1174   [ -  +  -  +  :         128 :         auto cmp = CompareChunks(chunking, old_chunking);
                   +  - ]
    1175         [ -  + ]:         128 :         assert(cmp >= 0);
    1176                 :         128 :     }
    1177                 :             : 
    1178                 :             :     // If the iteration count is sufficiently high, an optimal linearization must be found.
    1179         [ +  + ]:         473 :     if (iter_count >= MaxOptimalLinearizationIters(depgraph.TxCount())) {
    1180         [ -  + ]:          98 :         assert(optimal);
    1181                 :             :     }
    1182                 :             : 
    1183                 :             :     // If Linearize claims optimal result, run quality tests.
    1184         [ +  + ]:         473 :     if (optimal) {
    1185                 :             :         // It must be as good as SimpleLinearize.
    1186         [ -  + ]:         330 :         auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
    1187         [ -  + ]:         330 :         SanityCheck(depgraph, simple_linearization);
    1188         [ -  + ]:         330 :         auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
    1189   [ -  +  -  +  :         330 :         auto cmp = CompareChunks(chunking, simple_chunking);
                   +  - ]
    1190         [ -  + ]:         330 :         assert(cmp >= 0);
    1191                 :             :         // If SimpleLinearize finds the optimal result too, they must be equal (if not,
    1192                 :             :         // SimpleLinearize is broken).
    1193   [ +  +  -  + ]:         330 :         if (simple_optimal) assert(cmp == 0);
    1194                 :             :         // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
    1195                 :             :         // chunking is claimed to be optimal, which implies minimal chunks).
    1196   [ +  +  -  +  :         330 :         if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
             -  +  -  + ]
    1197                 :             : 
    1198                 :             :         // Compare with a linearization read from the fuzz input.
    1199         [ +  - ]:         330 :         auto read = ReadLinearization(depgraph, reader);
    1200         [ -  + ]:         330 :         auto read_chunking = ChunkLinearization(depgraph, read);
    1201   [ -  +  -  +  :         330 :         auto cmp_read = CompareChunks(chunking, read_chunking);
                   +  - ]
    1202         [ -  + ]:         330 :         assert(cmp_read >= 0);
    1203                 :         330 :     }
    1204                 :         473 : }
    1205                 :             : 
    1206         [ +  - ]:         557 : FUZZ_TARGET(clusterlin_postlinearize)
    1207                 :             : {
    1208                 :             :     // Verify expected properties of PostLinearize() on arbitrary linearizations.
    1209                 :             : 
    1210                 :             :     // Retrieve a depgraph from the fuzz input.
    1211                 :          99 :     SpanReader reader(buffer);
    1212                 :          99 :     DepGraph<TestBitSet> depgraph;
    1213                 :          99 :     try {
    1214         [ +  - ]:          99 :         reader >> Using<DepGraphFormatter>(depgraph);
    1215         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
    1216                 :             : 
    1217                 :             :     // Retrieve a linearization from the fuzz input.
    1218                 :          99 :     std::vector<DepGraphIndex> linearization;
    1219         [ +  - ]:         198 :     linearization = ReadLinearization(depgraph, reader);
    1220         [ -  + ]:          99 :     SanityCheck(depgraph, linearization);
    1221                 :             : 
    1222                 :             :     // Produce a post-processed version.
    1223         [ +  - ]:          99 :     auto post_linearization = linearization;
    1224   [ -  +  +  - ]:          99 :     PostLinearize(depgraph, post_linearization);
    1225         [ -  + ]:          99 :     SanityCheck(depgraph, post_linearization);
    1226                 :             : 
    1227                 :             :     // Compare diagrams: post-linearization cannot worsen anywhere.
    1228         [ -  + ]:          99 :     auto chunking = ChunkLinearization(depgraph, linearization);
    1229         [ -  + ]:          99 :     auto post_chunking = ChunkLinearization(depgraph, post_linearization);
    1230   [ -  +  -  +  :          99 :     auto cmp = CompareChunks(post_chunking, chunking);
                   +  - ]
    1231         [ -  + ]:          99 :     assert(cmp >= 0);
    1232                 :             : 
    1233                 :             :     // Run again, things can keep improving (and never get worse)
    1234         [ +  - ]:          99 :     auto post_post_linearization = post_linearization;
    1235   [ -  +  +  - ]:          99 :     PostLinearize(depgraph, post_post_linearization);
    1236         [ -  + ]:          99 :     SanityCheck(depgraph, post_post_linearization);
    1237         [ -  + ]:          99 :     auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
    1238   [ -  +  -  +  :          99 :     cmp = CompareChunks(post_post_chunking, post_chunking);
                   +  - ]
    1239         [ -  + ]:          99 :     assert(cmp >= 0);
    1240                 :             : 
    1241                 :             :     // The chunks that come out of postlinearizing are always connected.
    1242         [ -  + ]:          99 :     LinearizationChunking linchunking(depgraph, post_linearization);
    1243   [ -  +  +  + ]:         988 :     while (linchunking.NumChunksLeft()) {
    1244         [ -  + ]:         790 :         assert(depgraph.IsConnected(linchunking.GetChunk(0).transactions));
    1245                 :         790 :         linchunking.MarkDone(linchunking.GetChunk(0).transactions);
    1246                 :             :     }
    1247                 :          99 : }
    1248                 :             : 
    1249         [ +  - ]:         857 : FUZZ_TARGET(clusterlin_postlinearize_tree)
    1250                 :             : {
    1251                 :             :     // Verify expected properties of PostLinearize() on linearizations of graphs that form either
    1252                 :             :     // an upright or reverse tree structure.
    1253                 :             : 
    1254                 :             :     // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
    1255                 :         399 :     SpanReader reader(buffer);
    1256                 :         399 :     uint64_t rng_seed{0};
    1257                 :         399 :     DepGraph<TestBitSet> depgraph_gen;
    1258                 :         399 :     uint8_t direction{0};
    1259                 :         399 :     try {
    1260   [ +  -  +  +  :         399 :         reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
                   +  - ]
    1261         [ -  + ]:           1 :     } catch (const std::ios_base::failure&) {}
    1262                 :             : 
    1263                 :             :     // Now construct a new graph, copying the nodes, but leaving only the first parent (even
    1264                 :             :     // direction) or the first child (odd direction).
    1265                 :         399 :     DepGraph<TestBitSet> depgraph_tree;
    1266   [ -  +  +  + ]:       11811 :     for (DepGraphIndex i = 0; i < depgraph_gen.PositionRange(); ++i) {
    1267         [ +  + ]:       11412 :         if (depgraph_gen.Positions()[i]) {
    1268                 :        9131 :             depgraph_tree.AddTransaction(depgraph_gen.FeeRate(i));
    1269                 :             :         } else {
    1270                 :             :             // For holes, add a dummy transaction which is deleted below, so that non-hole
    1271                 :             :             // transactions retain their position.
    1272                 :        2281 :             depgraph_tree.AddTransaction(FeeFrac{});
    1273                 :             :         }
    1274                 :             :     }
    1275                 :         399 :     depgraph_tree.RemoveTransactions(TestBitSet::Fill(depgraph_gen.PositionRange()) - depgraph_gen.Positions());
    1276                 :             : 
    1277         [ +  + ]:         399 :     if (direction & 1) {
    1278         [ +  + ]:        5932 :         for (DepGraphIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
    1279                 :        5693 :             auto children = depgraph_gen.GetReducedChildren(i);
    1280         [ +  + ]:        5693 :             if (children.Any()) {
    1281                 :        3104 :                 depgraph_tree.AddDependencies(TestBitSet::Singleton(i), children.First());
    1282                 :             :             }
    1283                 :             :          }
    1284                 :             :     } else {
    1285         [ +  + ]:        3598 :         for (DepGraphIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
    1286                 :        3438 :             auto parents = depgraph_gen.GetReducedParents(i);
    1287         [ +  + ]:        3438 :             if (parents.Any()) {
    1288                 :        1535 :                 depgraph_tree.AddDependencies(TestBitSet::Singleton(parents.First()), i);
    1289                 :             :             }
    1290                 :             :         }
    1291                 :             :     }
    1292                 :             : 
    1293                 :             :     // Retrieve a linearization from the fuzz input.
    1294                 :         399 :     std::vector<DepGraphIndex> linearization;
    1295         [ +  - ]:         798 :     linearization = ReadLinearization(depgraph_tree, reader);
    1296         [ -  + ]:         399 :     SanityCheck(depgraph_tree, linearization);
    1297                 :             : 
    1298                 :             :     // Produce a postlinearized version.
    1299         [ +  - ]:         399 :     auto post_linearization = linearization;
    1300   [ -  +  +  - ]:         399 :     PostLinearize(depgraph_tree, post_linearization);
    1301         [ -  + ]:         399 :     SanityCheck(depgraph_tree, post_linearization);
    1302                 :             : 
    1303                 :             :     // Compare diagrams.
    1304         [ -  + ]:         399 :     auto chunking = ChunkLinearization(depgraph_tree, linearization);
    1305         [ -  + ]:         399 :     auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
    1306   [ -  +  -  +  :         399 :     auto cmp = CompareChunks(post_chunking, chunking);
                   +  - ]
    1307         [ -  + ]:         399 :     assert(cmp >= 0);
    1308                 :             : 
    1309                 :             :     // Verify that post-linearizing again does not change the diagram. The result must be identical
    1310                 :             :     // as post_linearization ought to be optimal already with a tree-structured graph.
    1311         [ +  - ]:         399 :     auto post_post_linearization = post_linearization;
    1312   [ -  +  +  - ]:         399 :     PostLinearize(depgraph_tree, post_linearization);
    1313         [ -  + ]:         399 :     SanityCheck(depgraph_tree, post_linearization);
    1314         [ -  + ]:         399 :     auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
    1315   [ -  +  -  +  :         399 :     auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
                   +  - ]
    1316         [ -  + ]:         399 :     assert(cmp_post == 0);
    1317                 :             : 
    1318                 :             :     // Try to find an even better linearization directly. This must not change the diagram for the
    1319                 :             :     // same reason.
    1320   [ -  +  -  + ]:         399 :     auto [opt_linearization, _optimal, _cost] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
    1321         [ -  + ]:         399 :     auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
    1322   [ -  +  -  +  :         399 :     auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
                   +  - ]
    1323         [ -  + ]:         399 :     assert(cmp_opt == 0);
    1324                 :         399 : }
    1325                 :             : 
    1326         [ +  - ]:         572 : FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
    1327                 :             : {
    1328                 :             :     // Verify that taking an existing linearization, and moving a leaf to the back, potentially
    1329                 :             :     // increasing its fee, and then post-linearizing, results in something as good as the
    1330                 :             :     // original. This guarantees that in an RBF that replaces a transaction with one of the same
    1331                 :             :     // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
    1332                 :             :     // process will never worsen linearization quality.
    1333                 :             : 
    1334                 :             :     // Construct an arbitrary graph and a fee from the fuzz input.
    1335                 :         114 :     SpanReader reader(buffer);
    1336                 :         114 :     DepGraph<TestBitSet> depgraph;
    1337                 :         114 :     int32_t fee_inc{0};
    1338                 :         114 :     try {
    1339                 :         114 :         uint64_t fee_inc_code;
    1340   [ +  -  +  + ]:         114 :         reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
    1341                 :          36 :         fee_inc = fee_inc_code & 0x3ffff;
    1342         [ -  + ]:          78 :     } catch (const std::ios_base::failure&) {}
    1343         [ +  + ]:         114 :     if (depgraph.TxCount() == 0) return;
    1344                 :             : 
    1345                 :             :     // Retrieve two linearizations from the fuzz input.
    1346         [ +  - ]:         105 :     auto lin = ReadLinearization(depgraph, reader);
    1347         [ +  - ]:         105 :     auto lin_leaf = ReadLinearization(depgraph, reader);
    1348                 :             : 
    1349                 :             :     // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
    1350                 :             :     // back.
    1351                 :         105 :     std::vector<DepGraphIndex> lin_moved;
    1352         [ +  + ]:        1265 :     for (auto i : lin) {
    1353   [ +  +  +  - ]:        1160 :         if (i != lin_leaf.back()) lin_moved.push_back(i);
    1354                 :             :     }
    1355         [ +  - ]:         105 :     lin_moved.push_back(lin_leaf.back());
    1356                 :             : 
    1357                 :             :     // Postlinearize lin_moved.
    1358   [ -  +  +  - ]:         105 :     PostLinearize(depgraph, lin_moved);
    1359         [ -  + ]:         105 :     SanityCheck(depgraph, lin_moved);
    1360                 :             : 
    1361                 :             :     // Compare diagrams (applying the fee delta after computing the old one).
    1362         [ -  + ]:         105 :     auto old_chunking = ChunkLinearization(depgraph, lin);
    1363         [ -  + ]:         105 :     depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
    1364         [ -  + ]:         105 :     auto new_chunking = ChunkLinearization(depgraph, lin_moved);
    1365   [ -  +  -  +  :         105 :     auto cmp = CompareChunks(new_chunking, old_chunking);
                   +  - ]
    1366         [ -  + ]:         105 :     assert(cmp >= 0);
    1367                 :         114 : }
    1368                 :             : 
    1369         [ +  - ]:         628 : FUZZ_TARGET(clusterlin_merge)
    1370                 :             : {
    1371                 :             :     // Construct an arbitrary graph from the fuzz input.
    1372                 :         170 :     SpanReader reader(buffer);
    1373                 :         170 :     DepGraph<TestBitSet> depgraph;
    1374                 :         170 :     try {
    1375         [ +  - ]:         170 :         reader >> Using<DepGraphFormatter>(depgraph);
    1376         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
    1377                 :             : 
    1378                 :             :     // Retrieve two linearizations from the fuzz input.
    1379         [ +  - ]:         170 :     auto lin1 = ReadLinearization(depgraph, reader);
    1380         [ +  - ]:         170 :     auto lin2 = ReadLinearization(depgraph, reader);
    1381                 :             : 
    1382                 :             :     // Merge the two.
    1383   [ -  +  -  +  :         170 :     auto lin_merged = MergeLinearizations(depgraph, lin1, lin2);
                   +  - ]
    1384                 :             : 
    1385                 :             :     // Compute chunkings and compare.
    1386         [ -  + ]:         170 :     auto chunking1 = ChunkLinearization(depgraph, lin1);
    1387         [ -  + ]:         170 :     auto chunking2 = ChunkLinearization(depgraph, lin2);
    1388         [ -  + ]:         170 :     auto chunking_merged = ChunkLinearization(depgraph, lin_merged);
    1389   [ -  +  -  +  :         170 :     auto cmp1 = CompareChunks(chunking_merged, chunking1);
                   +  - ]
    1390         [ -  + ]:         170 :     assert(cmp1 >= 0);
    1391   [ -  +  -  +  :         170 :     auto cmp2 = CompareChunks(chunking_merged, chunking2);
                   +  - ]
    1392         [ -  + ]:         170 :     assert(cmp2 >= 0);
    1393                 :         170 : }
    1394                 :             : 
    1395         [ +  - ]:         554 : FUZZ_TARGET(clusterlin_fix_linearization)
    1396                 :             : {
    1397                 :             :     // Verify expected properties of FixLinearization() on arbitrary linearizations.
    1398                 :             : 
    1399                 :             :     // Retrieve a depgraph from the fuzz input.
    1400                 :          96 :     SpanReader reader(buffer);
    1401                 :          96 :     DepGraph<TestBitSet> depgraph;
    1402                 :          96 :     try {
    1403         [ +  - ]:          96 :         reader >> Using<DepGraphFormatter>(depgraph);
    1404         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
    1405                 :             : 
    1406                 :             :     // Construct an arbitrary linearization (not necessarily topological for depgraph).
    1407                 :          96 :     std::vector<DepGraphIndex> linearization;
    1408                 :             :     /** Which transactions of depgraph are yet to be included in linearization. */
    1409                 :          96 :     TestBitSet todo = depgraph.Positions();
    1410         [ +  + ]:        1120 :     while (todo.Any()) {
    1411                 :             :         // Read a number from the fuzz input in range [0, todo.Count()).
    1412                 :         928 :         uint64_t val{0};
    1413                 :         928 :         try {
    1414         [ +  + ]:         928 :             reader >> VARINT(val);
    1415         [ -  + ]:         706 :         } catch (const std::ios_base::failure&) {}
    1416         [ +  - ]:         928 :         val %= todo.Count();
    1417                 :             :         // Find the val'th element in todo, remove it from todo, and append it to linearization.
    1418   [ +  -  +  - ]:        2557 :         for (auto idx : todo) {
    1419         [ +  + ]:        1629 :             if (val == 0) {
    1420         [ +  - ]:         928 :                 linearization.push_back(idx);
    1421                 :         928 :                 todo.Reset(idx);
    1422                 :         928 :                 break;
    1423                 :             :             }
    1424                 :         701 :             --val;
    1425                 :             :         }
    1426                 :             :     }
    1427   [ -  +  -  + ]:          96 :     assert(linearization.size() == depgraph.TxCount());
    1428                 :             : 
    1429                 :             :     // Determine what prefix of linearization is topological, i.e., the position of the first entry
    1430                 :             :     // in linearization which corresponds to a transaction that is not preceded by all its
    1431                 :             :     // ancestors.
    1432                 :          96 :     size_t topo_prefix = 0;
    1433                 :          96 :     todo = depgraph.Positions();
    1434   [ -  +  +  + ]:         614 :     while (topo_prefix < linearization.size()) {
    1435                 :         552 :         DepGraphIndex idx = linearization[topo_prefix];
    1436                 :         552 :         todo.Reset(idx);
    1437         [ +  + ]:         552 :         if (todo.Overlaps(depgraph.Ancestors(idx))) break;
    1438                 :         518 :         ++topo_prefix;
    1439                 :             :     }
    1440                 :             : 
    1441                 :             :     // Then make a fixed copy of linearization.
    1442         [ +  - ]:          96 :     auto linearization_fixed = linearization;
    1443         [ -  + ]:          96 :     FixLinearization(depgraph, linearization_fixed);
    1444                 :             :     // Sanity check it (which includes testing whether it is topological).
    1445         [ -  + ]:          96 :     SanityCheck(depgraph, linearization_fixed);
    1446                 :             : 
    1447                 :             :     // FixLinearization does not modify the topological prefix of linearization.
    1448         [ -  + ]:          96 :     assert(std::equal(linearization.begin(), linearization.begin() + topo_prefix,
    1449                 :             :                       linearization_fixed.begin()));
    1450                 :             :     // This also means that if linearization was entirely topological, FixLinearization cannot have
    1451                 :             :     // modified it. This is implied by the assertion above already, but repeat it explicitly.
    1452   [ -  +  +  + ]:          96 :     if (topo_prefix == linearization.size()) {
    1453         [ -  + ]:          62 :         assert(linearization == linearization_fixed);
    1454                 :             :     }
    1455                 :          96 : }
        

Generated by: LCOV version 2.0-1