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.3 % 602 592
Test Date: 2024-12-04 04:00:22 Functions: 100.0 % 37 37
Branches: 68.7 % 712 489

             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/fuzz.h>
      10                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      11                 :             : #include <test/util/cluster_linearize.h>
      12                 :             : #include <util/bitset.h>
      13                 :             : #include <util/feefrac.h>
      14                 :             : 
      15                 :             : #include <algorithm>
      16                 :             : #include <stdint.h>
      17                 :             : #include <vector>
      18                 :             : #include <utility>
      19                 :             : 
      20                 :             : using namespace cluster_linearize;
      21                 :             : 
      22                 :             : namespace {
      23                 :             : 
      24                 :             : /** A simple finder class for candidate sets.
      25                 :             :  *
      26                 :             :  * This class matches SearchCandidateFinder in interface and behavior, though with fewer
      27                 :             :  * optimizations.
      28                 :             :  */
      29                 :             : template<typename SetType>
      30                 :             : class SimpleCandidateFinder
      31                 :             : {
      32                 :             :     /** Internal dependency graph. */
      33                 :             :     const DepGraph<SetType>& m_depgraph;
      34                 :             :     /** Which transaction are left to include. */
      35                 :             :     SetType m_todo;
      36                 :             : 
      37                 :             : public:
      38                 :             :     /** Construct an SimpleCandidateFinder for a given graph. */
      39                 :         646 :     SimpleCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
      40                 :         646 :         m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
      41                 :             : 
      42                 :             :     /** Remove a set of transactions from the set of to-be-linearized ones. */
      43                 :        4282 :     void MarkDone(SetType select) noexcept { m_todo -= select; }
      44                 :             : 
      45                 :             :     /** Determine whether unlinearized transactions remain. */
      46                 :        2662 :     bool AllDone() const noexcept { return m_todo.None(); }
      47                 :             : 
      48                 :             :     /** Find a candidate set using at most max_iterations iterations, and the number of iterations
      49                 :             :      *  actually performed. If that number is less than max_iterations, then the result is optimal.
      50                 :             :      *
      51                 :             :      * Complexity: O(N * M), where M is the number of connected topological subsets of the cluster.
      52                 :             :      *             That number is bounded by M <= 2^(N-1).
      53                 :             :      */
      54                 :        3375 :     std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations) const noexcept
      55                 :             :     {
      56                 :        3375 :         uint64_t iterations_left = max_iterations;
      57                 :             :         // Queue of work units. Each consists of:
      58                 :             :         // - inc: set of transactions definitely included
      59                 :             :         // - und: set of transactions that can be added to inc still
      60                 :        3375 :         std::vector<std::pair<SetType, SetType>> queue;
      61                 :             :         // Initially we have just one queue element, with the entire graph in und.
      62                 :        3375 :         queue.emplace_back(SetType{}, m_todo);
      63                 :             :         // Best solution so far.
      64                 :        3375 :         SetInfo best(m_depgraph, m_todo);
      65                 :             :         // Process the queue.
      66   [ +  +  +  + ]:     8841396 :         while (!queue.empty() && iterations_left) {
      67                 :     8838021 :             --iterations_left;
      68                 :             :             // Pop top element of the queue.
      69                 :     8838021 :             auto [inc, und] = queue.back();
      70                 :     8838021 :             queue.pop_back();
      71                 :             :             // Look for a transaction to consider adding/removing.
      72                 :     8838021 :             bool inc_none = inc.None();
      73         [ +  + ]:    14650337 :             for (auto split : und) {
      74                 :             :                 // If inc is empty, consider any split transaction. Otherwise only consider
      75                 :             :                 // transactions that share ancestry with inc so far (which means only connected
      76                 :             :                 // sets will be considered).
      77   [ +  +  +  + ]:    10229741 :                 if (inc_none || inc.Overlaps(m_depgraph.Ancestors(split))) {
      78                 :             :                     // Add a queue entry with split included.
      79                 :     4417425 :                     SetInfo new_inc(m_depgraph, inc | (m_todo & m_depgraph.Ancestors(split)));
      80                 :     4417425 :                     queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
      81                 :             :                     // Add a queue entry with split excluded.
      82                 :     4417425 :                     queue.emplace_back(inc, und - m_depgraph.Descendants(split));
      83                 :             :                     // Update statistics to account for the candidate new_inc.
      84         [ +  + ]:     4417425 :                     if (new_inc.feerate > best.feerate) best = new_inc;
      85                 :             :                     break;
      86                 :             :                 }
      87                 :             :             }
      88                 :             :         }
      89                 :        3375 :         return {std::move(best), max_iterations - iterations_left};
      90                 :        3375 :     }
      91                 :             : };
      92                 :             : 
      93                 :             : /** A very simple finder class for optimal candidate sets, which tries every subset.
      94                 :             :  *
      95                 :             :  * It is even simpler than SimpleCandidateFinder, and is primarily included here to test the
      96                 :             :  * correctness of SimpleCandidateFinder, which is then used to test the correctness of
      97                 :             :  * SearchCandidateFinder.
      98                 :             :  */
      99                 :             : template<typename SetType>
     100                 :             : class ExhaustiveCandidateFinder
     101                 :             : {
     102                 :             :     /** Internal dependency graph. */
     103                 :             :     const DepGraph<SetType>& m_depgraph;
     104                 :             :     /** Which transaction are left to include. */
     105                 :             :     SetType m_todo;
     106                 :             : 
     107                 :             : public:
     108                 :             :     /** Construct an ExhaustiveCandidateFinder for a given graph. */
     109                 :         331 :     ExhaustiveCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
     110                 :         331 :         m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
     111                 :             : 
     112                 :             :     /** Remove a set of transactions from the set of to-be-linearized ones. */
     113                 :        2331 :     void MarkDone(SetType select) noexcept { m_todo -= select; }
     114                 :             : 
     115                 :             :     /** Determine whether unlinearized transactions remain. */
     116                 :        2662 :     bool AllDone() const noexcept { return m_todo.None(); }
     117                 :             : 
     118                 :             :     /** Find the optimal remaining candidate set.
     119                 :             :      *
     120                 :             :      * Complexity: O(N * 2^N).
     121                 :             :      */
     122                 :         820 :     SetInfo<SetType> FindCandidateSet() const noexcept
     123                 :             :     {
     124                 :             :         // Best solution so far.
     125                 :         820 :         SetInfo<SetType> best{m_todo, m_depgraph.FeeRate(m_todo)};
     126                 :             :         // The number of combinations to try.
     127                 :         820 :         uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
     128                 :             :         // Try the transitive closure of every non-empty subset of m_todo.
     129         [ +  + ]:      342318 :         for (uint64_t x = 1; x < limit; ++x) {
     130                 :             :             // If bit number b is set in x, then the remaining ancestors of the b'th remaining
     131                 :             :             // transaction in m_todo are included.
     132                 :      341498 :             SetType txn;
     133                 :      341498 :             auto x_shifted{x};
     134   [ +  -  +  + ]:     4386076 :             for (auto i : m_todo) {
     135         [ +  + ]:     3703080 :                 if (x_shifted & 1) txn |= m_depgraph.Ancestors(i);
     136                 :     3703080 :                 x_shifted >>= 1;
     137                 :             :             }
     138                 :      341498 :             SetInfo cur(m_depgraph, txn & m_todo);
     139         [ +  + ]:      341498 :             if (cur.feerate > best.feerate) best = cur;
     140                 :             :         }
     141                 :         820 :         return best;
     142                 :             :     }
     143                 :             : };
     144                 :             : 
     145                 :             : /** A simple linearization algorithm.
     146                 :             :  *
     147                 :             :  * This matches Linearize() in interface and behavior, though with fewer optimizations, lacking
     148                 :             :  * the ability to pass in an existing linearization, and using just SimpleCandidateFinder rather
     149                 :             :  * than AncestorCandidateFinder and SearchCandidateFinder.
     150                 :             :  */
     151                 :             : template<typename SetType>
     152                 :         315 : std::pair<std::vector<ClusterIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations)
     153                 :             : {
     154                 :         315 :     std::vector<ClusterIndex> linearization;
     155                 :         315 :     SimpleCandidateFinder finder(depgraph);
     156                 :         315 :     SetType todo = depgraph.Positions();
     157                 :         315 :     bool optimal = true;
     158         [ +  + ]:        2266 :     while (todo.Any()) {
     159         [ +  + ]:        1951 :         auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
     160         [ +  + ]:        1951 :         if (iterations_done == max_iterations) optimal = false;
     161                 :        1951 :         depgraph.AppendTopo(linearization, candidate.transactions);
     162                 :        1951 :         todo -= candidate.transactions;
     163                 :        1951 :         finder.MarkDone(candidate.transactions);
     164                 :        1951 :         max_iterations -= iterations_done;
     165                 :             :     }
     166                 :         315 :     return {std::move(linearization), optimal};
     167                 :         315 : }
     168                 :             : 
     169                 :             : /** Stitch connected components together in a DepGraph, guaranteeing its corresponding cluster is connected. */
     170                 :             : template<typename BS>
     171                 :         922 : void MakeConnected(DepGraph<BS>& depgraph)
     172                 :             : {
     173                 :         922 :     auto todo = depgraph.Positions();
     174                 :         922 :     auto comp = depgraph.FindConnectedComponent(todo);
     175                 :         922 :     Assume(depgraph.IsConnected(comp));
     176                 :         922 :     todo -= comp;
     177         [ +  + ]:        9703 :     while (todo.Any()) {
     178                 :        8781 :         auto nextcomp = depgraph.FindConnectedComponent(todo);
     179                 :        8781 :         Assume(depgraph.IsConnected(nextcomp));
     180                 :        8781 :         depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
     181                 :        8781 :         todo -= nextcomp;
     182                 :        8781 :         comp = nextcomp;
     183                 :             :     }
     184                 :         922 : }
     185                 :             : 
     186                 :             : /** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
     187                 :             : template<typename SetType>
     188                 :        7134 : SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
     189                 :             : {
     190         [ +  + ]:        7134 :     uint64_t mask{0};
     191                 :             :     try {
     192         [ +  + ]:        7134 :         reader >> VARINT(mask);
     193         [ -  + ]:        4152 :     } catch(const std::ios_base::failure&) {}
     194                 :        7134 :     SetType ret;
     195   [ +  +  +  + ]:      103613 :     for (auto i : todo) {
     196         [ +  + ]:       89352 :         if (!ret[i]) {
     197         [ +  + ]:       86563 :             if (mask & 1) ret |= depgraph.Ancestors(i);
     198                 :       86563 :             mask >>= 1;
     199                 :             :         }
     200                 :             :     }
     201                 :        7134 :     return ret & todo;
     202                 :             : }
     203                 :             : 
     204                 :             : /** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
     205                 :             : template<typename BS>
     206                 :        1500 : std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
     207                 :             : {
     208                 :        1500 :     std::vector<ClusterIndex> linearization;
     209                 :        1500 :     TestBitSet todo = depgraph.Positions();
     210                 :             :     // In every iteration one topologically-valid transaction is appended to linearization.
     211         [ +  + ]:       31898 :     while (todo.Any()) {
     212                 :             :         // Compute the set of transactions with no not-yet-included ancestors.
     213                 :       28898 :         TestBitSet potential_next;
     214         [ +  + ]:      428104 :         for (auto j : todo) {
     215         [ +  + ]:      631994 :             if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
     216                 :      232788 :                 potential_next.Set(j);
     217                 :             :             }
     218                 :             :         }
     219                 :             :         // There must always be one (otherwise there is a cycle in the graph).
     220         [ -  + ]:       28898 :         assert(potential_next.Any());
     221                 :             :         // Read a number from reader, and interpret it as index into potential_next.
     222         [ +  + ]:       28898 :         uint64_t idx{0};
     223                 :             :         try {
     224   [ +  +  +  - ]:       57796 :             reader >> VARINT(idx);
     225         [ -  + ]:       22616 :         } catch (const std::ios_base::failure&) {}
     226                 :       28898 :         idx %= potential_next.Count();
     227                 :             :         // Find out which transaction that corresponds to.
     228   [ +  -  +  - ]:       78772 :         for (auto j : potential_next) {
     229         [ +  + ]:       49874 :             if (idx == 0) {
     230                 :             :                 // When found, add it to linearization and remove it from todo.
     231         [ +  - ]:       28898 :                 linearization.push_back(j);
     232         [ -  + ]:       28898 :                 assert(todo[j]);
     233                 :       28898 :                 todo.Reset(j);
     234                 :       28898 :                 break;
     235                 :             :             }
     236                 :       20976 :             --idx;
     237                 :             :         }
     238                 :             :     }
     239                 :        1500 :     return linearization;
     240                 :           0 : }
     241                 :             : 
     242                 :             : } // namespace
     243                 :             : 
     244         [ +  - ]:         551 : FUZZ_TARGET(clusterlin_depgraph_sim)
     245                 :             : {
     246                 :             :     // Simulation test to verify the full behavior of DepGraph.
     247                 :             : 
     248                 :         139 :     FuzzedDataProvider provider(buffer.data(), buffer.size());
     249                 :             : 
     250                 :             :     /** Real DepGraph being tested. */
     251                 :         139 :     DepGraph<TestBitSet> real;
     252                 :             :     /** Simulated DepGraph (sim[i] is std::nullopt if position i does not exist; otherwise,
     253                 :             :      *  sim[i]->first is its individual feerate, and sim[i]->second is its set of ancestors. */
     254                 :         139 :     std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
     255                 :             :     /** The number of non-nullopt position in sim. */
     256                 :         139 :     ClusterIndex num_tx_sim{0};
     257                 :             : 
     258                 :             :     /** Read a valid index of a transaction from the provider. */
     259                 :       10137 :     auto idx_fn = [&]() {
     260                 :        9998 :         auto offset = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx_sim - 1);
     261         [ +  - ]:      121017 :         for (ClusterIndex i = 0; i < sim.size(); ++i) {
     262         [ +  + ]:      121017 :             if (!sim[i].has_value()) continue;
     263         [ +  + ]:      103266 :             if (offset == 0) return i;
     264                 :       93268 :             --offset;
     265                 :             :         }
     266                 :           0 :         assert(false);
     267                 :             :         return ClusterIndex(-1);
     268                 :         139 :     };
     269                 :             : 
     270                 :             :     /** Read a valid subset of the transactions from the provider. */
     271                 :       10137 :     auto subset_fn = [&]() {
     272                 :        9998 :         auto range = (uint64_t{1} << num_tx_sim) - 1;
     273                 :        9998 :         const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
     274                 :        9998 :         auto mask_shifted = mask;
     275                 :        9998 :         TestBitSet subset;
     276         [ +  + ]:      329934 :         for (ClusterIndex i = 0; i < sim.size(); ++i) {
     277         [ +  + ]:      319936 :             if (!sim[i].has_value()) continue;
     278         [ +  + ]:      183675 :             if (mask_shifted & 1) {
     279                 :       78431 :                 subset.Set(i);
     280                 :             :             }
     281                 :      183675 :             mask_shifted >>= 1;
     282                 :             :         }
     283         [ -  + ]:        9998 :         assert(mask_shifted == 0);
     284                 :        9998 :         return subset;
     285                 :         139 :     };
     286                 :             : 
     287                 :             :     /** Read any set of transactions from the provider (including unused positions). */
     288                 :        3977 :     auto set_fn = [&]() {
     289                 :        3838 :         auto range = (uint64_t{1} << sim.size()) - 1;
     290                 :        3838 :         const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
     291                 :        3838 :         TestBitSet set;
     292         [ +  + ]:      126654 :         for (ClusterIndex i = 0; i < sim.size(); ++i) {
     293         [ +  + ]:      122816 :             if ((mask >> i) & 1) {
     294                 :       45802 :                 set.Set(i);
     295                 :             :             }
     296                 :             :         }
     297                 :        3838 :         return set;
     298                 :         139 :     };
     299                 :             : 
     300                 :             :     /** Propagate ancestor information in sim. */
     301                 :        4116 :     auto anc_update_fn = [&]() {
     302                 :        4403 :         while (true) {
     303                 :        4403 :             bool updates{false};
     304         [ +  + ]:      145299 :             for (ClusterIndex chl = 0; chl < sim.size(); ++chl) {
     305         [ +  + ]:      140896 :                 if (!sim[chl].has_value()) continue;
     306   [ +  -  +  + ]:      196709 :                 for (auto par : sim[chl]->second) {
     307         [ +  + ]:      115885 :                     if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
     308                 :        1820 :                         sim[chl]->second |= sim[par]->second;
     309                 :        1820 :                         updates = true;
     310                 :             :                     }
     311                 :             :                 }
     312                 :             :             }
     313         [ +  + ]:        4403 :             if (!updates) break;
     314                 :             :         }
     315                 :        4116 :     };
     316                 :             : 
     317                 :             :     /** Compare the state of transaction i in the simulation with the real one. */
     318                 :       50389 :     auto check_fn = [&](ClusterIndex i) {
     319                 :             :         // Compare used positions.
     320         [ -  + ]:       50250 :         assert(real.Positions()[i] == sim[i].has_value());
     321         [ +  + ]:       50250 :         if (sim[i].has_value()) {
     322                 :             :             // Compare feerate.
     323         [ +  - ]:        6450 :             assert(real.FeeRate(i) == sim[i]->first);
     324                 :             :             // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
     325                 :             :             // and descendants, so we can restrict ourselves to ancestors here).
     326         [ -  + ]:        6450 :             assert(real.Ancestors(i) == sim[i]->second);
     327                 :             :         }
     328                 :       50389 :     };
     329                 :             : 
     330   [ +  +  +  + ]:       20425 :     LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
     331                 :       20286 :         uint8_t command = provider.ConsumeIntegral<uint8_t>();
     332   [ +  +  +  +  :       20286 :         if (num_tx_sim == 0 || ((command % 3) <= 0 && num_tx_sim < TestBitSet::Size())) {
                   +  + ]
     333                 :             :             // AddTransaction.
     334                 :        6450 :             auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
     335                 :        6450 :             auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
     336                 :        6450 :             FeeFrac feerate{fee, size};
     337                 :             :             // Apply to DepGraph.
     338                 :        6450 :             auto idx = real.AddTransaction(feerate);
     339                 :             :             // Verify that the returned index is correct.
     340         [ -  + ]:        6450 :             assert(!sim[idx].has_value());
     341         [ +  - ]:       80361 :             for (ClusterIndex i = 0; i < TestBitSet::Size(); ++i) {
     342         [ +  + ]:       80361 :                 if (!sim[i].has_value()) {
     343         [ -  + ]:        6450 :                     assert(idx == i);
     344                 :             :                     break;
     345                 :             :                 }
     346                 :             :             }
     347                 :             :             // Update sim.
     348         [ -  + ]:        6450 :             sim[idx] = {feerate, TestBitSet::Singleton(idx)};
     349                 :        6450 :             ++num_tx_sim;
     350                 :        6450 :             continue;
     351                 :        6450 :         }
     352         [ +  + ]:       13836 :         if ((command % 3) <= 1 && num_tx_sim > 0) {
     353                 :             :             // AddDependencies.
     354                 :        9998 :             ClusterIndex child = idx_fn();
     355                 :        9998 :             auto parents = subset_fn();
     356                 :             :             // Apply to DepGraph.
     357                 :        9998 :             real.AddDependencies(parents, child);
     358                 :             :             // Apply to sim.
     359                 :        9998 :             sim[child]->second |= parents;
     360                 :        9998 :             continue;
     361                 :        9998 :         }
     362                 :        3838 :         if (num_tx_sim > 0) {
     363                 :             :             // Remove transactions.
     364                 :        3838 :             auto del = set_fn();
     365                 :             :             // Propagate all ancestry information before deleting anything in the simulation (as
     366                 :             :             // intermediary transactions may be deleted which impact connectivity).
     367                 :        3838 :             anc_update_fn();
     368                 :             :             // Compare the state of the transactions being deleted.
     369   [ +  +  +  + ]:       53176 :             for (auto i : del) check_fn(i);
     370                 :             :             // Apply to DepGraph.
     371                 :        3838 :             real.RemoveTransactions(del);
     372                 :             :             // Apply to sim.
     373         [ +  + ]:      126654 :             for (ClusterIndex i = 0; i < sim.size(); ++i) {
     374         [ +  + ]:      122816 :                 if (sim[i].has_value()) {
     375         [ +  + ]:       30300 :                     if (del[i]) {
     376                 :        4409 :                         --num_tx_sim;
     377         [ +  - ]:      127225 :                         sim[i] = std::nullopt;
     378                 :             :                     } else {
     379                 :       25891 :                         sim[i]->second -= del;
     380                 :             :                     }
     381                 :             :                 }
     382                 :             :             }
     383                 :        3838 :             continue;
     384                 :        3838 :         }
     385                 :             :         // This should be unreachable (one of the 3 above actions should always be possible).
     386                 :             :         assert(false);
     387                 :             :     }
     388                 :             : 
     389                 :             :     // Compare the real obtained depgraph against the simulation.
     390                 :         139 :     anc_update_fn();
     391         [ +  + ]:        4587 :     for (ClusterIndex i = 0; i < sim.size(); ++i) check_fn(i);
     392         [ -  + ]:         139 :     assert(real.TxCount() == num_tx_sim);
     393                 :             :     // Sanity check the result (which includes round-tripping serialization, if applicable).
     394         [ +  - ]:         139 :     SanityCheck(real);
     395                 :         139 : }
     396                 :             : 
     397         [ +  - ]:         589 : FUZZ_TARGET(clusterlin_depgraph_serialization)
     398                 :             : {
     399                 :             :     // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
     400                 :             : 
     401                 :             :     // Construct a graph by deserializing.
     402         [ +  - ]:         177 :     SpanReader reader(buffer);
     403                 :         177 :     DepGraph<TestBitSet> depgraph;
     404                 :         177 :     try {
     405         [ +  - ]:         177 :         reader >> Using<DepGraphFormatter>(depgraph);
     406         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     407         [ +  - ]:         177 :     SanityCheck(depgraph);
     408                 :             : 
     409                 :             :     // Verify the graph is a DAG.
     410         [ -  + ]:         177 :     assert(IsAcyclic(depgraph));
     411                 :         177 : }
     412                 :             : 
     413         [ +  - ]:         506 : FUZZ_TARGET(clusterlin_components)
     414                 :             : {
     415                 :             :     // Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
     416                 :             : 
     417                 :             :     // Construct a depgraph.
     418         [ +  - ]:          94 :     SpanReader reader(buffer);
     419                 :          94 :     DepGraph<TestBitSet> depgraph;
     420                 :          94 :     try {
     421         [ +  - ]:          94 :         reader >> Using<DepGraphFormatter>(depgraph);
     422         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     423                 :             : 
     424                 :          94 :     TestBitSet todo = depgraph.Positions();
     425         [ +  + ]:        1199 :     while (todo.Any()) {
     426                 :             :         // Find a connected component inside todo.
     427                 :        1105 :         auto component = depgraph.FindConnectedComponent(todo);
     428                 :             : 
     429                 :             :         // The component must be a subset of todo and non-empty.
     430         [ -  + ]:        1105 :         assert(component.IsSubsetOf(todo));
     431         [ -  + ]:        1105 :         assert(component.Any());
     432                 :             : 
     433                 :             :         // If todo is the entire graph, and the entire graph is connected, then the component must
     434                 :             :         // be the entire graph.
     435         [ +  + ]:        1105 :         if (todo == depgraph.Positions()) {
     436   [ +  +  -  + ]:         150 :             assert((component == todo) == depgraph.IsConnected());
     437                 :             :         }
     438                 :             : 
     439                 :             :         // If subset is connected, then component must match subset.
     440   [ +  +  -  + ]:        1901 :         assert((component == todo) == depgraph.IsConnected(todo));
     441                 :             : 
     442                 :             :         // The component cannot have any ancestors or descendants outside of component but in todo.
     443         [ +  + ]:        7785 :         for (auto i : component) {
     444         [ -  + ]:        6680 :             assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
     445         [ -  + ]:        6680 :             assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
     446                 :             :         }
     447                 :             : 
     448                 :             :         // Starting from any component element, we must be able to reach every element.
     449         [ +  + ]:        7785 :         for (auto i : component) {
     450                 :             :             // Start with just i as reachable.
     451                 :        6680 :             TestBitSet reachable = TestBitSet::Singleton(i);
     452                 :             :             // Add in-todo descendants and ancestors to reachable until it does not change anymore.
     453                 :       51950 :             while (true) {
     454                 :       29315 :                 TestBitSet new_reachable = reachable;
     455   [ +  -  +  + ]:      363262 :                 for (auto j : new_reachable) {
     456                 :      304632 :                     new_reachable |= depgraph.Ancestors(j) & todo;
     457                 :      304632 :                     new_reachable |= depgraph.Descendants(j) & todo;
     458                 :             :                 }
     459         [ +  + ]:       29315 :                 if (new_reachable == reachable) break;
     460                 :       22635 :                 reachable = new_reachable;
     461                 :       22635 :             }
     462                 :             :             // Verify that the result is the entire component.
     463         [ -  + ]:        6680 :             assert(component == reachable);
     464                 :             :         }
     465                 :             : 
     466                 :             :         // Construct an arbitrary subset of todo.
     467                 :        1105 :         uint64_t subset_bits{0};
     468                 :        1105 :         try {
     469         [ +  + ]:        1105 :             reader >> VARINT(subset_bits);
     470         [ -  + ]:         903 :         } catch (const std::ios_base::failure&) {}
     471                 :        1105 :         TestBitSet subset;
     472   [ +  -  +  + ]:       27002 :         for (ClusterIndex i : depgraph.Positions()) {
     473         [ +  + ]:       24792 :             if (todo[i]) {
     474         [ +  + ]:       12788 :                 if (subset_bits & 1) subset.Set(i);
     475                 :       12788 :                 subset_bits >>= 1;
     476                 :             :             }
     477                 :             :         }
     478                 :             :         // Which must be non-empty.
     479         [ +  + ]:        1105 :         if (subset.None()) subset = TestBitSet::Singleton(todo.First());
     480                 :             :         // Remove it from todo.
     481                 :        1105 :         todo -= subset;
     482                 :             :     }
     483                 :             : 
     484                 :             :     // No components can be found in an empty subset.
     485         [ -  + ]:          94 :     assert(depgraph.FindConnectedComponent(todo).None());
     486                 :          94 : }
     487                 :             : 
     488         [ +  - ]:         604 : FUZZ_TARGET(clusterlin_make_connected)
     489                 :             : {
     490                 :             :     // Verify that MakeConnected makes graphs connected.
     491                 :             : 
     492         [ +  - ]:         192 :     SpanReader reader(buffer);
     493                 :         192 :     DepGraph<TestBitSet> depgraph;
     494                 :         192 :     try {
     495         [ +  - ]:         192 :         reader >> Using<DepGraphFormatter>(depgraph);
     496         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     497         [ +  - ]:         192 :     MakeConnected(depgraph);
     498         [ +  - ]:         192 :     SanityCheck(depgraph);
     499         [ -  + ]:         192 :     assert(depgraph.IsConnected());
     500                 :         192 : }
     501                 :             : 
     502         [ +  - ]:         503 : FUZZ_TARGET(clusterlin_chunking)
     503                 :             : {
     504                 :             :     // Verify the correctness of the ChunkLinearization function.
     505                 :             : 
     506                 :             :     // Construct a graph by deserializing.
     507         [ +  - ]:          91 :     SpanReader reader(buffer);
     508                 :          91 :     DepGraph<TestBitSet> depgraph;
     509                 :          91 :     try {
     510         [ +  - ]:          91 :         reader >> Using<DepGraphFormatter>(depgraph);
     511         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     512                 :             : 
     513                 :             :     // Read a valid linearization for depgraph.
     514         [ +  - ]:          91 :     auto linearization = ReadLinearization(depgraph, reader);
     515                 :             : 
     516                 :             :     // Invoke the chunking function.
     517                 :          91 :     auto chunking = ChunkLinearization(depgraph, linearization);
     518                 :             : 
     519                 :             :     // Verify that chunk feerates are monotonically non-increasing.
     520         [ +  + ]:         381 :     for (size_t i = 1; i < chunking.size(); ++i) {
     521         [ -  + ]:         290 :         assert(!(chunking[i] >> chunking[i - 1]));
     522                 :             :     }
     523                 :             : 
     524                 :             :     // Naively recompute the chunks (each is the highest-feerate prefix of what remains).
     525                 :          91 :     auto todo = depgraph.Positions();
     526         [ +  + ]:         466 :     for (const auto& chunk_feerate : chunking) {
     527         [ -  + ]:         375 :         assert(todo.Any());
     528                 :         375 :         SetInfo<TestBitSet> accumulator, best;
     529         [ +  + ]:        8300 :         for (ClusterIndex idx : linearization) {
     530         [ +  + ]:        7925 :             if (todo[idx]) {
     531                 :        4247 :                 accumulator.Set(depgraph, idx);
     532   [ +  +  +  + ]:        4247 :                 if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
     533                 :         794 :                     best = accumulator;
     534                 :             :                 }
     535                 :             :             }
     536                 :             :         }
     537         [ +  - ]:         375 :         assert(chunk_feerate == best.feerate);
     538         [ -  + ]:         375 :         assert(best.transactions.IsSubsetOf(todo));
     539                 :         375 :         todo -= best.transactions;
     540                 :             :     }
     541         [ -  + ]:          91 :     assert(todo.None());
     542                 :          91 : }
     543                 :             : 
     544         [ +  - ]:         515 : FUZZ_TARGET(clusterlin_ancestor_finder)
     545                 :             : {
     546                 :             :     // Verify that AncestorCandidateFinder works as expected.
     547                 :             : 
     548                 :             :     // Retrieve a depgraph from the fuzz input.
     549         [ +  - ]:         103 :     SpanReader reader(buffer);
     550                 :         103 :     DepGraph<TestBitSet> depgraph;
     551                 :         103 :     try {
     552         [ +  - ]:         103 :         reader >> Using<DepGraphFormatter>(depgraph);
     553         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     554                 :             : 
     555                 :         103 :     AncestorCandidateFinder anc_finder(depgraph);
     556                 :         103 :     auto todo = depgraph.Positions();
     557         [ +  + ]:        1047 :     while (todo.Any()) {
     558                 :             :         // Call the ancestor finder's FindCandidateSet for what remains of the graph.
     559         [ -  + ]:         944 :         assert(!anc_finder.AllDone());
     560         [ -  + ]:         944 :         assert(todo.Count() == anc_finder.NumRemaining());
     561                 :         944 :         auto best_anc = anc_finder.FindCandidateSet();
     562                 :             :         // Sanity check the result.
     563         [ -  + ]:         944 :         assert(best_anc.transactions.Any());
     564         [ -  + ]:         944 :         assert(best_anc.transactions.IsSubsetOf(todo));
     565         [ +  - ]:         944 :         assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
     566         [ -  + ]:         944 :         assert(depgraph.IsConnected(best_anc.transactions));
     567                 :             :         // Check that it is topologically valid.
     568   [ +  -  +  + ]:        3484 :         for (auto i : best_anc.transactions) {
     569         [ -  + ]:        1596 :             assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
     570                 :             :         }
     571                 :             : 
     572                 :             :         // Compute all remaining ancestor sets.
     573                 :         944 :         std::optional<SetInfo<TestBitSet>> real_best_anc;
     574   [ +  -  +  + ]:       12753 :         for (auto i : todo) {
     575                 :       10865 :             SetInfo info(depgraph, todo & depgraph.Ancestors(i));
     576   [ +  +  +  + ]:       10865 :             if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
     577         [ +  + ]:       13081 :                 real_best_anc = info;
     578                 :             :             }
     579                 :             :         }
     580                 :             :         // The set returned by anc_finder must equal the real best ancestor sets.
     581         [ -  + ]:         944 :         assert(real_best_anc.has_value());
     582         [ +  - ]:         944 :         assert(*real_best_anc == best_anc);
     583                 :             : 
     584                 :             :         // Find a topologically valid subset of transactions to remove from the graph.
     585         [ +  - ]:         944 :         auto del_set = ReadTopologicalSet(depgraph, todo, reader);
     586                 :             :         // If we did not find anything, use best_anc itself, because we should remove something.
     587         [ +  + ]:         944 :         if (del_set.None()) del_set = best_anc.transactions;
     588                 :         944 :         todo -= del_set;
     589                 :         944 :         anc_finder.MarkDone(del_set);
     590                 :             :     }
     591         [ -  + ]:         103 :     assert(anc_finder.AllDone());
     592         [ -  + ]:         103 :     assert(anc_finder.NumRemaining() == 0);
     593                 :         103 : }
     594                 :             : 
     595                 :             : static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
     596                 :             : 
     597         [ +  - ]:         743 : FUZZ_TARGET(clusterlin_search_finder)
     598                 :             : {
     599                 :             :     // Verify that SearchCandidateFinder works as expected by sanity checking the results
     600                 :             :     // and comparing with the results from SimpleCandidateFinder, ExhaustiveCandidateFinder, and
     601                 :             :     // AncestorCandidateFinder.
     602                 :             : 
     603                 :             :     // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input.
     604         [ +  - ]:         331 :     SpanReader reader(buffer);
     605                 :         331 :     DepGraph<TestBitSet> depgraph;
     606                 :         331 :     uint64_t rng_seed{0};
     607                 :         331 :     uint8_t make_connected{1};
     608                 :         331 :     try {
     609   [ +  -  +  +  :         331 :         reader >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
                   +  + ]
     610         [ -  + ]:         132 :     } catch (const std::ios_base::failure&) {}
     611                 :             :     // The most complicated graphs are connected ones (other ones just split up). Optionally force
     612                 :             :     // the graph to be connected.
     613   [ +  +  +  - ]:         331 :     if (make_connected) MakeConnected(depgraph);
     614                 :             : 
     615                 :             :     // Instantiate ALL the candidate finders.
     616                 :         331 :     SearchCandidateFinder src_finder(depgraph, rng_seed);
     617                 :         331 :     SimpleCandidateFinder smp_finder(depgraph);
     618                 :         331 :     ExhaustiveCandidateFinder exh_finder(depgraph);
     619                 :         331 :     AncestorCandidateFinder anc_finder(depgraph);
     620                 :             : 
     621                 :         331 :     auto todo = depgraph.Positions();
     622         [ +  + ]:        2662 :     while (todo.Any()) {
     623         [ -  + ]:        2331 :         assert(!src_finder.AllDone());
     624         [ -  + ]:        2331 :         assert(!smp_finder.AllDone());
     625         [ -  + ]:        2331 :         assert(!exh_finder.AllDone());
     626         [ -  + ]:        2331 :         assert(!anc_finder.AllDone());
     627         [ -  + ]:        2331 :         assert(anc_finder.NumRemaining() == todo.Count());
     628                 :             : 
     629                 :             :         // For each iteration, read an iteration count limit from the fuzz input.
     630                 :        2331 :         uint64_t max_iterations = 1;
     631                 :        2331 :         try {
     632         [ +  + ]:        2331 :             reader >> VARINT(max_iterations);
     633         [ -  + ]:        1028 :         } catch (const std::ios_base::failure&) {}
     634                 :        2331 :         max_iterations &= 0xfffff;
     635                 :             : 
     636                 :             :         // Read an initial subset from the fuzz input.
     637         [ +  - ]:        2331 :         SetInfo init_best(depgraph, ReadTopologicalSet(depgraph, todo, reader));
     638                 :             : 
     639                 :             :         // Call the search finder's FindCandidateSet for what remains of the graph.
     640         [ -  + ]:        2331 :         auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best);
     641                 :             : 
     642                 :             :         // Sanity check the result.
     643         [ -  + ]:        2331 :         assert(iterations_done <= max_iterations);
     644         [ -  + ]:        2331 :         assert(found.transactions.Any());
     645         [ -  + ]:        2331 :         assert(found.transactions.IsSubsetOf(todo));
     646         [ +  - ]:        2331 :         assert(depgraph.FeeRate(found.transactions) == found.feerate);
     647   [ +  +  -  + ]:        2331 :         if (!init_best.feerate.IsEmpty()) assert(found.feerate >= init_best.feerate);
     648                 :             :         // Check that it is topologically valid.
     649   [ +  -  +  + ]:       11361 :         for (auto i : found.transactions) {
     650         [ -  + ]:        6699 :             assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
     651                 :             :         }
     652                 :             : 
     653                 :             :         // At most 2^(N-1) iterations can be required: the maximum number of non-empty topological
     654                 :             :         // subsets a (connected) cluster with N transactions can have. Even when the cluster is no
     655                 :             :         // longer connected after removing certain transactions, this holds, because the connected
     656                 :             :         // components are searched separately.
     657         [ -  + ]:        2331 :         assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
     658                 :             :         // Additionally, test that no more than sqrt(2^N)+1 iterations are required. This is just
     659                 :             :         // an empirical bound that seems to hold, without proof. Still, add a test for it so we
     660                 :             :         // can learn about counterexamples if they exist.
     661   [ +  +  +  - ]:        2331 :         if (iterations_done >= 1 && todo.Count() <= 63) {
     662         [ +  - ]:        1642 :             Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count());
     663                 :             :         }
     664                 :             : 
     665                 :             :         // Perform quality checks only if SearchCandidateFinder claims an optimal result.
     666         [ +  + ]:        2331 :         if (iterations_done < max_iterations) {
     667                 :             :             // Optimal sets are always connected.
     668         [ -  + ]:        1424 :             assert(depgraph.IsConnected(found.transactions));
     669                 :             : 
     670                 :             :             // Compare with SimpleCandidateFinder.
     671         [ -  + ]:        1424 :             auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
     672         [ -  + ]:        1424 :             assert(found.feerate >= simple.feerate);
     673         [ +  + ]:        1424 :             if (simple_iters < MAX_SIMPLE_ITERATIONS) {
     674         [ +  - ]:        1410 :                 assert(found.feerate == simple.feerate);
     675                 :             :             }
     676                 :             : 
     677                 :             :             // Compare with AncestorCandidateFinder;
     678                 :        1424 :             auto anc = anc_finder.FindCandidateSet();
     679         [ -  + ]:        1424 :             assert(found.feerate >= anc.feerate);
     680                 :             : 
     681                 :             :             // Compare with ExhaustiveCandidateFinder. This quickly gets computationally expensive
     682                 :             :             // for large clusters (O(2^n)), so only do it for sufficiently small ones.
     683         [ +  + ]:        1424 :             if (todo.Count() <= 12) {
     684                 :         820 :                 auto exhaustive = exh_finder.FindCandidateSet();
     685         [ +  - ]:         820 :                 assert(exhaustive.feerate == found.feerate);
     686                 :             :                 // Also compare ExhaustiveCandidateFinder with SimpleCandidateFinder (this is
     687                 :             :                 // primarily a test for SimpleCandidateFinder's correctness).
     688         [ -  + ]:         820 :                 assert(exhaustive.feerate >= simple.feerate);
     689         [ +  - ]:         820 :                 if (simple_iters < MAX_SIMPLE_ITERATIONS) {
     690         [ +  - ]:         820 :                     assert(exhaustive.feerate == simple.feerate);
     691                 :             :                 }
     692                 :             :             }
     693                 :             :         }
     694                 :             : 
     695                 :             :         // Find a topologically valid subset of transactions to remove from the graph.
     696         [ +  - ]:        2331 :         auto del_set = ReadTopologicalSet(depgraph, todo, reader);
     697                 :             :         // If we did not find anything, use found itself, because we should remove something.
     698         [ +  + ]:        2331 :         if (del_set.None()) del_set = found.transactions;
     699                 :        2331 :         todo -= del_set;
     700                 :        2331 :         src_finder.MarkDone(del_set);
     701                 :        2331 :         smp_finder.MarkDone(del_set);
     702                 :        2331 :         exh_finder.MarkDone(del_set);
     703                 :        2331 :         anc_finder.MarkDone(del_set);
     704                 :             :     }
     705                 :             : 
     706         [ -  + ]:         331 :     assert(src_finder.AllDone());
     707         [ -  + ]:         331 :     assert(smp_finder.AllDone());
     708         [ -  + ]:         331 :     assert(exh_finder.AllDone());
     709         [ -  + ]:         331 :     assert(anc_finder.AllDone());
     710         [ -  + ]:         331 :     assert(anc_finder.NumRemaining() == 0);
     711                 :         331 : }
     712                 :             : 
     713         [ +  - ]:         548 : FUZZ_TARGET(clusterlin_linearization_chunking)
     714                 :             : {
     715                 :             :     // Verify the behavior of LinearizationChunking.
     716                 :             : 
     717                 :             :     // Retrieve a depgraph from the fuzz input.
     718         [ +  - ]:         136 :     SpanReader reader(buffer);
     719                 :         136 :     DepGraph<TestBitSet> depgraph;
     720                 :         136 :     try {
     721         [ +  - ]:         136 :         reader >> Using<DepGraphFormatter>(depgraph);
     722         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     723                 :             : 
     724                 :             :     // Retrieve a topologically-valid subset of depgraph.
     725                 :         136 :     auto todo = depgraph.Positions();
     726         [ +  - ]:         136 :     auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader));
     727                 :             : 
     728                 :             :     // Retrieve a valid linearization for depgraph.
     729         [ +  - ]:         136 :     auto linearization = ReadLinearization(depgraph, reader);
     730                 :             : 
     731                 :             :     // Construct a LinearizationChunking object, initially for the whole linearization.
     732                 :         136 :     LinearizationChunking chunking(depgraph, linearization);
     733                 :             : 
     734                 :             :     // Incrementally remove transactions from the chunking object, and check various properties at
     735                 :             :     // every step.
     736         [ +  + ]:        1664 :     while (todo.Any()) {
     737         [ -  + ]:        1392 :         assert(chunking.NumChunksLeft() > 0);
     738                 :             : 
     739                 :             :         // Construct linearization with just todo.
     740                 :        1392 :         std::vector<ClusterIndex> linearization_left;
     741         [ +  + ]:       33637 :         for (auto i : linearization) {
     742   [ +  +  +  - ]:       32245 :             if (todo[i]) linearization_left.push_back(i);
     743                 :             :         }
     744                 :             : 
     745                 :             :         // Compute the chunking for linearization_left.
     746                 :        1392 :         auto chunking_left = ChunkLinearization(depgraph, linearization_left);
     747                 :             : 
     748                 :             :         // Verify that it matches the feerates of the chunks of chunking.
     749         [ -  + ]:        1392 :         assert(chunking.NumChunksLeft() == chunking_left.size());
     750         [ +  + ]:        8129 :         for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
     751         [ +  - ]:       13474 :             assert(chunking.GetChunk(i).feerate == chunking_left[i]);
     752                 :             :         }
     753                 :             : 
     754                 :             :         // Check consistency of chunking.
     755                 :        1392 :         TestBitSet combined;
     756         [ +  + ]:        8129 :         for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
     757                 :        6737 :             const auto& chunk_info = chunking.GetChunk(i);
     758                 :             :             // Chunks must be non-empty.
     759         [ -  + ]:        6737 :             assert(chunk_info.transactions.Any());
     760                 :             :             // Chunk feerates must be monotonically non-increasing.
     761   [ +  +  -  + ]:        6737 :             if (i > 0) assert(!(chunk_info.feerate >> chunking.GetChunk(i - 1).feerate));
     762                 :             :             // Chunks must be a subset of what is left of the linearization.
     763         [ -  + ]:        6737 :             assert(chunk_info.transactions.IsSubsetOf(todo));
     764                 :             :             // Chunks' claimed feerates must match their transactions' aggregate feerate.
     765         [ +  - ]:        6737 :             assert(depgraph.FeeRate(chunk_info.transactions) == chunk_info.feerate);
     766                 :             :             // Chunks must be the highest-feerate remaining prefix.
     767                 :        6737 :             SetInfo<TestBitSet> accumulator, best;
     768         [ +  + ]:      186828 :             for (auto j : linearization) {
     769   [ +  +  +  + ]:      180091 :                 if (todo[j] && !combined[j]) {
     770                 :       63271 :                     accumulator.Set(depgraph, j);
     771   [ +  +  +  + ]:       63271 :                     if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) {
     772                 :       12502 :                         best = accumulator;
     773                 :             :                     }
     774                 :             :                 }
     775                 :             :             }
     776         [ -  + ]:        6737 :             assert(best.transactions == chunk_info.transactions);
     777         [ +  - ]:        6737 :             assert(best.feerate == chunk_info.feerate);
     778                 :             :             // Chunks cannot overlap.
     779         [ -  + ]:        6737 :             assert(!chunk_info.transactions.Overlaps(combined));
     780         [ +  - ]:        6737 :             combined |= chunk_info.transactions;
     781                 :             :             // Chunks must be topological.
     782   [ +  -  +  + ]:       29605 :             for (auto idx : chunk_info.transactions) {
     783         [ -  + ]:       16131 :                 assert((depgraph.Ancestors(idx) & todo).IsSubsetOf(combined));
     784                 :             :             }
     785                 :             :         }
     786         [ -  + ]:        1392 :         assert(combined == todo);
     787                 :             : 
     788                 :             :         // Verify the expected properties of LinearizationChunking::IntersectPrefixes:
     789                 :        1392 :         auto intersect = chunking.IntersectPrefixes(subset);
     790                 :             :         // - Intersecting again doesn't change the result.
     791         [ +  - ]:        1392 :         assert(chunking.IntersectPrefixes(intersect) == intersect);
     792                 :             :         // - The intersection is topological.
     793                 :        1392 :         TestBitSet intersect_anc;
     794   [ +  +  +  + ]:        5156 :         for (auto idx : intersect.transactions) {
     795                 :        2957 :             intersect_anc |= (depgraph.Ancestors(idx) & todo);
     796                 :             :         }
     797         [ -  + ]:        1392 :         assert(intersect.transactions == intersect_anc);
     798                 :             :         // - The claimed intersection feerate matches its transactions.
     799         [ +  - ]:        1392 :         assert(intersect.feerate == depgraph.FeeRate(intersect.transactions));
     800                 :             :         // - The intersection may only be empty if its input is empty.
     801         [ -  + ]:        1392 :         assert(intersect.transactions.Any() == subset.transactions.Any());
     802                 :             :         // - The intersection feerate must be as high as the input.
     803         [ -  + ]:        1392 :         assert(intersect.feerate >= subset.feerate);
     804                 :             :         // - No non-empty intersection between the intersection and a prefix of the chunks of the
     805                 :             :         //   remainder of the linearization may be better than the intersection.
     806                 :        1392 :         TestBitSet prefix;
     807         [ +  + ]:        8129 :         for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
     808                 :        6737 :             prefix |= chunking.GetChunk(i).transactions;
     809                 :        6737 :             auto reintersect = SetInfo(depgraph, prefix & intersect.transactions);
     810         [ +  + ]:        6737 :             if (!reintersect.feerate.IsEmpty()) {
     811         [ -  + ]:        3933 :                 assert(reintersect.feerate <= intersect.feerate);
     812                 :             :             }
     813                 :             :         }
     814                 :             : 
     815                 :             :         // Find a subset to remove from linearization.
     816         [ +  - ]:        1392 :         auto done = ReadTopologicalSet(depgraph, todo, reader);
     817         [ +  + ]:        1392 :         if (done.None()) {
     818                 :             :             // We need to remove a non-empty subset, so fall back to the unlinearized ancestors of
     819                 :             :             // the first transaction in todo if done is empty.
     820                 :        1203 :             done = depgraph.Ancestors(todo.First()) & todo;
     821                 :             :         }
     822                 :        1392 :         todo -= done;
     823                 :        1392 :         chunking.MarkDone(done);
     824                 :        1392 :         subset = SetInfo(depgraph, subset.transactions - done);
     825                 :        1392 :     }
     826                 :             : 
     827         [ -  + ]:         136 :     assert(chunking.NumChunksLeft() == 0);
     828                 :         136 : }
     829                 :             : 
     830         [ +  - ]:         858 : FUZZ_TARGET(clusterlin_linearize)
     831                 :             : {
     832                 :             :     // Verify the behavior of Linearize().
     833                 :             : 
     834                 :             :     // Retrieve an RNG seed, an iteration count, a depgraph, and whether to make it connected from
     835                 :             :     // the fuzz input.
     836         [ +  + ]:         446 :     SpanReader reader(buffer);
     837                 :         446 :     DepGraph<TestBitSet> depgraph;
     838                 :         446 :     uint64_t rng_seed{0};
     839                 :         446 :     uint64_t iter_count{0};
     840                 :         446 :     uint8_t make_connected{1};
     841                 :         446 :     try {
     842   [ +  +  +  -  :         446 :         reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
             +  +  +  + ]
     843         [ -  + ]:         323 :     } catch (const std::ios_base::failure&) {}
     844                 :             :     // The most complicated graphs are connected ones (other ones just split up). Optionally force
     845                 :             :     // the graph to be connected.
     846   [ +  +  +  - ]:         446 :     if (make_connected) MakeConnected(depgraph);
     847                 :             : 
     848                 :             :     // Optionally construct an old linearization for it.
     849                 :         446 :     std::vector<ClusterIndex> old_linearization;
     850                 :         446 :     {
     851                 :         446 :         uint8_t have_old_linearization{0};
     852                 :         446 :         try {
     853         [ +  + ]:         446 :             reader >> have_old_linearization;
     854         [ -  + ]:         212 :         } catch(const std::ios_base::failure&) {}
     855         [ +  + ]:         446 :         if (have_old_linearization & 1) {
     856         [ +  - ]:         328 :             old_linearization = ReadLinearization(depgraph, reader);
     857                 :         164 :             SanityCheck(depgraph, old_linearization);
     858                 :             :         }
     859                 :             :     }
     860                 :             : 
     861                 :             :     // Invoke Linearize().
     862                 :         446 :     iter_count &= 0x7ffff;
     863                 :         446 :     auto [linearization, optimal] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
     864                 :         446 :     SanityCheck(depgraph, linearization);
     865                 :         446 :     auto chunking = ChunkLinearization(depgraph, linearization);
     866                 :             : 
     867                 :             :     // Linearization must always be as good as the old one, if provided.
     868         [ +  + ]:         446 :     if (!old_linearization.empty()) {
     869                 :         162 :         auto old_chunking = ChunkLinearization(depgraph, old_linearization);
     870         [ +  - ]:         162 :         auto cmp = CompareChunks(chunking, old_chunking);
     871         [ -  + ]:         162 :         assert(cmp >= 0);
     872                 :         162 :     }
     873                 :             : 
     874                 :             :     // If the iteration count is sufficiently high, an optimal linearization must be found.
     875                 :             :     // Each linearization step can use up to 2^(k-1) iterations, with steps k=1..n. That sum is
     876                 :             :     // 2^n - 1.
     877         [ +  + ]:         446 :     const uint64_t n = depgraph.TxCount();
     878   [ +  +  +  + ]:         446 :     if (n <= 19 && iter_count > (uint64_t{1} << n)) {
     879         [ -  + ]:          63 :         assert(optimal);
     880                 :             :     }
     881                 :             :     // Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, plus ceil(k/4)
     882                 :             :     // start-up cost per step, plus ceil(n^2/64) start-up cost overall, we can compute the upper
     883                 :             :     // bound for a whole linearization (summing for k=1..n) using the Python expression
     884                 :             :     // [sum((k+3)//4 + int(math.sqrt(2**k)) + 1 for k in range(1, n + 1)) + (n**2 + 63) // 64 for n in range(0, 35)]:
     885                 :         446 :     static constexpr uint64_t MAX_OPTIMAL_ITERS[] = {
     886                 :             :         0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545,
     887                 :             :         3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936,
     888                 :             :         316629, 447712
     889                 :             :     };
     890   [ +  -  +  + ]:         446 :     if (n < std::size(MAX_OPTIMAL_ITERS) && iter_count >= MAX_OPTIMAL_ITERS[n]) {
     891         [ +  - ]:         137 :         Assume(optimal);
     892                 :             :     }
     893                 :             : 
     894                 :             :     // If Linearize claims optimal result, run quality tests.
     895         [ +  + ]:         446 :     if (optimal) {
     896                 :             :         // It must be as good as SimpleLinearize.
     897                 :         315 :         auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
     898                 :         315 :         SanityCheck(depgraph, simple_linearization);
     899                 :         315 :         auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
     900         [ +  - ]:         315 :         auto cmp = CompareChunks(chunking, simple_chunking);
     901         [ -  + ]:         315 :         assert(cmp >= 0);
     902                 :             :         // If SimpleLinearize finds the optimal result too, they must be equal (if not,
     903                 :             :         // SimpleLinearize is broken).
     904   [ +  +  -  + ]:         315 :         if (simple_optimal) assert(cmp == 0);
     905                 :             : 
     906                 :             :         // Only for very small clusters, test every topologically-valid permutation.
     907         [ +  + ]:         315 :         if (depgraph.TxCount() <= 7) {
     908                 :          68 :             std::vector<ClusterIndex> perm_linearization;
     909   [ +  +  +  -  :         385 :             for (ClusterIndex i : depgraph.Positions()) perm_linearization.push_back(i);
                   +  + ]
     910                 :             :             // Iterate over all valid permutations.
     911                 :       80951 :             do {
     912                 :             :                 // Determine whether perm_linearization is topological.
     913                 :       80951 :                 TestBitSet perm_done;
     914                 :       80951 :                 bool perm_is_topo{true};
     915         [ +  + ]:      135644 :                 for (auto i : perm_linearization) {
     916                 :      133961 :                     perm_done.Set(i);
     917         [ +  + ]:      133961 :                     if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) {
     918                 :             :                         perm_is_topo = false;
     919                 :             :                         break;
     920                 :             :                     }
     921                 :             :                 }
     922                 :             :                 // If so, verify that the obtained linearization is as good as the permutation.
     923         [ +  + ]:       80951 :                 if (perm_is_topo) {
     924                 :        1683 :                     auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
     925         [ +  - ]:        1683 :                     auto cmp = CompareChunks(chunking, perm_chunking);
     926         [ -  + ]:        1683 :                     assert(cmp >= 0);
     927                 :        1683 :                 }
     928         [ +  + ]:       80951 :             } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
     929                 :          68 :         }
     930                 :         315 :     }
     931                 :         446 : }
     932                 :             : 
     933         [ +  - ]:         515 : FUZZ_TARGET(clusterlin_postlinearize)
     934                 :             : {
     935                 :             :     // Verify expected properties of PostLinearize() on arbitrary linearizations.
     936                 :             : 
     937                 :             :     // Retrieve a depgraph from the fuzz input.
     938         [ +  - ]:         103 :     SpanReader reader(buffer);
     939                 :         103 :     DepGraph<TestBitSet> depgraph;
     940                 :         103 :     try {
     941         [ +  - ]:         103 :         reader >> Using<DepGraphFormatter>(depgraph);
     942         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
     943                 :             : 
     944                 :             :     // Retrieve a linearization from the fuzz input.
     945                 :         103 :     std::vector<ClusterIndex> linearization;
     946         [ +  - ]:         206 :     linearization = ReadLinearization(depgraph, reader);
     947                 :         103 :     SanityCheck(depgraph, linearization);
     948                 :             : 
     949                 :             :     // Produce a post-processed version.
     950         [ +  - ]:         103 :     auto post_linearization = linearization;
     951         [ +  - ]:         103 :     PostLinearize(depgraph, post_linearization);
     952                 :         103 :     SanityCheck(depgraph, post_linearization);
     953                 :             : 
     954                 :             :     // Compare diagrams: post-linearization cannot worsen anywhere.
     955                 :         103 :     auto chunking = ChunkLinearization(depgraph, linearization);
     956                 :         103 :     auto post_chunking = ChunkLinearization(depgraph, post_linearization);
     957         [ +  - ]:         103 :     auto cmp = CompareChunks(post_chunking, chunking);
     958         [ -  + ]:         103 :     assert(cmp >= 0);
     959                 :             : 
     960                 :             :     // Run again, things can keep improving (and never get worse)
     961         [ +  - ]:         103 :     auto post_post_linearization = post_linearization;
     962         [ +  - ]:         103 :     PostLinearize(depgraph, post_post_linearization);
     963                 :         103 :     SanityCheck(depgraph, post_post_linearization);
     964                 :         103 :     auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
     965         [ +  - ]:         103 :     cmp = CompareChunks(post_post_chunking, post_chunking);
     966         [ -  + ]:         103 :     assert(cmp >= 0);
     967                 :             : 
     968                 :             :     // The chunks that come out of postlinearizing are always connected.
     969                 :         103 :     LinearizationChunking linchunking(depgraph, post_linearization);
     970         [ +  + ]:        1054 :     while (linchunking.NumChunksLeft()) {
     971         [ -  + ]:         848 :         assert(depgraph.IsConnected(linchunking.GetChunk(0).transactions));
     972                 :         848 :         linchunking.MarkDone(linchunking.GetChunk(0).transactions);
     973                 :             :     }
     974                 :         103 : }
     975                 :             : 
     976         [ +  - ]:         782 : FUZZ_TARGET(clusterlin_postlinearize_tree)
     977                 :             : {
     978                 :             :     // Verify expected properties of PostLinearize() on linearizations of graphs that form either
     979                 :             :     // an upright or reverse tree structure.
     980                 :             : 
     981                 :             :     // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
     982         [ +  - ]:         370 :     SpanReader reader(buffer);
     983                 :         370 :     uint64_t rng_seed{0};
     984                 :         370 :     DepGraph<TestBitSet> depgraph_gen;
     985                 :         370 :     uint8_t direction{0};
     986                 :         370 :     try {
     987   [ +  -  +  +  :         370 :         reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
                   +  - ]
     988         [ -  + ]:           1 :     } catch (const std::ios_base::failure&) {}
     989                 :             : 
     990                 :             :     // Now construct a new graph, copying the nodes, but leaving only the first parent (even
     991                 :             :     // direction) or the first child (odd direction).
     992                 :         370 :     DepGraph<TestBitSet> depgraph_tree;
     993         [ +  + ]:       11274 :     for (ClusterIndex i = 0; i < depgraph_gen.PositionRange(); ++i) {
     994         [ +  + ]:       10904 :         if (depgraph_gen.Positions()[i]) {
     995                 :        8863 :             depgraph_tree.AddTransaction(depgraph_gen.FeeRate(i));
     996                 :             :         } else {
     997                 :             :             // For holes, add a dummy transaction which is deleted below, so that non-hole
     998                 :             :             // transactions retain their position.
     999                 :        2041 :             depgraph_tree.AddTransaction(FeeFrac{});
    1000                 :             :         }
    1001                 :             :     }
    1002                 :         370 :     depgraph_tree.RemoveTransactions(TestBitSet::Fill(depgraph_gen.PositionRange()) - depgraph_gen.Positions());
    1003                 :             : 
    1004         [ +  + ]:         370 :     if (direction & 1) {
    1005         [ +  + ]:        5814 :         for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
    1006                 :        5587 :             auto children = depgraph_gen.GetReducedChildren(i);
    1007         [ +  + ]:        5587 :             if (children.Any()) {
    1008                 :        3787 :                 depgraph_tree.AddDependencies(TestBitSet::Singleton(i), children.First());
    1009                 :             :             }
    1010                 :             :          }
    1011                 :             :     } else {
    1012         [ +  + ]:        3419 :         for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
    1013                 :        3276 :             auto parents = depgraph_gen.GetReducedParents(i);
    1014         [ +  + ]:        3276 :             if (parents.Any()) {
    1015                 :        1286 :                 depgraph_tree.AddDependencies(TestBitSet::Singleton(parents.First()), i);
    1016                 :             :             }
    1017                 :             :         }
    1018                 :             :     }
    1019                 :             : 
    1020                 :             :     // Retrieve a linearization from the fuzz input.
    1021                 :         370 :     std::vector<ClusterIndex> linearization;
    1022         [ +  - ]:         740 :     linearization = ReadLinearization(depgraph_tree, reader);
    1023                 :         370 :     SanityCheck(depgraph_tree, linearization);
    1024                 :             : 
    1025                 :             :     // Produce a postlinearized version.
    1026         [ +  - ]:         370 :     auto post_linearization = linearization;
    1027         [ +  - ]:         370 :     PostLinearize(depgraph_tree, post_linearization);
    1028                 :         370 :     SanityCheck(depgraph_tree, post_linearization);
    1029                 :             : 
    1030                 :             :     // Compare diagrams.
    1031                 :         370 :     auto chunking = ChunkLinearization(depgraph_tree, linearization);
    1032                 :         370 :     auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
    1033         [ +  - ]:         370 :     auto cmp = CompareChunks(post_chunking, chunking);
    1034         [ -  + ]:         370 :     assert(cmp >= 0);
    1035                 :             : 
    1036                 :             :     // Verify that post-linearizing again does not change the diagram. The result must be identical
    1037                 :             :     // as post_linearization ought to be optimal already with a tree-structured graph.
    1038         [ +  - ]:         370 :     auto post_post_linearization = post_linearization;
    1039         [ +  - ]:         370 :     PostLinearize(depgraph_tree, post_linearization);
    1040                 :         370 :     SanityCheck(depgraph_tree, post_linearization);
    1041                 :         370 :     auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
    1042         [ +  - ]:         370 :     auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
    1043         [ -  + ]:         370 :     assert(cmp_post == 0);
    1044                 :             : 
    1045                 :             :     // Try to find an even better linearization directly. This must not change the diagram for the
    1046                 :             :     // same reason.
    1047                 :         370 :     auto [opt_linearization, _optimal] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
    1048                 :         370 :     auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
    1049         [ +  - ]:         370 :     auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
    1050         [ -  + ]:         370 :     assert(cmp_opt == 0);
    1051                 :         370 : }
    1052                 :             : 
    1053         [ +  - ]:         519 : FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
    1054                 :             : {
    1055                 :             :     // Verify that taking an existing linearization, and moving a leaf to the back, potentially
    1056                 :             :     // increasing its fee, and then post-linearizing, results in something as good as the
    1057                 :             :     // original. This guarantees that in an RBF that replaces a transaction with one of the same
    1058                 :             :     // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
    1059                 :             :     // process will never worsen linearization quality.
    1060                 :             : 
    1061                 :             :     // Construct an arbitrary graph and a fee from the fuzz input.
    1062         [ +  - ]:         107 :     SpanReader reader(buffer);
    1063                 :         107 :     DepGraph<TestBitSet> depgraph;
    1064                 :         107 :     int32_t fee_inc{0};
    1065                 :         107 :     try {
    1066                 :         107 :         uint64_t fee_inc_code;
    1067   [ +  -  +  + ]:         107 :         reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
    1068                 :          41 :         fee_inc = fee_inc_code & 0x3ffff;
    1069         [ -  + ]:          66 :     } catch (const std::ios_base::failure&) {}
    1070         [ +  + ]:         107 :     if (depgraph.TxCount() == 0) return;
    1071                 :             : 
    1072                 :             :     // Retrieve two linearizations from the fuzz input.
    1073         [ +  - ]:          98 :     auto lin = ReadLinearization(depgraph, reader);
    1074         [ +  - ]:          98 :     auto lin_leaf = ReadLinearization(depgraph, reader);
    1075                 :             : 
    1076                 :             :     // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
    1077                 :             :     // back.
    1078                 :          98 :     std::vector<ClusterIndex> lin_moved;
    1079         [ +  + ]:        1408 :     for (auto i : lin) {
    1080   [ +  +  +  - ]:        1310 :         if (i != lin_leaf.back()) lin_moved.push_back(i);
    1081                 :             :     }
    1082         [ +  - ]:          98 :     lin_moved.push_back(lin_leaf.back());
    1083                 :             : 
    1084                 :             :     // Postlinearize lin_moved.
    1085         [ +  - ]:          98 :     PostLinearize(depgraph, lin_moved);
    1086                 :          98 :     SanityCheck(depgraph, lin_moved);
    1087                 :             : 
    1088                 :             :     // Compare diagrams (applying the fee delta after computing the old one).
    1089                 :          98 :     auto old_chunking = ChunkLinearization(depgraph, lin);
    1090                 :          98 :     depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
    1091                 :          98 :     auto new_chunking = ChunkLinearization(depgraph, lin_moved);
    1092         [ +  - ]:          98 :     auto cmp = CompareChunks(new_chunking, old_chunking);
    1093         [ -  + ]:          98 :     assert(cmp >= 0);
    1094                 :         107 : }
    1095                 :             : 
    1096         [ +  - ]:         632 : FUZZ_TARGET(clusterlin_merge)
    1097                 :             : {
    1098                 :             :     // Construct an arbitrary graph from the fuzz input.
    1099         [ +  - ]:         220 :     SpanReader reader(buffer);
    1100                 :         220 :     DepGraph<TestBitSet> depgraph;
    1101                 :         220 :     try {
    1102         [ +  - ]:         220 :         reader >> Using<DepGraphFormatter>(depgraph);
    1103         [ -  - ]:           0 :     } catch (const std::ios_base::failure&) {}
    1104                 :             : 
    1105                 :             :     // Retrieve two linearizations from the fuzz input.
    1106         [ +  - ]:         220 :     auto lin1 = ReadLinearization(depgraph, reader);
    1107         [ +  - ]:         220 :     auto lin2 = ReadLinearization(depgraph, reader);
    1108                 :             : 
    1109                 :             :     // Merge the two.
    1110         [ +  - ]:         220 :     auto lin_merged = MergeLinearizations(depgraph, lin1, lin2);
    1111                 :             : 
    1112                 :             :     // Compute chunkings and compare.
    1113                 :         220 :     auto chunking1 = ChunkLinearization(depgraph, lin1);
    1114                 :         220 :     auto chunking2 = ChunkLinearization(depgraph, lin2);
    1115                 :         220 :     auto chunking_merged = ChunkLinearization(depgraph, lin_merged);
    1116         [ +  - ]:         220 :     auto cmp1 = CompareChunks(chunking_merged, chunking1);
    1117         [ -  + ]:         220 :     assert(cmp1 >= 0);
    1118         [ +  - ]:         220 :     auto cmp2 = CompareChunks(chunking_merged, chunking2);
    1119         [ -  + ]:         220 :     assert(cmp2 >= 0);
    1120                 :         220 : }
        

Generated by: LCOV version 2.0-1