Skip to content

Commit 325afe6

Browse files
committed
net: delay stale evaluation and expose time_added in private broadcast
1 parent 999d18a commit 325afe6

5 files changed

Lines changed: 50 additions & 12 deletions

File tree

src/net_processing.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,8 @@ std::vector<CTransactionRef> PeerManagerImpl::AbortPrivateBroadcast(const uint25
18631863
std::vector<CTransactionRef> removed_txs;
18641864

18651865
size_t connections_cancelled{0};
1866-
for (const auto& [tx, _] : snapshot) {
1866+
for (const auto& tx_info : snapshot) {
1867+
const CTransactionRef& tx{tx_info.tx};
18671868
if (tx->GetHash().ToUint256() != id && tx->GetWitnessHash().ToUint256() != id) continue;
18681869
if (const auto peer_acks{m_tx_for_private_broadcast.Remove(tx)}) {
18691870
removed_txs.push_back(tx);

src/private_broadcast.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77

88
#include <algorithm>
99

10-
/// If a transaction is not received back from the network for this duration
11-
/// after it is broadcast, then we consider it stale / for rebroadcasting.
12-
static constexpr auto STALE_DURATION{1min};
1310

1411
bool PrivateBroadcast::Add(const CTransactionRef& tx)
1512
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
@@ -93,12 +90,14 @@ std::vector<CTransactionRef> PrivateBroadcast::GetStale() const
9390
EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
9491
{
9592
LOCK(m_mutex);
96-
const auto stale_time{NodeClock::now() - STALE_DURATION};
93+
const auto now{NodeClock::now()};
9794
std::vector<CTransactionRef> stale;
9895
for (const auto& [tx, state] : m_transactions) {
9996
const Priority p{DerivePriority(state.send_statuses)};
100-
if (p.last_confirmed < stale_time) {
101-
stale.push_back(tx);
97+
if (p.num_confirmed == 0) {
98+
if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx);
99+
} else {
100+
if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx);
102101
}
103102
}
104103
return stale;
@@ -117,7 +116,7 @@ std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInf
117116
for (const auto& status : state.send_statuses) {
118117
peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});
119118
}
120-
entries.emplace_back(TxBroadcastInfo{.tx = tx, .peers = std::move(peers)});
119+
entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .peers = std::move(peers)});
121120
}
122121

123122
return entries;

