net: Add PrivateBroadcast::GetBroadcastInfo

Co-authored-by: Daniela Brozzoni <danielabrozzoni@protonmail.com>
Co-authored-by: l0rinc <pap.lorinc@gmail.com>
This commit is contained in:
Andrew Toth 2026-01-17 18:29:37 -05:00
parent 573bb542be
commit 5e64982541
No known key found for this signature in database
GPG Key ID: 60007AFC8938B018
3 changed files with 75 additions and 3 deletions

View File

@ -104,6 +104,25 @@ std::vector<CTransactionRef> PrivateBroadcast::GetStale() const
return stale;
}
std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
{
LOCK(m_mutex);
std::vector<TxBroadcastInfo> entries;
entries.reserve(m_transactions.size());
for (const auto& [tx, sent_to] : m_transactions) {
std::vector<PeerSendInfo> peers;
peers.reserve(sent_to.size());
for (const auto& status : sent_to) {
peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});
}
entries.emplace_back(TxBroadcastInfo{.tx = tx, .peers = std::move(peers)});
}
return entries;
}
PrivateBroadcast::Priority PrivateBroadcast::DerivePriority(const std::vector<SendStatus>& sent_to)
{
Priority p;

View File

@ -30,6 +30,17 @@
class PrivateBroadcast
{
public:
struct PeerSendInfo {
CService address;
NodeClock::time_point sent;
std::optional<NodeClock::time_point> received;
};
struct TxBroadcastInfo {
CTransactionRef tx;
std::vector<PeerSendInfo> peers;
};
/**
* Add a transaction to the storage.
* @param[in] tx The transaction to add.
@ -97,6 +108,12 @@ public:
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 {

View File

@ -7,6 +7,7 @@
#include <test/util/setup_common.h>
#include <util/time.h>
#include <algorithm>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
@ -29,12 +30,15 @@ BOOST_AUTO_TEST_CASE(basic)
PrivateBroadcast pb;
const NodeId recipient1{1};
const CService addr1{};
in_addr ipv4Addr;
ipv4Addr.s_addr = 0xa0b0c001;
const CService addr1{ipv4Addr, 1111};
// No transactions initially.
BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).has_value());
BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
BOOST_CHECK(!pb.HavePendingTransactions());
BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
// Make a transaction and add it.
const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
@ -48,17 +52,32 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());
BOOST_CHECK(pb.Add(tx2));
const auto find_tx_info{[](auto& infos, const CTransactionRef& tx) -> const PrivateBroadcast::TxBroadcastInfo& {
const auto it{std::ranges::find(infos, tx->GetWitnessHash(), [](const auto& info) { return info.tx->GetWitnessHash(); })};
BOOST_REQUIRE(it != infos.end());
return *it;
}};
const auto check_peer_counts{[&](size_t tx1_peer_count, size_t tx2_peer_count) {
const auto infos{pb.GetBroadcastInfo()};
BOOST_CHECK_EQUAL(infos.size(), 2);
BOOST_CHECK_EQUAL(find_tx_info(infos, tx1).peers.size(), tx1_peer_count);
BOOST_CHECK_EQUAL(find_tx_info(infos, tx2).peers.size(), tx2_peer_count);
}};
check_peer_counts(/*tx1_peer_count=*/0, /*tx2_peer_count=*/0);
const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).value()};
BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);
// A second pick must return the other transaction.
const NodeId recipient2{2};
const CService addr2{};
const CService addr2{ipv4Addr, 2222};
const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2, /*will_send_to_address=*/addr2).value()};
BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);
BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);
check_peer_counts(/*tx1_peer_count=*/1, /*tx2_peer_count=*/1);
const NodeId nonexistent_recipient{0};
// Confirm transactions <-> recipients mapping is correct.
@ -80,6 +99,21 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_CHECK(pb.DidNodeConfirmReception(recipient1));
BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
const auto infos{pb.GetBroadcastInfo()};
BOOST_CHECK_EQUAL(infos.size(), 2);
{
const auto& [tx, peers]{find_tx_info(infos, tx_for_recipient1)};
BOOST_CHECK_EQUAL(peers.size(), 1);
BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
BOOST_CHECK(peers[0].received.has_value());
}
{
const auto& [tx, peers]{find_tx_info(infos, tx_for_recipient2)};
BOOST_CHECK_EQUAL(peers.size(), 1);
BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
BOOST_CHECK(!peers[0].received.has_value());
}
BOOST_CHECK_EQUAL(pb.GetStale().size(), 1);
BOOST_CHECK_EQUAL(pb.GetStale()[0], tx_for_recipient2);
@ -92,7 +126,9 @@ BOOST_AUTO_TEST_CASE(basic)
BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 0);
BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());
BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/CService{}).has_value());
BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);
const CService addr_nonexistent{ipv4Addr, 3333};
BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
}
BOOST_AUTO_TEST_SUITE_END()