From 5e64982541f301773156a87988c60ca7797a3f06 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Sat, 17 Jan 2026 18:29:37 -0500 Subject: [PATCH] net: Add PrivateBroadcast::GetBroadcastInfo Co-authored-by: Daniela Brozzoni Co-authored-by: l0rinc --- src/private_broadcast.cpp | 19 +++++++++++++ src/private_broadcast.h | 17 +++++++++++ src/test/private_broadcast_tests.cpp | 42 ++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/private_broadcast.cpp b/src/private_broadcast.cpp index fd36a94088e..3900f10a932 100644 --- a/src/private_broadcast.cpp +++ b/src/private_broadcast.cpp @@ -104,6 +104,25 @@ std::vector PrivateBroadcast::GetStale() const return stale; } +std::vector PrivateBroadcast::GetBroadcastInfo() const + EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) +{ + LOCK(m_mutex); + std::vector entries; + entries.reserve(m_transactions.size()); + + for (const auto& [tx, sent_to] : m_transactions) { + std::vector 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& sent_to) { Priority p; diff --git a/src/private_broadcast.h b/src/private_broadcast.h index 3beed5eef4c..286344248d9 100644 --- a/src/private_broadcast.h +++ b/src/private_broadcast.h @@ -30,6 +30,17 @@ class PrivateBroadcast { public: + struct PeerSendInfo { + CService address; + NodeClock::time_point sent; + std::optional received; + }; + + struct TxBroadcastInfo { + CTransactionRef tx; + std::vector peers; + }; + /** * Add a transaction to the storage. * @param[in] tx The transaction to add. @@ -97,6 +108,12 @@ public: std::vector GetStale() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + /** + * Get stats about all transactions currently being privately broadcast. + */ + std::vector GetBroadcastInfo() const + EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); + private: /// Status of a transaction sent to a given node. struct SendStatus { diff --git a/src/test/private_broadcast_tests.cpp b/src/test/private_broadcast_tests.cpp index e0ab64cd4c2..73bdbbb54f6 100644 --- a/src/test/private_broadcast_tests.cpp +++ b/src/test/private_broadcast_tests.cpp @@ -7,6 +7,7 @@ #include #include +#include #include 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()