Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501// If time went backwards, we may need to demote CANDIDATE_BEST and CANDIDATE_READY announcements back502// to CANDIDATE_DELAYED. This is an unusual edge case, and unlikely to matter in production. However,503// it makes it much easier to specify and test TxRequestTracker::Impl's behaviour.504auto it = std::prev(m_index.get<ByTime>().end());505if (it->IsSelectable() && it->m_time > now) {506ChangeAndReselect(m_index.project<ByTxHash>(it), State::CANDIDATE_DELAYED);507} else {508break;509}510}511}513public:514explicit Impl(bool deterministic) :515m_computer(deterministic),516// Explicitly initialize m_index as we need to pass a reference to m_computer to ByTxHashViewExtractor.517m_index(boost::make_tuple(518boost::make_tuple(ByPeerViewExtractor(), std::less<ByPeerView>()),519boost::make_tuple(ByTxHashViewExtractor(m_computer), std::less<ByTxHashView>()),520boost::make_tuple(ByTimeViewExtractor(), std::less<ByTimeView>())521)) {}523// Disable copying and assigning (a default copy won't work due the stateful ByTxHashViewExtractor).524Impl(const Impl&) = delete;525Impl& operator=(const Impl&) = delete;527void DisconnectedPeer(NodeId peer)528{529auto& index = m_index.get<ByPeer>();530auto it = index.lower_bound(ByPeerView{peer, false, uint256::ZERO});531while (it != index.end() && it->m_peer == peer) {532// Check what to continue with after this iteration. 'it' will be deleted in what follows, so we need to533// decide what to continue with afterwards. There are a number of cases to consider:534// - std::next(it) is end() or belongs to a different peer. In that case, this is the last iteration535// of the loop (denote this by setting it_next to end()).536// - 'it' is not the only non-COMPLETED announcement for its txhash. This means it will be deleted, but537// no other Announcement objects will be modified. Continue with std::next(it) if it belongs to the538// same peer, but decide this ahead of time (as 'it' may change position in what follows).539// - 'it' is the only non-COMPLETED announcement for its txhash. This means it will be deleted along540// with all other announcements for the same txhash - which may include std::next(it). However, other541// than 'it', no announcements for the same peer can be affected (due to (peer, txhash) uniqueness).542// In other words, the situation where std::next(it) is deleted can only occur if std::next(it)543// belongs to a different peer but the same txhash as 'it'. This is covered by the first bulletpoint544// already, and we'll have set it_next to end().545auto it_next = (std::next(it) == index.end() || std::next(it)->m_peer != peer) ? index.end() :546std::next(it);547// If the announcement isn't already COMPLETED, first make it COMPLETED (which will mark other548// CANDIDATEs as CANDIDATE_BEST, or delete all of a txhash's announcements if no non-COMPLETED ones are549// left).550if (MakeCompleted(m_index.project<ByTxHash>(it))) {551// Then actually delete the announcement (unless it was already deleted by MakeCompleted).552Erase<ByPeer>(it);553}554it = it_next;555}556}558void ForgetTxHash(const uint256& txhash)559{560auto it = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});561while (it != m_index.get<ByTxHash>().end() && it->m_gtxid.ToUint256() == txhash) {562it = Erase<ByTxHash>(it);563}564}566void GetCandidatePeers(const uint256& txhash, std::vector<NodeId>& result_peers) const567{568auto it = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_DELAYED, 0});569while (it != m_index.get<ByTxHash>().end() && it->m_gtxid.ToUint256() == txhash && it->GetState() != State::COMPLETED) {570result_peers.push_back(it->m_peer);571++it;572}573}575void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,576std::chrono::microseconds reqtime)577{578// Bail out if we already have a CANDIDATE_BEST announcement for this (txhash, peer) combination. The case579// where there is a non-CANDIDATE_BEST announcement already will be caught by the uniqueness property of the580// ByPeer index when we try to emplace the new object below.581if (m_index.get<ByPeer>().count(ByPeerView{peer, true, gtxid.ToUint256()})) return;583// Try creating the announcement with CANDIDATE_DELAYED state (which will fail due to the uniqueness584// of the ByPeer index if a non-CANDIDATE_BEST announcement already exists with the same txhash and peer).585// Bail out in that case.586auto ret = m_index.get<ByPeer>().emplace(gtxid, peer, preferred, reqtime, m_current_sequence);587if (!ret.second) return;589// Update accounting metadata.590++m_peerinfo[peer].m_total;591++m_current_sequence;592}594//! Find the GenTxids to request now from peer.595std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now,596std::vector<std::pair<NodeId, GenTxid>>* expired)597{598// Move time.599SetTimePoint(now, expired);601// Find all CANDIDATE_BEST announcements for this peer.602std::vector<const Announcement*> selected;603auto it_peer = m_index.get<ByPeer>().lower_bound(ByPeerView{peer, true, uint256::ZERO});604while (it_peer != m_index.get<ByPeer>().end() && it_peer->m_peer == peer &&605it_peer->GetState() == State::CANDIDATE_BEST) {606selected.emplace_back(&*it_peer);607++it_peer;608}610// Sort by sequence number.611std::sort(selected.begin(), selected.end(), [](const Announcement* a, const Announcement* b) {612return a->m_sequence < b->m_sequence;613});615// Convert to GenTxid and return.616std::vector<GenTxid> ret;617ret.reserve(selected.size());618std::transform(selected.begin(), selected.end(), std::back_inserter(ret), [](const Announcement* ann) {619return ann->m_gtxid;620});621return ret;622}624void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry)625{626auto it = m_index.get<ByPeer>().find(ByPeerView{peer, true, txhash});627if (it == m_index.get<ByPeer>().end()) {628// There is no CANDIDATE_BEST announcement, look for a _READY or _DELAYED instead. If the caller only629// ever invokes RequestedTx with the values returned by GetRequestable, and no other non-const functions630// other than ForgetTxHash and GetRequestable in between, this branch will never execute (as txhashes631// returned by GetRequestable always correspond to CANDIDATE_BEST announcements).633it = m_index.get<ByPeer>().find(ByPeerView{peer, false, txhash});634if (it == m_index.get<ByPeer>().end() || (it->GetState() != State::CANDIDATE_DELAYED &&635it->GetState() != State::CANDIDATE_READY)) {636// There is no CANDIDATE announcement tracked for this peer, so we have nothing to do. Either this637// txhash wasn't tracked at all (and the caller should have called ReceivedInv), or it was already638// requested and/or completed for other reasons and this is just a superfluous RequestedTx call.639return;640}642// Look for an existing CANDIDATE_BEST or REQUESTED with the same txhash. We only need to do this if the643// found announcement had a different state than CANDIDATE_BEST. If it did, invariants guarantee that no644// other CANDIDATE_BEST or REQUESTED can exist.645auto it_old = m_index.get<ByTxHash>().lower_bound(ByTxHashView{txhash, State::CANDIDATE_BEST, 0});646if (it_old != m_index.get<ByTxHash>().end() && it_old->m_gtxid.ToUint256() == txhash) {647if (it_old->GetState() == State::CANDIDATE_BEST) {648// The data structure's invariants require that there can be at most one CANDIDATE_BEST or one649// REQUESTED announcement per txhash (but not both simultaneously), so we have to convert any650// existing CANDIDATE_BEST to another CANDIDATE_* when constructing another REQUESTED.651// It doesn't matter whether we pick CANDIDATE_READY or _DELAYED here, as SetTimePoint()652// will correct it at GetRequestable() time. If time only goes forward, it will always be653// _READY, so pick that to avoid extra work in SetTimePoint().654Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.SetState(State::CANDIDATE_READY); });655} else if (it_old->GetState() == State::REQUESTED) {656// As we're no longer waiting for a response to the previous REQUESTED announcement, convert it657// to COMPLETED. This also helps guaranteeing progress.658Modify<ByTxHash>(it_old, [](Announcement& ann) { ann.SetState(State::COMPLETED); });659}660}661}663Modify<ByPeer>(it, [expiry](Announcement& ann) {664ann.SetState(State::REQUESTED);665ann.m_time = expiry;666});667}669void ReceivedResponse(NodeId peer, const uint256& txhash)670{671// We need to search the ByPeer index for both (peer, false, txhash) and (peer, true, txhash).672auto it = m_index.get<ByPeer>().find(ByPeerView{peer, false, txhash});673if (it == m_index.get<ByPeer>().end()) {674it = m_index.get<ByPeer>().find(ByPeerView{peer, true, txhash});675}676if (it != m_index.get<ByPeer>().end()) MakeCompleted(m_index.project<ByTxHash>(it));677}679size_t CountInFlight(NodeId peer) const680{681auto it = m_peerinfo.find(peer);682if (it != m_peerinfo.end()) return it->second.m_requested;683return 0;684}686size_t CountCandidates(NodeId peer) const687{688auto it = m_peerinfo.find(peer);689if (it != m_peerinfo.end()) return it->second.m_total - it->second.m_requested - it->second.m_completed;690return 0;691}693size_t Count(NodeId peer) const694{695auto it = m_peerinfo.find(peer);696if (it != m_peerinfo.end()) return it->second.m_total;697return 0;698}700//! Count how many announcements are being tracked in total across all peers and transactions.701size_t Size() const { return m_index.size(); }703uint64_t ComputePriority(const uint256& txhash, NodeId peer, bool preferred) const704{705// Return Priority as a uint64_t as Priority is internal.706return uint64_t{m_computer(txhash, peer, preferred)};707}709};711TxRequestTracker::TxRequestTracker(bool deterministic) :712m_impl{std::make_unique<TxRequestTracker::Impl>(deterministic)} {}714TxRequestTracker::~TxRequestTracker() = default;716void TxRequestTracker::ForgetTxHash(const uint256& txhash) { m_impl->ForgetTxHash(txhash); }717void TxRequestTracker::DisconnectedPeer(NodeId peer) { m_impl->DisconnectedPeer(peer); }718size_t TxRequestTracker::CountInFlight(NodeId peer) const { return m_impl->CountInFlight(peer); }719size_t TxRequestTracker::CountCandidates(NodeId peer) const { return m_impl->CountCandidates(peer); }720size_t TxRequestTracker::Count(NodeId peer) const { return m_impl->Count(peer); }721size_t TxRequestTracker::Size() const { return m_impl->Size(); }722void TxRequestTracker::GetCandidatePeers(const uint256& txhash, std::vector<NodeId>& result_peers) const { return m_impl->GetCandidatePeers(txhash, result_peers); }723void TxRequestTracker::SanityCheck() const { m_impl->SanityCheck(); }725void TxRequestTracker::PostGetRequestableSanityCheck(std::chrono::microseconds now) const726{727m_impl->PostGetRequestableSanityCheck(now);728}730void TxRequestTracker::ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,731std::chrono::microseconds reqtime)732{733m_impl->ReceivedInv(peer, gtxid, preferred, reqtime);734}736void TxRequestTracker::RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry)737{738m_impl->RequestedTx(peer, txhash, expiry);739}741void TxRequestTracker::ReceivedResponse(NodeId peer, const uint256& txhash)742{743m_impl->ReceivedResponse(peer, txhash);744}746std::vector<GenTxid> TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now,747std::vector<std::pair<NodeId, GenTxid>>* expired)748{749return m_impl->GetRequestable(peer, now, expired);750}
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.