LCOV - code coverage report
Current view: top level - src/test/fuzz - txrequest.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 98.8 % 172 170
Test Date: 2024-12-04 04:00:22 Functions: 100.0 % 15 15
Branches: 87.1 % 178 155

             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                 :             : #include <crypto/common.h>
       6                 :             : #include <crypto/sha256.h>
       7                 :             : #include <crypto/siphash.h>
       8                 :             : #include <primitives/transaction.h>
       9                 :             : #include <test/fuzz/fuzz.h>
      10                 :             : #include <txrequest.h>
      11                 :             : 
      12                 :             : #include <bitset>
      13                 :             : #include <cstdint>
      14                 :             : #include <queue>
      15                 :             : #include <vector>
      16                 :             : 
      17                 :             : namespace {
      18                 :             : 
      19                 :             : constexpr int MAX_TXHASHES = 16;
      20                 :             : constexpr int MAX_PEERS = 16;
      21                 :             : 
      22                 :             : //! Randomly generated GenTxids used in this test (length is MAX_TXHASHES).
      23                 :             : uint256 TXHASHES[MAX_TXHASHES];
      24                 :             : 
      25                 :             : //! Precomputed random durations (positive and negative, each ~exponentially distributed).
      26                 :             : std::chrono::microseconds DELAYS[256];
      27                 :             : 
      28                 :             : struct Initializer
      29                 :             : {
      30                 :         206 :     Initializer()
      31                 :             :     {
      32         [ +  + ]:        3502 :         for (uint8_t txhash = 0; txhash < MAX_TXHASHES; txhash += 1) {
      33                 :        3296 :             CSHA256().Write(&txhash, 1).Finalize(TXHASHES[txhash].begin());
      34                 :             :         }
      35                 :             :         int i = 0;
      36                 :             :         // DELAYS[N] for N=0..15 is just N microseconds.
      37         [ +  + ]:        3502 :         for (; i < 16; ++i) {
      38                 :        3296 :             DELAYS[i] = std::chrono::microseconds{i};
      39                 :             :         }
      40                 :             :         // DELAYS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to
      41                 :             :         // 198.416453 seconds.
      42         [ +  + ]:       23278 :         for (; i < 128; ++i) {
      43                 :       23072 :             int diff_bits = ((i - 10) * 2) / 9;
      44                 :       23072 :             uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits));
      45                 :       23072 :             DELAYS[i] = DELAYS[i - 1] + std::chrono::microseconds{diff};
      46                 :             :         }
      47                 :             :         // DELAYS[N] for N=128..255 are negative delays with the same magnitude as N=0..127.
      48         [ +  + ]:       26574 :         for (; i < 256; ++i) {
      49                 :       26368 :             DELAYS[i] = -DELAYS[255 - i];
      50                 :             :         }
      51                 :         206 :     }
      52                 :             : } g_initializer;
      53                 :             : 
      54                 :             : /** Tester class for TxRequestTracker
      55                 :             :  *
      56                 :             :  * It includes a naive reimplementation of its behavior, for a limited set
      57                 :             :  * of MAX_TXHASHES distinct txids, and MAX_PEERS peer identifiers.
      58                 :             :  *
      59                 :             :  * All of the public member functions perform the same operation on
      60                 :             :  * an actual TxRequestTracker and on the state of the reimplementation.
      61                 :             :  * The output of GetRequestable is compared with the expected value
      62                 :             :  * as well.
      63                 :             :  *
      64                 :             :  * Check() calls the TxRequestTracker's sanity check, plus compares the
      65                 :             :  * output of the constant accessors (Size(), CountLoad(), CountTracked())
      66                 :             :  * with expected values.
      67                 :             :  */
      68                 :         305 : class Tester
      69                 :             : {
      70                 :             :     //! TxRequestTracker object being tested.
      71                 :             :     TxRequestTracker m_tracker;
      72                 :             : 
      73                 :             :     //! States for txid/peer combinations in the naive data structure.
      74                 :             :     enum class State {
      75                 :             :         NOTHING, //!< Absence of this txid/peer combination
      76                 :             : 
      77                 :             :         // Note that this implementation does not distinguish between DELAYED/READY/BEST variants of CANDIDATE.
      78                 :             :         CANDIDATE,
      79                 :             :         REQUESTED,
      80                 :             :         COMPLETED,
      81                 :             :     };
      82                 :             : 
      83                 :             :     //! Sequence numbers, incremented whenever a new CANDIDATE is added.
      84                 :             :     uint64_t m_current_sequence{0};
      85                 :             : 
      86                 :             :     //! List of future 'events' (all inserted reqtimes/exptimes). This is used to implement AdvanceToEvent.
      87                 :             :     std::priority_queue<std::chrono::microseconds, std::vector<std::chrono::microseconds>,
      88                 :             :         std::greater<std::chrono::microseconds>> m_events;
      89                 :             : 
      90                 :             :     //! Information about a txhash/peer combination.
      91                 :       78080 :     struct Announcement
      92                 :             :     {
      93                 :             :         std::chrono::microseconds m_time;
      94                 :             :         uint64_t m_sequence;
      95                 :             :         State m_state{State::NOTHING};
      96                 :             :         bool m_preferred;
      97                 :             :         bool m_is_wtxid;
      98                 :             :         uint64_t m_priority; //!< Precomputed priority.
      99                 :             :     };
     100                 :             : 
     101                 :             :     //! Information about all txhash/peer combination.
     102                 :             :     Announcement m_announcements[MAX_TXHASHES][MAX_PEERS];
     103                 :             : 
     104                 :             :     //! The current time; can move forward and backward.
     105                 :             :     std::chrono::microseconds m_now{244466666};
     106                 :             : 
     107                 :             :     //! Delete txhashes whose only announcements are COMPLETED.
     108                 :     6709388 :     void Cleanup(int txhash)
     109                 :             :     {
     110                 :     6709388 :         bool all_nothing = true;
     111         [ +  + ]:    73259191 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     112                 :    70313733 :             const Announcement& ann = m_announcements[txhash][peer];
     113         [ +  + ]:    70313733 :             if (ann.m_state != State::NOTHING) {
     114         [ +  + ]:     4171621 :                 if (ann.m_state != State::COMPLETED) return;
     115                 :             :                 all_nothing = false;
     116                 :             :             }
     117                 :             :         }
     118         [ +  + ]:     2945458 :         if (all_nothing) return;
     119         [ +  + ]:     1041913 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     120                 :      980624 :             m_announcements[txhash][peer].m_state = State::NOTHING;
     121                 :             :         }
     122                 :             :     }
     123                 :             : 
     124                 :             :     //! Find the current best peer to request from for a txhash (or -1 if none).
     125                 :      477402 :     int GetSelected(int txhash) const
     126                 :             :     {
     127                 :      477402 :         int ret = -1;
     128                 :      477402 :         uint64_t ret_priority = 0;
     129         [ +  + ]:     7949945 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     130                 :     7491778 :             const Announcement& ann = m_announcements[txhash][peer];
     131                 :             :             // Return -1 if there already is a (non-expired) in-flight request.
     132         [ +  + ]:     7491778 :             if (ann.m_state == State::REQUESTED) return -1;
     133                 :             :             // If it's a viable candidate, see if it has lower priority than the best one so far.
     134   [ +  +  +  + ]:     7472543 :             if (ann.m_state == State::CANDIDATE && ann.m_time <= m_now) {
     135   [ +  +  +  + ]:     1040879 :                 if (ret == -1 || ann.m_priority > ret_priority) {
     136                 :      650501 :                     std::tie(ret, ret_priority) = std::tie(peer, ann.m_priority);
     137                 :             :                 }
     138                 :             :             }
     139                 :             :         }
     140                 :      458167 :         return ret;
     141                 :             :     }
     142                 :             : 
     143                 :             : public:
     144   [ +  +  +  + ]:       83265 :     Tester() : m_tracker(true) {}
     145                 :             : 
     146                 :     1312064 :     std::chrono::microseconds Now() const { return m_now; }
     147                 :             : 
     148                 :     1871478 :     void AdvanceTime(std::chrono::microseconds offset)
     149                 :             :     {
     150                 :     1871478 :         m_now += offset;
     151   [ +  +  +  + ]:     2045546 :         while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
     152                 :     1871478 :     }
     153                 :             : 
     154                 :     1728630 :     void AdvanceToEvent()
     155                 :             :     {
     156   [ +  +  +  + ]:     1880984 :         while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
     157         [ +  + ]:     1728630 :         if (!m_events.empty()) {
     158                 :      258747 :             m_now = m_events.top();
     159                 :      258747 :             m_events.pop();
     160                 :             :         }
     161                 :     1728630 :     }
     162                 :             : 
     163                 :      305117 :     void DisconnectedPeer(int peer)
     164                 :             :     {
     165                 :             :         // Apply to naive structure: all announcements for that peer are wiped.
     166         [ +  + ]:     5186989 :         for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     167         [ +  + ]:     4881872 :             if (m_announcements[txhash][peer].m_state != State::NOTHING) {
     168                 :      306565 :                 m_announcements[txhash][peer].m_state = State::NOTHING;
     169                 :      306565 :                 Cleanup(txhash);
     170                 :             :             }
     171                 :             :         }
     172                 :             : 
     173                 :             :         // Call TxRequestTracker's implementation.
     174                 :      305117 :         m_tracker.DisconnectedPeer(peer);
     175                 :      305117 :     }
     176                 :             : 
     177                 :      466891 :     void ForgetTxHash(int txhash)
     178                 :             :     {
     179                 :             :         // Apply to naive structure: all announcements for that txhash are wiped.
     180         [ +  + ]:     7937147 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     181                 :     7470256 :             m_announcements[txhash][peer].m_state = State::NOTHING;
     182                 :             :         }
     183                 :      466891 :         Cleanup(txhash);
     184                 :             : 
     185                 :             :         // Call TxRequestTracker's implementation.
     186                 :      466891 :         m_tracker.ForgetTxHash(TXHASHES[txhash]);
     187                 :      466891 :     }
     188                 :             : 
     189                 :     1382149 :     void ReceivedInv(int peer, int txhash, bool is_wtxid, bool preferred, std::chrono::microseconds reqtime)
     190                 :             :     {
     191                 :             :         // Apply to naive structure: if no announcement for txidnum/peer combination
     192                 :             :         // already, create a new CANDIDATE; otherwise do nothing.
     193                 :     1382149 :         Announcement& ann = m_announcements[txhash][peer];
     194         [ +  + ]:     1382149 :         if (ann.m_state == State::NOTHING) {
     195                 :      822970 :             ann.m_preferred = preferred;
     196                 :      822970 :             ann.m_state = State::CANDIDATE;
     197                 :      822970 :             ann.m_time = reqtime;
     198                 :      822970 :             ann.m_is_wtxid = is_wtxid;
     199                 :      822970 :             ann.m_sequence = m_current_sequence++;
     200                 :      822970 :             ann.m_priority = m_tracker.ComputePriority(TXHASHES[txhash], peer, ann.m_preferred);
     201                 :             : 
     202                 :             :             // Add event so that AdvanceToEvent can quickly jump to the point where its reqtime passes.
     203         [ +  + ]:      822970 :             if (reqtime > m_now) m_events.push(reqtime);
     204                 :             :         }
     205                 :             : 
     206                 :             :         // Call TxRequestTracker's implementation.
     207         [ +  + ]:     1382149 :         m_tracker.ReceivedInv(peer, is_wtxid ? GenTxid::Wtxid(TXHASHES[txhash]) : GenTxid::Txid(TXHASHES[txhash]), preferred, reqtime);
     208                 :     1382149 :     }
     209                 :             : 
     210                 :      640765 :     void RequestedTx(int peer, int txhash, std::chrono::microseconds exptime)
     211                 :             :     {
     212                 :             :         // Apply to naive structure: if a CANDIDATE announcement exists for peer/txhash,
     213                 :             :         // convert it to REQUESTED, and change any existing REQUESTED announcement for the same txhash to COMPLETED.
     214         [ +  + ]:      640765 :         if (m_announcements[txhash][peer].m_state == State::CANDIDATE) {
     215         [ +  + ]:     1413414 :             for (int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
     216         [ +  + ]:     1330272 :                 if (m_announcements[txhash][peer2].m_state == State::REQUESTED) {
     217                 :       10026 :                     m_announcements[txhash][peer2].m_state = State::COMPLETED;
     218                 :             :                 }
     219                 :             :             }
     220                 :       83142 :             m_announcements[txhash][peer].m_state = State::REQUESTED;
     221                 :       83142 :             m_announcements[txhash][peer].m_time = exptime;
     222                 :             :         }
     223                 :             : 
     224                 :             :         // Add event so that AdvanceToEvent can quickly jump to the point where its exptime passes.
     225         [ +  + ]:      640765 :         if (exptime > m_now) m_events.push(exptime);
     226                 :             : 
     227                 :             :         // Call TxRequestTracker's implementation.
     228                 :      640765 :         m_tracker.RequestedTx(peer, TXHASHES[txhash], exptime);
     229                 :      640765 :     }
     230                 :             : 
     231                 :      226863 :     void ReceivedResponse(int peer, int txhash)
     232                 :             :     {
     233                 :             :         // Apply to naive structure: convert anything to COMPLETED.
     234         [ +  + ]:      226863 :         if (m_announcements[txhash][peer].m_state != State::NOTHING) {
     235                 :       57116 :             m_announcements[txhash][peer].m_state = State::COMPLETED;
     236                 :       57116 :             Cleanup(txhash);
     237                 :             :         }
     238                 :             : 
     239                 :             :         // Call TxRequestTracker's implementation.
     240                 :      226863 :         m_tracker.ReceivedResponse(peer, TXHASHES[txhash]);
     241                 :      226863 :     }
     242                 :             : 
     243                 :      367426 :     void GetRequestable(int peer)
     244                 :             :     {
     245                 :             :         // Implement using naive structure:
     246                 :             : 
     247                 :             :         //! list of (sequence number, txhash, is_wtxid) tuples.
     248                 :      367426 :         std::vector<std::tuple<uint64_t, int, bool>> result;
     249                 :      367426 :         std::vector<std::pair<NodeId, GenTxid>> expected_expired;
     250         [ +  + ]:     6246242 :         for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     251                 :             :             // Mark any expired REQUESTED announcements as COMPLETED.
     252         [ +  + ]:    99376068 :             for (int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
     253                 :    93559489 :                 Announcement& ann2 = m_announcements[txhash][peer2];
     254   [ +  +  +  + ]:    93559489 :                 if (ann2.m_state == State::REQUESTED && ann2.m_time <= m_now) {
     255   [ +  +  +  - ]:       62237 :                     expected_expired.emplace_back(peer2, ann2.m_is_wtxid ? GenTxid::Wtxid(TXHASHES[txhash]) : GenTxid::Txid(TXHASHES[txhash]));
     256                 :       62237 :                     ann2.m_state = State::COMPLETED;
     257                 :       62237 :                     break;
     258                 :             :                 }
     259                 :             :             }
     260                 :             :             // And delete txids with only COMPLETED announcements left.
     261                 :     5878816 :             Cleanup(txhash);
     262                 :             :             // CANDIDATEs for which this announcement has the highest priority get returned.
     263                 :     5878816 :             const Announcement& ann = m_announcements[txhash][peer];
     264   [ +  +  +  + ]:     5878816 :             if (ann.m_state == State::CANDIDATE && GetSelected(txhash) == peer) {
     265         [ +  - ]:      206853 :                 result.emplace_back(ann.m_sequence, txhash, ann.m_is_wtxid);
     266                 :             :             }
     267                 :             :         }
     268                 :             :         // Sort the results by sequence number.
     269                 :      367426 :         std::sort(result.begin(), result.end());
     270                 :      367426 :         std::sort(expected_expired.begin(), expected_expired.end());
     271                 :             : 
     272                 :             :         // Compare with TxRequestTracker's implementation.
     273                 :      367426 :         std::vector<std::pair<NodeId, GenTxid>> expired;
     274         [ +  - ]:      367426 :         const auto actual = m_tracker.GetRequestable(peer, m_now, &expired);
     275                 :      367426 :         std::sort(expired.begin(), expired.end());
     276         [ -  + ]:      367426 :         assert(expired == expected_expired);
     277                 :             : 
     278         [ +  - ]:      367426 :         m_tracker.PostGetRequestableSanityCheck(m_now);
     279         [ -  + ]:      367426 :         assert(result.size() == actual.size());
     280         [ +  + ]:      574279 :         for (size_t pos = 0; pos < actual.size(); ++pos) {
     281         [ -  + ]:      206853 :             assert(TXHASHES[std::get<1>(result[pos])] == actual[pos].GetHash());
     282         [ -  + ]:      206853 :             assert(std::get<2>(result[pos]) == actual[pos].IsWtxid());
     283                 :             :         }
     284                 :      367426 :     }
     285                 :             : 
     286                 :         305 :     void Check()
     287                 :             :     {
     288                 :             :         // Compare CountTracked and CountLoad with naive structure.
     289                 :         305 :         size_t total = 0;
     290         [ +  + ]:        5185 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     291                 :             :             size_t tracked = 0;
     292                 :             :             size_t inflight = 0;
     293                 :             :             size_t candidates = 0;
     294         [ +  + ]:       82960 :             for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     295                 :       78080 :                 tracked += m_announcements[txhash][peer].m_state != State::NOTHING;
     296                 :       78080 :                 inflight += m_announcements[txhash][peer].m_state == State::REQUESTED;
     297                 :       78080 :                 candidates += m_announcements[txhash][peer].m_state == State::CANDIDATE;
     298                 :             :             }
     299         [ -  + ]:        4880 :             assert(m_tracker.Count(peer) == tracked);
     300         [ -  + ]:        4880 :             assert(m_tracker.CountInFlight(peer) == inflight);
     301         [ -  + ]:        4880 :             assert(m_tracker.CountCandidates(peer) == candidates);
     302                 :        4880 :             total += tracked;
     303                 :             :         }
     304                 :             :         // Compare Size.
     305         [ -  + ]:         305 :         assert(m_tracker.Size() == total);
     306                 :             : 
     307                 :             :         // Invoke internal consistency check of TxRequestTracker object.
     308                 :         305 :         m_tracker.SanityCheck();
     309                 :         305 :     }
     310                 :             : };
     311                 :             : } // namespace
     312                 :             : 
     313         [ +  - ]:         717 : FUZZ_TARGET(txrequest)
     314                 :             : {
     315                 :             :     // Tester object (which encapsulates a TxRequestTracker).
     316                 :         305 :     Tester tester;
     317                 :             : 
     318                 :             :     // Decode the input as a sequence of instructions with parameters
     319                 :         305 :     auto it = buffer.begin();
     320         [ +  + ]:     6989624 :     while (it != buffer.end()) {
     321   [ +  +  +  +  :     6989319 :         int cmd = *(it++) % 11;
          +  +  +  +  +  
                      - ]
     322                 :     6989319 :         int peer, txidnum, delaynum;
     323   [ +  +  +  +  :     6989319 :         switch (cmd) {
          +  +  +  +  +  
                      - ]
     324                 :     1728630 :         case 0: // Make time jump to the next event (m_time of CANDIDATE or REQUESTED)
     325                 :     1728630 :             tester.AdvanceToEvent();
     326                 :             :             break;
     327                 :     1871478 :         case 1: // Change time
     328         [ +  + ]:     1871478 :             delaynum = it == buffer.end() ? 0 : *(it++);
     329                 :     1871478 :             tester.AdvanceTime(DELAYS[delaynum]);
     330                 :             :             break;
     331                 :      367426 :         case 2: // Query for requestable txs
     332         [ +  + ]:      367426 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     333         [ +  - ]:      367426 :             tester.GetRequestable(peer);
     334                 :             :             break;
     335                 :      305117 :         case 3: // Peer went offline
     336         [ +  + ]:      305117 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     337         [ +  - ]:      305117 :             tester.DisconnectedPeer(peer);
     338                 :             :             break;
     339                 :      466891 :         case 4: // No longer need tx
     340         [ +  + ]:      466891 :             txidnum = it == buffer.end() ? 0 : *(it++);
     341         [ +  - ]:      466891 :             tester.ForgetTxHash(txidnum % MAX_TXHASHES);
     342                 :             :             break;
     343                 :      710850 :         case 5: // Received immediate preferred inv
     344                 :      710850 :         case 6: // Same, but non-preferred.
     345         [ +  + ]:      710850 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     346         [ +  + ]:      710850 :             txidnum = it == buffer.end() ? 0 : *(it++);
     347         [ +  - ]:      710850 :             tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1, cmd & 1,
     348                 :             :                 std::chrono::microseconds::min());
     349                 :             :             break;
     350                 :      671299 :         case 7: // Received delayed preferred inv
     351                 :      671299 :         case 8: // Same, but non-preferred.
     352         [ +  + ]:      671299 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     353         [ +  + ]:      671299 :             txidnum = it == buffer.end() ? 0 : *(it++);
     354         [ +  + ]:      671299 :             delaynum = it == buffer.end() ? 0 : *(it++);
     355                 :      671299 :             tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1, cmd & 1,
     356         [ +  - ]:      671299 :                 tester.Now() + DELAYS[delaynum]);
     357                 :             :             break;
     358                 :      640765 :         case 9: // Requested tx from peer
     359         [ +  + ]:      640765 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     360         [ +  + ]:      640765 :             txidnum = it == buffer.end() ? 0 : *(it++);
     361         [ +  + ]:      640765 :             delaynum = it == buffer.end() ? 0 : *(it++);
     362         [ +  - ]:      640765 :             tester.RequestedTx(peer, txidnum % MAX_TXHASHES, tester.Now() + DELAYS[delaynum]);
     363                 :             :             break;
     364                 :      226863 :         case 10: // Received response
     365         [ +  + ]:      226863 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     366         [ +  + ]:      226863 :             txidnum = it == buffer.end() ? 0 : *(it++);
     367         [ +  - ]:      226863 :             tester.ReceivedResponse(peer, txidnum % MAX_TXHASHES);
     368                 :             :             break;
     369                 :           0 :         default:
     370                 :           0 :             assert(false);
     371                 :             :         }
     372                 :             :     }
     373         [ +  - ]:         305 :     tester.Check();
     374                 :         305 : }
        

Generated by: LCOV version 2.0-1