11#include <boost/multi_index/ordered_index.hpp>
12#include <boost/multi_index_container.hpp>
17#include <unordered_map>
42enum class State : uint8_t {
63using SequenceNumber = uint64_t;
76 std::chrono::microseconds m_time;
80 const SequenceNumber m_sequence : 60;
82 const bool m_preferred : 1;
93 State GetState()
const {
return static_cast<State
>(m_state); }
96 void SetState(State state) { m_state =
static_cast<uint8_t
>(state); }
102 bool IsSelected()
const {
103 return GetState() == State::CANDIDATE_BEST ||
104 GetState() == State::REQUESTED;
108 bool IsWaiting()
const {
109 return GetState() == State::REQUESTED ||
110 GetState() == State::CANDIDATE_DELAYED;
117 bool IsSelectable()
const {
118 return GetState() == State::CANDIDATE_READY ||
119 GetState() == State::CANDIDATE_BEST;
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)) {}
134using Priority = uint64_t;
141class PriorityComputer {
142 const uint64_t m_k0, m_k1;
145 explicit PriorityComputer(
bool deterministic)
146 : m_k0{deterministic ? 0 :
GetRand(0xFFFFFFFFFFFFFFFF)},
147 m_k1{deterministic ? 0 :
GetRand(0xFFFFFFFFFFFFFFFF)} {}
150 bool preferred)
const {
153 return low_bits | uint64_t{preferred} << 63;
156 Priority operator()(
const Announcement &ann)
const {
157 return operator()(ann.m_invid, ann.m_peer, ann.m_preferred);
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,
197using ByInvIdView = std::tuple<const uint256 &, State, Priority>;
198class ByInvIdViewExtractor {
199 const PriorityComputer &m_computer;
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};
212enum class WaitState {
223WaitState GetWaitState(
const Announcement &ann) {
224 if (ann.IsWaiting()) {
225 return WaitState::FUTURE_EVENT;
227 if (ann.IsSelectable()) {
228 return WaitState::PAST_EVENT;
230 return WaitState::NO_EVENT;
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};
257using Index = boost::multi_index_container<
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>>>;
268template <
typename Tag>
using Iter =
typename Index::index<Tag>::type::iterator;
275 size_t m_completed = 0;
277 size_t m_requested = 0;
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;
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);
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];
316 info.m_requested += (ann.GetState() == State::REQUESTED);
317 info.m_completed += (ann.GetState() == State::COMPLETED);
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];
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);
336 if (ann.GetState() == State::CANDIDATE_BEST) {
337 info.m_priority_candidate_best = computer(ann);
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));
345 info.m_peers.push_back(ann.m_peer);
378 InvIdInfo &info = item.second;
382 assert(info.m_candidate_delayed + info.m_candidate_ready +
383 info.m_candidate_best + info.m_requested >
387 assert(info.m_candidate_best + info.m_requested <= 1);
391 if (info.m_candidate_ready > 0) {
392 assert(info.m_candidate_best + info.m_requested == 1);
398 if (info.m_candidate_ready && info.m_candidate_best) {
399 assert(info.m_priority_candidate_best >=
400 info.m_priority_best_candidate_ready);
404 std::sort(info.m_peers.begin(), info.m_peers.end());
406 std::adjacent_find(info.m_peers.begin(), info.m_peers.end()) ==
412 for (
const Announcement &ann :
m_index) {
413 if (ann.IsWaiting()) {
418 }
else if (ann.IsSelectable()) {
423 assert(ann.m_time <= now);
430 template <
typename Tag> Iter<Tag>
Erase(Iter<Tag> it) {
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) {
437 return m_index.get<Tag>().erase(it);
441 template <
typename Tag,
typename Modifier>
442 void Modify(Iter<Tag> it, Modifier modifier) {
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;
457 assert(it->GetState() == State::CANDIDATE_DELAYED);
459 Modify<ByInvId>(it, [](Announcement &ann) {
460 ann.SetState(State::CANDIDATE_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) {
474 Modify<ByInvId>(it, [](Announcement &ann) {
475 ann.SetState(State::CANDIDATE_BEST);
477 }
else if (it_next->GetState() == State::CANDIDATE_BEST) {
480 if (priority_new > priority_old) {
483 Modify<ByInvId>(it_next, [](Announcement &ann) {
484 ann.SetState(State::CANDIDATE_READY);
486 Modify<ByInvId>(it, [](Announcement &ann) {
487 ann.SetState(State::CANDIDATE_BEST);
497 assert(new_state == State::COMPLETED ||
498 new_state == State::CANDIDATE_DELAYED);
500 if (it->IsSelected() && it !=
m_index.get<ByInvId>().begin()) {
501 auto it_prev = std::prev(it);
504 if (it_prev->m_invid == it->m_invid &&
505 it_prev->GetState() == State::CANDIDATE_READY) {
508 Modify<ByInvId>(it_prev, [](Announcement &ann) {
509 ann.SetState(State::CANDIDATE_BEST);
514 it, [new_state](Announcement &ann) { ann.SetState(new_state); });
522 assert(it->GetState() != State::COMPLETED);
527 if (it !=
m_index.get<ByInvId>().begin() &&
528 std::prev(it)->m_invid == it->m_invid) {
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) {
554 if (it->GetState() == State::COMPLETED) {
563 it = Erase<ByInvId>(it);
564 }
while (it !=
m_index.get<ByInvId>().end() &&
565 it->m_invid == invid);
590 auto it =
m_index.get<ByTime>().begin();
591 if (it->GetState() == State::CANDIDATE_DELAYED &&
594 }
else if (it->GetState() == State::REQUESTED &&
596 emplaceExpired(it->m_peer, it->m_invid);
609 auto it = std::prev(
m_index.get<ByTime>().end());
610 if (it->IsSelectable() && it->m_time > now) {
612 State::CANDIDATE_DELAYED);
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>()))) {}
639 auto &index =
m_index.get<ByPeer>();
642 while (it != index.end() && it->m_peer == peer) {
664 (std::next(it) == index.end() || std::next(it)->m_peer != peer)
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);
689 std::chrono::microseconds reqtime) {
695 if (
m_index.get<ByPeer>().count(ByPeerView{peer, true, invid})) {
703 auto ret =
m_index.get<ByPeer>().emplace(invid, peer, preferred,
716 std::chrono::microseconds now,
723 std::vector<const Announcement *> selected;
724 auto it_peer =
m_index.get<ByPeer>().lower_bound(
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);
734 std::sort(selected.begin(), selected.end(),
735 [](
const Announcement *a,
const Announcement *b) {
736 return a->m_sequence < b->m_sequence;
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; });
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()) {
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)) {
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) {
791 Modify<ByInvId>(it_old, [](Announcement &ann) {
792 ann.SetState(State::CANDIDATE_READY);
794 }
else if (it_old->GetState() == State::REQUESTED) {
798 Modify<ByInvId>(it_old, [](Announcement &ann) {
799 ann.SetState(State::COMPLETED);
805 Modify<ByPeer>(it, [expiry](Announcement &ann) {
806 ann.SetState(State::REQUESTED);
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});
818 if (it !=
m_index.get<ByPeer>().end()) {
826 return it->second.m_requested;
834 return it->second.m_total - it->second.m_requested -
835 it->second.m_completed;
843 return it->second.m_total;
853 bool preferred)
const {
855 return uint64_t{
m_computer(invid, peer, preferred)};
859std::unique_ptr<InvRequestTrackerImplInterface>
861 return std::make_unique<InvRequestTrackerImpl>(deterministic);
uint64_t Finalize() const
Compute the 64-bit SipHash-2-4 of the data written so far.
CSipHasher & Write(uint64_t data)
Hash a 64-bit integer worth of data.
Actual implementation for InvRequestTracker's data structure.
InvRequestTrackerImpl(const InvRequestTrackerImpl &)=delete
size_t CountInFlight(NodeId peer) const
void Modify(Iter< Tag > it, Modifier modifier)
Wrapper around Index::...::modify that keeps m_peerinfo up to date.
SequenceNumber m_current_sequence
The current sequence number.
std::vector< uint256 > GetRequestable(NodeId peer, std::chrono::microseconds now, ClearExpiredFun clearExpired, EmplaceExpiredFun emplaceExpired)
Find the InvIds to request now from peer.
void ReceivedResponse(NodeId peer, const uint256 &invid)
Iter< Tag > Erase(Iter< Tag > it)
Wrapper around Index::...::erase that keeps m_peerinfo up to date.
std::unordered_map< NodeId, PeerInfo > m_peerinfo
Map with this tracker's per-peer statistics.
InvRequestTrackerImpl(bool deterministic)
bool MakeCompleted(Iter< ByInvId > it)
Convert any announcement to a COMPLETED one.
void SetTimePoint(std::chrono::microseconds now, ClearExpiredFun clearExpired, EmplaceExpiredFun emplaceExpired)
Make the data structure consistent with a given point in time:
void DisconnectedPeer(NodeId peer)
uint64_t ComputePriority(const uint256 &invid, NodeId peer, bool preferred) const
void ForgetInvId(const uint256 &invid)
InvRequestTrackerImpl & operator=(const InvRequestTrackerImpl &)=delete
void ReceivedInv(NodeId peer, const uint256 &invid, bool preferred, std::chrono::microseconds reqtime)
bool IsOnlyNonCompleted(Iter< ByInvId > it)
Check if 'it' is the only announcement for a given invid that isn't COMPLETED.
void RequestedData(NodeId peer, const uint256 &invid, std::chrono::microseconds expiry)
size_t Count(NodeId peer) const
~InvRequestTrackerImpl()=default
size_t Size() const
Count how many announcements are being tracked in total across all peers and transactions.
void ChangeAndReselect(Iter< ByInvId > it, State new_state)
Change the state of an announcement to something non-IsSelected().
void PromoteCandidateReady(Iter< ByInvId > it)
Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY.
const PriorityComputer m_computer
This tracker's priority computer.
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
Index m_index
This tracker's main data structure.
size_t CountCandidates(NodeId peer) const
Data structure to keep track of, and schedule, inventory downloads from peers.
static std::unique_ptr< InvRequestTrackerImplInterface > BuildImpl(bool deterministic)
const std::function< void()> & ClearExpiredFun
const std::function< void(const NodeId &, const uint256 &)> & EmplaceExpiredFun
static const uint256 ZERO
Implement std::hash so RCUPtr can be used as a key for maps or sets.
bool operator==(const CNetAddr &a, const CNetAddr &b)
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...