13#include <boost/multi_index/indexed_by.hpp> 
   14#include <boost/multi_index/ordered_index.hpp> 
   15#include <boost/multi_index/sequenced_index.hpp> 
   16#include <boost/multi_index/tag.hpp> 
   17#include <boost/multi_index_container.hpp> 
   18#include <boost/tuple/tuple.hpp> 
   21#include <unordered_map> 
   42enum class State : uint8_t {
 
   58using SequenceNumber = uint64_t;
 
   65    std::chrono::microseconds m_time;
 
   69    const SequenceNumber m_sequence : 59;
 
   71    const bool m_preferred : 1;
 
   73    State m_state : 3 {State::CANDIDATE_DELAYED};
 
   74    State GetState()
 const { 
return m_state; }
 
   75    void SetState(
State state) { m_state = state; }
 
   78    bool IsSelected()
 const 
   80        return GetState() == State::CANDIDATE_BEST || GetState() == State::REQUESTED;
 
   84    bool IsWaiting()
 const 
   86        return GetState() == State::REQUESTED || GetState() == State::CANDIDATE_DELAYED;
 
   90    bool IsSelectable()
 const 
   92        return GetState() == State::CANDIDATE_READY || GetState() == State::CANDIDATE_BEST;
 
   96    Announcement(
const GenTxid& gtxid, 
NodeId peer, 
bool preferred, std::chrono::microseconds reqtime,
 
   98        : m_gtxid(gtxid), m_time(reqtime), m_peer(peer), m_sequence(
sequence), m_preferred(preferred) {}
 
  102using Priority = uint64_t;
 
  108class PriorityComputer {
 
  109    const uint64_t m_k0, m_k1;
 
  111    explicit PriorityComputer(
bool deterministic) :
 
  115    Priority operator()(
const uint256& txhash, 
NodeId peer, 
bool preferred)
 const 
  118        return low_bits | uint64_t{preferred} << 63;
 
  121    Priority operator()(
const Announcement& ann)
 const 
  123        return operator()(ann.m_gtxid.ToUint256(), ann.m_peer, ann.m_preferred);
 
  141using ByPeerView = std::tuple<NodeId, bool, const uint256&>;
 
  142struct ByPeerViewExtractor
 
  144    using result_type = ByPeerView;
 
  145    result_type operator()(
const Announcement& ann)
 const 
  147        return ByPeerView{ann.m_peer, ann.GetState() == State::CANDIDATE_BEST, ann.m_gtxid.ToUint256()};
 
  162using ByTxHashView = std::tuple<const uint256&, State, Priority>;
 
  163class ByTxHashViewExtractor {
 
  164    const PriorityComputer& m_computer;
 
  166    explicit ByTxHashViewExtractor(
const PriorityComputer& computer) : m_computer(computer) {}
 
  167    using result_type = ByTxHashView;
 
  168    result_type operator()(
const Announcement& ann)
 const 
  170        const Priority prio = (ann.GetState() == State::CANDIDATE_READY) ? m_computer(ann) : 0;
 
  171        return ByTxHashView{ann.m_gtxid.ToUint256(), ann.GetState(), prio};
 
  175enum class WaitState {
 
  184WaitState GetWaitState(
const Announcement& ann)
 
  186    if (ann.IsWaiting()) 
return WaitState::FUTURE_EVENT;
 
  187    if (ann.IsSelectable()) 
return WaitState::PAST_EVENT;
 
  188    return WaitState::NO_EVENT;
 
  201using ByTimeView = std::pair<WaitState, std::chrono::microseconds>;
 
  202struct ByTimeViewExtractor
 
  204    using result_type = ByTimeView;
 
  205    result_type operator()(
const Announcement& ann)
 const 
  207        return ByTimeView{GetWaitState(ann), ann.m_time};
 
  211struct Announcement_Indices final : boost::multi_index::indexed_by<
 
  212    boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor>,
 
  213    boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByTxHash>, ByTxHashViewExtractor>,
 
  214    boost::multi_index::ordered_non_unique<boost::multi_index::tag<ByTime>, ByTimeViewExtractor>
 
  219using Index = boost::multi_index_container<
 
  225template<
typename Tag>
 
  226using Iter = 
typename Index::index<Tag>::type::iterator;
 
  231    size_t m_completed = 0; 
 
  232    size_t m_requested = 0; 
 
  239    size_t m_candidate_delayed = 0;
 
  241    size_t m_candidate_ready = 0;
 
  243    size_t m_candidate_best = 0;
 
  245    size_t m_requested = 0;
 
  247    Priority m_priority_candidate_best = std::numeric_limits<Priority>::max();
 
  249    Priority m_priority_best_candidate_ready = std::numeric_limits<Priority>::min();
 
  251    std::vector<NodeId> m_peers;
 
  255bool operator==(
const PeerInfo& a, 
const PeerInfo& b)
 
  257    return std::tie(a.m_total, a.m_completed, a.m_requested) ==
 
  258           std::tie(b.m_total, b.m_completed, b.m_requested);
 
  262std::unordered_map<NodeId, PeerInfo> RecomputePeerInfo(
const Index& index)
 
  264    std::unordered_map<NodeId, PeerInfo> 
ret;
 
  265    for (
const Announcement& ann : index) {
 
  266        PeerInfo& info = 
ret[ann.m_peer];
 
  268        info.m_requested += (ann.GetState() == State::REQUESTED);
 
  269        info.m_completed += (ann.GetState() == State::COMPLETED);
 
  275std::map<uint256, TxHashInfo> ComputeTxHashInfo(
const Index& index, 
const PriorityComputer& computer)
 
  277    std::map<uint256, TxHashInfo> 
ret;
 
  278    for (
const Announcement& ann : index) {
 
  279        TxHashInfo& info = 
ret[ann.m_gtxid.ToUint256()];
 
  281        info.m_candidate_delayed += (ann.GetState() == State::CANDIDATE_DELAYED);
 
  282        info.m_candidate_ready += (ann.GetState() == State::CANDIDATE_READY);
 
  283        info.m_candidate_best += (ann.GetState() == State::CANDIDATE_BEST);
 
  284        info.m_requested += (ann.GetState() == State::REQUESTED);
 
  286        if (ann.GetState() == State::CANDIDATE_BEST) {
 
  287            info.m_priority_candidate_best = computer(ann);
 
  289        if (ann.GetState() == State::CANDIDATE_READY) {
 
  290            info.m_priority_best_candidate_ready = std::max(info.m_priority_best_candidate_ready, computer(ann));
 
  293        info.m_peers.push_back(ann.m_peer);
 
  324            TxHashInfo& info = item.second;
 
  327            assert(info.m_candidate_delayed + info.m_candidate_ready + info.m_candidate_best + info.m_requested > 0);
 
  330            assert(info.m_candidate_best + info.m_requested <= 1);
 
  334            if (info.m_candidate_ready > 0) {
 
  335                assert(info.m_candidate_best + info.m_requested == 1);
 
  340            if (info.m_candidate_ready && info.m_candidate_best) {
 
  341                assert(info.m_priority_candidate_best >= info.m_priority_best_candidate_ready);
 
  345            std::sort(info.m_peers.begin(), info.m_peers.end());
 
  346            assert(std::adjacent_find(info.m_peers.begin(), info.m_peers.end()) == info.m_peers.end());
 
  352        for (
const Announcement& ann : 
m_index) {
 
  353            if (ann.IsWaiting()) {
 
  357            } 
else if (ann.IsSelectable()) {
 
  360                assert(ann.m_time <= now);
 
  367    template<
typename Tag>
 
  371        peerit->second.m_completed -= it->GetState() == State::COMPLETED;
 
  372        peerit->second.m_requested -= it->GetState() == State::REQUESTED;
 
  373        if (--peerit->second.m_total == 0) 
m_peerinfo.erase(peerit);
 
  374        return m_index.get<Tag>().erase(it);
 
  378    template<
typename Tag, 
typename Modifier>
 
  379    void Modify(Iter<Tag> it, Modifier modifier)
 
  382        peerit->second.m_completed -= it->GetState() == State::COMPLETED;
 
  383        peerit->second.m_requested -= it->GetState() == State::REQUESTED;
 
  384        m_index.get<Tag>().modify(it, std::move(modifier));
 
  385        peerit->second.m_completed += it->GetState() == State::COMPLETED;
 
  386        peerit->second.m_requested += it->GetState() == State::REQUESTED;
 
  395        assert(it->GetState() == State::CANDIDATE_DELAYED);
 
  397        Modify<ByTxHash>(it, [](Announcement& ann){ ann.SetState(State::CANDIDATE_READY); });
 
  402        auto it_next = std::next(it);
 
  403        if (it_next == 
m_index.get<ByTxHash>().end() || it_next->m_gtxid.ToUint256() != it->m_gtxid.ToUint256() ||
 
  404            it_next->GetState() == State::COMPLETED) {
 
  407            Modify<ByTxHash>(it, [](Announcement& ann){ ann.SetState(State::CANDIDATE_BEST); });
 
  408        } 
else if (it_next->GetState() == State::CANDIDATE_BEST) {
 
  411            if (priority_new > priority_old) {
 
  413                Modify<ByTxHash>(it_next, [](Announcement& ann){ ann.SetState(State::CANDIDATE_READY); });
 
  414                Modify<ByTxHash>(it, [](Announcement& ann){ ann.SetState(State::CANDIDATE_BEST); });
 
  423        assert(new_state == State::COMPLETED || new_state == State::CANDIDATE_DELAYED);
 
  425        if (it->IsSelected() && it != 
m_index.get<ByTxHash>().begin()) {
 
  426            auto it_prev = std::prev(it);
 
  429            if (it_prev->m_gtxid.ToUint256() == it->m_gtxid.ToUint256() && it_prev->GetState() == State::CANDIDATE_READY) {
 
  431                Modify<ByTxHash>(it_prev, [](Announcement& ann){ ann.SetState(State::CANDIDATE_BEST); });
 
  434        Modify<ByTxHash>(it, [new_state](Announcement& ann){ ann.SetState(new_state); });
 
  441        assert(it->GetState() != State::COMPLETED); 
 
  445        if (it != 
m_index.get<ByTxHash>().begin() && std::prev(it)->m_gtxid.ToUint256() == it->m_gtxid.ToUint256()) 
return false;
 
  448        if (std::next(it) != 
m_index.get<ByTxHash>().end() && std::next(it)->m_gtxid.ToUint256() == it->m_gtxid.ToUint256() &&
 
  449            std::next(it)->GetState() != State::COMPLETED) 
return false;
 
  462        if (it->GetState() == State::COMPLETED) 
return true;
 
  466            uint256 txhash = it->m_gtxid.ToUint256();
 
  468                it = Erase<ByTxHash>(it);
 
  469            } 
while (it != 
m_index.get<ByTxHash>().end() && it->m_gtxid.ToUint256() == txhash);
 
  484    void SetTimePoint(std::chrono::microseconds now, std::vector<std::pair<NodeId, GenTxid>>* expired)
 
  486        if (expired) expired->clear();
 
  491            auto it = 
m_index.get<ByTime>().begin();
 
  492            if (it->GetState() == State::CANDIDATE_DELAYED && it->m_time <= now) {
 
  494            } 
else if (it->GetState() == State::REQUESTED && it->m_time <= now) {
 
  495                if (expired) expired->emplace_back(it->m_peer, it->m_gtxid);
 
  506            auto it = std::prev(
m_index.get<ByTime>().end());
 
  507            if (it->IsSelectable() && it->m_time > now) {
 
  516    explicit Impl(
bool deterministic) :
 
  520            boost::make_tuple(ByPeerViewExtractor(), 
std::less<ByPeerView>()),
 
  521            boost::make_tuple(ByTxHashViewExtractor(
m_computer), 
std::less<ByTxHashView>()),
 
  522            boost::make_tuple(ByTimeViewExtractor(), 
std::less<ByTimeView>())
 
  531        auto& index = 
m_index.get<ByPeer>();
 
  532        auto it = index.lower_bound(ByPeerView{peer, 
false, 
uint256::ZERO});
 
  533        while (it != index.end() && it->m_peer == peer) {
 
  547            auto it_next = (std::next(it) == index.end() || std::next(it)->m_peer != peer) ? index.end() :
 
  562        auto it = 
m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});
 
  563        while (it != 
m_index.get<ByTxHash>().end() && it->m_gtxid.ToUint256() == txhash) {
 
  564            it = Erase<ByTxHash>(it);
 
  570        auto it = 
m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});
 
  571        while (it != 
m_index.get<ByTxHash>().end() && it->m_gtxid.ToUint256() == txhash && it->GetState() != State::COMPLETED) {
 
  572            result_peers.push_back(it->m_peer);
 
  578                     std::chrono::microseconds reqtime)
 
  583        if (
m_index.get<ByPeer>().count(ByPeerView{peer, true, gtxid.ToUint256()})) 
return;
 
  589        if (!
ret.second) 
return;
 
  598                                        std::vector<std::pair<NodeId, GenTxid>>* expired)
 
  604        std::vector<const Announcement*> selected;
 
  606        while (it_peer != 
m_index.get<ByPeer>().end() && it_peer->m_peer == peer &&
 
  607            it_peer->GetState() == State::CANDIDATE_BEST) {
 
  608            selected.emplace_back(&*it_peer);
 
  613        std::sort(selected.begin(), selected.end(), [](
const Announcement* a, 
const Announcement* b) {
 
  614            return a->m_sequence < b->m_sequence;
 
  618        std::vector<GenTxid> 
ret;
 
  619        ret.reserve(selected.size());
 
  620        std::transform(selected.begin(), selected.end(), std::back_inserter(
ret), [](
const Announcement* ann) {
 
  628        auto it = 
m_index.get<ByPeer>().find(ByPeerView{peer, 
true, txhash});
 
  629        if (it == 
m_index.get<ByPeer>().end()) {
 
  635            it = 
m_index.get<ByPeer>().find(ByPeerView{peer, 
false, txhash});
 
  636            if (it == 
m_index.get<ByPeer>().end() || (it->GetState() != State::CANDIDATE_DELAYED &&
 
  637                                                      it->GetState() != State::CANDIDATE_READY)) {
 
  647            auto it_old = 
m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_BEST, 0});
 
  648            if (it_old != 
m_index.get<ByTxHash>().end() && it_old->m_gtxid.ToUint256() == txhash) {
 
  649                if (it_old->GetState() == State::CANDIDATE_BEST) {
 
  656                    Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.SetState(State::CANDIDATE_READY); });
 
  657                } 
else if (it_old->GetState() == State::REQUESTED) {
 
  660                    Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.SetState(State::COMPLETED); });
 
  665        Modify<ByPeer>(it, [expiry](Announcement& ann) {
 
  666            ann.SetState(State::REQUESTED);
 
  674        auto it = 
m_index.get<ByPeer>().find(ByPeerView{peer, 
false, txhash});
 
  675        if (it == 
m_index.get<ByPeer>().end()) {
 
  676            it = 
m_index.get<ByPeer>().find(ByPeerView{peer, 
true, txhash});
 
  684        if (it != 
m_peerinfo.end()) 
return it->second.m_requested;
 
  691        if (it != 
m_peerinfo.end()) 
return it->second.m_total - it->second.m_requested - it->second.m_completed;
 
  698        if (it != 
m_peerinfo.end()) 
return it->second.m_total;
 
  708        return uint64_t{
m_computer(txhash, peer, preferred)};
 
  729    m_impl->PostGetRequestableSanityCheck(now);
 
  733                                   std::chrono::microseconds reqtime)
 
  735    m_impl->ReceivedInv(peer, gtxid, preferred, reqtime);
 
  740    m_impl->RequestedTx(peer, txhash, expiry);
 
  745    m_impl->ReceivedResponse(peer, txhash);
 
  749                                                      std::vector<std::pair<NodeId, GenTxid>>* expired)
 
  751    return m_impl->GetRequestable(peer, now, expired);
 
  756    return m_impl->ComputePriority(txhash, peer, preferred);
 
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 It is treated as if this was the little-endian interpretation of ...
 
Actual implementation for TxRequestTracker's data structure.
 
const PriorityComputer m_computer
This tracker's priority computer.
 
std::vector< GenTxid > GetRequestable(NodeId peer, std::chrono::microseconds now, std::vector< std::pair< NodeId, GenTxid > > *expired)
Find the GenTxids to request now from peer.
 
void SetTimePoint(std::chrono::microseconds now, std::vector< std::pair< NodeId, GenTxid > > *expired)
Make the data structure consistent with a given point in time:
 
void PromoteCandidateReady(Iter< ByTxHash > it)
Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY.
 
SequenceNumber m_current_sequence
The current sequence number.
 
Iter< Tag > Erase(Iter< Tag > it)
Wrapper around Index::...::erase that keeps m_peerinfo up to date.
 
void GetCandidatePeers(const uint256 &txhash, std::vector< NodeId > &result_peers) const
 
void ReceivedResponse(NodeId peer, const uint256 &txhash)
 
size_t CountInFlight(NodeId peer) const
 
size_t Size() const
Count how many announcements are being tracked in total across all peers and transactions.
 
void ReceivedInv(NodeId peer, const GenTxid >xid, bool preferred, std::chrono::microseconds reqtime)
 
Impl(const Impl &)=delete
 
bool MakeCompleted(Iter< ByTxHash > it)
Convert any announcement to a COMPLETED one.
 
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
 
Index m_index
This tracker's main data structure. See SanityCheck() for the invariants that apply to it.
 
bool IsOnlyNonCompleted(Iter< ByTxHash > it)
Check if 'it' is the only announcement for a given txhash that isn't COMPLETED.
 
void ForgetTxHash(const uint256 &txhash)
 
uint64_t ComputePriority(const uint256 &txhash, NodeId peer, bool preferred) const
 
std::unordered_map< NodeId, PeerInfo > m_peerinfo
Map with this tracker's per-peer statistics.
 
void RequestedTx(NodeId peer, const uint256 &txhash, std::chrono::microseconds expiry)
 
void ChangeAndReselect(Iter< ByTxHash > it, State new_state)
Change the state of an announcement to something non-IsSelected().
 
size_t CountCandidates(NodeId peer) const
 
Impl & operator=(const Impl &)=delete
 
size_t Count(NodeId peer) const
 
void Modify(Iter< Tag > it, Modifier modifier)
Wrapper around Index::...::modify that keeps m_peerinfo up to date.
 
void DisconnectedPeer(NodeId peer)
 
Data structure to keep track of, and schedule, transaction downloads from peers.
 
void ReceivedInv(NodeId peer, const GenTxid >xid, bool preferred, std::chrono::microseconds reqtime)
Adds a new CANDIDATE announcement.
 
void SanityCheck() const
Run internal consistency check (testing only).
 
size_t CountInFlight(NodeId peer) const
Count how many REQUESTED announcements a peer has.
 
void GetCandidatePeers(const uint256 &txhash, std::vector< NodeId > &result_peers) const
For some txhash (txid or wtxid), finds all peers with non-COMPLETED announcements and appends them to...
 
size_t CountCandidates(NodeId peer) const
Count how many CANDIDATE announcements a peer has.
 
TxRequestTracker(bool deterministic=false)
Construct a TxRequestTracker.
 
const std::unique_ptr< Impl > m_impl
 
void DisconnectedPeer(NodeId peer)
Deletes all announcements for a given peer.
 
void ReceivedResponse(NodeId peer, const uint256 &txhash)
Converts a CANDIDATE or REQUESTED announcement to a COMPLETED one.
 
uint64_t ComputePriority(const uint256 &txhash, NodeId peer, bool preferred) const
Access to the internal priority computation (testing only)
 
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
Run a time-dependent internal consistency check (testing only).
 
void RequestedTx(NodeId peer, const uint256 &txhash, std::chrono::microseconds expiry)
Marks a transaction as requested, with a specified expiry.
 
size_t Count(NodeId peer) const
Count how many announcements a peer has (REQUESTED, CANDIDATE, and COMPLETED combined).
 
size_t Size() const
Count how many announcements are being tracked in total across all peers and transaction hashes.
 
std::vector< GenTxid > GetRequestable(NodeId peer, std::chrono::microseconds now, std::vector< std::pair< NodeId, GenTxid > > *expired=nullptr)
Find the txids to request now from peer.
 
void ForgetTxHash(const uint256 &txhash)
Deletes all announcements for a given txhash (both txid and wtxid ones).
 
static const uint256 ZERO
 
bool operator==(const CNetAddr &a, const CNetAddr &b)