Skip to content

Commit fd9a006

Browse files
committedOct 12, 2020
Report and verify expirations
1 parent 86f50ed commit fd9a006

File tree

6 files changed

+64
-13
lines changed

6 files changed

+64
-13
lines changed
 

‎src/net_processing.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -4488,7 +4488,13 @@ bool PeerManager::SendMessages(CNode* pto)
44884488
//
44894489
// Message: getdata (non-blocks)
44904490
//
4491-
for (const GenTxid& gtxid : m_txrequest.GetRequestable(pto->GetId(), current_time)) {
4491+
std::vector<std::pair<NodeId, GenTxid>> expired;
4492+
auto requestable = m_txrequest.GetRequestable(pto->GetId(), current_time, &expired);
4493+
for (const auto& entry : expired) {
4494+
LogPrint(BCLog::NET, "timeout of inflight %s %s from peer=%d\n", entry.second.IsWtxid() ? "wtx" : "tx",
4495+
entry.second.GetHash().ToString(), entry.first);
4496+
}
4497+
for (const GenTxid& gtxid : requestable) {
44924498
if (!AlreadyHaveTx(gtxid, m_mempool)) {
44934499
LogPrint(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx",
44944500
gtxid.GetHash().ToString(), pto->GetId());

‎src/primitives/transaction.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ template <typename Tx> static inline CTransactionRef MakeTransactionRef(Tx&& txI
399399
/** A generic txid reference (txid or wtxid). */
400400
class GenTxid
401401
{
402-
const bool m_is_wtxid;
403-
const uint256 m_hash;
402+
bool m_is_wtxid;
403+
uint256 m_hash;
404404
public:
405405
GenTxid(bool is_wtxid, const uint256& hash) : m_is_wtxid(is_wtxid), m_hash(hash) {}
406406
bool IsWtxid() const { return m_is_wtxid; }

‎src/test/fuzz/txrequest.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@ class Tester
246246

247247
//! list of (sequence number, txhash, is_wtxid) tuples.
248248
std::vector<std::tuple<uint64_t, int, bool>> result;
249+
std::vector<std::pair<NodeId, GenTxid>> expected_expired;
249250
for (int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
250251
// Mark any expired REQUESTED announcements as COMPLETED.
251252
for (int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
252253
Announcement& ann2 = m_announcements[txhash][peer2];
253254
if (ann2.m_state == State::REQUESTED && ann2.m_time <= m_now) {
255+
expected_expired.emplace_back(peer2, GenTxid{ann2.m_is_wtxid, TXHASHES[txhash]});
254256
ann2.m_state = State::COMPLETED;
255257
break;
256258
}
@@ -265,9 +267,13 @@ class Tester
265267
}
266268
// Sort the results by sequence number.
267269
std::sort(result.begin(), result.end());
270+
std::sort(expected_expired.begin(), expected_expired.end());
268271

269272
// Compare with TxRequestTracker's implementation.
270-
const auto actual = m_tracker.GetRequestable(peer, m_now);
273+
std::vector<std::pair<NodeId, GenTxid>> expired;
274+
const auto actual = m_tracker.GetRequestable(peer, m_now, &expired);
275+
std::sort(expired.begin(), expired.end());
276+
assert(expired == expected_expired);
271277

272278
m_tracker.PostGetRequestableSanityCheck(m_now);
273279
assert(result.size() == actual.size());

‎src/test/txrequest_tests.cpp

+29-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ struct Runner
4343

4444
/** Which txhashes have been assigned already (to prevent reuse). */
4545
std::set<uint256> txhashset;
46+
47+
/** Which (peer, gtxid) combinations are known to be expired. These need to be accumulated here instead of
48+
* checked directly in the GetRequestable return value to avoid introducing a dependency between the various
49+
* parallel tests. */
50+
std::multiset<std::pair<NodeId, GenTxid>> expired;
4651
};
4752

4853
std::chrono::microseconds RandomTime8s() { return std::chrono::microseconds{1 + InsecureRandBits(23)}; }
@@ -149,7 +154,9 @@ class Scenario
149154
const auto now = m_now;
150155
assert(offset.count() <= 0);
151156
runner.actions.emplace_back(m_now, [=,&runner]() {
152-
auto ret = runner.txrequest.GetRequestable(peer, now + offset);
157+
std::vector<std::pair<NodeId, GenTxid>> expired_now;
158+
auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);
159+
for (const auto& entry : expired_now) runner.expired.insert(entry);
153160
runner.txrequest.SanityCheck();
154161
runner.txrequest.PostGetRequestableSanityCheck(now + offset);
155162
size_t total = candidates + inflight + completed;
@@ -163,6 +170,21 @@ class Scenario
163170
});
164171
}
165172

173+
/** Verify that an announcement for gtxid by peer has expired some time before this check is scheduled.
174+
*
175+
* Every expected expiration should be accounted for through exactly one call to this function.
176+
*/
177+
void CheckExpired(NodeId peer, GenTxid gtxid)
178+
{
179+
const auto& testname = m_testname;
180+
auto& runner = m_runner;
181+
runner.actions.emplace_back(m_now, [=,&runner]() {
182+
auto it = runner.expired.find(std::pair<NodeId, GenTxid>{peer, gtxid});
183+
BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");
184+
if (it != runner.expired.end()) runner.expired.erase(it);
185+
});
186+
}
187+
166188
/** Generate a random txhash, whose priorities for certain peers are constrained.
167189
*
168190
* For example, NewTxHash({{p1,p2,p3},{p2,p4,p5}}) will generate a txhash T such that both:
@@ -256,6 +278,7 @@ void BuildSingleTest(Scenario& scenario, int config)
256278
scenario.Check(peer, {}, 0, 1, 0, "s7");
257279
scenario.AdvanceTime(MICROSECOND);
258280
scenario.Check(peer, {}, 0, 0, 0, "s8");
281+
scenario.CheckExpired(peer, gtxid);
259282
return;
260283
} else {
261284
scenario.AdvanceTime(std::chrono::microseconds{InsecureRandRange(expiry.count())});
@@ -268,7 +291,6 @@ void BuildSingleTest(Scenario& scenario, int config)
268291
}
269292
}
270293

271-
if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
272294
if (config & 4) { // The peer will go offline
273295
scenario.DisconnectedPeer(peer);
274296
} else { // The transaction is no longer needed
@@ -519,9 +541,11 @@ void BuildWtxidTest(Scenario& scenario, int config)
519541
if (config & 2) {
520542
scenario.Check(peerT, {}, 0, 0, 1, "w9");
521543
scenario.Check(peerW, {wtxid}, 1, 0, 0, "w10");
544+
scenario.CheckExpired(peerT, txid);
522545
} else {
523546
scenario.Check(peerT, {txid}, 1, 0, 0, "w11");
524547
scenario.Check(peerW, {}, 0, 0, 1, "w12");
548+
scenario.CheckExpired(peerW, wtxid);
525549
}
526550

527551
// If a good transaction with either that hash as wtxid or txid arrives, both
@@ -567,6 +591,7 @@ void BuildTimeBackwardsTest(Scenario& scenario)
567591
scenario.AdvanceTime(expiry - scenario.Now());
568592
scenario.Check(peer1, {}, 0, 0, 1, "r9");
569593
scenario.Check(peer2, {gtxid}, 1, 0, 0, "r10"); // Request goes back to peer2.
594+
scenario.CheckExpired(peer1, gtxid);
570595
scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); // Going back does not unexpire.
571596
scenario.Check(peer2, {gtxid}, 1, 0, 0, "r12", -MICROSECOND);
572597

@@ -623,6 +648,7 @@ void BuildWeirdRequestsTest(Scenario& scenario)
623648
scenario.AdvanceTime(expiryA - scenario.Now());
624649
scenario.Check(peer1, {}, 0, 0, 1, "q12");
625650
scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q13");
651+
scenario.CheckExpired(peer1, gtxid1);
626652

627653
// Requesting it yet again from peer1 doesn't do anything, as it's already COMPLETED.
628654
if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
@@ -697,6 +723,7 @@ void TestInterleavedScenarios()
697723
}
698724

699725
BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U);
726+
BOOST_CHECK(runner.expired.empty());
700727
}
701728

702729
} // namespace

‎src/txrequest.cpp

+16-6
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ std::map<uint256, TxHashInfo> ComputeTxHashInfo(const Index& index, const Priori
291291
return ret;
292292
}
293293

294+
GenTxid ToGenTxid(const Announcement& ann)
295+
{
296+
return {ann.m_is_wtxid, ann.m_txhash};
297+
}
298+
294299
} // namespace
295300

296301
/** Actual implementation for TxRequestTracker's data structure. */
@@ -477,15 +482,18 @@ class TxRequestTracker::Impl {
477482
//! - REQUESTED annoucements with expiry <= now are turned into COMPLETED.
478483
//! - CANDIDATE_DELAYED announcements with reqtime <= now are turned into CANDIDATE_{READY,BEST}.
479484
//! - CANDIDATE_{READY,BEST} announcements with reqtime > now are turned into CANDIDATE_DELAYED.
480-
void SetTimePoint(std::chrono::microseconds now)
485+
void SetTimePoint(std::chrono::microseconds now, std::vector<std::pair<NodeId, GenTxid>>* expired)
481486
{
487+
if (expired) expired->clear();
488+
482489
// Iterate over all CANDIDATE_DELAYED and REQUESTED from old to new, as long as they're in the past,
483490
// and convert them to CANDIDATE_READY and COMPLETED respectively.
484491
while (!m_index.empty()) {
485492
auto it = m_index.get<ByTime>().begin();
486493
if (it->m_state == State::CANDIDATE_DELAYED && it->m_time <= now) {
487494
PromoteCandidateReady(m_index.project<ByTxHash>(it));
488495
} else if (it->m_state == State::REQUESTED && it->m_time <= now) {
496+
if (expired) expired->emplace_back(it->m_peer, ToGenTxid(*it));
489497
MakeCompleted(m_index.project<ByTxHash>(it));
490498
} else {
491499
break;
@@ -578,10 +586,11 @@ class TxRequestTracker::Impl {
578586
}
579587

580588
//! Find the GenTxids to request now from peer.
581-
std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now)
589+
std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now,
590+
std::vector<std::pair<NodeId, GenTxid>>* expired)
582591
{
583592
// Move time.
584-
SetTimePoint(now);
593+
SetTimePoint(now, expired);
585594

586595
// Find all CANDIDATE_BEST announcements for this peer.
587596
std::vector<const Announcement*> selected;
@@ -601,7 +610,7 @@ class TxRequestTracker::Impl {
601610
std::vector<GenTxid> ret;
602611
ret.reserve(selected.size());
603612
std::transform(selected.begin(), selected.end(), std::back_inserter(ret), [](const Announcement* ann) {
604-
return GenTxid{ann->m_is_wtxid, ann->m_txhash};
613+
return ToGenTxid(*ann);
605614
});
606615
return ret;
607616
}
@@ -727,9 +736,10 @@ void TxRequestTracker::ReceivedResponse(NodeId peer, const uint256& txhash)
727736
m_impl->ReceivedResponse(peer, txhash);
728737
}
729738

730-
std::vector<GenTxid> TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now)
739+
std::vector<GenTxid> TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now,
740+
std::vector<std::pair<NodeId, GenTxid>>* expired)
731741
{
732-
return m_impl->GetRequestable(peer, now);
742+
return m_impl->GetRequestable(peer, now, expired);
733743
}
734744

735745
uint64_t TxRequestTracker::ComputePriority(const uint256& txhash, NodeId peer, bool preferred) const

‎src/txrequest.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class TxRequestTracker {
148148
*
149149
* It does the following:
150150
* - Convert all REQUESTED announcements (for all txhashes/peers) with (expiry <= now) to COMPLETED ones.
151+
* These are returned in expired, if non-nullptr.
151152
* - Requestable announcements are selected: CANDIDATE announcements from the specified peer with
152153
* (reqtime <= now) for which no existing REQUESTED announcement with the same txhash from a different peer
153154
* exists, and for which the specified peer is the best choice among all (reqtime <= now) CANDIDATE
@@ -159,7 +160,8 @@ class TxRequestTracker {
159160
* out of order: if multiple dependent transactions are announced simultaneously by one peer, and end up
160161
* being requested from them, the requests will happen in announcement order.
161162
*/
162-
std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now);
163+
std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now,
164+
std::vector<std::pair<NodeId, GenTxid>>* expired = nullptr);
163165

164166
/** Marks a transaction as requested, with a specified expiry.
165167
*

0 commit comments

Comments
 (0)
Please sign in to comment.