Branch data Line data Source code
# 1 : : // Copyright (c) 2020 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 <txrequest.h>
# 6 : :
# 7 : : #include <crypto/siphash.h>
# 8 : : #include <net.h>
# 9 : : #include <primitives/transaction.h>
# 10 : : #include <random.h>
# 11 : : #include <uint256.h>
# 12 : : #include <util/memory.h>
# 13 : :
# 14 : : #include <boost/multi_index_container.hpp>
# 15 : : #include <boost/multi_index/ordered_index.hpp>
# 16 : :
# 17 : : #include <chrono>
# 18 : : #include <unordered_map>
# 19 : : #include <utility>
# 20 : :
# 21 : : #include <assert.h>
# 22 : :
# 23 : : namespace {
# 24 : :
# 25 : : /** The various states a (txhash,peer) pair can be in.
# 26 : : *
# 27 : : * Note that CANDIDATE is split up into 3 substates (DELAYED, BEST, READY), allowing more efficient implementation.
# 28 : : * Also note that the sorting order of ByTxHashView relies on the specific order of values in this enum.
# 29 : : *
# 30 : : * Expected behaviour is:
# 31 : : * - When first announced by a peer, the state is CANDIDATE_DELAYED until reqtime is reached.
# 32 : : * - Announcements that have reached their reqtime but not been requested will be either CANDIDATE_READY or
# 33 : : * CANDIDATE_BEST
# 34 : : * - When requested, an announcement will be in state REQUESTED until expiry is reached.
# 35 : : * - If expiry is reached, or the peer replies to the request (either with NOTFOUND or the tx), the state becomes
# 36 : : * COMPLETED
# 37 : : */
# 38 : : enum class State : uint8_t {
# 39 : : /** A CANDIDATE announcement whose reqtime is in the future. */
# 40 : : CANDIDATE_DELAYED,
# 41 : : /** A CANDIDATE announcement that's not CANDIDATE_DELAYED or CANDIDATE_BEST. */
# 42 : : CANDIDATE_READY,
# 43 : : /** The best CANDIDATE for a given txhash; only if there is no REQUESTED announcement already for that txhash.
# 44 : : * The CANDIDATE_BEST is the highest-priority announcement among all CANDIDATE_READY (and _BEST) ones for that
# 45 : : * txhash. */
# 46 : : CANDIDATE_BEST,
# 47 : : /** A REQUESTED announcement. */
# 48 : : REQUESTED,
# 49 : : /** A COMPLETED announcement. */
# 50 : : COMPLETED,
# 51 : : };
# 52 : :
# 53 : : //! Type alias for sequence numbers.
# 54 : : using SequenceNumber = uint64_t;
# 55 : :
# 56 : : /** An announcement. This is the data we track for each txid or wtxid that is announced to us by each peer. */
# 57 : : struct Announcement {
# 58 : : /** Txid or wtxid that was announced. */
# 59 : : const uint256 m_txhash;
# 60 : : /** For CANDIDATE_{DELAYED,BEST,READY} the reqtime; for REQUESTED the expiry. */
# 61 : : std::chrono::microseconds m_time;
# 62 : : /** What peer the request was from. */
# 63 : : const NodeId m_peer;
# 64 : : /** What sequence number this announcement has. */
# 65 : : const SequenceNumber m_sequence : 59;
# 66 : : /** Whether the request is preferred. */
# 67 : : const bool m_preferred : 1;
# 68 : : /** Whether this is a wtxid request. */
# 69 : : const bool m_is_wtxid : 1;
# 70 : :
# 71 : : /** What state this announcement is in. */
# 72 : : State m_state : 3;
# 73 : :
# 74 : : /** Whether this announcement is selected. There can be at most 1 selected peer per txhash. */
# 75 : : bool IsSelected() const
# 76 : 4305 : {
# 77 : 4305 : return m_state == State::CANDIDATE_BEST || m_state == State::REQUESTED;
# 78 : 4305 : }
# 79 : :
# 80 : : /** Whether this announcement is waiting for a certain time to pass. */
# 81 : : bool IsWaiting() const
# 82 : 2128533 : {
# 83 : 2128533 : return m_state == State::REQUESTED || m_state == State::CANDIDATE_DELAYED;
# 84 : 2128533 : }
# 85 : :
# 86 : : /** Whether this announcement can feasibly be selected if the current IsSelected() one disappears. */
# 87 : : bool IsSelectable() const
# 88 : 1300803 : {
# 89 : 1300803 : return m_state == State::CANDIDATE_READY || m_state == State::CANDIDATE_BEST;
# 90 : 1300803 : }
# 91 : :
# 92 : : /** Construct a new announcement from scratch, initially in CANDIDATE_DELAYED state. */
# 93 : : Announcement(const GenTxid& gtxid, NodeId peer, bool preferred, std::chrono::microseconds reqtime,
# 94 : : SequenceNumber sequence) :
# 95 : : m_txhash(gtxid.GetHash()), m_time(reqtime), m_peer(peer), m_sequence(sequence), m_preferred(preferred),
# 96 : 15435 : m_is_wtxid(gtxid.IsWtxid()), m_state(State::CANDIDATE_DELAYED) {}
# 97 : : };
# 98 : :
# 99 : : //! Type alias for priorities.
# 100 : : using Priority = uint64_t;
# 101 : :
# 102 : : /** A functor with embedded salt that computes priority of an announcement.
# 103 : : *
# 104 : : * Higher priorities are selected first.
# 105 : : */
# 106 : : class PriorityComputer {
# 107 : : const uint64_t m_k0, m_k1;
# 108 : : public:
# 109 : : explicit PriorityComputer(bool deterministic) :
# 110 : : m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)},
# 111 : 690 : m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {}
# 112 : :
# 113 : : Priority operator()(const uint256& txhash, NodeId peer, bool preferred) const
# 114 : 4793871 : {
# 115 : 4793871 : uint64_t low_bits = CSipHasher(m_k0, m_k1).Write(txhash.begin(), txhash.size()).Write(peer).Finalize() >> 1;
# 116 : 4793871 : return low_bits | uint64_t{preferred} << 63;
# 117 : 4793871 : }
# 118 : :
# 119 : : Priority operator()(const Announcement& ann) const
# 120 : 1335563 : {
# 121 : 1335563 : return operator()(ann.m_txhash, ann.m_peer, ann.m_preferred);
# 122 : 1335563 : }
# 123 : : };
# 124 : :
# 125 : : // Definitions for the 3 indexes used in the main data structure.
# 126 : : //
# 127 : : // Each index has a By* type to identify it, a By*View data type to represent the view of announcement it is sorted
# 128 : : // by, and an By*ViewExtractor type to convert an announcement into the By*View type.
# 129 : : // See https://www.boost.org/doc/libs/1_54_0/libs/multi_index/doc/reference/key_extraction.html#key_extractors
# 130 : : // for more information about the key extraction concept.
# 131 : :
# 132 : : // The ByPeer index is sorted by (peer, state == CANDIDATE_BEST, txhash)
# 133 : : //
# 134 : : // Uses:
# 135 : : // * Looking up existing announcements by peer/txhash, by checking both (peer, false, txhash) and
# 136 : : // (peer, true, txhash).
# 137 : : // * Finding all CANDIDATE_BEST announcements for a given peer in GetRequestable.
# 138 : : struct ByPeer {};
# 139 : : using ByPeerView = std::tuple<NodeId, bool, const uint256&>;
# 140 : : struct ByPeerViewExtractor
# 141 : : {
# 142 : : using result_type = ByPeerView;
# 143 : : result_type operator()(const Announcement& ann) const
# 144 : 882574 : {
# 145 : 882574 : return ByPeerView{ann.m_peer, ann.m_state == State::CANDIDATE_BEST, ann.m_txhash};
# 146 : 882574 : }
# 147 : : };
# 148 : :
# 149 : : // The ByTxHash index is sorted by (txhash, state, priority).
# 150 : : //
# 151 : : // Note: priority == 0 whenever state != CANDIDATE_READY.
# 152 : : //
# 153 : : // Uses:
# 154 : : // * Deleting all announcements with a given txhash in ForgetTxHash.
# 155 : : // * Finding the best CANDIDATE_READY to convert to CANDIDATE_BEST, when no other CANDIDATE_READY or REQUESTED
# 156 : : // announcement exists for that txhash.
# 157 : : // * Determining when no more non-COMPLETED announcements for a given txhash exist, so the COMPLETED ones can be
# 158 : : // deleted.
# 159 : : struct ByTxHash {};
# 160 : : using ByTxHashView = std::tuple<const uint256&, State, Priority>;
# 161 : : class ByTxHashViewExtractor {
# 162 : : const PriorityComputer& m_computer;
# 163 : : public:
# 164 : 690 : ByTxHashViewExtractor(const PriorityComputer& computer) : m_computer(computer) {}
# 165 : : using result_type = ByTxHashView;
# 166 : : result_type operator()(const Announcement& ann) const
# 167 : 375065 : {
# 168 : 294698 : const Priority prio = (ann.m_state == State::CANDIDATE_READY) ? m_computer(ann) : 0;
# 169 : 375065 : return ByTxHashView{ann.m_txhash, ann.m_state, prio};
# 170 : 375065 : }
# 171 : : };
# 172 : :
# 173 : : enum class WaitState {
# 174 : : //! Used for announcements that need efficient testing of "is their timestamp in the future?".
# 175 : : FUTURE_EVENT,
# 176 : : //! Used for announcements whose timestamp is not relevant.
# 177 : : NO_EVENT,
# 178 : : //! Used for announcements that need efficient testing of "is their timestamp in the past?".
# 179 : : PAST_EVENT,
# 180 : : };
# 181 : :
# 182 : : WaitState GetWaitState(const Announcement& ann)
# 183 : 364673 : {
# 184 : 364673 : if (ann.IsWaiting()) return WaitState::FUTURE_EVENT;
# 185 : 189638 : if (ann.IsSelectable()) return WaitState::PAST_EVENT;
# 186 : 21882 : return WaitState::NO_EVENT;
# 187 : 21882 : }
# 188 : :
# 189 : : // The ByTime index is sorted by (wait_state, time).
# 190 : : //
# 191 : : // All announcements with a timestamp in the future can be found by iterating the index forward from the beginning.
# 192 : : // All announcements with a timestamp in the past can be found by iterating the index backwards from the end.
# 193 : : //
# 194 : : // Uses:
# 195 : : // * Finding CANDIDATE_DELAYED announcements whose reqtime has passed, and REQUESTED announcements whose expiry has
# 196 : : // passed.
# 197 : : // * Finding CANDIDATE_READY/BEST announcements whose reqtime is in the future (when the clock time went backwards).
# 198 : : struct ByTime {};
# 199 : : using ByTimeView = std::pair<WaitState, std::chrono::microseconds>;
# 200 : : struct ByTimeViewExtractor
# 201 : : {
# 202 : : using result_type = ByTimeView;
# 203 : : result_type operator()(const Announcement& ann) const
# 204 : 364673 : {
# 205 : 364673 : return ByTimeView{GetWaitState(ann), ann.m_time};
# 206 : 364673 : }
# 207 : : };
# 208 : :
# 209 : : /** Data type for the main data structure (Announcement objects with ByPeer/ByTxHash/ByTime indexes). */
# 210 : : using Index = boost::multi_index_container<
# 211 : : Announcement,
# 212 : : boost::multi_index::indexed_by<
# 213 : : boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor>,
# 214 : : boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByTxHash>, ByTxHashViewExtractor>,
# 215 : : boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByTime>, ByTimeViewExtractor>
# 216 : : >
# 217 : : >;
# 218 : :
# 219 : : /** Helper type to simplify syntax of iterator types. */
# 220 : : template<typename Tag>
# 221 : : using Iter = typename Index::index<Tag>::type::iterator;
# 222 : :
# 223 : : /** Per-peer statistics object. */
# 224 : : struct PeerInfo {
# 225 : : size_t m_total = 0; //!< Total number of announcements for this peer.
# 226 : : size_t m_completed = 0; //!< Number of COMPLETED announcements for this peer.
# 227 : : size_t m_requested = 0; //!< Number of REQUESTED announcements for this peer.
# 228 : : };
# 229 : :
# 230 : : /** Per-txhash statistics object. Only used for sanity checking. */
# 231 : : struct TxHashInfo
# 232 : : {
# 233 : : //! Number of CANDIDATE_DELAYED announcements for this txhash.
# 234 : : size_t m_candidate_delayed = 0;
# 235 : : //! Number of CANDIDATE_READY announcements for this txhash.
# 236 : : size_t m_candidate_ready = 0;
# 237 : : //! Number of CANDIDATE_BEST announcements for this txhash (at most one).
# 238 : : size_t m_candidate_best = 0;
# 239 : : //! Number of REQUESTED announcements for this txhash.
# 240 : : size_t m_requested = 0;
# 241 : : //! The priority of the CANDIDATE_BEST announcement if one exists, or max() otherwise.
# 242 : : Priority m_priority_candidate_best = std::numeric_limits<Priority>::max();
# 243 : : //! The lowest priority of all CANDIDATE_READY announcements (or min() if none exist).
# 244 : : Priority m_priority_best_candidate_ready = std::numeric_limits<Priority>::min();
# 245 : : //! All peers we have an announcement for this txhash for.
# 246 : : std::vector<NodeId> m_peers;
# 247 : : };
# 248 : :
# 249 : : /** Compare two PeerInfo objects. Only used for sanity checking. */
# 250 : : bool operator==(const PeerInfo& a, const PeerInfo& b)
# 251 : 2409486 : {
# 252 : 2409486 : return std::tie(a.m_total, a.m_completed, a.m_requested) ==
# 253 : 2409486 : std::tie(b.m_total, b.m_completed, b.m_requested);
# 254 : 2409486 : };
# 255 : :
# 256 : : /** (Re)compute the PeerInfo map from the index. Only used for sanity checking. */
# 257 : : std::unordered_map<NodeId, PeerInfo> RecomputePeerInfo(const Index& index)
# 258 : 40888 : {
# 259 : 40888 : std::unordered_map<NodeId, PeerInfo> ret;
# 260 : 2415454 : for (const Announcement& ann : index) {
# 261 : 2415454 : PeerInfo& info = ret[ann.m_peer];
# 262 : 2415454 : ++info.m_total;
# 263 : 2415454 : info.m_requested += (ann.m_state == State::REQUESTED);
# 264 : 2415454 : info.m_completed += (ann.m_state == State::COMPLETED);
# 265 : 2415454 : }
# 266 : 40888 : return ret;
# 267 : 40888 : }
# 268 : :
# 269 : : /** Compute the TxHashInfo map. Only used for sanity checking. */
# 270 : : std::map<uint256, TxHashInfo> ComputeTxHashInfo(const Index& index, const PriorityComputer& computer)
# 271 : 40888 : {
# 272 : 40888 : std::map<uint256, TxHashInfo> ret;
# 273 : 2415454 : for (const Announcement& ann : index) {
# 274 : 2415454 : TxHashInfo& info = ret[ann.m_txhash];
# 275 : : // Classify how many announcements of each state we have for this txhash.
# 276 : 2415454 : info.m_candidate_delayed += (ann.m_state == State::CANDIDATE_DELAYED);
# 277 : 2415454 : info.m_candidate_ready += (ann.m_state == State::CANDIDATE_READY);
# 278 : 2415454 : info.m_candidate_best += (ann.m_state == State::CANDIDATE_BEST);
# 279 : 2415454 : info.m_requested += (ann.m_state == State::REQUESTED);
# 280 : : // And track the priority of the best CANDIDATE_READY/CANDIDATE_BEST announcements.
# 281 : 2415454 : if (ann.m_state == State::CANDIDATE_BEST) {
# 282 : 477822 : info.m_priority_candidate_best = computer(ann);
# 283 : 477822 : }
# 284 : 2415454 : if (ann.m_state == State::CANDIDATE_READY) {
# 285 : 770404 : info.m_priority_best_candidate_ready = std::max(info.m_priority_best_candidate_ready, computer(ann));
# 286 : 770404 : }
# 287 : : // Also keep track of which peers this txhash has an announcement for (so we can detect duplicates).
# 288 : 2415454 : info.m_peers.push_back(ann.m_peer);
# 289 : 2415454 : }
# 290 : 40888 : return ret;
# 291 : 40888 : }
# 292 : :
# 293 : : } // namespace
# 294 : :
# 295 : : /** Actual implementation for TxRequestTracker's data structure. */
# 296 : : class TxRequestTracker::Impl {
# 297 : : //! The current sequence number. Increases for every announcement. This is used to sort txhashes returned by
# 298 : : //! GetRequestable in announcement order.
# 299 : : SequenceNumber m_current_sequence{0};
# 300 : :
# 301 : : //! This tracker's priority computer.
# 302 : : const PriorityComputer m_computer;
# 303 : :
# 304 : : //! This tracker's main data structure. See SanityCheck() for the invariants that apply to it.
# 305 : : Index m_index;
# 306 : :
# 307 : : //! Map with this tracker's per-peer statistics.
# 308 : : std::unordered_map<NodeId, PeerInfo> m_peerinfo;
# 309 : :
# 310 : : public:
# 311 : : void SanityCheck() const
# 312 : 40888 : {
# 313 : : // Recompute m_peerdata from m_index. This verifies the data in it as it should just be caching statistics
# 314 : : // on m_index. It also verifies the invariant that no PeerInfo announcements with m_total==0 exist.
# 315 : 40888 : assert(m_peerinfo == RecomputePeerInfo(m_index));
# 316 : :
# 317 : : // Calculate per-txhash statistics from m_index, and validate invariants.
# 318 : 599410 : for (auto& item : ComputeTxHashInfo(m_index, m_computer)) {
# 319 : 599410 : TxHashInfo& info = item.second;
# 320 : :
# 321 : : // Cannot have only COMPLETED peer (txhash should have been forgotten already)
# 322 : 599410 : assert(info.m_candidate_delayed + info.m_candidate_ready + info.m_candidate_best + info.m_requested > 0);
# 323 : :
# 324 : : // Can have at most 1 CANDIDATE_BEST/REQUESTED peer
# 325 : 599410 : assert(info.m_candidate_best + info.m_requested <= 1);
# 326 : :
# 327 : : // If there are any CANDIDATE_READY announcements, there must be exactly one CANDIDATE_BEST or REQUESTED
# 328 : : // announcement.
# 329 : 599410 : if (info.m_candidate_ready > 0) {
# 330 : 304574 : assert(info.m_candidate_best + info.m_requested == 1);
# 331 : 304574 : }
# 332 : :
# 333 : : // If there is both a CANDIDATE_READY and a CANDIDATE_BEST announcement, the CANDIDATE_BEST one must be
# 334 : : // at least as good (equal or lower priority) as the best CANDIDATE_READY.
# 335 : 599410 : if (info.m_candidate_ready && info.m_candidate_best) {
# 336 : 287486 : assert(info.m_priority_candidate_best >= info.m_priority_best_candidate_ready);
# 337 : 287486 : }
# 338 : :
# 339 : : // No txhash can have been announced by the same peer twice.
# 340 : 599410 : std::sort(info.m_peers.begin(), info.m_peers.end());
# 341 : 599410 : assert(std::adjacent_find(info.m_peers.begin(), info.m_peers.end()) == info.m_peers.end());
# 342 : 599410 : }
# 343 : 40888 : }
# 344 : :
# 345 : : void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
# 346 : 29728 : {
# 347 : 1763860 : for (const Announcement& ann : m_index) {
# 348 : 1763860 : if (ann.IsWaiting()) {
# 349 : : // REQUESTED and CANDIDATE_DELAYED must have a time in the future (they should have been converted
# 350 : : // to COMPLETED/CANDIDATE_READY respectively).
# 351 : 718822 : assert(ann.m_time > now);
# 352 : 1045038 : } else if (ann.IsSelectable()) {
# 353 : : // CANDIDATE_READY and CANDIDATE_BEST cannot have a time in the future (they should have remained
# 354 : : // CANDIDATE_DELAYED, or should have been converted back to it if time went backwards).
# 355 : 915570 : assert(ann.m_time <= now);
# 356 : 915570 : }
# 357 : 1763860 : }
# 358 : 29728 : }
# 359 : :
# 360 : : private:
# 361 : : //! Wrapper around Index::...::erase that keeps m_peerinfo up to date.
# 362 : : template<typename Tag>
# 363 : : Iter<Tag> Erase(Iter<Tag> it)
# 364 : 15435 : {
# 365 : 15435 : auto peerit = m_peerinfo.find(it->m_peer);
# 366 : 15435 : peerit->second.m_completed -= it->m_state == State::COMPLETED;
# 367 : 15435 : peerit->second.m_requested -= it->m_state == State::REQUESTED;
# 368 : 15435 : if (--peerit->second.m_total == 0) m_peerinfo.erase(peerit);
# 369 : 15435 : return m_index.get<Tag>().erase(it);
# 370 : 15435 : }
# 371 : :
# 372 : : //! Wrapper around Index::...::modify that keeps m_peerinfo up to date.
# 373 : : template<typename Tag, typename Modifier>
# 374 : : void Modify(Iter<Tag> it, Modifier modifier)
# 375 : 49042 : {
# 376 : 49042 : auto peerit = m_peerinfo.find(it->m_peer);
# 377 : 49042 : peerit->second.m_completed -= it->m_state == State::COMPLETED;
# 378 : 49042 : peerit->second.m_requested -= it->m_state == State::REQUESTED;
# 379 : 49042 : m_index.get<Tag>().modify(it, std::move(modifier));
# 380 : 49042 : peerit->second.m_completed += it->m_state == State::COMPLETED;
# 381 : 49042 : peerit->second.m_requested += it->m_state == State::REQUESTED;
# 382 : 49042 : }
# 383 : :
# 384 : : //! Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY. If this makes it the new best
# 385 : : //! CANDIDATE_READY (and no REQUESTED exists) and better than the CANDIDATE_BEST (if any), it becomes the new
# 386 : : //! CANDIDATE_BEST.
# 387 : : void PromoteCandidateReady(Iter<ByTxHash> it)
# 388 : 15319 : {
# 389 : 15319 : assert(it != m_index.get<ByTxHash>().end());
# 390 : 15319 : assert(it->m_state == State::CANDIDATE_DELAYED);
# 391 : : // Convert CANDIDATE_DELAYED to CANDIDATE_READY first.
# 392 : 15319 : Modify<ByTxHash>(it, [](Announcement& ann){ ann.m_state = State::CANDIDATE_READY; });
# 393 : : // The following code relies on the fact that the ByTxHash is sorted by txhash, and then by state (first
# 394 : : // _DELAYED, then _READY, then _BEST/REQUESTED). Within the _READY announcements, the best one (highest
# 395 : : // priority) comes last. Thus, if an existing _BEST exists for the same txhash that this announcement may
# 396 : : // be preferred over, it must immediately follow the newly created _READY.
# 397 : 15319 : auto it_next = std::next(it);
# 398 : 15319 : if (it_next == m_index.get<ByTxHash>().end() || it_next->m_txhash != it->m_txhash ||
# 399 : 11019 : it_next->m_state == State::COMPLETED) {
# 400 : : // This is the new best CANDIDATE_READY, and there is no IsSelected() announcement for this txhash
# 401 : : // already.
# 402 : 11019 : Modify<ByTxHash>(it, [](Announcement& ann){ ann.m_state = State::CANDIDATE_BEST; });
# 403 : 4300 : } else if (it_next->m_state == State::CANDIDATE_BEST) {
# 404 : 3485 : Priority priority_old = m_computer(*it_next);
# 405 : 3485 : Priority priority_new = m_computer(*it);
# 406 : 3485 : if (priority_new > priority_old) {
# 407 : : // There is a CANDIDATE_BEST announcement already, but this one is better.
# 408 : 3149 : Modify<ByTxHash>(it_next, [](Announcement& ann){ ann.m_state = State::CANDIDATE_READY; });
# 409 : 3149 : Modify<ByTxHash>(it, [](Announcement& ann){ ann.m_state = State::CANDIDATE_BEST; });
# 410 : 3149 : }
# 411 : 3485 : }
# 412 : 15319 : }
# 413 : :
# 414 : : //! Change the state of an announcement to something non-IsSelected(). If it was IsSelected(), the next best
# 415 : : //! announcement will be marked CANDIDATE_BEST.
# 416 : : void ChangeAndReselect(Iter<ByTxHash> it, State new_state)
# 417 : 4305 : {
# 418 : 4305 : assert(new_state == State::COMPLETED || new_state == State::CANDIDATE_DELAYED);
# 419 : 4305 : assert(it != m_index.get<ByTxHash>().end());
# 420 : 4305 : if (it->IsSelected() && it != m_index.get<ByTxHash>().begin()) {
# 421 : 2357 : auto it_prev = std::prev(it);
# 422 : : // The next best CANDIDATE_READY, if any, immediately preceeds the REQUESTED or CANDIDATE_BEST
# 423 : : // announcement in the ByTxHash index.
# 424 : 2357 : if (it_prev->m_txhash == it->m_txhash && it_prev->m_state == State::CANDIDATE_READY) {
# 425 : : // If one such CANDIDATE_READY exists (for this txhash), convert it to CANDIDATE_BEST.
# 426 : 2352 : Modify<ByTxHash>(it_prev, [](Announcement& ann){ ann.m_state = State::CANDIDATE_BEST; });
# 427 : 2352 : }
# 428 : 2357 : }
# 429 : 4305 : Modify<ByTxHash>(it, [new_state](Announcement& ann){ ann.m_state = new_state; });
# 430 : 4305 : }
# 431 : :
# 432 : : //! Check if 'it' is the only announcement for a given txhash that isn't COMPLETED.
# 433 : : bool IsOnlyNonCompleted(Iter<ByTxHash> it)
# 434 : 14504 : {
# 435 : 14504 : assert(it != m_index.get<ByTxHash>().end());
# 436 : 14504 : assert(it->m_state != State::COMPLETED); // Not allowed to call this on COMPLETED announcements.
# 437 : :
# 438 : : // This announcement has a predecessor that belongs to the same txhash. Due to ordering, and the
# 439 : : // fact that 'it' is not COMPLETED, its predecessor cannot be COMPLETED here.
# 440 : 14504 : if (it != m_index.get<ByTxHash>().begin() && std::prev(it)->m_txhash == it->m_txhash) return false;
# 441 : :
# 442 : : // This announcement has predecessor that belongs to the same txhash, and is not COMPLETED.
# 443 : 11031 : if (std::next(it) != m_index.get<ByTxHash>().end() && std::next(it)->m_txhash == it->m_txhash &&
# 444 : 1772 : std::next(it)->m_state != State::COMPLETED) return false;
# 445 : :
# 446 : 10199 : return true;
# 447 : 10199 : }
# 448 : :
# 449 : : /** Convert any announcement to a COMPLETED one. If there are no non-COMPLETED announcements left for this
# 450 : : * txhash, they are deleted. If this was a REQUESTED announcement, and there are other CANDIDATEs left, the
# 451 : : * best one is made CANDIDATE_BEST. Returns whether the announcement still exists. */
# 452 : : bool MakeCompleted(Iter<ByTxHash> it)
# 453 : 14510 : {
# 454 : 14510 : assert(it != m_index.get<ByTxHash>().end());
# 455 : :
# 456 : : // Nothing to be done if it's already COMPLETED.
# 457 : 14510 : if (it->m_state == State::COMPLETED) return true;
# 458 : :
# 459 : 14504 : if (IsOnlyNonCompleted(it)) {
# 460 : : // This is the last non-COMPLETED announcement for this txhash. Delete all.
# 461 : 10199 : uint256 txhash = it->m_txhash;
# 462 : 11967 : do {
# 463 : 11967 : it = Erase<ByTxHash>(it);
# 464 : 11967 : } while (it != m_index.get<ByTxHash>().end() && it->m_txhash == txhash);
# 465 : 10199 : return false;
# 466 : 10199 : }
# 467 : :
# 468 : : // Mark the announcement COMPLETED, and select the next best announcement (the first CANDIDATE_READY) if
# 469 : : // needed.
# 470 : 4305 : ChangeAndReselect(it, State::COMPLETED);
# 471 : :
# 472 : 4305 : return true;
# 473 : 4305 : }
# 474 : :
# 475 : : //! Make the data structure consistent with a given point in time:
# 476 : : //! - REQUESTED annoucements with expiry <= now are turned into COMPLETED.
# 477 : : //! - CANDIDATE_DELAYED announcements with reqtime <= now are turned into CANDIDATE_{READY,BEST}.
# 478 : : //! - CANDIDATE_{READY,BEST} announcements with reqtime > now are turned into CANDIDATE_DELAYED.
# 479 : : void SetTimePoint(std::chrono::microseconds now)
# 480 : 253765 : {
# 481 : : // Iterate over all CANDIDATE_DELAYED and REQUESTED from old to new, as long as they're in the past,
# 482 : : // and convert them to CANDIDATE_READY and COMPLETED respectively.
# 483 : 269241 : while (!m_index.empty()) {
# 484 : 81603 : auto it = m_index.get<ByTime>().begin();
# 485 : 81603 : if (it->m_state == State::CANDIDATE_DELAYED && it->m_time <= now) {
# 486 : 15319 : PromoteCandidateReady(m_index.project<ByTxHash>(it));
# 487 : 66284 : } else if (it->m_state == State::REQUESTED && it->m_time <= now) {
# 488 : 157 : MakeCompleted(m_index.project<ByTxHash>(it));
# 489 : 66127 : } else {
# 490 : 66127 : break;
# 491 : 66127 : }
# 492 : 81603 : }
# 493 : :
# 494 : 253765 : while (!m_index.empty()) {
# 495 : : // If time went backwards, we may need to demote CANDIDATE_BEST and CANDIDATE_READY announcements back
# 496 : : // to CANDIDATE_DELAYED. This is an unusual edge case, and unlikely to matter in production. However,
# 497 : : // it makes it much easier to specify and test TxRequestTracker::Impl's behaviour.
# 498 : 66127 : auto it = std::prev(m_index.get<ByTime>().end());
# 499 : 66127 : if (it->IsSelectable() && it->m_time > now) {
# 500 : 0 : ChangeAndReselect(m_index.project<ByTxHash>(it), State::CANDIDATE_DELAYED);
# 501 : 66127 : } else {
# 502 : 66127 : break;
# 503 : 66127 : }
# 504 : 66127 : }
# 505 : 253765 : }
# 506 : :
# 507 : : public:
# 508 : : Impl(bool deterministic) :
# 509 : : m_computer(deterministic),
# 510 : : // Explicitly initialize m_index as we need to pass a reference to m_computer to ByTxHashViewExtractor.
# 511 : : m_index(boost::make_tuple(
# 512 : : boost::make_tuple(ByPeerViewExtractor(), std::less<ByPeerView>()),
# 513 : : boost::make_tuple(ByTxHashViewExtractor(m_computer), std::less<ByTxHashView>()),
# 514 : : boost::make_tuple(ByTimeViewExtractor(), std::less<ByTimeView>())
# 515 : 690 : )) {}
# 516 : :
# 517 : : // Disable copying and assigning (a default copy won't work due the stateful ByTxHashViewExtractor).
# 518 : : Impl(const Impl&) = delete;
# 519 : : Impl& operator=(const Impl&) = delete;
# 520 : :
# 521 : : void DisconnectedPeer(NodeId peer)
# 522 : 3627 : {
# 523 : 3627 : auto& index = m_index.get<ByPeer>();
# 524 : 3627 : auto it = index.lower_bound(ByPeerView{peer, false, uint256::ZERO});
# 525 : 6679 : while (it != index.end() && it->m_peer == peer) {
# 526 : : // Check what to continue with after this iteration. Note that 'it' may change position, and
# 527 : : // std::next(it) may be deleted in the process, so this needs to be decided beforehand.
# 528 : 3052 : auto it_next = (std::next(it) == index.end() || std::next(it)->m_peer != peer) ?
# 529 : 2808 : index.end() : std::next(it);
# 530 : : // If the announcement isn't already COMPLETED, first make it COMPLETED (which will mark other
# 531 : : // CANDIDATEs as CANDIDATE_BEST, or delete all of a txhash's announcements if no non-COMPLETED ones are
# 532 : : // left).
# 533 : 3052 : if (MakeCompleted(m_index.project<ByTxHash>(it))) {
# 534 : : // Then actually delete the announcement (unless it was already deleted by MakeCompleted).
# 535 : 1682 : Erase<ByPeer>(it);
# 536 : 1682 : }
# 537 : 3052 : it = it_next;
# 538 : 3052 : }
# 539 : 3627 : }
# 540 : :
# 541 : : void ForgetTxHash(const uint256& txhash)
# 542 : 192735 : {
# 543 : 192735 : auto it = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});
# 544 : 194521 : while (it != m_index.get<ByTxHash>().end() && it->m_txhash == txhash) {
# 545 : 1786 : it = Erase<ByTxHash>(it);
# 546 : 1786 : }
# 547 : 192735 : }
# 548 : :
# 549 : : void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,
# 550 : : std::chrono::microseconds reqtime)
# 551 : 15435 : {
# 552 : : // Bail out if we already have a CANDIDATE_BEST announcement for this (txhash, peer) combination. The case
# 553 : : // where there is a non-CANDIDATE_BEST announcement already will be caught by the uniqueness property of the
# 554 : : // ByPeer index when we try to emplace the new object below.
# 555 : 15435 : if (m_index.get<ByPeer>().count(ByPeerView{peer, true, gtxid.GetHash()})) return;
# 556 : :
# 557 : : // Try creating the announcement with CANDIDATE_DELAYED state (which will fail due to the uniqueness
# 558 : : // of the ByPeer index if a non-CANDIDATE_BEST announcement already exists with the same txhash and peer).
# 559 : : // Bail out in that case.
# 560 : 15435 : auto ret = m_index.get<ByPeer>().emplace(gtxid, peer, preferred, reqtime, m_current_sequence);
# 561 : 15435 : if (!ret.second) return;
# 562 : :
# 563 : : // Update accounting metadata.
# 564 : 15435 : ++m_peerinfo[peer].m_total;
# 565 : 15435 : ++m_current_sequence;
# 566 : 15435 : }
# 567 : :
# 568 : : //! Find the GenTxids to request now from peer.
# 569 : : std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now)
# 570 : 253765 : {
# 571 : : // Move time.
# 572 : 253765 : SetTimePoint(now);
# 573 : :
# 574 : : // Find all CANDIDATE_BEST announcements for this peer.
# 575 : 253765 : std::vector<const Announcement*> selected;
# 576 : 253765 : auto it_peer = m_index.get<ByPeer>().lower_bound(ByPeerView{peer, true, uint256::ZERO});
# 577 : 273002 : while (it_peer != m_index.get<ByPeer>().end() && it_peer->m_peer == peer &&
# 578 : 19237 : it_peer->m_state == State::CANDIDATE_BEST) {
# 579 : 19237 : selected.emplace_back(&*it_peer);
# 580 : 19237 : ++it_peer;
# 581 : 19237 : }
# 582 : :
# 583 : : // Sort by sequence number.
# 584 : 26204 : std::sort(selected.begin(), selected.end(), [](const Announcement* a, const Announcement* b) {
# 585 : 26204 : return a->m_sequence < b->m_sequence;
# 586 : 26204 : });
# 587 : :
# 588 : : // Convert to GenTxid and return.
# 589 : 253765 : std::vector<GenTxid> ret;
# 590 : 253765 : ret.reserve(selected.size());
# 591 : 19237 : std::transform(selected.begin(), selected.end(), std::back_inserter(ret), [](const Announcement* ann) {
# 592 : 19237 : return GenTxid{ann->m_is_wtxid, ann->m_txhash};
# 593 : 19237 : });
# 594 : 253765 : return ret;
# 595 : 253765 : }
# 596 : :
# 597 : : void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry)
# 598 : 9749 : {
# 599 : 9749 : auto it = m_index.get<ByPeer>().find(ByPeerView{peer, true, txhash});
# 600 : 9749 : if (it == m_index.get<ByPeer>().end()) {
# 601 : : // There is no CANDIDATE_BEST announcement, look for a _READY or _DELAYED instead. If the caller only
# 602 : : // ever invokes RequestedTx with the values returned by GetRequestable, and no other non-const functions
# 603 : : // other than ForgetTxHash and GetRequestable in between, this branch will never execute (as txhashes
# 604 : : // returned by GetRequestable always correspond to CANDIDATE_BEST announcements).
# 605 : :
# 606 : 0 : it = m_index.get<ByPeer>().find(ByPeerView{peer, false, txhash});
# 607 : 0 : if (it == m_index.get<ByPeer>().end() || (it->m_state != State::CANDIDATE_DELAYED &&
# 608 : 0 : it->m_state != State::CANDIDATE_READY)) {
# 609 : : // There is no CANDIDATE announcement tracked for this peer, so we have nothing to do. Either this
# 610 : : // txhash wasn't tracked at all (and the caller should have called ReceivedInv), or it was already
# 611 : : // requested and/or completed for other reasons and this is just a superfluous RequestedTx call.
# 612 : 0 : return;
# 613 : 0 : }
# 614 : :
# 615 : : // Look for an existing CANDIDATE_BEST or REQUESTED.
# 616 : 0 : auto it_old = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_BEST, 0});
# 617 : 0 : if (it_old != m_index.get<ByTxHash>().end() && it_old->m_txhash == txhash) {
# 618 : 0 : if (it_old->m_state == State::CANDIDATE_BEST) {
# 619 : : // The data structure's invariants require that there can be at most one CANDIDATE_BEST or one
# 620 : : // REQUESTED announcement per txhash (but not both simultaneously), so we have to convert any
# 621 : : // existing CANDIDATE_BEST to another CANDIDATE_* when constructing another REQUESTED.
# 622 : : // It doesn't matter whether we pick CANDIDATE_READY or _DELAYED here, as SetTimePoint()
# 623 : : // will correct it at GetRequestable() time. If time only goes forward, it will always be
# 624 : : // _READY, so pick that to avoid extra work in SetTimePoint().
# 625 : 0 : Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.m_state = State::CANDIDATE_READY; });
# 626 : 0 : } else if (it_old->m_state == State::REQUESTED) {
# 627 : : // As we're no longer waiting for a response to the previous REQUESTED announcement, convert it
# 628 : : // to COMPLETED. This also helps guaranteeing progress.
# 629 : 0 : Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.m_state = State::COMPLETED; });
# 630 : 0 : }
# 631 : 0 : }
# 632 : 0 : }
# 633 : :
# 634 : 9749 : Modify<ByPeer>(it, [expiry](Announcement& ann) {
# 635 : 9749 : ann.m_state = State::REQUESTED;
# 636 : 9749 : ann.m_time = expiry;
# 637 : 9749 : });
# 638 : 9749 : }
# 639 : :
# 640 : : void ReceivedResponse(NodeId peer, const uint256& txhash)
# 641 : 12202 : {
# 642 : : // We need to search the ByPeer index for both (peer, false, txhash) and (peer, true, txhash).
# 643 : 12202 : auto it = m_index.get<ByPeer>().find(ByPeerView{peer, false, txhash});
# 644 : 12202 : if (it == m_index.get<ByPeer>().end()) {
# 645 : 1915 : it = m_index.get<ByPeer>().find(ByPeerView{peer, true, txhash});
# 646 : 1915 : }
# 647 : 12202 : if (it != m_index.get<ByPeer>().end()) MakeCompleted(m_index.project<ByTxHash>(it));
# 648 : 12202 : }
# 649 : :
# 650 : : size_t CountInFlight(NodeId peer) const
# 651 : 39803 : {
# 652 : 39803 : auto it = m_peerinfo.find(peer);
# 653 : 39803 : if (it != m_peerinfo.end()) return it->second.m_requested;
# 654 : 8738 : return 0;
# 655 : 8738 : }
# 656 : :
# 657 : : size_t CountCandidates(NodeId peer) const
# 658 : 29728 : {
# 659 : 29728 : auto it = m_peerinfo.find(peer);
# 660 : 29728 : if (it != m_peerinfo.end()) return it->second.m_total - it->second.m_requested - it->second.m_completed;
# 661 : 7796 : return 0;
# 662 : 7796 : }
# 663 : :
# 664 : : size_t Count(NodeId peer) const
# 665 : 39803 : {
# 666 : 39803 : auto it = m_peerinfo.find(peer);
# 667 : 39803 : if (it != m_peerinfo.end()) return it->second.m_total;
# 668 : 8738 : return 0;
# 669 : 8738 : }
# 670 : :
# 671 : : //! Count how many announcements are being tracked in total across all peers and transactions.
# 672 : 477 : size_t Size() const { return m_index.size(); }
# 673 : :
# 674 : : uint64_t ComputePriority(const uint256& txhash, NodeId peer, bool preferred) const
# 675 : 3458308 : {
# 676 : : // Return Priority as a uint64_t as Priority is internal.
# 677 : 3458308 : return uint64_t{m_computer(txhash, peer, preferred)};
# 678 : 3458308 : }
# 679 : :
# 680 : : };
# 681 : :
# 682 : : TxRequestTracker::TxRequestTracker(bool deterministic) :
# 683 : 690 : m_impl{MakeUnique<TxRequestTracker::Impl>(deterministic)} {}
# 684 : :
# 685 : 690 : TxRequestTracker::~TxRequestTracker() = default;
# 686 : :
# 687 : 192735 : void TxRequestTracker::ForgetTxHash(const uint256& txhash) { m_impl->ForgetTxHash(txhash); }
# 688 : 3627 : void TxRequestTracker::DisconnectedPeer(NodeId peer) { m_impl->DisconnectedPeer(peer); }
# 689 : 39803 : size_t TxRequestTracker::CountInFlight(NodeId peer) const { return m_impl->CountInFlight(peer); }
# 690 : 29728 : size_t TxRequestTracker::CountCandidates(NodeId peer) const { return m_impl->CountCandidates(peer); }
# 691 : 39803 : size_t TxRequestTracker::Count(NodeId peer) const { return m_impl->Count(peer); }
# 692 : 477 : size_t TxRequestTracker::Size() const { return m_impl->Size(); }
# 693 : 40888 : void TxRequestTracker::SanityCheck() const { m_impl->SanityCheck(); }
# 694 : :
# 695 : : void TxRequestTracker::PostGetRequestableSanityCheck(std::chrono::microseconds now) const
# 696 : 29728 : {
# 697 : 29728 : m_impl->PostGetRequestableSanityCheck(now);
# 698 : 29728 : }
# 699 : :
# 700 : : void TxRequestTracker::ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,
# 701 : : std::chrono::microseconds reqtime)
# 702 : 15435 : {
# 703 : 15435 : m_impl->ReceivedInv(peer, gtxid, preferred, reqtime);
# 704 : 15435 : }
# 705 : :
# 706 : : void TxRequestTracker::RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry)
# 707 : 9749 : {
# 708 : 9749 : m_impl->RequestedTx(peer, txhash, expiry);
# 709 : 9749 : }
# 710 : :
# 711 : : void TxRequestTracker::ReceivedResponse(NodeId peer, const uint256& txhash)
# 712 : 12202 : {
# 713 : 12202 : m_impl->ReceivedResponse(peer, txhash);
# 714 : 12202 : }
# 715 : :
# 716 : : std::vector<GenTxid> TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now)
# 717 : 253765 : {
# 718 : 253765 : return m_impl->GetRequestable(peer, now);
# 719 : 253765 : }
# 720 : :
# 721 : : uint64_t TxRequestTracker::ComputePriority(const uint256& txhash, NodeId peer, bool preferred) const
# 722 : 3458308 : {
# 723 : 3458308 : return m_impl->ComputePriority(txhash, peer, preferred);
# 724 : 3458308 : }
|