test: add unit test for the private broadcast storage
What changed, and why it matters
This commit only adds a new unit test file for an existing feature called private broadcast storage. It does not change any production code, so it cannot introduce a security vulnerability or fix one on its own.
No security action needed. Treat as routine test coverage addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit adds src/test/private_broadcast_tests.cpp and registers it in src/test/CMakeLists.txt. The test exercises the PrivateBroadcast class: adding transactions, picking them for recipients, confirming reception, retrieving stale entries, and removing entries. There are no modifications to src/ production code, consensus logic, networking, or wallet behavior.
Changed components
src/test/private_broadcast_tests.cppsrc/test/CMakeLists.txtInspect captured patch +97 / −0
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 83cb989a..e70eaf5c 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -76,6 +76,7 @@ add_executable(test_bitcoin
pool_tests.cpp
pow_tests.cpp
prevector_tests.cpp
+ private_broadcast_tests.cpp
raii_event_tests.cpp
random_tests.cpp
rbf_tests.cpp
diff --git a/src/test/private_broadcast_tests.cpp b/src/test/private_broadcast_tests.cpp
new file mode 100644
index 00000000..6c5ef36f
--- /dev/null
+++ b/src/test/private_broadcast_tests.cpp
@@ -0,0 +1,96 @@
+// Copyright (c) 2025-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <primitives/transaction.h>
+#include <private_broadcast.h>
+#include <test/util/setup_common.h>
+#include <util/time.h>
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)
+
+static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)
+{
+ CMutableTransaction mtx;
+ mtx.vin.resize(1);
+ mtx.vin[0].nSequence = id;
+ if (num_witness > 0) {
+ mtx.vin[0].scriptWitness = CScriptWitness{};
+ mtx.vin[0].scriptWitness.stack.resize(num_witness);
+ }
+ return MakeTransactionRef(mtx);
+}
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+ SetMockTime(Now<NodeSeconds>());
+
+ PrivateBroadcast pb;
+ const NodeId recipient1{1};
+
+ // No transactions initially.
+ BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1).has_value());
+ BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
+ BOOST_CHECK(!pb.HavePendingTransactions());
+
+ // Make a transaction and add it.
+ const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};
+
+ BOOST_CHECK(pb.Add(tx1));
+ BOOST_CHECK(!pb.Add(tx1));
+
+ // Make another transaction with same txid, different wtxid and add it.
+ const auto tx2{MakeDummyTx(/*id=*/1, /*num_witness=*/1)};
+ BOOST_REQUIRE(tx1->GetHash() == tx2->GetHash());
+ BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());
+
+ BOOST_CHECK(pb.Add(tx2));
+
+ const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1).value()};
+ BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);
+
+ // A second pick must return the other transaction.
+ const NodeId recipient2{2};
+ const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2).value()};
+ BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);
+ BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);
+
+ const NodeId nonexistent_recipient{0};
+
+ // Confirm transactions <-> recipients mapping is correct.
+ BOOST_CHECK(!pb.GetTxForNode(nonexistent_recipient).has_value());
+ BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient1).value(), tx_for_recipient1);
+ BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient2).value(), tx_for_recipient2);
+
+ // Confirm none of the transactions' reception have been confirmed.
+ BOOST_CHECK(!pb.DidNodeConfirmReception(recipient1));
+ BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
+ BOOST_CHECK(!pb.DidNodeConfirmReception(nonexistent_recipient));
+
+ BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
+
+ // Confirm reception by recipient1.
+ pb.NodeConfirmedReception(nonexistent_recipient); // Dummy call.
+ pb.NodeConfirmedReception(recipient1);
+
+ BOOST_CHECK(pb.DidNodeConfirmReception(recipient1));
+ BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
+
+ BOOST_CHECK_EQUAL(pb.GetStale().size(), 1);
+ BOOST_CHECK_EQUAL(pb.GetStale()[0], tx_for_recipient2);
+
+ SetMockTime(Now<NodeSeconds>() + 10h);
+
+ BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
+
+ BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 1);
+ BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());
+ 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).has_value());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.