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) 2025-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <primitives/transaction.h>6#include <private_broadcast.h>7#include <test/util/setup_common.h>8#include <test/util/time.h>9#include <util/time.h>11#include <algorithm>12#include <ostream>13#include <boost/test/unit_test.hpp>15std::ostream& operator<<(std::ostream& os, PrivateBroadcast::AddResult r)16{17switch (r) {18case PrivateBroadcast::AddResult::Added: return os << "Added";19case PrivateBroadcast::AddResult::AlreadyPresent: return os << "AlreadyPresent";20case PrivateBroadcast::AddResult::QueueFull: return os << "QueueFull";21} // no default case, so the compiler can warn about missing cases22assert(false);23}25BOOST_FIXTURE_TEST_SUITE(private_broadcast_tests, BasicTestingSetup)27static CTransactionRef MakeDummyTx(uint32_t id, size_t num_witness)28{29CMutableTransaction mtx;30mtx.vin.resize(1);31mtx.vin[0].nSequence = id;32if (num_witness > 0) {33mtx.vin[0].scriptWitness = CScriptWitness{};34mtx.vin[0].scriptWitness.stack.resize(num_witness);35}36return MakeTransactionRef(mtx);37}39BOOST_AUTO_TEST_CASE(basic)40{41FakeNodeClock clock{};43PrivateBroadcast pb;44const NodeId recipient1{1};45in_addr ipv4Addr;46ipv4Addr.s_addr = 0xa0b0c001;47const CService addr1{ipv4Addr, 1111};49// No transactions initially.50BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).has_value());51BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);52BOOST_CHECK(!pb.HavePendingTransactions());53BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);55// Make a transaction and add it.56const auto tx1{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};58BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::Added);59BOOST_CHECK_EQUAL(pb.Add(tx1), PrivateBroadcast::AddResult::AlreadyPresent);61// Make another transaction with same txid, different wtxid and add it.62const auto tx2{MakeDummyTx(/*id=*/1, /*num_witness=*/1)};63BOOST_REQUIRE(tx1->GetHash() == tx2->GetHash());64BOOST_REQUIRE(tx1->GetWitnessHash() != tx2->GetWitnessHash());66BOOST_CHECK_EQUAL(pb.Add(tx2), PrivateBroadcast::AddResult::Added);67const auto find_tx_info{[](auto& infos, const CTransactionRef& tx) -> const PrivateBroadcast::TxBroadcastInfo& {68const auto it{std::ranges::find(infos, tx->GetWitnessHash(), [](const auto& info) { return info.tx->GetWitnessHash(); })};69BOOST_REQUIRE(it != infos.end());70return *it;71}};72const auto check_peer_counts{[&](size_t tx1_peer_count, size_t tx2_peer_count) {73const auto infos{pb.GetBroadcastInfo()};74BOOST_CHECK_EQUAL(infos.size(), 2);75BOOST_CHECK_EQUAL(find_tx_info(infos, tx1).peers.size(), tx1_peer_count);76BOOST_CHECK_EQUAL(find_tx_info(infos, tx2).peers.size(), tx2_peer_count);77}};79check_peer_counts(/*tx1_peer_count=*/0, /*tx2_peer_count=*/0);81const auto tx_for_recipient1{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient1, /*will_send_to_address=*/addr1).value()};82BOOST_CHECK(tx_for_recipient1 == tx1 || tx_for_recipient1 == tx2);84// A second pick must return the other transaction.85const NodeId recipient2{2};86const CService addr2{ipv4Addr, 2222};87const auto tx_for_recipient2{pb.PickTxForSend(/*will_send_to_nodeid=*/recipient2, /*will_send_to_address=*/addr2).value()};88BOOST_CHECK(tx_for_recipient2 == tx1 || tx_for_recipient2 == tx2);89BOOST_CHECK_NE(tx_for_recipient1, tx_for_recipient2);91check_peer_counts(/*tx1_peer_count=*/1, /*tx2_peer_count=*/1);93const NodeId nonexistent_recipient{0};95// Confirm transactions <-> recipients mapping is correct.96BOOST_CHECK(!pb.GetTxForNode(nonexistent_recipient).has_value());97BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient1).value(), tx_for_recipient1);98BOOST_CHECK_EQUAL(pb.GetTxForNode(recipient2).value(), tx_for_recipient2);100// Confirm none of the transactions' reception have been confirmed.101for (const auto& info : pb.GetBroadcastInfo()) {102BOOST_CHECK(std::ranges::none_of(info.peers, [](const auto& peer) { return peer.received.has_value(); }));103}105// 1. Freshly added transactions should NOT be stale yet.106BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);108// 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION.109clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min;111// 3. Now that the initial duration has passed, both unconfirmed transactions should be stale.112BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);114// Confirm reception by recipient1.115pb.NodeConfirmedReception(nonexistent_recipient); // Dummy call.116pb.NodeConfirmedReception(recipient1);118const auto infos{pb.GetBroadcastInfo()};119BOOST_CHECK_EQUAL(infos.size(), 2);120{121const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};122BOOST_CHECK_EQUAL(peers.size(), 1);123BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());124BOOST_CHECK(peers[0].received.has_value());125}126{127const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};128BOOST_CHECK_EQUAL(peers.size(), 1);129BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());130BOOST_CHECK(!peers[0].received.has_value());131}133const auto stale_state{pb.GetStale()};134BOOST_CHECK_EQUAL(stale_state.size(), 1);135BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);137clock += 10h;139BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);141BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient1).value(), 2);142BOOST_CHECK(!pb.Remove(tx_for_recipient1).has_value());143BOOST_CHECK_EQUAL(pb.Remove(tx_for_recipient2).value(), 2);144BOOST_CHECK(!pb.Remove(tx_for_recipient2).has_value());146BOOST_CHECK_EQUAL(pb.GetBroadcastInfo().size(), 0);147const CService addr_nonexistent{ipv4Addr, 3333};148BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());149}151BOOST_AUTO_TEST_CASE(complete_all_initial_sends)152{153PrivateBroadcast pb;154const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};155BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);157in_addr ipv4_addr;158ipv4_addr.s_addr = 0xa0b0c001;159const CService address{ipv4_addr, 1111};161// Pick the first send, then receive the transaction back from the network.162BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/1, address).value(), tx);163BOOST_CHECK_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::AlreadyPresent);164BOOST_REQUIRE(pb.MarkResolved(tx));165BOOST_CHECK_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::AlreadyPresent);166BOOST_REQUIRE_EQUAL(pb.GetTxForNode(1).value(), tx);167BOOST_CHECK(pb.GetBroadcastInfo().empty());169// Receipt must not prevent either of the two remaining initial sends.170BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/2, address).value(), tx);171BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/3, address).value(), tx);173BOOST_CHECK_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::AlreadyPresent);174pb.NodeDisconnected(/*nodeid=*/1);175BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/4, address).has_value());176}178BOOST_AUTO_TEST_CASE(stale_unpicked_tx)179{180FakeNodeClock clock{};182PrivateBroadcast pb;183const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};184BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);186// Unpicked transactions use the longer INITIAL_STALE_DURATION.187BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);188clock += PrivateBroadcast::INITIAL_STALE_DURATION - 1min;189BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);190clock += 2min;191const auto stale_state{pb.GetStale()};192BOOST_REQUIRE_EQUAL(stale_state.size(), 1);193BOOST_CHECK_EQUAL(stale_state[0], tx);194}196BOOST_AUTO_TEST_CASE(send_attempt_limit)197{198FakeNodeClock clock{};200constexpr size_t max_attempts{5};201PrivateBroadcast pb{PrivateBroadcast::MAX_TRANSACTIONS, max_attempts};202const auto tx{MakeDummyTx(/*id=*/1, /*num_witness=*/0)};203BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);205in_addr ipv4_addr;206ipv4_addr.s_addr = 0xa0b0c001;207const CService address{ipv4_addr, 1111};209NodeId node_id{0};210for (size_t attempt{0}; attempt < max_attempts; ++attempt) {211if (attempt >= PrivateBroadcast::INITIAL_CONNECTION_COUNT) {212BOOST_REQUIRE(pb.TryGrantRetry(tx));213}214BOOST_CHECK(pb.HavePendingTransactions());215BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);216}218// The transaction and its complete send history remain available, but no219// further connections should be opened for it.220BOOST_CHECK(!pb.HavePendingTransactions());221BOOST_CHECK(!pb.TryGrantRetry(tx));222BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).has_value());223const auto info{pb.GetBroadcastInfo()};224BOOST_REQUIRE_EQUAL(info.size(), 1);225BOOST_CHECK_EQUAL(info[0].peers.size(), max_attempts);226BOOST_CHECK_EQUAL(info[0].attempts_remaining, 0);228clock += PrivateBroadcast::INITIAL_STALE_DURATION + 1min;229BOOST_CHECK(pb.GetStale().empty());231// An exhausted transaction does not prevent another transaction from being sent.232const auto next_tx{MakeDummyTx(/*id=*/2, /*num_witness=*/0)};233BOOST_REQUIRE_EQUAL(pb.Add(next_tx), PrivateBroadcast::AddResult::Added);234BOOST_CHECK(pb.HavePendingTransactions());235BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), next_tx);237// Re-adding an exhausted transaction resets its state and starts a fresh238// initial stale period.239BOOST_REQUIRE_EQUAL(pb.Add(tx), PrivateBroadcast::AddResult::Added);240BOOST_CHECK(pb.HavePendingTransactions());241BOOST_CHECK(pb.GetStale().empty());242const auto reset_info{pb.GetBroadcastInfo()};243const auto reset_tx_info{std::ranges::find(reset_info, tx->GetWitnessHash(), [](const auto& entry) { return entry.tx->GetWitnessHash(); })};244BOOST_REQUIRE(reset_tx_info != reset_info.end());245BOOST_CHECK(reset_tx_info->peers.empty());246BOOST_CHECK_EQUAL(reset_tx_info->attempts_remaining, max_attempts);247BOOST_REQUIRE_EQUAL(pb.PickTxForSend(/*will_send_to_nodeid=*/node_id++, address).value(), tx);248}250BOOST_AUTO_TEST_CASE(reset_with_equivalent_transaction_reference)
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.