Changeset View
Changeset View
Standalone View
Standalone View
src/txrequest.cpp
Show First 20 Lines • Show All 249 Lines • ▼ Show 20 Lines | |||||
struct PeerInfo { | struct PeerInfo { | ||||
size_t m_total = 0; //!< Total number of announcements for this peer. | size_t m_total = 0; //!< Total number of announcements for this peer. | ||||
size_t m_completed = | size_t m_completed = | ||||
0; //!< Number of COMPLETED announcements for this peer. | 0; //!< Number of COMPLETED announcements for this peer. | ||||
size_t m_requested = | size_t m_requested = | ||||
0; //!< Number of REQUESTED announcements for this peer. | 0; //!< Number of REQUESTED announcements for this peer. | ||||
}; | }; | ||||
/** Per-txhash statistics object. Only used for sanity checking. */ | |||||
struct TxHashInfo { | |||||
//! Number of CANDIDATE_DELAYED announcements for this txhash. | |||||
size_t m_candidate_delayed = 0; | |||||
//! Number of CANDIDATE_READY announcements for this txhash. | |||||
size_t m_candidate_ready = 0; | |||||
//! Number of CANDIDATE_BEST announcements for this txhash (at most one). | |||||
size_t m_candidate_best = 0; | |||||
//! Number of REQUESTED announcements for this txhash (at most one; mutually | |||||
//! exclusive with CANDIDATE_BEST). | |||||
size_t m_requested = 0; | |||||
//! The priority of the CANDIDATE_BEST announcement if one exists, or max() | |||||
//! otherwise. | |||||
Priority m_priority_candidate_best = std::numeric_limits<Priority>::max(); | |||||
//! The highest priority of all CANDIDATE_READY announcements (or min() if | |||||
//! none exist). | |||||
Priority m_priority_best_candidate_ready = | |||||
std::numeric_limits<Priority>::min(); | |||||
//! All peers we have an announcement for this txhash for. | |||||
std::vector<NodeId> m_peers; | |||||
}; | |||||
/** Compare two PeerInfo objects. Only used for sanity checking. */ | |||||
bool operator==(const PeerInfo &a, const PeerInfo &b) { | |||||
return std::tie(a.m_total, a.m_completed, a.m_requested) == | |||||
std::tie(b.m_total, b.m_completed, b.m_requested); | |||||
}; | |||||
/** (Re)compute the PeerInfo map from the index. Only used for sanity checking. | |||||
*/ | |||||
std::unordered_map<NodeId, PeerInfo> RecomputePeerInfo(const Index &index) { | |||||
std::unordered_map<NodeId, PeerInfo> ret; | |||||
for (const Announcement &ann : index) { | |||||
PeerInfo &info = ret[ann.m_peer]; | |||||
++info.m_total; | |||||
info.m_requested += (ann.m_state == State::REQUESTED); | |||||
info.m_completed += (ann.m_state == State::COMPLETED); | |||||
} | |||||
return ret; | |||||
} | |||||
/** Compute the TxHashInfo map. Only used for sanity checking. */ | |||||
std::map<uint256, TxHashInfo> | |||||
ComputeTxHashInfo(const Index &index, const PriorityComputer &computer) { | |||||
std::map<uint256, TxHashInfo> ret; | |||||
for (const Announcement &ann : index) { | |||||
TxHashInfo &info = ret[ann.m_txhash]; | |||||
// Classify how many announcements of each state we have for this | |||||
// txhash. | |||||
info.m_candidate_delayed += (ann.m_state == State::CANDIDATE_DELAYED); | |||||
info.m_candidate_ready += (ann.m_state == State::CANDIDATE_READY); | |||||
info.m_candidate_best += (ann.m_state == State::CANDIDATE_BEST); | |||||
info.m_requested += (ann.m_state == State::REQUESTED); | |||||
// And track the priority of the best CANDIDATE_READY/CANDIDATE_BEST | |||||
// announcements. | |||||
if (ann.m_state == State::CANDIDATE_BEST) { | |||||
info.m_priority_candidate_best = computer(ann); | |||||
} | |||||
if (ann.m_state == State::CANDIDATE_READY) { | |||||
info.m_priority_best_candidate_ready = | |||||
std::max(info.m_priority_best_candidate_ready, computer(ann)); | |||||
} | |||||
// Also keep track of which peers this txhash has an announcement for | |||||
// (so we can detect duplicates). | |||||
info.m_peers.push_back(ann.m_peer); | |||||
} | |||||
return ret; | |||||
} | |||||
} // namespace | } // namespace | ||||
/** Actual implementation for TxRequestTracker's data structure. */ | /** Actual implementation for TxRequestTracker's data structure. */ | ||||
class TxRequestTracker::Impl { | class TxRequestTracker::Impl { | ||||
//! The current sequence number. Increases for every announcement. This is | //! The current sequence number. Increases for every announcement. This is | ||||
//! used to sort txhashes returned by GetRequestable in announcement order. | //! used to sort txhashes returned by GetRequestable in announcement order. | ||||
SequenceNumber m_current_sequence{0}; | SequenceNumber m_current_sequence{0}; | ||||
//! This tracker's priority computer. | //! This tracker's priority computer. | ||||
const PriorityComputer m_computer; | const PriorityComputer m_computer; | ||||
//! This tracker's main data structure. | //! This tracker's main data structure. See SanityCheck() for the invariants | ||||
//! that apply to it. | |||||
Index m_index; | Index m_index; | ||||
//! Map with this tracker's per-peer statistics. | //! Map with this tracker's per-peer statistics. | ||||
std::unordered_map<NodeId, PeerInfo> m_peerinfo; | std::unordered_map<NodeId, PeerInfo> m_peerinfo; | ||||
public: | |||||
void SanityCheck() const { | |||||
// Recompute m_peerdata from m_index. This verifies the data in it as it | |||||
// should just be caching statistics on m_index. It also verifies the | |||||
// invariant that no PeerInfo announcements with m_total==0 exist. | |||||
assert(m_peerinfo == RecomputePeerInfo(m_index)); | |||||
// Calculate per-txhash statistics from m_index, and validate | |||||
// invariants. | |||||
for (auto &item : ComputeTxHashInfo(m_index, m_computer)) { | |||||
TxHashInfo &info = item.second; | |||||
// Cannot have only COMPLETED peer (txhash should have been | |||||
// forgotten already) | |||||
assert(info.m_candidate_delayed + info.m_candidate_ready + | |||||
info.m_candidate_best + info.m_requested > | |||||
0); | |||||
// Can have at most 1 CANDIDATE_BEST/REQUESTED peer | |||||
assert(info.m_candidate_best + info.m_requested <= 1); | |||||
// If there are any CANDIDATE_READY announcements, there must be | |||||
// exactly one CANDIDATE_BEST or REQUESTED announcement. | |||||
if (info.m_candidate_ready > 0) { | |||||
assert(info.m_candidate_best + info.m_requested == 1); | |||||
} | |||||
// If there is both a CANDIDATE_READY and a CANDIDATE_BEST | |||||
// announcement, the CANDIDATE_BEST one must be at least as good | |||||
// (equal or higher priority) as the best CANDIDATE_READY. | |||||
if (info.m_candidate_ready && info.m_candidate_best) { | |||||
assert(info.m_priority_candidate_best >= | |||||
info.m_priority_best_candidate_ready); | |||||
} | |||||
// No txhash can have been announced by the same peer twice. | |||||
std::sort(info.m_peers.begin(), info.m_peers.end()); | |||||
assert( | |||||
std::adjacent_find(info.m_peers.begin(), info.m_peers.end()) == | |||||
info.m_peers.end()); | |||||
} | |||||
} | |||||
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const { | |||||
for (const Announcement &ann : m_index) { | |||||
if (ann.IsWaiting()) { | |||||
// REQUESTED and CANDIDATE_DELAYED must have a time in the | |||||
// future (they should have been converted to | |||||
// COMPLETED/CANDIDATE_READY respectively). | |||||
assert(ann.m_time > now); | |||||
} else if (ann.IsSelectable()) { | |||||
// CANDIDATE_READY and CANDIDATE_BEST cannot have a time in the | |||||
// future (they should have remained CANDIDATE_DELAYED, or | |||||
// should have been converted back to it if time went | |||||
// backwards). | |||||
assert(ann.m_time <= now); | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
//! Wrapper around Index::...::erase that keeps m_peerinfo up to date. | //! Wrapper around Index::...::erase that keeps m_peerinfo up to date. | ||||
template <typename Tag> Iter<Tag> Erase(Iter<Tag> it) { | template <typename Tag> Iter<Tag> Erase(Iter<Tag> it) { | ||||
auto peerit = m_peerinfo.find(it->m_peer); | auto peerit = m_peerinfo.find(it->m_peer); | ||||
peerit->second.m_completed -= it->m_state == State::COMPLETED; | peerit->second.m_completed -= it->m_state == State::COMPLETED; | ||||
peerit->second.m_requested -= it->m_state == State::REQUESTED; | peerit->second.m_requested -= it->m_state == State::REQUESTED; | ||||
if (--peerit->second.m_total == 0) { | if (--peerit->second.m_total == 0) { | ||||
m_peerinfo.erase(peerit); | m_peerinfo.erase(peerit); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 394 Lines • ▼ Show 20 Lines | size_t Count(NodeId peer) const { | ||||
return it->second.m_total; | return it->second.m_total; | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
//! Count how many announcements are being tracked in total across all peers | //! Count how many announcements are being tracked in total across all peers | ||||
//! and transactions. | //! and transactions. | ||||
size_t Size() const { return m_index.size(); } | size_t Size() const { return m_index.size(); } | ||||
uint64_t ComputePriority(const uint256 &txhash, NodeId peer, | |||||
bool preferred) const { | |||||
// Return Priority as a uint64_t as Priority is internal. | |||||
return uint64_t{m_computer(txhash, peer, preferred)}; | |||||
} | |||||
}; | }; | ||||
TxRequestTracker::TxRequestTracker(bool deterministic) | TxRequestTracker::TxRequestTracker(bool deterministic) | ||||
: m_impl{std::make_unique<TxRequestTracker::Impl>(deterministic)} {} | : m_impl{std::make_unique<TxRequestTracker::Impl>(deterministic)} {} | ||||
TxRequestTracker::~TxRequestTracker() = default; | TxRequestTracker::~TxRequestTracker() = default; | ||||
void TxRequestTracker::ForgetTxHash(const uint256 &txhash) { | void TxRequestTracker::ForgetTxHash(const uint256 &txhash) { | ||||
Show All 9 Lines | size_t TxRequestTracker::CountCandidates(NodeId peer) const { | ||||
return m_impl->CountCandidates(peer); | return m_impl->CountCandidates(peer); | ||||
} | } | ||||
size_t TxRequestTracker::Count(NodeId peer) const { | size_t TxRequestTracker::Count(NodeId peer) const { | ||||
return m_impl->Count(peer); | return m_impl->Count(peer); | ||||
} | } | ||||
size_t TxRequestTracker::Size() const { | size_t TxRequestTracker::Size() const { | ||||
return m_impl->Size(); | return m_impl->Size(); | ||||
} | } | ||||
void TxRequestTracker::SanityCheck() const { | |||||
m_impl->SanityCheck(); | |||||
} | |||||
void TxRequestTracker::PostGetRequestableSanityCheck( | |||||
std::chrono::microseconds now) const { | |||||
m_impl->PostGetRequestableSanityCheck(now); | |||||
} | |||||
void TxRequestTracker::ReceivedInv(NodeId peer, const TxId >xid, | void TxRequestTracker::ReceivedInv(NodeId peer, const TxId >xid, | ||||
bool preferred, | bool preferred, | ||||
std::chrono::microseconds reqtime) { | std::chrono::microseconds reqtime) { | ||||
m_impl->ReceivedInv(peer, gtxid, preferred, reqtime); | m_impl->ReceivedInv(peer, gtxid, preferred, reqtime); | ||||
} | } | ||||
void TxRequestTracker::RequestedTx(NodeId peer, const uint256 &txhash, | void TxRequestTracker::RequestedTx(NodeId peer, const uint256 &txhash, | ||||
std::chrono::microseconds expiry) { | std::chrono::microseconds expiry) { | ||||
m_impl->RequestedTx(peer, txhash, expiry); | m_impl->RequestedTx(peer, txhash, expiry); | ||||
} | } | ||||
void TxRequestTracker::ReceivedResponse(NodeId peer, const uint256 &txhash) { | void TxRequestTracker::ReceivedResponse(NodeId peer, const uint256 &txhash) { | ||||
m_impl->ReceivedResponse(peer, txhash); | m_impl->ReceivedResponse(peer, txhash); | ||||
} | } | ||||
std::vector<TxId> | std::vector<TxId> | ||||
TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now) { | TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now) { | ||||
return m_impl->GetRequestable(peer, now); | return m_impl->GetRequestable(peer, now); | ||||
} | } | ||||
uint64_t TxRequestTracker::ComputePriority(const uint256 &txhash, NodeId peer, | |||||
bool preferred) const { | |||||
return m_impl->ComputePriority(txhash, peer, preferred); | |||||
} |