LCOV - code coverage report
Current view: top level - src/test - txrequest_tests.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 0 431 0.0 %
Date: 2023-10-05 12:38:51 Functions: 0 60 0.0 %
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : // Copyright (c) 2020-2021 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                 :            : 
       6                 :            : #include <txrequest.h>
       7                 :            : #include <uint256.h>
       8                 :            : 
       9                 :            : #include <test/util/random.h>
      10                 :            : #include <test/util/setup_common.h>
      11                 :            : 
      12                 :            : #include <algorithm>
      13                 :            : #include <functional>
      14                 :            : #include <vector>
      15                 :            : 
      16                 :            : #include <boost/test/unit_test.hpp>
      17                 :          0 : 
      18                 :          0 : BOOST_FIXTURE_TEST_SUITE(txrequest_tests, BasicTestingSetup)
      19                 :            : 
      20                 :            : namespace {
      21                 :            : 
      22                 :            : constexpr std::chrono::microseconds MIN_TIME = std::chrono::microseconds::min();
      23                 :            : constexpr std::chrono::microseconds MAX_TIME = std::chrono::microseconds::max();
      24                 :            : constexpr std::chrono::microseconds MICROSECOND = std::chrono::microseconds{1};
      25                 :            : constexpr std::chrono::microseconds NO_TIME = std::chrono::microseconds{0};
      26                 :            : 
      27                 :            : /** An Action is a function to call at a particular (simulated) timestamp. */
      28                 :            : using Action = std::pair<std::chrono::microseconds, std::function<void()>>;
      29                 :            : 
      30                 :            : /** Object that stores actions from multiple interleaved scenarios, and data shared across them.
      31                 :            :  *
      32                 :            :  * The Scenario below is used to fill this.
      33                 :            :  */
      34                 :            : struct Runner
      35                 :            : {
      36                 :            :     /** The TxRequestTracker being tested. */
      37                 :            :     TxRequestTracker txrequest;
      38                 :            : 
      39                 :            :     /** List of actions to be executed (in order of increasing timestamp). */
      40                 :            :     std::vector<Action> actions;
      41                 :            : 
      42                 :            :     /** Which node ids have been assigned already (to prevent reuse). */
      43                 :            :     std::set<NodeId> peerset;
      44                 :            : 
      45                 :            :     /** Which txhashes have been assigned already (to prevent reuse). */
      46                 :            :     std::set<uint256> txhashset;
      47                 :            : 
      48                 :            :     /** Which (peer, gtxid) combinations are known to be expired. These need to be accumulated here instead of
      49                 :            :      *  checked directly in the GetRequestable return value to avoid introducing a dependency between the various
      50                 :            :      *  parallel tests. */
      51                 :            :     std::multiset<std::pair<NodeId, GenTxid>> expired;
      52                 :            : };
      53                 :            : 
      54                 :          0 : std::chrono::microseconds RandomTime8s() { return std::chrono::microseconds{1 + InsecureRandBits(23)}; }
      55                 :          0 : std::chrono::microseconds RandomTime1y() { return std::chrono::microseconds{1 + InsecureRandBits(45)}; }
      56                 :            : 
      57                 :            : /** A proxy for a Runner that helps build a sequence of consecutive test actions on a TxRequestTracker.
      58                 :            :  *
      59                 :            :  * Each Scenario is a proxy through which actions for the (sequential) execution of various tests are added to a
      60                 :            :  * Runner. The actions from multiple scenarios are then run concurrently, resulting in these tests being performed
      61                 :            :  * against a TxRequestTracker in parallel. Every test has its own unique txhashes and NodeIds which are not
      62                 :            :  * reused in other tests, and thus they should be independent from each other. Running them in parallel however
      63                 :            :  * means that we verify the behavior (w.r.t. one test's txhashes and NodeIds) even when the state of the data
      64                 :            :  * structure is more complicated due to the presence of other tests.
      65                 :            :  */
      66                 :            : class Scenario
      67                 :            : {
      68                 :            :     Runner& m_runner;
      69                 :            :     std::chrono::microseconds m_now;
      70                 :            :     std::string m_testname;
      71                 :            : 
      72                 :            : public:
      73                 :          0 :     Scenario(Runner& runner, std::chrono::microseconds starttime) : m_runner(runner), m_now(starttime) {}
      74                 :          0 : 
      75                 :            :     /** Set a name for the current test, to give more clear error messages. */
      76                 :          0 :     void SetTestName(std::string testname)
      77                 :            :     {
      78                 :          0 :         m_testname = std::move(testname);
      79                 :          0 :     }
      80                 :            : 
      81                 :            :     /** Advance this Scenario's time; this affects the timestamps newly scheduled events get. */
      82                 :          0 :     void AdvanceTime(std::chrono::microseconds amount)
      83                 :          0 :     {
      84                 :          0 :         assert(amount.count() >= 0);
      85                 :          0 :         m_now += amount;
      86                 :          0 :     }
      87                 :            : 
      88                 :            :     /** Schedule a ForgetTxHash call at the Scheduler's current time. */
      89                 :          0 :     void ForgetTxHash(const uint256& txhash)
      90                 :            :     {
      91                 :          0 :         auto& runner = m_runner;
      92                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
      93                 :          0 :             runner.txrequest.ForgetTxHash(txhash);
      94                 :          0 :             runner.txrequest.SanityCheck();
      95                 :          0 :         });
      96                 :          0 :     }
      97                 :            : 
      98                 :            :     /** Schedule a ReceivedInv call at the Scheduler's current time. */
      99                 :          0 :     void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool pref, std::chrono::microseconds reqtime)
     100                 :            :     {
     101                 :          0 :         auto& runner = m_runner;
     102                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     103                 :          0 :             runner.txrequest.ReceivedInv(peer, gtxid, pref, reqtime);
     104                 :          0 :             runner.txrequest.SanityCheck();
     105                 :          0 :         });
     106                 :          0 :     }
     107                 :            : 
     108                 :            :     /** Schedule a DisconnectedPeer call at the Scheduler's current time. */
     109                 :          0 :     void DisconnectedPeer(NodeId peer)
     110                 :            :     {
     111                 :          0 :         auto& runner = m_runner;
     112                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     113                 :          0 :             runner.txrequest.DisconnectedPeer(peer);
     114                 :          0 :             runner.txrequest.SanityCheck();
     115                 :          0 :         });
     116                 :          0 :     }
     117                 :            : 
     118                 :            :     /** Schedule a RequestedTx call at the Scheduler's current time. */
     119                 :          0 :     void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds exptime)
     120                 :            :     {
     121                 :          0 :         auto& runner = m_runner;
     122                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     123                 :          0 :             runner.txrequest.RequestedTx(peer, txhash, exptime);
     124                 :          0 :             runner.txrequest.SanityCheck();
     125                 :          0 :         });
     126                 :          0 :     }
     127                 :            : 
     128                 :            :     /** Schedule a ReceivedResponse call at the Scheduler's current time. */
     129                 :          0 :     void ReceivedResponse(NodeId peer, const uint256& txhash)
     130                 :            :     {
     131                 :          0 :         auto& runner = m_runner;
     132                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     133                 :          0 :             runner.txrequest.ReceivedResponse(peer, txhash);
     134                 :          0 :             runner.txrequest.SanityCheck();
     135                 :          0 :         });
     136                 :          0 :     }
     137                 :            : 
     138                 :            :     /** Schedule calls to verify the TxRequestTracker's state at the Scheduler's current time.
     139                 :            :      *
     140                 :            :      * @param peer       The peer whose state will be inspected.
     141                 :            :      * @param expected   The expected return value for GetRequestable(peer)
     142                 :            :      * @param candidates The expected return value CountCandidates(peer)
     143                 :            :      * @param inflight   The expected return value CountInFlight(peer)
     144                 :            :      * @param completed  The expected return value of Count(peer), minus candidates and inflight.
     145                 :            :      * @param checkname  An arbitrary string to include in error messages, for test identificatrion.
     146                 :            :      * @param offset     Offset with the current time to use (must be <= 0). This allows simulations of time going
     147                 :            :      *                   backwards (but note that the ordering of this event only follows the scenario's m_now.
     148                 :            :      */
     149                 :          0 :     void Check(NodeId peer, const std::vector<GenTxid>& expected, size_t candidates, size_t inflight,
     150                 :            :         size_t completed, const std::string& checkname,
     151                 :            :         std::chrono::microseconds offset = std::chrono::microseconds{0})
     152                 :            :     {
     153                 :          0 :         const auto comment = m_testname + " " + checkname;
     154                 :          0 :         auto& runner = m_runner;
     155                 :          0 :         const auto now = m_now;
     156                 :          0 :         assert(offset.count() <= 0);
     157                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     158                 :          0 :             std::vector<std::pair<NodeId, GenTxid>> expired_now;
     159                 :          0 :             auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);
     160                 :          0 :             for (const auto& entry : expired_now) runner.expired.insert(entry);
     161                 :          0 :             runner.txrequest.SanityCheck();
     162                 :          0 :             runner.txrequest.PostGetRequestableSanityCheck(now + offset);
     163                 :          0 :             size_t total = candidates + inflight + completed;
     164                 :          0 :             size_t real_total = runner.txrequest.Count(peer);
     165                 :          0 :             size_t real_candidates = runner.txrequest.CountCandidates(peer);
     166                 :          0 :             size_t real_inflight = runner.txrequest.CountInFlight(peer);
     167                 :          0 :             BOOST_CHECK_MESSAGE(real_total == total, strprintf("[" + comment + "] total %i (%i expected)", real_total, total));
     168                 :          0 :             BOOST_CHECK_MESSAGE(real_inflight == inflight, strprintf("[" + comment + "] inflight %i (%i expected)", real_inflight, inflight));
     169                 :          0 :             BOOST_CHECK_MESSAGE(real_candidates == candidates, strprintf("[" + comment + "] candidates %i (%i expected)", real_candidates, candidates));
     170                 :          0 :             BOOST_CHECK_MESSAGE(ret == expected, "[" + comment + "] mismatching requestables");
     171                 :          0 :         });
     172                 :          0 :     }
     173                 :            : 
     174                 :            :     /** Verify that an announcement for gtxid by peer has expired some time before this check is scheduled.
     175                 :            :      *
     176                 :            :      * Every expected expiration should be accounted for through exactly one call to this function.
     177                 :            :      */
     178                 :          0 :     void CheckExpired(NodeId peer, GenTxid gtxid)
     179                 :            :     {
     180                 :          0 :         const auto& testname = m_testname;
     181                 :          0 :         auto& runner = m_runner;
     182                 :          0 :         runner.actions.emplace_back(m_now, [=,&runner]() {
     183                 :          0 :             auto it = runner.expired.find(std::pair<NodeId, GenTxid>{peer, gtxid});
     184                 :          0 :             BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");
     185                 :          0 :             if (it != runner.expired.end()) runner.expired.erase(it);
     186                 :          0 :         });
     187                 :          0 :     }
     188                 :            : 
     189                 :            :     /** Generate a random txhash, whose priorities for certain peers are constrained.
     190                 :            :      *
     191                 :            :      * For example, NewTxHash({{p1,p2,p3},{p2,p4,p5}}) will generate a txhash T such that both:
     192                 :            :      *  - priority(p1,T) > priority(p2,T) > priority(p3,T)
     193                 :            :      *  - priority(p2,T) > priority(p4,T) > priority(p5,T)
     194                 :            :      * where priority is the predicted internal TxRequestTracker's priority, assuming all announcements
     195                 :            :      * are within the same preferredness class.
     196                 :            :      */
     197                 :          0 :     uint256 NewTxHash(const std::vector<std::vector<NodeId>>& orders = {})
     198                 :            :     {
     199                 :          0 :         uint256 ret;
     200                 :            :         bool ok;
     201                 :          0 :         do {
     202                 :          0 :             ret = InsecureRand256();
     203                 :          0 :             ok = true;
     204                 :          0 :             for (const auto& order : orders) {
     205                 :          0 :                 for (size_t pos = 1; pos < order.size(); ++pos) {
     206                 :          0 :                     uint64_t prio_prev = m_runner.txrequest.ComputePriority(ret, order[pos - 1], true);
     207                 :          0 :                     uint64_t prio_cur = m_runner.txrequest.ComputePriority(ret, order[pos], true);
     208                 :          0 :                     if (prio_prev <= prio_cur) {
     209                 :          0 :                         ok = false;
     210                 :          0 :                         break;
     211                 :            :                     }
     212                 :          0 :                 }
     213                 :          0 :                 if (!ok) break;
     214                 :            :             }
     215                 :          0 :             if (ok) {
     216                 :          0 :                 ok = m_runner.txhashset.insert(ret).second;
     217                 :          0 :             }
     218                 :          0 :         } while(!ok);
     219                 :          0 :         return ret;
     220                 :            :     }
     221                 :            : 
     222                 :            :     /** Generate a random GenTxid; the txhash follows NewTxHash; the is_wtxid flag is random. */
     223                 :          0 :     GenTxid NewGTxid(const std::vector<std::vector<NodeId>>& orders = {})
     224                 :            :     {
     225                 :          0 :         return InsecureRandBool() ? GenTxid::Wtxid(NewTxHash(orders)) : GenTxid::Txid(NewTxHash(orders));
     226                 :            :     }
     227                 :          0 : 
     228                 :            :     /** Generate a new random NodeId to use as peer. The same NodeId is never returned twice
     229                 :            :      *  (across all Scenarios combined). */
     230                 :          0 :     NodeId NewPeer()
     231                 :            :     {
     232                 :            :         bool ok;
     233                 :            :         NodeId ret;
     234                 :          0 :         do {
     235                 :          0 :             ret = InsecureRandBits(63);
     236                 :          0 :             ok = m_runner.peerset.insert(ret).second;
     237                 :          0 :         } while(!ok);
     238                 :          0 :         return ret;
     239                 :            :     }
     240                 :            : 
     241                 :          0 :     std::chrono::microseconds Now() const { return m_now; }
     242                 :            : };
     243                 :            : 
     244                 :            : /** Add to scenario a test with a single tx announced by a single peer.
     245                 :            :  *
     246                 :            :  * config is an integer in [0, 32), which controls which variant of the test is used.
     247                 :            :  */
     248                 :          0 : void BuildSingleTest(Scenario& scenario, int config)
     249                 :            : {
     250                 :          0 :     auto peer = scenario.NewPeer();
     251                 :          0 :     auto gtxid = scenario.NewGTxid();
     252                 :          0 :     bool immediate = config & 1;
     253                 :          0 :     bool preferred = config & 2;
     254                 :          0 :     auto delay = immediate ? NO_TIME : RandomTime8s();
     255                 :            : 
     256                 :          0 :     scenario.SetTestName(strprintf("Single(config=%i)", config));
     257                 :            : 
     258                 :            :     // Receive an announcement, either immediately requestable or delayed.
     259                 :          0 :     scenario.ReceivedInv(peer, gtxid, preferred, immediate ? MIN_TIME : scenario.Now() + delay);
     260                 :          0 :     if (immediate) {
     261                 :          0 :         scenario.Check(peer, {gtxid}, 1, 0, 0, "s1");
     262                 :          0 :     } else {
     263                 :          0 :         scenario.Check(peer, {}, 1, 0, 0, "s2");
     264                 :          0 :         scenario.AdvanceTime(delay - MICROSECOND);
     265                 :          0 :         scenario.Check(peer, {}, 1, 0, 0, "s3");
     266                 :          0 :         scenario.AdvanceTime(MICROSECOND);
     267                 :          0 :         scenario.Check(peer, {gtxid}, 1, 0, 0, "s4");
     268                 :            :     }
     269                 :            : 
     270                 :          0 :     if (config >> 3) { // We'll request the transaction
     271                 :          0 :         scenario.AdvanceTime(RandomTime8s());
     272                 :          0 :         auto expiry = RandomTime8s();
     273                 :          0 :         scenario.Check(peer, {gtxid}, 1, 0, 0, "s5");
     274                 :          0 :         scenario.RequestedTx(peer, gtxid.GetHash(), scenario.Now() + expiry);
     275                 :          0 :         scenario.Check(peer, {}, 0, 1, 0, "s6");
     276                 :            : 
     277                 :          0 :         if ((config >> 3) == 1) { // The request will time out
     278                 :          0 :             scenario.AdvanceTime(expiry - MICROSECOND);
     279                 :          0 :             scenario.Check(peer, {}, 0, 1, 0, "s7");
     280                 :          0 :             scenario.AdvanceTime(MICROSECOND);
     281                 :          0 :             scenario.Check(peer, {}, 0, 0, 0, "s8");
     282                 :          0 :             scenario.CheckExpired(peer, gtxid);
     283                 :          0 :             return;
     284                 :            :         } else {
     285                 :          0 :             scenario.AdvanceTime(std::chrono::microseconds{InsecureRandRange(expiry.count())});
     286                 :          0 :             scenario.Check(peer, {}, 0, 1, 0, "s9");
     287                 :          0 :             if ((config >> 3) == 3) { // A response will arrive for the transaction
     288                 :          0 :                 scenario.ReceivedResponse(peer, gtxid.GetHash());
     289                 :          0 :                 scenario.Check(peer, {}, 0, 0, 0, "s10");
     290                 :          0 :                 return;
     291                 :            :             }
     292                 :            :         }
     293                 :          0 :     }
     294                 :            : 
     295                 :          0 :     if (config & 4) { // The peer will go offline
     296                 :          0 :         scenario.DisconnectedPeer(peer);
     297                 :          0 :     } else { // The transaction is no longer needed
     298                 :          0 :         scenario.ForgetTxHash(gtxid.GetHash());
     299                 :            :     }
     300                 :          0 :     scenario.Check(peer, {}, 0, 0, 0, "s11");
     301                 :          0 : }
     302                 :            : 
     303                 :            : /** Add to scenario a test with a single tx announced by two peers, to verify the
     304                 :            :  *  right peer is selected for requests.
     305                 :            :  *
     306                 :            :  * config is an integer in [0, 32), which controls which variant of the test is used.
     307                 :            :  */
     308                 :          0 : void BuildPriorityTest(Scenario& scenario, int config)
     309                 :            : {
     310                 :          0 :     scenario.SetTestName(strprintf("Priority(config=%i)", config));
     311                 :            : 
     312                 :            :     // Two peers. They will announce in order {peer1, peer2}.
     313                 :          0 :     auto peer1 = scenario.NewPeer(), peer2 = scenario.NewPeer();
     314                 :            :     // Construct a transaction that under random rules would be preferred by peer2 or peer1,
     315                 :            :     // depending on configuration.
     316                 :          0 :     bool prio1 = config & 1;
     317                 :          0 :     auto gtxid = prio1 ? scenario.NewGTxid({{peer1, peer2}}) : scenario.NewGTxid({{peer2, peer1}});
     318                 :          0 :     bool pref1 = config & 2, pref2 = config & 4;
     319                 :            : 
     320                 :          0 :     scenario.ReceivedInv(peer1, gtxid, pref1, MIN_TIME);
     321                 :          0 :     scenario.Check(peer1, {gtxid}, 1, 0, 0, "p1");
     322                 :          0 :     if (InsecureRandBool()) {
     323                 :          0 :         scenario.AdvanceTime(RandomTime8s());
     324                 :          0 :         scenario.Check(peer1, {gtxid}, 1, 0, 0, "p2");
     325                 :          0 :     }
     326                 :            : 
     327                 :          0 :     scenario.ReceivedInv(peer2, gtxid, pref2, MIN_TIME);
     328                 :          0 :     bool stage2_prio =
     329                 :            :         // At this point, peer2 will be given priority if:
     330                 :            :         // - It is preferred and peer1 is not
     331                 :          0 :         (pref2 && !pref1) ||
     332                 :            :         // - They're in the same preference class,
     333                 :            :         //   and the randomized priority favors peer2 over peer1.
     334                 :          0 :         (pref1 == pref2 && !prio1);
     335                 :          0 :     NodeId priopeer = stage2_prio ? peer2 : peer1, otherpeer = stage2_prio ? peer1 : peer2;
     336                 :          0 :     scenario.Check(otherpeer, {}, 1, 0, 0, "p3");
     337                 :          0 :     scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p4");
     338                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     339                 :          0 :     scenario.Check(otherpeer, {}, 1, 0, 0, "p5");
     340                 :          0 :     scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p6");
     341                 :            : 
     342                 :            :     // We possibly request from the selected peer.
     343                 :          0 :     if (config & 8) {
     344                 :          0 :         scenario.RequestedTx(priopeer, gtxid.GetHash(), MAX_TIME);
     345                 :          0 :         scenario.Check(priopeer, {}, 0, 1, 0, "p7");
     346                 :          0 :         scenario.Check(otherpeer, {}, 1, 0, 0, "p8");
     347                 :          0 :         if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     348                 :          0 :     }
     349                 :            : 
     350                 :            :     // The peer which was selected (or requested from) now goes offline, or a NOTFOUND is received from them.
     351                 :          0 :     if (config & 16) {
     352                 :          0 :         scenario.DisconnectedPeer(priopeer);
     353                 :          0 :     } else {
     354                 :          0 :         scenario.ReceivedResponse(priopeer, gtxid.GetHash());
     355                 :            :     }
     356                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     357                 :          0 :     scenario.Check(priopeer, {}, 0, 0, !(config & 16), "p8");
     358                 :          0 :     scenario.Check(otherpeer, {gtxid}, 1, 0, 0, "p9");
     359                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     360                 :            : 
     361                 :            :     // Now the other peer goes offline.
     362                 :          0 :     scenario.DisconnectedPeer(otherpeer);
     363                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     364                 :          0 :     scenario.Check(peer1, {}, 0, 0, 0, "p10");
     365                 :          0 :     scenario.Check(peer2, {}, 0, 0, 0, "p11");
     366                 :          0 : }
     367                 :            : 
     368                 :            : /** Add to scenario a randomized test in which N peers announce the same transaction, to verify
     369                 :            :  *  the order in which they are requested. */
     370                 :          0 : void BuildBigPriorityTest(Scenario& scenario, int peers)
     371                 :            : {
     372                 :          0 :     scenario.SetTestName(strprintf("BigPriority(peers=%i)", peers));
     373                 :            : 
     374                 :            :     // We will have N peers announce the same transaction.
     375                 :          0 :     std::map<NodeId, bool> preferred;
     376                 :          0 :     std::vector<NodeId> pref_peers, npref_peers;
     377                 :          0 :     int num_pref = InsecureRandRange(peers + 1) ; // Some preferred, ...
     378                 :          0 :     int num_npref = peers - num_pref; // some not preferred.
     379                 :          0 :     for (int i = 0; i < num_pref; ++i) {
     380                 :          0 :         pref_peers.push_back(scenario.NewPeer());
     381                 :          0 :         preferred[pref_peers.back()] = true;
     382                 :          0 :     }
     383                 :          0 :     for (int i = 0; i < num_npref; ++i) {
     384                 :          0 :         npref_peers.push_back(scenario.NewPeer());
     385                 :          0 :         preferred[npref_peers.back()] = false;
     386                 :          0 :     }
     387                 :            :     // Make a list of all peers, in order of intended request order (concatenation of pref_peers and npref_peers).
     388                 :          0 :     std::vector<NodeId> request_order;
     389                 :          0 :     request_order.reserve(num_pref + num_npref);
     390                 :          0 :     for (int i = 0; i < num_pref; ++i) request_order.push_back(pref_peers[i]);
     391                 :          0 :     for (int i = 0; i < num_npref; ++i) request_order.push_back(npref_peers[i]);
     392                 :            : 
     393                 :            :     // Determine the announcement order randomly.
     394                 :          0 :     std::vector<NodeId> announce_order = request_order;
     395                 :          0 :     Shuffle(announce_order.begin(), announce_order.end(), g_insecure_rand_ctx);
     396                 :            : 
     397                 :            :     // Find a gtxid whose txhash prioritization is consistent with the required ordering within pref_peers and
     398                 :            :     // within npref_peers.
     399                 :          0 :     auto gtxid = scenario.NewGTxid({pref_peers, npref_peers});
     400                 :            : 
     401                 :            :     // Decide reqtimes in opposite order of the expected request order. This means that as time passes we expect the
     402                 :            :     // to-be-requested-from-peer will change every time a subsequent reqtime is passed.
     403                 :          0 :     std::map<NodeId, std::chrono::microseconds> reqtimes;
     404                 :          0 :     auto reqtime = scenario.Now();
     405                 :          0 :     for (int i = peers - 1; i >= 0; --i) {
     406                 :          0 :         reqtime += RandomTime8s();
     407                 :          0 :         reqtimes[request_order[i]] = reqtime;
     408                 :          0 :     }
     409                 :            : 
     410                 :            :     // Actually announce from all peers simultaneously (but in announce_order).
     411                 :          0 :     for (const auto peer : announce_order) {
     412                 :          0 :         scenario.ReceivedInv(peer, gtxid, preferred[peer], reqtimes[peer]);
     413                 :            :     }
     414                 :          0 :     for (const auto peer : announce_order) {
     415                 :          0 :         scenario.Check(peer, {}, 1, 0, 0, "b1");
     416                 :            :     }
     417                 :            : 
     418                 :            :     // Let time pass and observe the to-be-requested-from peer change, from nonpreferred to preferred, and from
     419                 :            :     // high priority to low priority within each class.
     420                 :          0 :     for (int i = peers - 1; i >= 0; --i) {
     421                 :          0 :         scenario.AdvanceTime(reqtimes[request_order[i]] - scenario.Now() - MICROSECOND);
     422                 :          0 :         scenario.Check(request_order[i], {}, 1, 0, 0, "b2");
     423                 :          0 :         scenario.AdvanceTime(MICROSECOND);
     424                 :          0 :         scenario.Check(request_order[i], {gtxid}, 1, 0, 0, "b3");
     425                 :          0 :     }
     426                 :            : 
     427                 :            :     // Peers now in random order go offline, or send NOTFOUNDs. At every point in time the new to-be-requested-from
     428                 :            :     // peer should be the best remaining one, so verify this after every response.
     429                 :          0 :     for (int i = 0; i < peers; ++i) {
     430                 :          0 :         if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     431                 :          0 :         const int pos = InsecureRandRange(request_order.size());
     432                 :          0 :         const auto peer = request_order[pos];
     433                 :          0 :         request_order.erase(request_order.begin() + pos);
     434                 :          0 :         if (InsecureRandBool()) {
     435                 :          0 :             scenario.DisconnectedPeer(peer);
     436                 :          0 :             scenario.Check(peer, {}, 0, 0, 0, "b4");
     437                 :          0 :         } else {
     438                 :          0 :             scenario.ReceivedResponse(peer, gtxid.GetHash());
     439                 :          0 :             scenario.Check(peer, {}, 0, 0, request_order.size() > 0, "b5");
     440                 :            :         }
     441                 :          0 :         if (request_order.size()) {
     442                 :          0 :             scenario.Check(request_order[0], {gtxid}, 1, 0, 0, "b6");
     443                 :          0 :         }
     444                 :          0 :     }
     445                 :            : 
     446                 :            :     // Everything is gone in the end.
     447                 :          0 :     for (const auto peer : announce_order) {
     448                 :          0 :         scenario.Check(peer, {}, 0, 0, 0, "b7");
     449                 :            :     }
     450                 :          0 : }
     451                 :            : 
     452                 :            : /** Add to scenario a test with one peer announcing two transactions, to verify they are
     453                 :            :  *  fetched in announcement order.
     454                 :            :  *
     455                 :            :  *  config is an integer in [0, 4) inclusive, and selects the variant of the test.
     456                 :            :  */
     457                 :          0 : void BuildRequestOrderTest(Scenario& scenario, int config)
     458                 :            : {
     459                 :          0 :     scenario.SetTestName(strprintf("RequestOrder(config=%i)", config));
     460                 :            : 
     461                 :          0 :     auto peer = scenario.NewPeer();
     462                 :          0 :     auto gtxid1 = scenario.NewGTxid();
     463                 :          0 :     auto gtxid2 = scenario.NewGTxid();
     464                 :            : 
     465                 :          0 :     auto reqtime2 = scenario.Now() + RandomTime8s();
     466                 :          0 :     auto reqtime1 = reqtime2 + RandomTime8s();
     467                 :            : 
     468                 :          0 :     scenario.ReceivedInv(peer, gtxid1, config & 1, reqtime1);
     469                 :            :     // Simulate time going backwards by giving the second announcement an earlier reqtime.
     470                 :          0 :     scenario.ReceivedInv(peer, gtxid2, config & 2, reqtime2);
     471                 :            : 
     472                 :          0 :     scenario.AdvanceTime(reqtime2 - MICROSECOND - scenario.Now());
     473                 :          0 :     scenario.Check(peer, {}, 2, 0, 0, "o1");
     474                 :          0 :     scenario.AdvanceTime(MICROSECOND);
     475                 :          0 :     scenario.Check(peer, {gtxid2}, 2, 0, 0, "o2");
     476                 :          0 :     scenario.AdvanceTime(reqtime1 - MICROSECOND - scenario.Now());
     477                 :          0 :     scenario.Check(peer, {gtxid2}, 2, 0, 0, "o3");
     478                 :          0 :     scenario.AdvanceTime(MICROSECOND);
     479                 :            :     // Even with time going backwards in between announcements, the return value of GetRequestable is in
     480                 :            :     // announcement order.
     481                 :          0 :     scenario.Check(peer, {gtxid1, gtxid2}, 2, 0, 0, "o4");
     482                 :            : 
     483                 :          0 :     scenario.DisconnectedPeer(peer);
     484                 :          0 :     scenario.Check(peer, {}, 0, 0, 0, "o5");
     485                 :          0 : }
     486                 :            : 
     487                 :            : /** Add to scenario a test that verifies behavior related to both txid and wtxid with the same
     488                 :            :  *  hash being announced.
     489                 :            :  *
     490                 :            :  *  config is an integer in [0, 4) inclusive, and selects the variant of the test used.
     491                 :            : */
     492                 :          0 : void BuildWtxidTest(Scenario& scenario, int config)
     493                 :            : {
     494                 :          0 :     scenario.SetTestName(strprintf("Wtxid(config=%i)", config));
     495                 :            : 
     496                 :          0 :     auto peerT = scenario.NewPeer();
     497                 :          0 :     auto peerW = scenario.NewPeer();
     498                 :          0 :     auto txhash = scenario.NewTxHash();
     499                 :          0 :     auto txid{GenTxid::Txid(txhash)};
     500                 :          0 :     auto wtxid{GenTxid::Wtxid(txhash)};
     501                 :            : 
     502                 :          0 :     auto reqtimeT = InsecureRandBool() ? MIN_TIME : scenario.Now() + RandomTime8s();
     503                 :          0 :     auto reqtimeW = InsecureRandBool() ? MIN_TIME : scenario.Now() + RandomTime8s();
     504                 :            : 
     505                 :            :     // Announce txid first or wtxid first.
     506                 :          0 :     if (config & 1) {
     507                 :          0 :         scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
     508                 :          0 :         if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     509                 :          0 :         scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
     510                 :          0 :     } else {
     511                 :          0 :         scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
     512                 :          0 :         if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     513                 :          0 :         scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
     514                 :            :     }
     515                 :            : 
     516                 :            :     // Let time pass if needed, and check that the preferred announcement (txid or wtxid)
     517                 :            :     // is correctly to-be-requested (and with the correct wtxidness).
     518                 :          0 :     auto max_reqtime = std::max(reqtimeT, reqtimeW);
     519                 :          0 :     if (max_reqtime > scenario.Now()) scenario.AdvanceTime(max_reqtime - scenario.Now());
     520                 :          0 :     if (config & 2) {
     521                 :          0 :         scenario.Check(peerT, {txid}, 1, 0, 0, "w1");
     522                 :          0 :         scenario.Check(peerW, {}, 1, 0, 0, "w2");
     523                 :          0 :     } else {
     524                 :          0 :         scenario.Check(peerT, {}, 1, 0, 0, "w3");
     525                 :          0 :         scenario.Check(peerW, {wtxid}, 1, 0, 0, "w4");
     526                 :            :     }
     527                 :            : 
     528                 :            :     // Let the preferred announcement be requested. It's not going to be delivered.
     529                 :          0 :     auto expiry = RandomTime8s();
     530                 :          0 :     if (config & 2) {
     531                 :          0 :         scenario.RequestedTx(peerT, txid.GetHash(), scenario.Now() + expiry);
     532                 :          0 :         scenario.Check(peerT, {}, 0, 1, 0, "w5");
     533                 :          0 :         scenario.Check(peerW, {}, 1, 0, 0, "w6");
     534                 :          0 :     } else {
     535                 :          0 :         scenario.RequestedTx(peerW, wtxid.GetHash(), scenario.Now() + expiry);
     536                 :          0 :         scenario.Check(peerT, {}, 1, 0, 0, "w7");
     537                 :          0 :         scenario.Check(peerW, {}, 0, 1, 0, "w8");
     538                 :            :     }
     539                 :            : 
     540                 :            :     // After reaching expiration time of the preferred announcement, verify that the
     541                 :            :     // remaining one is requestable
     542                 :          0 :     scenario.AdvanceTime(expiry);
     543                 :          0 :     if (config & 2) {
     544                 :          0 :         scenario.Check(peerT, {}, 0, 0, 1, "w9");
     545                 :          0 :         scenario.Check(peerW, {wtxid}, 1, 0, 0, "w10");
     546                 :          0 :         scenario.CheckExpired(peerT, txid);
     547                 :          0 :     } else {
     548                 :          0 :         scenario.Check(peerT, {txid}, 1, 0, 0, "w11");
     549                 :          0 :         scenario.Check(peerW, {}, 0, 0, 1, "w12");
     550                 :          0 :         scenario.CheckExpired(peerW, wtxid);
     551                 :            :     }
     552                 :            : 
     553                 :            :     // If a good transaction with either that hash as wtxid or txid arrives, both
     554                 :            :     // announcements are gone.
     555                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     556                 :          0 :     scenario.ForgetTxHash(txhash);
     557                 :          0 :     scenario.Check(peerT, {}, 0, 0, 0, "w13");
     558                 :          0 :     scenario.Check(peerW, {}, 0, 0, 0, "w14");
     559                 :          0 : }
     560                 :            : 
     561                 :            : /** Add to scenario a test that exercises clocks that go backwards. */
     562                 :          0 : void BuildTimeBackwardsTest(Scenario& scenario)
     563                 :            : {
     564                 :          0 :     auto peer1 = scenario.NewPeer();
     565                 :          0 :     auto peer2 = scenario.NewPeer();
     566                 :          0 :     auto gtxid = scenario.NewGTxid({{peer1, peer2}});
     567                 :            : 
     568                 :            :     // Announce from peer2.
     569                 :          0 :     auto reqtime = scenario.Now() + RandomTime8s();
     570                 :          0 :     scenario.ReceivedInv(peer2, gtxid, true, reqtime);
     571                 :          0 :     scenario.Check(peer2, {}, 1, 0, 0, "r1");
     572                 :          0 :     scenario.AdvanceTime(reqtime - scenario.Now());
     573                 :          0 :     scenario.Check(peer2, {gtxid}, 1, 0, 0, "r2");
     574                 :            :     // Check that if the clock goes backwards by 1us, the transaction would stop being requested.
     575                 :          0 :     scenario.Check(peer2, {}, 1, 0, 0, "r3", -MICROSECOND);
     576                 :            :     // But it reverts to being requested if time goes forward again.
     577                 :          0 :     scenario.Check(peer2, {gtxid}, 1, 0, 0, "r4");
     578                 :            : 
     579                 :            :     // Announce from peer1.
     580                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     581                 :          0 :     scenario.ReceivedInv(peer1, gtxid, true, MAX_TIME);
     582                 :          0 :     scenario.Check(peer2, {gtxid}, 1, 0, 0, "r5");
     583                 :          0 :     scenario.Check(peer1, {}, 1, 0, 0, "r6");
     584                 :            : 
     585                 :            :     // Request from peer1.
     586                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     587                 :          0 :     auto expiry = scenario.Now() + RandomTime8s();
     588                 :          0 :     scenario.RequestedTx(peer1, gtxid.GetHash(), expiry);
     589                 :          0 :     scenario.Check(peer1, {}, 0, 1, 0, "r7");
     590                 :          0 :     scenario.Check(peer2, {}, 1, 0, 0, "r8");
     591                 :            : 
     592                 :            :     // Expiration passes.
     593                 :          0 :     scenario.AdvanceTime(expiry - scenario.Now());
     594                 :          0 :     scenario.Check(peer1, {}, 0, 0, 1, "r9");
     595                 :          0 :     scenario.Check(peer2, {gtxid}, 1, 0, 0, "r10"); // Request goes back to peer2.
     596                 :          0 :     scenario.CheckExpired(peer1, gtxid);
     597                 :          0 :     scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); // Going back does not unexpire.
     598                 :          0 :     scenario.Check(peer2, {gtxid}, 1, 0, 0, "r12", -MICROSECOND);
     599                 :            : 
     600                 :            :     // Peer2 goes offline, meaning no viable announcements remain.
     601                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     602                 :          0 :     scenario.DisconnectedPeer(peer2);
     603                 :          0 :     scenario.Check(peer1, {}, 0, 0, 0, "r13");
     604                 :          0 :     scenario.Check(peer2, {}, 0, 0, 0, "r14");
     605                 :          0 : }
     606                 :            : 
     607                 :            : /** Add to scenario a test that involves RequestedTx() calls for txhashes not returned by GetRequestable. */
     608                 :          0 : void BuildWeirdRequestsTest(Scenario& scenario)
     609                 :            : {
     610                 :          0 :     auto peer1 = scenario.NewPeer();
     611                 :          0 :     auto peer2 = scenario.NewPeer();
     612                 :          0 :     auto gtxid1 = scenario.NewGTxid({{peer1, peer2}});
     613                 :          0 :     auto gtxid2 = scenario.NewGTxid({{peer2, peer1}});
     614                 :            : 
     615                 :            :     // Announce gtxid1 by peer1.
     616                 :          0 :     scenario.ReceivedInv(peer1, gtxid1, true, MIN_TIME);
     617                 :          0 :     scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q1");
     618                 :            : 
     619                 :            :     // Announce gtxid2 by peer2.
     620                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     621                 :          0 :     scenario.ReceivedInv(peer2, gtxid2, true, MIN_TIME);
     622                 :          0 :     scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q2");
     623                 :          0 :     scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q3");
     624                 :            : 
     625                 :            :     // We request gtxid2 from *peer1* - no effect.
     626                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     627                 :          0 :     scenario.RequestedTx(peer1, gtxid2.GetHash(), MAX_TIME);
     628                 :          0 :     scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q4");
     629                 :          0 :     scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q5");
     630                 :            : 
     631                 :            :     // Now request gtxid1 from peer1 - marks it as REQUESTED.
     632                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     633                 :          0 :     auto expiryA = scenario.Now() + RandomTime8s();
     634                 :          0 :     scenario.RequestedTx(peer1, gtxid1.GetHash(), expiryA);
     635                 :          0 :     scenario.Check(peer1, {}, 0, 1, 0, "q6");
     636                 :          0 :     scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q7");
     637                 :            : 
     638                 :            :     // Request it a second time - nothing happens, as it's already REQUESTED.
     639                 :          0 :     auto expiryB = expiryA + RandomTime8s();
     640                 :          0 :     scenario.RequestedTx(peer1, gtxid1.GetHash(), expiryB);
     641                 :          0 :     scenario.Check(peer1, {}, 0, 1, 0, "q8");
     642                 :          0 :     scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q9");
     643                 :            : 
     644                 :            :     // Also announce gtxid1 from peer2 now, so that the txhash isn't forgotten when the peer1 request expires.
     645                 :          0 :     scenario.ReceivedInv(peer2, gtxid1, true, MIN_TIME);
     646                 :          0 :     scenario.Check(peer1, {}, 0, 1, 0, "q10");
     647                 :          0 :     scenario.Check(peer2, {gtxid2}, 2, 0, 0, "q11");
     648                 :            : 
     649                 :            :     // When reaching expiryA, it expires (not expiryB, which is later).
     650                 :          0 :     scenario.AdvanceTime(expiryA - scenario.Now());
     651                 :          0 :     scenario.Check(peer1, {}, 0, 0, 1, "q12");
     652                 :          0 :     scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q13");
     653                 :          0 :     scenario.CheckExpired(peer1, gtxid1);
     654                 :            : 
     655                 :            :     // Requesting it yet again from peer1 doesn't do anything, as it's already COMPLETED.
     656                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     657                 :          0 :     scenario.RequestedTx(peer1, gtxid1.GetHash(), MAX_TIME);
     658                 :          0 :     scenario.Check(peer1, {}, 0, 0, 1, "q14");
     659                 :          0 :     scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q15");
     660                 :            : 
     661                 :            :     // Now announce gtxid2 from peer1.
     662                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     663                 :          0 :     scenario.ReceivedInv(peer1, gtxid2, true, MIN_TIME);
     664                 :          0 :     scenario.Check(peer1, {}, 1, 0, 1, "q16");
     665                 :          0 :     scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q17");
     666                 :            : 
     667                 :            :     // And request it from peer1 (weird as peer2 has the preference).
     668                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     669                 :          0 :     scenario.RequestedTx(peer1, gtxid2.GetHash(), MAX_TIME);
     670                 :          0 :     scenario.Check(peer1, {}, 0, 1, 1, "q18");
     671                 :          0 :     scenario.Check(peer2, {gtxid1}, 2, 0, 0, "q19");
     672                 :            : 
     673                 :            :     // If peer2 now (normally) requests gtxid2, the existing request by peer1 becomes COMPLETED.
     674                 :          0 :     if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
     675                 :          0 :     scenario.RequestedTx(peer2, gtxid2.GetHash(), MAX_TIME);
     676                 :          0 :     scenario.Check(peer1, {}, 0, 0, 2, "q20");
     677                 :          0 :     scenario.Check(peer2, {gtxid1}, 1, 1, 0, "q21");
     678                 :            : 
     679                 :            :     // If peer2 goes offline, no viable announcements remain.
     680                 :          0 :     scenario.DisconnectedPeer(peer2);
     681                 :          0 :     scenario.Check(peer1, {}, 0, 0, 0, "q22");
     682                 :          0 :     scenario.Check(peer2, {}, 0, 0, 0, "q23");
     683                 :          0 : }
     684                 :            : 
     685                 :          0 : void TestInterleavedScenarios()
     686                 :            : {
     687                 :            :     // Create a list of functions which add tests to scenarios.
     688                 :          0 :     std::vector<std::function<void(Scenario&)>> builders;
     689                 :            :     // Add instances of every test, for every configuration.
     690                 :          0 :     for (int n = 0; n < 64; ++n) {
     691                 :          0 :         builders.emplace_back([n](Scenario& scenario){ BuildWtxidTest(scenario, n); });
     692                 :          0 :         builders.emplace_back([n](Scenario& scenario){ BuildRequestOrderTest(scenario, n & 3); });
     693                 :          0 :         builders.emplace_back([n](Scenario& scenario){ BuildSingleTest(scenario, n & 31); });
     694                 :          0 :         builders.emplace_back([n](Scenario& scenario){ BuildPriorityTest(scenario, n & 31); });
     695                 :          0 :         builders.emplace_back([n](Scenario& scenario){ BuildBigPriorityTest(scenario, (n & 7) + 1); });
     696                 :          0 :         builders.emplace_back([](Scenario& scenario){ BuildTimeBackwardsTest(scenario); });
     697                 :          0 :         builders.emplace_back([](Scenario& scenario){ BuildWeirdRequestsTest(scenario); });
     698                 :          0 :     }
     699                 :            :     // Randomly shuffle all those functions.
     700                 :          0 :     Shuffle(builders.begin(), builders.end(), g_insecure_rand_ctx);
     701                 :            : 
     702                 :          0 :     Runner runner;
     703                 :          0 :     auto starttime = RandomTime1y();
     704                 :            :     // Construct many scenarios, and run (up to) 10 randomly-chosen tests consecutively in each.
     705                 :          0 :     while (builders.size()) {
     706                 :            :         // Introduce some variation in the start time of each scenario, so they don't all start off
     707                 :            :         // concurrently, but get a more random interleaving.
     708                 :          0 :         auto scenario_start = starttime + RandomTime8s() + RandomTime8s() + RandomTime8s();
     709                 :          0 :         Scenario scenario(runner, scenario_start);
     710                 :          0 :         for (int j = 0; builders.size() && j < 10; ++j) {
     711                 :          0 :             builders.back()(scenario);
     712                 :          0 :             builders.pop_back();
     713                 :          0 :         }
     714                 :          0 :     }
     715                 :            :     // Sort all the actions from all those scenarios chronologically, resulting in the actions from
     716                 :            :     // distinct scenarios to become interleaved. Use stable_sort so that actions from one scenario
     717                 :            :     // aren't reordered w.r.t. each other.
     718                 :          0 :     std::stable_sort(runner.actions.begin(), runner.actions.end(), [](const Action& a1, const Action& a2) {
     719                 :          0 :         return a1.first < a2.first;
     720                 :            :     });
     721                 :            : 
     722                 :            :     // Run all actions from all scenarios, in order.
     723                 :          0 :     for (auto& action : runner.actions) {
     724                 :          0 :         action.second();
     725                 :            :     }
     726                 :            : 
     727                 :          0 :     BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U);
     728                 :          0 :     BOOST_CHECK(runner.expired.empty());
     729                 :          0 : }
     730                 :            : 
     731                 :            : }  // namespace
     732                 :            : 
     733                 :          0 : BOOST_AUTO_TEST_CASE(TxRequestTest)
     734                 :            : {
     735                 :          0 :     for (int i = 0; i < 5; ++i) {
     736                 :          0 :         TestInterleavedScenarios();
     737                 :          0 :     }
     738                 :          0 : }
     739                 :            : 
     740                 :          0 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14