Bitcoin ABC 0.32.4
P2P Digital Currency
invrequest.cpp
Go to the documentation of this file.
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 <invrequest.h>
6
7#include <crypto/siphash.h>
8#include <net.h>
9#include <random.h>
10
11#include <boost/multi_index/ordered_index.hpp>
12#include <boost/multi_index_container.hpp>
13
14#include <cassert>
15#include <chrono>
16#include <functional>
17#include <unordered_map>
18#include <utility>
19
20namespace {
21
42enum class State : uint8_t {
44 CANDIDATE_DELAYED,
48 CANDIDATE_READY,
55 CANDIDATE_BEST,
57 REQUESTED,
59 COMPLETED,
60};
61
63using SequenceNumber = uint64_t;
64
69struct Announcement {
71 const uint256 m_invid;
76 std::chrono::microseconds m_time;
78 const NodeId m_peer;
80 const SequenceNumber m_sequence : 60;
82 const bool m_preferred : 1;
83
90 uint8_t m_state : 3;
91
93 State GetState() const { return static_cast<State>(m_state); }
94
96 void SetState(State state) { m_state = static_cast<uint8_t>(state); }
97
102 bool IsSelected() const {
103 return GetState() == State::CANDIDATE_BEST ||
104 GetState() == State::REQUESTED;
105 }
106
108 bool IsWaiting() const {
109 return GetState() == State::REQUESTED ||
110 GetState() == State::CANDIDATE_DELAYED;
111 }
112
117 bool IsSelectable() const {
118 return GetState() == State::CANDIDATE_READY ||
119 GetState() == State::CANDIDATE_BEST;
120 }
121
126 Announcement(const uint256 &invid, NodeId peer, bool preferred,
127 std::chrono::microseconds reqtime, SequenceNumber sequence)
128 : m_invid(invid), m_time(reqtime), m_peer(peer), m_sequence(sequence),
129 m_preferred(preferred),
130 m_state(static_cast<uint8_t>(State::CANDIDATE_DELAYED)) {}
131};
132
134using Priority = uint64_t;
135
141class PriorityComputer {
142 const uint64_t m_k0, m_k1;
143
144public:
145 explicit PriorityComputer(bool deterministic)
146 : m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)},
147 m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {}
148
149 Priority operator()(const uint256 &invid, NodeId peer,
150 bool preferred) const {
151 uint64_t low_bits =
152 CSipHasher(m_k0, m_k1).Write(invid).Write(peer).Finalize() >> 1;
153 return low_bits | uint64_t{preferred} << 63;
154 }
155
156 Priority operator()(const Announcement &ann) const {
157 return operator()(ann.m_invid, ann.m_peer, ann.m_preferred);
158 }
159};
160
161// Definitions for the 3 indexes used in the main data structure.
162//
163// Each index has a By* type to identify it, a By*View data type to represent
164// the view of announcement it is sorted by, and an By*ViewExtractor type to
165// convert an announcement into the By*View type. See
166// https://www.boost.org/doc/libs/1_58_0/libs/multi_index/doc/reference/key_extraction.html#key_extractors
167// for more information about the key extraction concept.
168
169// The ByPeer index is sorted by (peer, state == CANDIDATE_BEST, invid)
170//
171// Uses:
172// * Looking up existing announcements by peer/invid, by checking both (peer,
173// false, invid) and (peer, true, invid).
174// * Finding all CANDIDATE_BEST announcements for a given peer in
175// GetRequestable.
176struct ByPeer {};
177using ByPeerView = std::tuple<NodeId, bool, const uint256 &>;
178struct ByPeerViewExtractor {
179 using result_type = ByPeerView;
180 result_type operator()(const Announcement &ann) const {
181 return ByPeerView{ann.m_peer, ann.GetState() == State::CANDIDATE_BEST,
182 ann.m_invid};
183 }
184};
185
186// The ByInvId index is sorted by (invid, state, priority).
187//
188// Note: priority == 0 whenever state != CANDIDATE_READY.
189//
190// Uses:
191// * Deleting all announcements with a given invid in ForgetInvId.
192// * Finding the best CANDIDATE_READY to convert to CANDIDATE_BEST, when no
193// other CANDIDATE_READY or REQUESTED announcement exists for that invid.
194// * Determining when no more non-COMPLETED announcements for a given invid
195// exist, so the COMPLETED ones can be deleted.
196struct ByInvId {};
197using ByInvIdView = std::tuple<const uint256 &, State, Priority>;
198class ByInvIdViewExtractor {
199 const PriorityComputer &m_computer;
200
201public:
202 explicit ByInvIdViewExtractor(const PriorityComputer &computer)
203 : m_computer(computer) {}
204 using result_type = ByInvIdView;
205 result_type operator()(const Announcement &ann) const {
206 const Priority prio =
207 (ann.GetState() == State::CANDIDATE_READY) ? m_computer(ann) : 0;
208 return ByInvIdView{ann.m_invid, ann.GetState(), prio};
209 }
210};
211
212enum class WaitState {
215 FUTURE_EVENT,
217 NO_EVENT,
220 PAST_EVENT,
221};
222
223WaitState GetWaitState(const Announcement &ann) {
224 if (ann.IsWaiting()) {
225 return WaitState::FUTURE_EVENT;
226 }
227 if (ann.IsSelectable()) {
228 return WaitState::PAST_EVENT;
229 }
230 return WaitState::NO_EVENT;
231}
232
233// The ByTime index is sorted by (wait_state, time).
234//
235// All announcements with a timestamp in the future can be found by iterating
236// the index forward from the beginning. All announcements with a timestamp in
237// the past can be found by iterating the index backwards from the end.
238//
239// Uses:
240// * Finding CANDIDATE_DELAYED announcements whose reqtime has passed, and
241// REQUESTED announcements whose expiry has passed.
242// * Finding CANDIDATE_READY/BEST announcements whose reqtime is in the future
243// (when the clock time went backwards).
244struct ByTime {};
245using ByTimeView = std::pair<WaitState, std::chrono::microseconds>;
246struct ByTimeViewExtractor {
247 using result_type = ByTimeView;
248 result_type operator()(const Announcement &ann) const {
249 return ByTimeView{GetWaitState(ann), ann.m_time};
250 }
251};
252
257using Index = boost::multi_index_container<
258 Announcement,
259 boost::multi_index::indexed_by<
260 boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>,
261 ByPeerViewExtractor>,
262 boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByInvId>,
263 ByInvIdViewExtractor>,
264 boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByTime>,
265 ByTimeViewExtractor>>>;
266
268template <typename Tag> using Iter = typename Index::index<Tag>::type::iterator;
269
271struct PeerInfo {
273 size_t m_total = 0;
275 size_t m_completed = 0;
277 size_t m_requested = 0;
278};
279
281struct InvIdInfo {
283 size_t m_candidate_delayed = 0;
285 size_t m_candidate_ready = 0;
287 size_t m_candidate_best = 0;
290 size_t m_requested = 0;
293 Priority m_priority_candidate_best = std::numeric_limits<Priority>::max();
296 Priority m_priority_best_candidate_ready =
297 std::numeric_limits<Priority>::min();
299 std::vector<NodeId> m_peers;
300};
301
303bool operator==(const PeerInfo &a, const PeerInfo &b) {
304 return std::tie(a.m_total, a.m_completed, a.m_requested) ==
305 std::tie(b.m_total, b.m_completed, b.m_requested);
306};
307
311std::unordered_map<NodeId, PeerInfo> RecomputePeerInfo(const Index &index) {
312 std::unordered_map<NodeId, PeerInfo> ret;
313 for (const Announcement &ann : index) {
314 PeerInfo &info = ret[ann.m_peer];
315 ++info.m_total;
316 info.m_requested += (ann.GetState() == State::REQUESTED);
317 info.m_completed += (ann.GetState() == State::COMPLETED);
318 }
319 return ret;
320}
321
323std::map<uint256, InvIdInfo>
324ComputeInvIdInfo(const Index &index, const PriorityComputer &computer) {
325 std::map<uint256, InvIdInfo> ret;
326 for (const Announcement &ann : index) {
327 InvIdInfo &info = ret[ann.m_invid];
328 // Classify how many announcements of each state we have for this invid.
329 info.m_candidate_delayed +=
330 (ann.GetState() == State::CANDIDATE_DELAYED);
331 info.m_candidate_ready += (ann.GetState() == State::CANDIDATE_READY);
332 info.m_candidate_best += (ann.GetState() == State::CANDIDATE_BEST);
333 info.m_requested += (ann.GetState() == State::REQUESTED);
334 // And track the priority of the best CANDIDATE_READY/CANDIDATE_BEST
335 // announcements.
336 if (ann.GetState() == State::CANDIDATE_BEST) {
337 info.m_priority_candidate_best = computer(ann);
338 }
339 if (ann.GetState() == State::CANDIDATE_READY) {
340 info.m_priority_best_candidate_ready =
341 std::max(info.m_priority_best_candidate_ready, computer(ann));
342 }
343 // Also keep track of which peers this invid has an announcement for
344 // (so we can detect duplicates).
345 info.m_peers.push_back(ann.m_peer);
346 }
347 return ret;
348}
349
350} // namespace
351
356 SequenceNumber m_current_sequence{0};
357
359 const PriorityComputer m_computer;
360
363 Index m_index;
364
366 std::unordered_map<NodeId, PeerInfo> m_peerinfo;
367
368public:
369 void SanityCheck() const {
370 // Recompute m_peerdata from m_index. This verifies the data in it as it
371 // should just be caching statistics on m_index. It also verifies the
372 // invariant that no PeerInfo announcements with m_total==0 exist.
373 assert(m_peerinfo == RecomputePeerInfo(m_index));
374
375 // Calculate per-invid statistics from m_index, and validate
376 // invariants.
377 for (auto &item : ComputeInvIdInfo(m_index, m_computer)) {
378 InvIdInfo &info = item.second;
379
380 // Cannot have only COMPLETED peer (invid should have been forgotten
381 // already)
382 assert(info.m_candidate_delayed + info.m_candidate_ready +
383 info.m_candidate_best + info.m_requested >
384 0);
385
386 // Can have at most 1 CANDIDATE_BEST/REQUESTED peer
387 assert(info.m_candidate_best + info.m_requested <= 1);
388
389 // If there are any CANDIDATE_READY announcements, there must be
390 // exactly one CANDIDATE_BEST or REQUESTED announcement.
391 if (info.m_candidate_ready > 0) {
392 assert(info.m_candidate_best + info.m_requested == 1);
393 }
394
395 // If there is both a CANDIDATE_READY and a CANDIDATE_BEST
396 // announcement, the CANDIDATE_BEST one must be at least as good
397 // (equal or higher priority) as the best CANDIDATE_READY.
398 if (info.m_candidate_ready && info.m_candidate_best) {
399 assert(info.m_priority_candidate_best >=
400 info.m_priority_best_candidate_ready);
401 }
402
403 // No invid can have been announced by the same peer twice.
404 std::sort(info.m_peers.begin(), info.m_peers.end());
405 assert(
406 std::adjacent_find(info.m_peers.begin(), info.m_peers.end()) ==
407 info.m_peers.end());
408 }
409 }
410
411 void PostGetRequestableSanityCheck(std::chrono::microseconds now) const {
412 for (const Announcement &ann : m_index) {
413 if (ann.IsWaiting()) {
414 // REQUESTED and CANDIDATE_DELAYED must have a time in the
415 // future (they should have been converted to
416 // COMPLETED/CANDIDATE_READY respectively).
417 assert(ann.m_time > now);
418 } else if (ann.IsSelectable()) {
419 // CANDIDATE_READY and CANDIDATE_BEST cannot have a time in the
420 // future (they should have remained CANDIDATE_DELAYED, or
421 // should have been converted back to it if time went
422 // backwards).
423 assert(ann.m_time <= now);
424 }
425 }
426 }
427
428private:
430 template <typename Tag> Iter<Tag> Erase(Iter<Tag> it) {
431 auto peerit = m_peerinfo.find(it->m_peer);
432 peerit->second.m_completed -= it->GetState() == State::COMPLETED;
433 peerit->second.m_requested -= it->GetState() == State::REQUESTED;
434 if (--peerit->second.m_total == 0) {
435 m_peerinfo.erase(peerit);
436 }
437 return m_index.get<Tag>().erase(it);
438 }
439
441 template <typename Tag, typename Modifier>
442 void Modify(Iter<Tag> it, Modifier modifier) {
443 auto peerit = m_peerinfo.find(it->m_peer);
444 peerit->second.m_completed -= it->GetState() == State::COMPLETED;
445 peerit->second.m_requested -= it->GetState() == State::REQUESTED;
446 m_index.get<Tag>().modify(it, std::move(modifier));
447 peerit->second.m_completed += it->GetState() == State::COMPLETED;
448 peerit->second.m_requested += it->GetState() == State::REQUESTED;
449 }
450
455 void PromoteCandidateReady(Iter<ByInvId> it) {
456 assert(it != m_index.get<ByInvId>().end());
457 assert(it->GetState() == State::CANDIDATE_DELAYED);
458 // Convert CANDIDATE_DELAYED to CANDIDATE_READY first.
459 Modify<ByInvId>(it, [](Announcement &ann) {
460 ann.SetState(State::CANDIDATE_READY);
461 });
462 // The following code relies on the fact that the ByInvId is sorted by
463 // invid, and then by state (first _DELAYED, then _READY, then
464 // _BEST/REQUESTED). Within the _READY announcements, the best one
465 // (highest priority) comes last. Thus, if an existing _BEST exists for
466 // the same invid that this announcement may be preferred over, it must
467 // immediately follow the newly created _READY.
468 auto it_next = std::next(it);
469 if (it_next == m_index.get<ByInvId>().end() ||
470 it_next->m_invid != it->m_invid ||
471 it_next->GetState() == State::COMPLETED) {
472 // This is the new best CANDIDATE_READY, and there is no
473 // IsSelected() announcement for this invid already.
474 Modify<ByInvId>(it, [](Announcement &ann) {
475 ann.SetState(State::CANDIDATE_BEST);
476 });
477 } else if (it_next->GetState() == State::CANDIDATE_BEST) {
478 Priority priority_old = m_computer(*it_next);
479 Priority priority_new = m_computer(*it);
480 if (priority_new > priority_old) {
481 // There is a CANDIDATE_BEST announcement already, but this one
482 // is better.
483 Modify<ByInvId>(it_next, [](Announcement &ann) {
484 ann.SetState(State::CANDIDATE_READY);
485 });
486 Modify<ByInvId>(it, [](Announcement &ann) {
487 ann.SetState(State::CANDIDATE_BEST);
488 });
489 }
490 }
491 }
492
496 void ChangeAndReselect(Iter<ByInvId> it, State new_state) {
497 assert(new_state == State::COMPLETED ||
498 new_state == State::CANDIDATE_DELAYED);
499 assert(it != m_index.get<ByInvId>().end());
500 if (it->IsSelected() && it != m_index.get<ByInvId>().begin()) {
501 auto it_prev = std::prev(it);
502 // The next best CANDIDATE_READY, if any, immediately precedes the
503 // REQUESTED or CANDIDATE_BEST announcement in the ByInvId index.
504 if (it_prev->m_invid == it->m_invid &&
505 it_prev->GetState() == State::CANDIDATE_READY) {
506 // If one such CANDIDATE_READY exists (for this invid), convert
507 // it to CANDIDATE_BEST.
508 Modify<ByInvId>(it_prev, [](Announcement &ann) {
509 ann.SetState(State::CANDIDATE_BEST);
510 });
511 }
512 }
513 Modify<ByInvId>(
514 it, [new_state](Announcement &ann) { ann.SetState(new_state); });
515 }
516
519 bool IsOnlyNonCompleted(Iter<ByInvId> it) {
520 assert(it != m_index.get<ByInvId>().end());
521 // Not allowed to call this on COMPLETED announcements.
522 assert(it->GetState() != State::COMPLETED);
523
524 // This announcement has a predecessor that belongs to the same invid.
525 // Due to ordering, and the fact that 'it' is not COMPLETED, its
526 // predecessor cannot be COMPLETED here.
527 if (it != m_index.get<ByInvId>().begin() &&
528 std::prev(it)->m_invid == it->m_invid) {
529 return false;
530 }
531
532 // This announcement has a successor that belongs to the same invid,
533 // and is not COMPLETED.
534 if (std::next(it) != m_index.get<ByInvId>().end() &&
535 std::next(it)->m_invid == it->m_invid &&
536 std::next(it)->GetState() != State::COMPLETED) {
537 return false;
538 }
539
540 return true;
541 }
542
550 bool MakeCompleted(Iter<ByInvId> it) {
551 assert(it != m_index.get<ByInvId>().end());
552
553 // Nothing to be done if it's already COMPLETED.
554 if (it->GetState() == State::COMPLETED) {
555 return true;
556 }
557
558 if (IsOnlyNonCompleted(it)) {
559 // This is the last non-COMPLETED announcement for this invid.
560 // Delete all.
561 uint256 invid = it->m_invid;
562 do {
563 it = Erase<ByInvId>(it);
564 } while (it != m_index.get<ByInvId>().end() &&
565 it->m_invid == invid);
566 return false;
567 }
568
569 // Mark the announcement COMPLETED, and select the next best
570 // announcement (the first CANDIDATE_READY) if needed.
571 ChangeAndReselect(it, State::COMPLETED);
572
573 return true;
574 }
575
582 void SetTimePoint(std::chrono::microseconds now,
583 ClearExpiredFun clearExpired,
584 EmplaceExpiredFun emplaceExpired) {
585 clearExpired();
586 // Iterate over all CANDIDATE_DELAYED and REQUESTED from old to new, as
587 // long as they're in the past, and convert them to CANDIDATE_READY andc
588 // COMPLETED respectively.
589 while (!m_index.empty()) {
590 auto it = m_index.get<ByTime>().begin();
591 if (it->GetState() == State::CANDIDATE_DELAYED &&
592 it->m_time <= now) {
593 PromoteCandidateReady(m_index.project<ByInvId>(it));
594 } else if (it->GetState() == State::REQUESTED &&
595 it->m_time <= now) {
596 emplaceExpired(it->m_peer, it->m_invid);
597 MakeCompleted(m_index.project<ByInvId>(it));
598 } else {
599 break;
600 }
601 }
602
603 while (!m_index.empty()) {
604 // If time went backwards, we may need to demote CANDIDATE_BEST and
605 // CANDIDATE_READY announcements back to CANDIDATE_DELAYED. This is
606 // an unusual edge case, and unlikely to matter in production.
607 // However, it makes it much easier to specify and test
608 // InvRequestTracker::Impl's behaviour.
609 auto it = std::prev(m_index.get<ByTime>().end());
610 if (it->IsSelectable() && it->m_time > now) {
611 ChangeAndReselect(m_index.project<ByInvId>(it),
612 State::CANDIDATE_DELAYED);
613 } else {
614 break;
615 }
616 }
617 }
618
619public:
620 explicit InvRequestTrackerImpl(bool deterministic)
621 : m_computer(deterministic),
622 // Explicitly initialize m_index as we need to pass a reference to
623 // m_computer to ByInvIdViewExtractor.
624 m_index(boost::make_tuple(
625 boost::make_tuple(ByPeerViewExtractor(), std::less<ByPeerView>()),
626 boost::make_tuple(ByInvIdViewExtractor(m_computer),
627 std::less<ByInvIdView>()),
628 boost::make_tuple(ByTimeViewExtractor(),
629 std::less<ByTimeView>()))) {}
630
631 // Disable copying and assigning (a default copy won't work due the stateful
632 // ByInvIdViewExtractor).
635
637
639 auto &index = m_index.get<ByPeer>();
640 auto it =
641 index.lower_bound(ByPeerView{peer, false, uint256(uint256::ZERO)});
642 while (it != index.end() && it->m_peer == peer) {
643 // Check what to continue with after this iteration. 'it' will be
644 // deleted in what follows, so we need to decide what to continue
645 // with afterwards. There are a number of cases to consider:
646 // - std::next(it) is end() or belongs to a different peer. In that
647 // case, this is the last iteration of the loop (denote this by
648 // setting it_next to end()).
649 // - 'it' is not the only non-COMPLETED announcement for its invid.
650 // This means it will be deleted, but no other Announcement
651 // objects will be modified. Continue with std::next(it) if it
652 // belongs to the same peer, but decide this ahead of time (as
653 // 'it' may change position in what follows).
654 // - 'it' is the only non-COMPLETED announcement for its invid. This
655 // means it will be deleted along with all other announcements for
656 // the same invid - which may include std::next(it). However,
657 // other than 'it', no announcements for the same peer can be
658 // affected (due to (peer, invid) uniqueness). In other words, the
659 // situation where std::next(it) is deleted can only occur if
660 // std::next(it) belongs to a different peer but the same invid as
661 // 'it'. This is covered by the first bulletpoint already, and
662 // we'll have set it_next to end().
663 auto it_next =
664 (std::next(it) == index.end() || std::next(it)->m_peer != peer)
665 ? index.end()
666 : std::next(it);
667 // If the announcement isn't already COMPLETED, first make it
668 // COMPLETED (which will mark other CANDIDATEs as CANDIDATE_BEST, or
669 // delete all of a invid's announcements if no non-COMPLETED ones
670 // are left).
671 if (MakeCompleted(m_index.project<ByInvId>(it))) {
672 // Then actually delete the announcement (unless it was already
673 // deleted by MakeCompleted).
674 Erase<ByPeer>(it);
675 }
676 it = it_next;
677 }
678 }
679
680 void ForgetInvId(const uint256 &invid) {
681 auto it = m_index.get<ByInvId>().lower_bound(
682 ByInvIdView{invid, State::CANDIDATE_DELAYED, 0});
683 while (it != m_index.get<ByInvId>().end() && it->m_invid == invid) {
684 it = Erase<ByInvId>(it);
685 }
686 }
687
688 void ReceivedInv(NodeId peer, const uint256 &invid, bool preferred,
689 std::chrono::microseconds reqtime) {
690 // Bail out if we already have a CANDIDATE_BEST announcement for this
691 // (invid, peer) combination. The case where there is a
692 // non-CANDIDATE_BEST announcement already will be caught by the
693 // uniqueness property of the ByPeer index when we try to emplace the
694 // new object below.
695 if (m_index.get<ByPeer>().count(ByPeerView{peer, true, invid})) {
696 return;
697 }
698
699 // Try creating the announcement with CANDIDATE_DELAYED state (which
700 // will fail due to the uniqueness of the ByPeer index if a
701 // non-CANDIDATE_BEST announcement already exists with the same invid
702 // and peer). Bail out in that case.
703 auto ret = m_index.get<ByPeer>().emplace(invid, peer, preferred,
704 reqtime, m_current_sequence);
705 if (!ret.second) {
706 return;
707 }
708
709 // Update accounting metadata.
710 ++m_peerinfo[peer].m_total;
712 }
713
715 std::vector<uint256> GetRequestable(NodeId peer,
716 std::chrono::microseconds now,
717 ClearExpiredFun clearExpired,
718 EmplaceExpiredFun emplaceExpired) {
719 // Move time.
720 SetTimePoint(now, clearExpired, emplaceExpired);
721
722 // Find all CANDIDATE_BEST announcements for this peer.
723 std::vector<const Announcement *> selected;
724 auto it_peer = m_index.get<ByPeer>().lower_bound(
725 ByPeerView{peer, true, uint256(uint256::ZERO)});
726 while (it_peer != m_index.get<ByPeer>().end() &&
727 it_peer->m_peer == peer &&
728 it_peer->GetState() == State::CANDIDATE_BEST) {
729 selected.emplace_back(&*it_peer);
730 ++it_peer;
731 }
732
733 // Sort by sequence number.
734 std::sort(selected.begin(), selected.end(),
735 [](const Announcement *a, const Announcement *b) {
736 return a->m_sequence < b->m_sequence;
737 });
738
739 // Convert to InvId and return.
740 std::vector<uint256> ret;
741 ret.reserve(selected.size());
742 std::transform(selected.begin(), selected.end(),
743 std::back_inserter(ret),
744 [](const Announcement *ann) { return ann->m_invid; });
745 return ret;
746 }
747
748 void RequestedData(NodeId peer, const uint256 &invid,
749 std::chrono::microseconds expiry) {
750 auto it = m_index.get<ByPeer>().find(ByPeerView{peer, true, invid});
751 if (it == m_index.get<ByPeer>().end()) {
752 // There is no CANDIDATE_BEST announcement, look for a _READY or
753 // _DELAYED instead. If the caller only ever invokes RequestedData
754 // with the values returned by GetRequestable, and no other
755 // non-const functions other than ForgetInvId and GetRequestable in
756 // between, this branch will never execute (as invids returned by
757 // GetRequestable always correspond to CANDIDATE_BEST
758 // announcements).
759
760 it = m_index.get<ByPeer>().find(ByPeerView{peer, false, invid});
761 if (it == m_index.get<ByPeer>().end() ||
762 (it->GetState() != State::CANDIDATE_DELAYED &&
763 it->GetState() != State::CANDIDATE_READY)) {
764 // There is no CANDIDATE announcement tracked for this peer, so
765 // we have nothing to do. Either this invid wasn't tracked at
766 // all (and the caller should have called ReceivedInv), or it
767 // was already requested and/or completed for other reasons and
768 // this is just a superfluous RequestedData call.
769 return;
770 }
771
772 // Look for an existing CANDIDATE_BEST or REQUESTED with the same
773 // invid. We only need to do this if the found announcement had a
774 // different state than CANDIDATE_BEST. If it did, invariants
775 // guarantee that no other CANDIDATE_BEST or REQUESTED can exist.
776 auto it_old = m_index.get<ByInvId>().lower_bound(
777 ByInvIdView{invid, State::CANDIDATE_BEST, 0});
778 if (it_old != m_index.get<ByInvId>().end() &&
779 it_old->m_invid == invid) {
780 if (it_old->GetState() == State::CANDIDATE_BEST) {
781 // The data structure's invariants require that there can be
782 // at most one CANDIDATE_BEST or one REQUESTED announcement
783 // per invid (but not both simultaneously), so we have to
784 // convert any existing CANDIDATE_BEST to another
785 // CANDIDATE_* when constructing another REQUESTED. It
786 // doesn't matter whether we pick CANDIDATE_READY or
787 // _DELAYED here, as SetTimePoint() will correct it at
788 // GetRequestable() time. If time only goes forward, it will
789 // always be _READY, so pick that to avoid extra work in
790 // SetTimePoint().
791 Modify<ByInvId>(it_old, [](Announcement &ann) {
792 ann.SetState(State::CANDIDATE_READY);
793 });
794 } else if (it_old->GetState() == State::REQUESTED) {
795 // As we're no longer waiting for a response to the previous
796 // REQUESTED announcement, convert it to COMPLETED. This
797 // also helps guaranteeing progress.
798 Modify<ByInvId>(it_old, [](Announcement &ann) {
799 ann.SetState(State::COMPLETED);
800 });
801 }
802 }
803 }
804
805 Modify<ByPeer>(it, [expiry](Announcement &ann) {
806 ann.SetState(State::REQUESTED);
807 ann.m_time = expiry;
808 });
809 }
810
811 void ReceivedResponse(NodeId peer, const uint256 &invid) {
812 // We need to search the ByPeer index for both (peer, false, invid) and
813 // (peer, true, invid).
814 auto it = m_index.get<ByPeer>().find(ByPeerView{peer, false, invid});
815 if (it == m_index.get<ByPeer>().end()) {
816 it = m_index.get<ByPeer>().find(ByPeerView{peer, true, invid});
817 }
818 if (it != m_index.get<ByPeer>().end()) {
819 MakeCompleted(m_index.project<ByInvId>(it));
820 }
821 }
822
823 size_t CountInFlight(NodeId peer) const {
824 auto it = m_peerinfo.find(peer);
825 if (it != m_peerinfo.end()) {
826 return it->second.m_requested;
827 }
828 return 0;
829 }
830
831 size_t CountCandidates(NodeId peer) const {
832 auto it = m_peerinfo.find(peer);
833 if (it != m_peerinfo.end()) {
834 return it->second.m_total - it->second.m_requested -
835 it->second.m_completed;
836 }
837 return 0;
838 }
839
840 size_t Count(NodeId peer) const {
841 auto it = m_peerinfo.find(peer);
842 if (it != m_peerinfo.end()) {
843 return it->second.m_total;
844 }
845 return 0;
846 }
847
850 size_t Size() const { return m_index.size(); }
851
852 uint64_t ComputePriority(const uint256 &invid, NodeId peer,
853 bool preferred) const {
854 // Return Priority as a uint64_t as Priority is internal.
855 return uint64_t{m_computer(invid, peer, preferred)};
856 }
857};
858
859std::unique_ptr<InvRequestTrackerImplInterface>
861 return std::make_unique<InvRequestTrackerImpl>(deterministic);
862}
SipHash-2-4.
Definition: siphash.h:14
uint64_t Finalize() const
Compute the 64-bit SipHash-2-4 of the data written so far.
Definition: siphash.cpp:83
CSipHasher & Write(uint64_t data)
Hash a 64-bit integer worth of data.
Definition: siphash.cpp:36
Actual implementation for InvRequestTracker's data structure.
Definition: invrequest.cpp:353
InvRequestTrackerImpl(const InvRequestTrackerImpl &)=delete
void SanityCheck() const
Definition: invrequest.cpp:369
size_t CountInFlight(NodeId peer) const
Definition: invrequest.cpp:823
void Modify(Iter< Tag > it, Modifier modifier)
Wrapper around Index::...::modify that keeps m_peerinfo up to date.
Definition: invrequest.cpp:442
SequenceNumber m_current_sequence
The current sequence number.
Definition: invrequest.cpp:356
std::vector< uint256 > GetRequestable(NodeId peer, std::chrono::microseconds now, ClearExpiredFun clearExpired, EmplaceExpiredFun emplaceExpired)
Find the InvIds to request now from peer.
Definition: invrequest.cpp:715
void ReceivedResponse(NodeId peer, const uint256 &invid)
Definition: invrequest.cpp:811
Iter< Tag > Erase(Iter< Tag > it)
Wrapper around Index::...::erase that keeps m_peerinfo up to date.
Definition: invrequest.cpp:430
std::unordered_map< NodeId, PeerInfo > m_peerinfo
Map with this tracker's per-peer statistics.
Definition: invrequest.cpp:366
InvRequestTrackerImpl(bool deterministic)
Definition: invrequest.cpp:620
bool MakeCompleted(Iter< ByInvId > it)
Convert any announcement to a COMPLETED one.
Definition: invrequest.cpp:550
void SetTimePoint(std::chrono::microseconds now, ClearExpiredFun clearExpired, EmplaceExpiredFun emplaceExpired)
Make the data structure consistent with a given point in time:
Definition: invrequest.cpp:582
void DisconnectedPeer(NodeId peer)
Definition: invrequest.cpp:638
uint64_t ComputePriority(const uint256 &invid, NodeId peer, bool preferred) const
Definition: invrequest.cpp:852
void ForgetInvId(const uint256 &invid)
Definition: invrequest.cpp:680
InvRequestTrackerImpl & operator=(const InvRequestTrackerImpl &)=delete
void ReceivedInv(NodeId peer, const uint256 &invid, bool preferred, std::chrono::microseconds reqtime)
Definition: invrequest.cpp:688
bool IsOnlyNonCompleted(Iter< ByInvId > it)
Check if 'it' is the only announcement for a given invid that isn't COMPLETED.
Definition: invrequest.cpp:519
void RequestedData(NodeId peer, const uint256 &invid, std::chrono::microseconds expiry)
Definition: invrequest.cpp:748
size_t Count(NodeId peer) const
Definition: invrequest.cpp:840
~InvRequestTrackerImpl()=default
size_t Size() const
Count how many announcements are being tracked in total across all peers and transactions.
Definition: invrequest.cpp:850
void ChangeAndReselect(Iter< ByInvId > it, State new_state)
Change the state of an announcement to something non-IsSelected().
Definition: invrequest.cpp:496
void PromoteCandidateReady(Iter< ByInvId > it)
Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY.
Definition: invrequest.cpp:455
const PriorityComputer m_computer
This tracker's priority computer.
Definition: invrequest.cpp:359
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
Definition: invrequest.cpp:411
Index m_index
This tracker's main data structure.
Definition: invrequest.cpp:363
size_t CountCandidates(NodeId peer) const
Definition: invrequest.cpp:831
Data structure to keep track of, and schedule, inventory downloads from peers.
Definition: invrequest.h:121
static std::unique_ptr< InvRequestTrackerImplInterface > BuildImpl(bool deterministic)
Definition: invrequest.cpp:860
const std::function< void()> & ClearExpiredFun
Definition: invrequest.h:131
const std::function< void(const NodeId &, const uint256 &)> & EmplaceExpiredFun
Definition: invrequest.h:133
256-bit opaque blob.
Definition: uint256.h:129
static const uint256 ZERO
Definition: uint256.h:134
int64_t NodeId
Definition: eviction.h:16
Implement std::hash so RCUPtr can be used as a key for maps or sets.
Definition: rcu.h:259
bool operator==(const CNetAddr &a, const CNetAddr &b)
Definition: netaddress.cpp:672
T GetRand(T nMax=std::numeric_limits< T >::max()) noexcept
Generate a uniform random integer of type T in the range [0..nMax) nMax defaults to std::numeric_limi...
Definition: random.h:85
assert(!tx.IsCoinBase())