mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-06 13:47:56 +02:00
325afe664dnet: delay stale evaluation and expose time_added in private broadcast (Mccalabrese)999d18ab1cnet: introduce TxSendStatus internal state container (Mccalabrese) Pull request description: **Motivation** Currently, freshly added transactions in `private_broadcast` are almost immediately flagged and logged as stale by the `resend-stale` job. **The Bug** `m_transactions` maps a transaction to a `std::vector<SendStatus>`. When `try_emplace` adds a new transaction, this vector is empty. When `GetStale()` runs, `DerivePriority()` evaluates the empty vector and returns a default `Priority` struct where `last_confirmed` evaluates to the Unix Epoch (Jan 1, 1970). The stale checker sees a 50-year-old timestamp and flags it on the next resend-stale cycle. **The Fix** Rather than modifying the transient `Priority` struct or creating a "Zombie Transaction" edge case by ignoring transactions with 0 picks, this PR modifies the state container: * Wraps the `SendStatus` vector in a new `TxSendStatus` struct inside `private_broadcast.h`. * `TxSendStatus` automatically captures `time_added` upon emplace. * `GetStale()` now checks `p.num_confirmed == 0` to measure age against `time_added` using a new 5-minute `INITIAL_STALE_DURATION` grace period, falling back to `last_confirmed` and the standard 1-minute `STALE_DURATION` once network interaction begins. **Additional Polish** * Exposed `time_added` via the `getprivatebroadcastinfo` RPC endpoint so users can see when a transaction entered the queue. * Added a dedicated `stale_unpicked_tx` test case and updated `private_broadcast_tests.cpp` to properly mock the passage of time for the new grace period. Closes #34862 ACKs for top commit: achow101: ACK325afe664dandrewtoth: ACK325afe664dvasild: ACK325afe664dTree-SHA512: b7790aa5468f7c161ed93e99e9a6d8b4db39ff7d6d6a920764afd18825e08d83bc30b3fb0debeb6175730b5d2496c6be67f3be8674be93f4d07b1e77d17b4a14
199 lines
7.9 KiB
C++
199 lines
7.9 KiB
C++
// Copyright (c) 2023-present The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://opensource.org/license/mit/.
|
|
|
|
#ifndef BITCOIN_PRIVATE_BROADCAST_H
|
|
#define BITCOIN_PRIVATE_BROADCAST_H
|
|
|
|
#include <net.h>
|
|
#include <primitives/transaction.h>
|
|
#include <primitives/transaction_identifier.h>
|
|
#include <sync.h>
|
|
#include <util/time.h>
|
|
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
/**
|
|
* Store a list of transactions to be broadcast privately. Supports the following operations:
|
|
* - Add a new transaction
|
|
* - Remove a transaction
|
|
* - Pick a transaction for sending to one recipient
|
|
* - Query which transaction has been picked for sending to a given recipient node
|
|
* - Mark that a given recipient node has confirmed receipt of a transaction
|
|
* - Query whether a given recipient node has confirmed reception
|
|
* - Query whether any transactions that need sending are currently on the list
|
|
*/
|
|
class PrivateBroadcast
|
|
{
|
|
public:
|
|
|
|
/// If a transaction is not sent to any peer for this duration,
|
|
/// then we consider it stale / for rebroadcasting.
|
|
static constexpr auto INITIAL_STALE_DURATION{5min};
|
|
|
|
/// If a transaction is not received back from the network for this duration
|
|
/// after it is broadcast, then we consider it stale / for rebroadcasting.
|
|
static constexpr auto STALE_DURATION{1min};
|
|
|
|
struct PeerSendInfo {
|
|
CService address;
|
|
NodeClock::time_point sent;
|
|
std::optional<NodeClock::time_point> received;
|
|
};
|
|
|
|
struct TxBroadcastInfo {
|
|
CTransactionRef tx;
|
|
NodeClock::time_point time_added;
|
|
std::vector<PeerSendInfo> peers;
|
|
};
|
|
|
|
/**
|
|
* Add a transaction to the storage.
|
|
* @param[in] tx The transaction to add.
|
|
* @retval true The transaction was added.
|
|
* @retval false The transaction was already present.
|
|
*/
|
|
bool Add(const CTransactionRef& tx)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Forget a transaction.
|
|
* @param[in] tx Transaction to forget.
|
|
* @retval !nullopt The number of times the transaction was sent and confirmed
|
|
* by the recipient (if the transaction existed and was removed).
|
|
* @retval nullopt The transaction was not in the storage.
|
|
*/
|
|
std::optional<size_t> Remove(const CTransactionRef& tx)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Pick the transaction with the fewest send attempts, and confirmations,
|
|
* and oldest send/confirm times.
|
|
* @param[in] will_send_to_nodeid Will remember that the returned transaction
|
|
* was picked for sending to this node.
|
|
* @param[in] will_send_to_address Address of the peer to which this transaction
|
|
* will be sent.
|
|
* @return Most urgent transaction or nullopt if there are no transactions.
|
|
*/
|
|
std::optional<CTransactionRef> PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Get the transaction that was picked for sending to a given node by PickTxForSend().
|
|
* @param[in] nodeid Node to which a transaction is being (or was) sent.
|
|
* @return Transaction or nullopt if the nodeid is unknown.
|
|
*/
|
|
std::optional<CTransactionRef> GetTxForNode(const NodeId& nodeid)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Mark that the node has confirmed reception of the transaction we sent it by
|
|
* responding with `PONG` to our `PING` message.
|
|
* @param[in] nodeid Node that we sent a transaction to.
|
|
*/
|
|
void NodeConfirmedReception(const NodeId& nodeid)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Check if the node has confirmed reception of the transaction.
|
|
* @retval true Node has confirmed, `NodeConfirmedReception()` has been called.
|
|
* @retval false Node has not confirmed, `NodeConfirmedReception()` has not been called.
|
|
*/
|
|
bool DidNodeConfirmReception(const NodeId& nodeid)
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Check if there are transactions that need to be broadcast.
|
|
*/
|
|
bool HavePendingTransactions()
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Get the transactions that have not been broadcast recently.
|
|
*/
|
|
std::vector<CTransactionRef> GetStale() const
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
/**
|
|
* Get stats about all transactions currently being privately broadcast.
|
|
*/
|
|
std::vector<TxBroadcastInfo> GetBroadcastInfo() const
|
|
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
|
|
|
|
private:
|
|
/// Status of a transaction sent to a given node.
|
|
struct SendStatus {
|
|
const NodeId nodeid; /// Node to which the transaction will be sent (or was sent).
|
|
const CService address; /// Address of the node.
|
|
const NodeClock::time_point picked; ///< When was the transaction picked for sending to the node.
|
|
std::optional<NodeClock::time_point> confirmed; ///< When was the transaction reception confirmed by the node (by PONG).
|
|
|
|
SendStatus(const NodeId& nodeid, const CService& address, const NodeClock::time_point& picked) : nodeid{nodeid}, address{address}, picked{picked} {}
|
|
};
|
|
|
|
/// Cumulative stats from all the send attempts for a transaction. Used to prioritize transactions.
|
|
struct Priority {
|
|
size_t num_picked{0}; ///< Number of times the transaction was picked for sending.
|
|
NodeClock::time_point last_picked{}; ///< The most recent time when the transaction was picked for sending.
|
|
size_t num_confirmed{0}; ///< Number of nodes that have confirmed reception of a transaction (by PONG).
|
|
NodeClock::time_point last_confirmed{}; ///< The most recent time when the transaction was confirmed.
|
|
|
|
auto operator<=>(const Priority& other) const
|
|
{
|
|
// Invert `other` and `this` in the comparison because smaller num_picked, num_confirmed or
|
|
// earlier times mean greater priority. In other words, if this.num_picked < other.num_picked
|
|
// then this > other.
|
|
return std::tie(other.num_picked, other.num_confirmed, other.last_picked, other.last_confirmed) <=>
|
|
std::tie(num_picked, num_confirmed, last_picked, last_confirmed);
|
|
}
|
|
};
|
|
|
|
/// A pair of a transaction and a sent status for a given node. Convenience return type of GetSendStatusByNode().
|
|
struct TxAndSendStatusForNode {
|
|
const CTransactionRef& tx;
|
|
SendStatus& send_status;
|
|
};
|
|
|
|
// No need for salted hasher because we are going to store just a bunch of locally originating transactions.
|
|
|
|
struct CTransactionRefHash {
|
|
size_t operator()(const CTransactionRef& tx) const
|
|
{
|
|
return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
|
|
}
|
|
};
|
|
|
|
struct CTransactionRefComp {
|
|
bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
|
|
{
|
|
return a->GetWitnessHash() == b->GetWitnessHash(); // If wtxid equals, then txid also equals.
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Derive the sending priority of a transaction.
|
|
* @param[in] sent_to List of nodes that the transaction has been sent to.
|
|
*/
|
|
static Priority DerivePriority(const std::vector<SendStatus>& sent_to);
|
|
|
|
/**
|
|
* Find which transaction we sent to a given node (marked by PickTxForSend()).
|
|
* @return That transaction together with the send status or nullopt if we did not
|
|
* send any transaction to the given node.
|
|
*/
|
|
std::optional<TxAndSendStatusForNode> GetSendStatusByNode(const NodeId& nodeid)
|
|
EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
|
|
struct TxSendStatus {
|
|
const NodeClock::time_point time_added{NodeClock::now()};
|
|
std::vector<SendStatus> send_statuses;
|
|
};
|
|
mutable Mutex m_mutex;
|
|
std::unordered_map<CTransactionRef, TxSendStatus, CTransactionRefHash, CTransactionRefComp>
|
|
m_transactions GUARDED_BY(m_mutex);
|
|
};
|
|
|
|
#endif // BITCOIN_PRIVATE_BROADCAST_H
|