LCOV - code coverage report
Current view: top level - src/test/fuzz - txrequest.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 24 209 11.5 %
Date: 2023-09-26 12:08:55 Functions: 8 24 33.3 %

          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           2 : namespace {
      18           2 : 
      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           2 : //! Precomputed random durations (positive and negative, each ~exponentially distributed).
      26             : std::chrono::microseconds DELAYS[256];
      27             : 
      28             : struct Initializer
      29             : {
      30           2 :     Initializer()
      31             :     {
      32          34 :         for (uint8_t txhash = 0; txhash < MAX_TXHASHES; txhash += 1) {
      33          32 :             CSHA256().Write(&txhash, 1).Finalize(TXHASHES[txhash].begin());
      34          32 :         }
      35           2 :         int i = 0;
      36             :         // DELAYS[N] for N=0..15 is just N microseconds.
      37          34 :         for (; i < 16; ++i) {
      38          32 :             DELAYS[i] = std::chrono::microseconds{i};
      39          32 :         }
      40             :         // DELAYS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to
      41             :         // 198.416453 seconds.
      42         226 :         for (; i < 128; ++i) {
      43         224 :             int diff_bits = ((i - 10) * 2) / 9;
      44         224 :             uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits));
      45         224 :             DELAYS[i] = DELAYS[i - 1] + std::chrono::microseconds{diff};
      46         224 :         }
      47             :         // DELAYS[N] for N=128..255 are negative delays with the same magnitude as N=0..127.
      48         258 :         for (; i < 256; ++i) {
      49         256 :             DELAYS[i] = -DELAYS[255 - i];
      50         256 :         }
      51           2 :     }
      52           2 : } 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             : 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           2 :     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           2 :     //! Sequence numbers, incremented whenever a new CANDIDATE is added.
      84           0 :     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           0 :     struct Announcement
      92             :     {
      93             :         std::chrono::microseconds m_time;
      94             :         uint64_t m_sequence;
      95           0 :         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           0 :     std::chrono::microseconds m_now{244466666};
     106             : 
     107             :     //! Delete txhashes whose only announcements are COMPLETED.
     108           0 :     void Cleanup(int txhash)
     109             :     {
     110           0 :         bool all_nothing = true;
     111           0 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     112           0 :             const Announcement& ann = m_announcements[txhash][peer];
     113           0 :             if (ann.m_state != State::NOTHING) {
     114           0 :                 if (ann.m_state != State::COMPLETED) return;
     115           0 :                 all_nothing = false;
     116           0 :             }
     117           0 :         }
     118           0 :         if (all_nothing) return;
     119           0 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     120           0 :             m_announcements[txhash][peer].m_state = State::NOTHING;
     121           0 :         }
     122           0 :     }
     123             : 
     124             :     //! Find the current best peer to request from for a txhash (or -1 if none).
     125           0 :     int GetSelected(int txhash) const
     126             :     {
     127           0 :         int ret = -1;
     128           0 :         uint64_t ret_priority = 0;
     129           0 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     130           0 :             const Announcement& ann = m_announcements[txhash][peer];
     131             :             // Return -1 if there already is a (non-expired) in-flight request.
     132           0 :             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           0 :             if (ann.m_state == State::CANDIDATE && ann.m_time <= m_now) {
     135           0 :                 if (ret == -1 || ann.m_priority > ret_priority) {
     136           0 :                     std::tie(ret, ret_priority) = std::tie(peer, ann.m_priority);
     137           0 :                 }
     138           0 :             }
     139           0 :         }
     140           0 :         return ret;
     141           0 :     }
     142             : 
     143             : public:
     144           0 :     Tester() : m_tracker(true) {}
     145             : 
     146           0 :     std::chrono::microseconds Now() const { return m_now; }
     147             : 
     148           0 :     void AdvanceTime(std::chrono::microseconds offset)
     149             :     {
     150           0 :         m_now += offset;
     151           0 :         while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
     152           0 :     }
     153             : 
     154           0 :     void AdvanceToEvent()
     155             :     {
     156           0 :         while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
     157           0 :         if (!m_events.empty()) {
     158           0 :             m_now = m_events.top();
     159           0 :             m_events.pop();
     160           0 :         }
     161           0 :     }
     162             : 
     163           0 :     void DisconnectedPeer(int peer)
     164             :     {
     165             :         // Apply to naive structure: all announcements for that peer are wiped.
     166           0 :         for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     167           0 :             if (m_announcements[txhash][peer].m_state != State::NOTHING) {
     168           0 :                 m_announcements[txhash][peer].m_state = State::NOTHING;
     169           0 :                 Cleanup(txhash);
     170           0 :             }
     171           0 :         }
     172             : 
     173             :         // Call TxRequestTracker's implementation.
     174           0 :         m_tracker.DisconnectedPeer(peer);
     175           0 :     }
     176             : 
     177           0 :     void ForgetTxHash(int txhash)
     178             :     {
     179             :         // Apply to naive structure: all announcements for that txhash are wiped.
     180           0 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     181           0 :             m_announcements[txhash][peer].m_state = State::NOTHING;
     182           0 :         }
     183           0 :         Cleanup(txhash);
     184             : 
     185             :         // Call TxRequestTracker's implementation.
     186           0 :         m_tracker.ForgetTxHash(TXHASHES[txhash]);
     187           0 :     }
     188             : 
     189           0 :     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           0 :         Announcement& ann = m_announcements[txhash][peer];
     194           0 :         if (ann.m_state == State::NOTHING) {
     195           0 :             ann.m_preferred = preferred;
     196           0 :             ann.m_state = State::CANDIDATE;
     197           0 :             ann.m_time = reqtime;
     198           0 :             ann.m_is_wtxid = is_wtxid;
     199           0 :             ann.m_sequence = m_current_sequence++;
     200           0 :             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           0 :             if (reqtime > m_now) m_events.push(reqtime);
     204           0 :         }
     205             : 
     206             :         // Call TxRequestTracker's implementation.
     207           0 :         m_tracker.ReceivedInv(peer, is_wtxid ? GenTxid::Wtxid(TXHASHES[txhash]) : GenTxid::Txid(TXHASHES[txhash]), preferred, reqtime);
     208           0 :     }
     209             : 
     210           0 :     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           0 :         if (m_announcements[txhash][peer].m_state == State::CANDIDATE) {
     215           0 :             for (int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
     216           0 :                 if (m_announcements[txhash][peer2].m_state == State::REQUESTED) {
     217           0 :                     m_announcements[txhash][peer2].m_state = State::COMPLETED;
     218           0 :                 }
     219           0 :             }
     220           0 :             m_announcements[txhash][peer].m_state = State::REQUESTED;
     221           0 :             m_announcements[txhash][peer].m_time = exptime;
     222           0 :         }
     223             : 
     224             :         // Add event so that AdvanceToEvent can quickly jump to the point where its exptime passes.
     225           0 :         if (exptime > m_now) m_events.push(exptime);
     226             : 
     227             :         // Call TxRequestTracker's implementation.
     228           0 :         m_tracker.RequestedTx(peer, TXHASHES[txhash], exptime);
     229           0 :     }
     230             : 
     231           0 :     void ReceivedResponse(int peer, int txhash)
     232             :     {
     233             :         // Apply to naive structure: convert anything to COMPLETED.
     234           0 :         if (m_announcements[txhash][peer].m_state != State::NOTHING) {
     235           0 :             m_announcements[txhash][peer].m_state = State::COMPLETED;
     236           0 :             Cleanup(txhash);
     237           0 :         }
     238             : 
     239             :         // Call TxRequestTracker's implementation.
     240           0 :         m_tracker.ReceivedResponse(peer, TXHASHES[txhash]);
     241           0 :     }
     242             : 
     243           0 :     void GetRequestable(int peer)
     244             :     {
     245             :         // Implement using naive structure:
     246             : 
     247             :         //! list of (sequence number, txhash, is_wtxid) tuples.
     248           0 :         std::vector<std::tuple<uint64_t, int, bool>> result;
     249           0 :         std::vector<std::pair<NodeId, GenTxid>> expected_expired;
     250           0 :         for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     251             :             // Mark any expired REQUESTED announcements as COMPLETED.
     252           0 :             for (int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
     253           0 :                 Announcement& ann2 = m_announcements[txhash][peer2];
     254           0 :                 if (ann2.m_state == State::REQUESTED && ann2.m_time <= m_now) {
     255           0 :                     expected_expired.emplace_back(peer2, ann2.m_is_wtxid ? GenTxid::Wtxid(TXHASHES[txhash]) : GenTxid::Txid(TXHASHES[txhash]));
     256           0 :                     ann2.m_state = State::COMPLETED;
     257           0 :                     break;
     258             :                 }
     259           0 :             }
     260             :             // And delete txids with only COMPLETED announcements left.
     261           0 :             Cleanup(txhash);
     262             :             // CANDIDATEs for which this announcement has the highest priority get returned.
     263           0 :             const Announcement& ann = m_announcements[txhash][peer];
     264           0 :             if (ann.m_state == State::CANDIDATE && GetSelected(txhash) == peer) {
     265           0 :                 result.emplace_back(ann.m_sequence, txhash, ann.m_is_wtxid);
     266           0 :             }
     267           0 :         }
     268             :         // Sort the results by sequence number.
     269           0 :         std::sort(result.begin(), result.end());
     270           0 :         std::sort(expected_expired.begin(), expected_expired.end());
     271             : 
     272             :         // Compare with TxRequestTracker's implementation.
     273           0 :         std::vector<std::pair<NodeId, GenTxid>> expired;
     274           0 :         const auto actual = m_tracker.GetRequestable(peer, m_now, &expired);
     275           0 :         std::sort(expired.begin(), expired.end());
     276           0 :         assert(expired == expected_expired);
     277             : 
     278           0 :         m_tracker.PostGetRequestableSanityCheck(m_now);
     279           0 :         assert(result.size() == actual.size());
     280           0 :         for (size_t pos = 0; pos < actual.size(); ++pos) {
     281           0 :             assert(TXHASHES[std::get<1>(result[pos])] == actual[pos].GetHash());
     282           0 :             assert(std::get<2>(result[pos]) == actual[pos].IsWtxid());
     283           0 :         }
     284           0 :     }
     285             : 
     286           0 :     void Check()
     287             :     {
     288             :         // Compare CountTracked and CountLoad with naive structure.
     289           0 :         size_t total = 0;
     290           0 :         for (int peer = 0; peer < MAX_PEERS; ++peer) {
     291           0 :             size_t tracked = 0;
     292           0 :             size_t inflight = 0;
     293           0 :             size_t candidates = 0;
     294           0 :             for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
     295           0 :                 tracked += m_announcements[txhash][peer].m_state != State::NOTHING;
     296           0 :                 inflight += m_announcements[txhash][peer].m_state == State::REQUESTED;
     297           0 :                 candidates += m_announcements[txhash][peer].m_state == State::CANDIDATE;
     298           0 :             }
     299           0 :             assert(m_tracker.Count(peer) == tracked);
     300           0 :             assert(m_tracker.CountInFlight(peer) == inflight);
     301           0 :             assert(m_tracker.CountCandidates(peer) == candidates);
     302           0 :             total += tracked;
     303           0 :         }
     304             :         // Compare Size.
     305           0 :         assert(m_tracker.Size() == total);
     306             : 
     307             :         // Invoke internal consistency check of TxRequestTracker object.
     308           0 :         m_tracker.SanityCheck();
     309           0 :     }
     310             : };
     311             : } // namespace
     312             : 
     313           4 : FUZZ_TARGET(txrequest)
     314             : {
     315             :     // Tester object (which encapsulates a TxRequestTracker).
     316           0 :     Tester tester;
     317             : 
     318             :     // Decode the input as a sequence of instructions with parameters
     319           0 :     auto it = buffer.begin();
     320           0 :     while (it != buffer.end()) {
     321           0 :         int cmd = *(it++) % 11;
     322             :         int peer, txidnum, delaynum;
     323           0 :         switch (cmd) {
     324             :         case 0: // Make time jump to the next event (m_time of CANDIDATE or REQUESTED)
     325           0 :             tester.AdvanceToEvent();
     326           0 :             break;
     327             :         case 1: // Change time
     328           0 :             delaynum = it == buffer.end() ? 0 : *(it++);
     329           0 :             tester.AdvanceTime(DELAYS[delaynum]);
     330           0 :             break;
     331             :         case 2: // Query for requestable txs
     332           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     333           0 :             tester.GetRequestable(peer);
     334           0 :             break;
     335             :         case 3: // Peer went offline
     336           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     337           0 :             tester.DisconnectedPeer(peer);
     338           0 :             break;
     339             :         case 4: // No longer need tx
     340           0 :             txidnum = it == buffer.end() ? 0 : *(it++);
     341           0 :             tester.ForgetTxHash(txidnum % MAX_TXHASHES);
     342           0 :             break;
     343             :         case 5: // Received immediate preferred inv
     344             :         case 6: // Same, but non-preferred.
     345           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     346           0 :             txidnum = it == buffer.end() ? 0 : *(it++);
     347           0 :             tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1, cmd & 1,
     348           0 :                 std::chrono::microseconds::min());
     349           0 :             break;
     350             :         case 7: // Received delayed preferred inv
     351             :         case 8: // Same, but non-preferred.
     352           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     353           0 :             txidnum = it == buffer.end() ? 0 : *(it++);
     354           0 :             delaynum = it == buffer.end() ? 0 : *(it++);
     355           0 :             tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1, cmd & 1,
     356           0 :                 tester.Now() + DELAYS[delaynum]);
     357           0 :             break;
     358             :         case 9: // Requested tx from peer
     359           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     360           0 :             txidnum = it == buffer.end() ? 0 : *(it++);
     361           0 :             delaynum = it == buffer.end() ? 0 : *(it++);
     362           0 :             tester.RequestedTx(peer, txidnum % MAX_TXHASHES, tester.Now() + DELAYS[delaynum]);
     363           0 :             break;
     364             :         case 10: // Received response
     365           0 :             peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
     366           0 :             txidnum = it == buffer.end() ? 0 : *(it++);
     367           0 :             tester.ReceivedResponse(peer, txidnum % MAX_TXHASHES);
     368           0 :             break;
     369             :         default:
     370           0 :             assert(false);
     371             :         }
     372             :     }
     373           0 :     tester.Check();
     374           0 : }

Generated by: LCOV version 1.14