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