src/private_broadcast.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@
3030
class PrivateBroadcast
3131
{
3232
public:
33+
34+
/// If a transaction is not sent to any peer for this duration,
35+
/// then we consider it stale / for rebroadcasting.
36+
static constexpr auto INITIAL_STALE_DURATION{5min};
37+
38+
/// If a transaction is not received back from the network for this duration
39+
/// after it is broadcast, then we consider it stale / for rebroadcasting.
40+
static constexpr auto STALE_DURATION{1min};
41+
3342
struct PeerSendInfo {
3443
CService address;
3544
NodeClock::time_point sent;
@@ -38,6 +47,7 @@ class PrivateBroadcast
3847

3948
struct TxBroadcastInfo {
4049
CTransactionRef tx;
50+
NodeClock::time_point time_added;
4151
std::vector<PeerSendInfo> peers;
4252
};
4353

src/rpc/mempool.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ static RPCHelpMan getprivatebroadcastinfo()
155155
{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"},
156156
{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"},
157157
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded transaction data"},
158+
{RPCResult::Type::NUM_TIME, "time_added", "The time this transaction was added to the private broadcast queue (seconds since epoch)"},
158159
{RPCResult::Type::ARR, "peers", "Per-peer send and acknowledgment information for this transaction",
159160
{
160161
{RPCResult::Type::OBJ, "", "",
@@ -183,6 +184,7 @@ static RPCHelpMan getprivatebroadcastinfo()
183184
o.pushKV("txid", tx_info.tx->GetHash().ToString());
184185
o.pushKV("wtxid", tx_info.tx->GetWitnessHash().ToString());
185186
o.pushKV("hex", EncodeHexTx(*tx_info.tx));
187+
o.pushKV("time_added", TicksSinceEpoch<std::chrono::seconds>(tx_info.time_added));
186188
UniValue peers(UniValue::VARR);
187189
for (const auto& peer : tx_info.peers) {
188190
UniValue p(UniValue::VOBJ);

src/test/private_broadcast_tests.cpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ BOOST_AUTO_TEST_CASE(basic)
9090
BOOST_CHECK(!pb.DidNodeConfirmReception(recipient2));
9191
BOOST_CHECK(!pb.DidNodeConfirmReception(nonexistent_recipient));
9292

93+
// 1. Freshly added transactions should NOT be stale yet.
94+
BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
95+
96+
// 2. Fast-forward the mock clock past the INITIAL_STALE_DURATION.
97+
SetMockTime(Now<NodeSeconds>() + PrivateBroadcast::INITIAL_STALE_DURATION + 1min);
98+
99+
// 3. Now that the initial duration has passed, both unconfirmed transactions should be stale.
93100
BOOST_CHECK_EQUAL(pb.GetStale().size(), 2);
94101

95102
// Confirm reception by recipient1.
@@ -102,20 +109,21 @@ BOOST_AUTO_TEST_CASE(basic)
102109
const auto infos{pb.GetBroadcastInfo()};
103110
BOOST_CHECK_EQUAL(infos.size(), 2);
104111
{
105-
const auto& [tx, peers]{find_tx_info(infos, tx_for_recipient1)};
112+
const auto& peers{find_tx_info(infos, tx_for_recipient1).peers};
106113
BOOST_CHECK_EQUAL(peers.size(), 1);
107114
BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr1.ToStringAddrPort());
108115
BOOST_CHECK(peers[0].received.has_value());
109116
}
110117
{
111-
const auto& [tx, peers]{find_tx_info(infos, tx_for_recipient2)};
118+
const auto& peers{find_tx_info(infos, tx_for_recipient2).peers};
112119
BOOST_CHECK_EQUAL(peers.size(), 1);
113120
BOOST_CHECK_EQUAL(peers[0].address.ToStringAddrPort(), addr2.ToStringAddrPort());
114121
BOOST_CHECK(!peers[0].received.has_value());
115122
}
116123

117-
BOOST_CHECK_EQUAL(pb.GetStale().size(), 1);
118-
BOOST_CHECK_EQUAL(pb.GetStale()[0], tx_for_recipient2);
124+
const auto stale_state{pb.GetStale()};
125+
BOOST_CHECK_EQUAL(stale_state.size(), 1);
126+
BOOST_CHECK_EQUAL(stale_state[0], tx_for_recipient2);
119127

120128
SetMockTime(Now<NodeSeconds>() + 10h);
121129

@@ -131,4 +139,22 @@ BOOST_AUTO_TEST_CASE(basic)
131139
BOOST_CHECK(!pb.PickTxForSend(/*will_send_to_nodeid=*/nonexistent_recipient, /*will_send_to_address=*/addr_nonexistent).has_value());
132140
}
133141

142+
BOOST_AUTO_TEST_CASE(stale_unpicked_tx)
143+
{
144+
SetMockTime(Now<NodeSeconds>());
145+
146+
PrivateBroadcast pb;
147+
const auto tx{MakeDummyTx(/*id=*/42, /*num_witness=*/0)};
148+
BOOST_REQUIRE(pb.Add(tx));
149+
150+
// Unpicked transactions use the longer INITIAL_STALE_DURATION.
151+
BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
152+
SetMockTime(Now<NodeSeconds>() + PrivateBroadcast::INITIAL_STALE_DURATION - 1min);
153+
BOOST_CHECK_EQUAL(pb.GetStale().size(), 0);
154+
SetMockTime(Now<NodeSeconds>() + 2min);
155+
const auto stale_state{pb.GetStale()};
156+
BOOST_REQUIRE_EQUAL(stale_state.size(), 1);
157+
BOOST_CHECK_EQUAL(stale_state[0], tx);
158+
}
159+
134160
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)