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.
1// Copyright (c) 2023-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or https://opensource.org/license/mit/.5#include <private_broadcast.h>7#include <util/check.h>9#include <algorithm>10#include <ranges>13PrivateBroadcast::AddResult PrivateBroadcast::Add(const CTransactionRef& tx)14EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)15{16LOCK(m_mutex);17// Cleanup finished transactions18std::erase_if(m_transactions, [this](const auto& entry) {19const auto& state{entry.second};20return state.resolved && !IsPending(state) &&21std::ranges::all_of(state.send_statuses, [](const auto& status) { return status.disconnected; });22});24if (const auto it{m_transactions.find(tx)}; it != m_transactions.end()) {25if (it->second.send_statuses.size() < m_max_send_attempts) return AddResult::AlreadyPresent;27// A transaction that has reached m_max_send_attempts can be explicitly retried by adding it again.28it->second.time_added = NodeClock::now();29it->second.send_statuses.clear();30it->second.planned_sends = INITIAL_CONNECTION_COUNT;31it->second.resolved = false;32return AddResult::Added;33}35if (m_transactions.size() >= m_max_transactions) return AddResult::QueueFull;37m_transactions.try_emplace(tx);38return AddResult::Added;39}41std::optional<size_t> PrivateBroadcast::Remove(const CTransactionRef& tx)42EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)43{44LOCK(m_mutex);45const auto handle{m_transactions.extract(tx)};46if (handle) {47const auto& state{handle.mapped()};48const size_t planned{std::min(state.planned_sends, m_max_send_attempts)};49const size_t actual{state.send_statuses.size()};50return planned > actual ? planned - actual : 0;51}52return std::nullopt;53}55bool PrivateBroadcast::MarkResolved(const CTransactionRef& tx)56{57LOCK(m_mutex);58const auto it{m_transactions.find(tx)};59if (it == m_transactions.end()) return false;60it->second.resolved = true;61return true;62}64void PrivateBroadcast::NodeDisconnected(NodeId nodeid)65{66LOCK(m_mutex);67if (const auto entry{GetSendStatusByNode(nodeid)}) {68entry->send_status.disconnected = true;69}70}72bool PrivateBroadcast::TryGrantRetry(const CTransactionRef& tx)73{74LOCK(m_mutex);75const auto it{m_transactions.find(tx)};76if (it == m_transactions.end() ||77it->second.resolved ||78IsPending(it->second) ||79it->second.planned_sends >= m_max_send_attempts) return false;80++it->second.planned_sends;81return true;82}84std::optional<CTransactionRef> PrivateBroadcast::PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address)85EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)86{87LOCK(m_mutex);89if (GetSendStatusByNode(will_send_to_nodeid).has_value()) { // nodeid reuse, shouldn't send >1 tx to a given node90Assume(false);91return std::nullopt;92}94auto pending_transactions{m_transactions | std::views::filter([this](const auto& entry) { return IsPending(entry.second); })};95const auto it{std::ranges::max_element(96pending_transactions,97[](const auto& a, const auto& b) { return a < b; },98[](const auto& el) { return DerivePriority(el.second.send_statuses); })};100if (it != pending_transactions.end()) {101auto& [tx, state]{*it};102state.send_statuses.emplace_back(will_send_to_nodeid, will_send_to_address, NodeClock::now());103return tx;104}106return std::nullopt;107}109std::optional<CTransactionRef> PrivateBroadcast::GetTxForNode(const NodeId& nodeid)110EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)111{112LOCK(m_mutex);113const auto tx_and_status{GetSendStatusByNode(nodeid)};114if (tx_and_status.has_value()) {115return tx_and_status.value().tx;116}117return std::nullopt;118}120void PrivateBroadcast::NodeConfirmedReception(const NodeId& nodeid)121EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)122{123LOCK(m_mutex);124const auto tx_and_status{GetSendStatusByNode(nodeid)};125if (tx_and_status.has_value()) {126tx_and_status.value().send_status.confirmed = NodeClock::now();127}128}130bool PrivateBroadcast::HavePendingTransactions()131EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)132{133LOCK(m_mutex);134return std::ranges::any_of(m_transactions, [this](const auto& entry) { return IsPending(entry.second); });135}137std::vector<CTransactionRef> PrivateBroadcast::GetStale() const138EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)139{140LOCK(m_mutex);141const auto now{NodeClock::now()};142std::vector<CTransactionRef> stale;143for (const auto& [tx, state] : m_transactions) {144if (state.resolved || state.planned_sends >= m_max_send_attempts) continue;145const Priority p{DerivePriority(state.send_statuses)};146if (p.num_confirmed == 0) {147if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx);148} else {149if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx);150}151}152return stale;153}155std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const156EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)157{158LOCK(m_mutex);159std::vector<TxBroadcastInfo> entries;160entries.reserve(m_transactions.size());162for (const auto& [tx, state] : m_transactions) {163if (state.resolved) continue;164std::vector<PeerSendInfo> peers;165peers.reserve(state.send_statuses.size());166for (const auto& status : state.send_statuses) {167peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});168}169const size_t attempts_remaining{m_max_send_attempts - std::min(state.send_statuses.size(), m_max_send_attempts)};170entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .attempts_remaining = attempts_remaining, .peers = std::move(peers)});171}173return entries;174}176bool PrivateBroadcast::IsPending(const TxSendStatus& status) const177{178// Deliberately ignore resolved so all initially scheduled connections can complete.179const size_t limit{std::min(status.planned_sends, m_max_send_attempts)};180return status.send_statuses.size() < limit;181}183PrivateBroadcast::Priority PrivateBroadcast::DerivePriority(const std::vector<SendStatus>& sent_to)184{185Priority p;186p.num_picked = sent_to.size();187for (const auto& send_status : sent_to) {188p.last_picked = std::max(p.last_picked, send_status.picked);189if (send_status.confirmed.has_value()) {190++p.num_confirmed;191p.last_confirmed = std::max(p.last_confirmed, send_status.confirmed.value());192}193}194return p;195}197std::optional<PrivateBroadcast::TxAndSendStatusForNode> PrivateBroadcast::GetSendStatusByNode(const NodeId& nodeid)198EXCLUSIVE_LOCKS_REQUIRED(m_mutex)199{200AssertLockHeld(m_mutex);201for (auto& [tx, state] : m_transactions) {202for (auto& send_status : state.send_statuses) {203if (send_status.nodeid == nodeid) {204return TxAndSendStatusForNode{.tx = tx, .send_status = send_status};205}206}207}208return std::nullopt;209}
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.