Changeset View
Changeset View
Standalone View
Standalone View
src/test/txrequest_tests.cpp
Show All 36 Lines | struct Runner { | ||||
/** List of actions to be executed (in order of increasing timestamp). */ | /** List of actions to be executed (in order of increasing timestamp). */ | ||||
std::vector<Action> actions; | std::vector<Action> actions; | ||||
/** Which node ids have been assigned already (to prevent reuse). */ | /** Which node ids have been assigned already (to prevent reuse). */ | ||||
std::set<NodeId> peerset; | std::set<NodeId> peerset; | ||||
/** Which txids have been assigned already (to prevent reuse). */ | /** Which txids have been assigned already (to prevent reuse). */ | ||||
std::set<TxId> txidset; | std::set<TxId> txidset; | ||||
/** | |||||
* Which (peer, txid) combinations are known to be expired. These need to be | |||||
* accumulated here instead of checked directly in the GetRequestable return | |||||
* value to avoid introducing a dependency between the various parallel | |||||
* tests. | |||||
*/ | |||||
std::multiset<std::pair<NodeId, TxId>> expired; | |||||
}; | }; | ||||
std::chrono::microseconds RandomTime8s() { | std::chrono::microseconds RandomTime8s() { | ||||
return std::chrono::microseconds{1 + InsecureRandBits(23)}; | return std::chrono::microseconds{1 + InsecureRandBits(23)}; | ||||
} | } | ||||
std::chrono::microseconds RandomTime1y() { | std::chrono::microseconds RandomTime1y() { | ||||
return std::chrono::microseconds{1 + InsecureRandBits(45)}; | return std::chrono::microseconds{1 + InsecureRandBits(45)}; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 100 Lines • ▼ Show 20 Lines | public: | ||||
Check(NodeId peer, const std::vector<TxId> &expected, size_t candidates, | Check(NodeId peer, const std::vector<TxId> &expected, size_t candidates, | ||||
size_t inflight, size_t completed, const std::string &checkname, | size_t inflight, size_t completed, const std::string &checkname, | ||||
std::chrono::microseconds offset = std::chrono::microseconds{0}) { | std::chrono::microseconds offset = std::chrono::microseconds{0}) { | ||||
const auto comment = m_testname + " " + checkname; | const auto comment = m_testname + " " + checkname; | ||||
auto &runner = m_runner; | auto &runner = m_runner; | ||||
const auto now = m_now; | const auto now = m_now; | ||||
assert(offset.count() <= 0); | assert(offset.count() <= 0); | ||||
runner.actions.emplace_back(m_now, [=, &runner]() { | runner.actions.emplace_back(m_now, [=, &runner]() { | ||||
auto ret = runner.txrequest.GetRequestable(peer, now + offset); | std::vector<std::pair<NodeId, TxId>> expired_now; | ||||
auto ret = runner.txrequest.GetRequestable(peer, now + offset, | |||||
&expired_now); | |||||
for (const auto &entry : expired_now) { | |||||
runner.expired.insert(entry); | |||||
} | |||||
runner.txrequest.SanityCheck(); | runner.txrequest.SanityCheck(); | ||||
runner.txrequest.PostGetRequestableSanityCheck(now + offset); | runner.txrequest.PostGetRequestableSanityCheck(now + offset); | ||||
size_t total = candidates + inflight + completed; | size_t total = candidates + inflight + completed; | ||||
size_t real_total = runner.txrequest.Count(peer); | size_t real_total = runner.txrequest.Count(peer); | ||||
size_t real_candidates = runner.txrequest.CountCandidates(peer); | size_t real_candidates = runner.txrequest.CountCandidates(peer); | ||||
size_t real_inflight = runner.txrequest.CountInFlight(peer); | size_t real_inflight = runner.txrequest.CountInFlight(peer); | ||||
BOOST_CHECK_MESSAGE( | BOOST_CHECK_MESSAGE( | ||||
real_total == total, | real_total == total, | ||||
strprintf("[" + comment + "] total %i (%i expected)", | strprintf("[" + comment + "] total %i (%i expected)", | ||||
real_total, total)); | real_total, total)); | ||||
BOOST_CHECK_MESSAGE( | BOOST_CHECK_MESSAGE( | ||||
real_inflight == inflight, | real_inflight == inflight, | ||||
strprintf("[" + comment + "] inflight %i (%i expected)", | strprintf("[" + comment + "] inflight %i (%i expected)", | ||||
real_inflight, inflight)); | real_inflight, inflight)); | ||||
BOOST_CHECK_MESSAGE( | BOOST_CHECK_MESSAGE( | ||||
real_candidates == candidates, | real_candidates == candidates, | ||||
strprintf("[" + comment + "] candidates %i (%i expected)", | strprintf("[" + comment + "] candidates %i (%i expected)", | ||||
real_candidates, candidates)); | real_candidates, candidates)); | ||||
BOOST_CHECK_MESSAGE(ret == expected, | BOOST_CHECK_MESSAGE(ret == expected, | ||||
"[" + comment + "] mismatching requestables"); | "[" + comment + "] mismatching requestables"); | ||||
}); | }); | ||||
} | } | ||||
/** | /** | ||||
* Verify that an announcement for txid by peer has expired some time before | |||||
* this check is scheduled. | |||||
* | |||||
* Every expected expiration should be accounted for through exactly one | |||||
* call to this function. | |||||
*/ | |||||
void CheckExpired(NodeId peer, TxId txid) { | |||||
const auto &testname = m_testname; | |||||
auto &runner = m_runner; | |||||
runner.actions.emplace_back(m_now, [=, &runner]() { | |||||
auto it = runner.expired.find(std::pair<NodeId, TxId>{peer, txid}); | |||||
BOOST_CHECK_MESSAGE(it != runner.expired.end(), | |||||
"[" + testname + "] missing expiration"); | |||||
if (it != runner.expired.end()) { | |||||
runner.expired.erase(it); | |||||
} | |||||
}); | |||||
} | |||||
/** | |||||
* Generate a random txid, whose priorities for certain peers are | * Generate a random txid, whose priorities for certain peers are | ||||
* constrained. | * constrained. | ||||
* | * | ||||
* For example, NewTxId({{p1,p2,p3},{p2,p4,p5}}) will generate a txid T | * For example, NewTxId({{p1,p2,p3},{p2,p4,p5}}) will generate a txid T | ||||
* such that both: | * such that both: | ||||
* - priority(p1,T) > priority(p2,T) > priority(p3,T) | * - priority(p1,T) > priority(p2,T) > priority(p3,T) | ||||
* - priority(p2,T) > priority(p4,T) > priority(p5,T) | * - priority(p2,T) > priority(p4,T) > priority(p5,T) | ||||
* where priority is the predicted internal TxRequestTracker's priority, | * where priority is the predicted internal TxRequestTracker's priority, | ||||
▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | if (config >> 3) { // We'll request the transaction | ||||
scenario.RequestedTx(peer, txid, scenario.Now() + expiry); | scenario.RequestedTx(peer, txid, scenario.Now() + expiry); | ||||
scenario.Check(peer, {}, 0, 1, 0, "s6"); | scenario.Check(peer, {}, 0, 1, 0, "s6"); | ||||
if ((config >> 3) == 1) { // The request will time out | if ((config >> 3) == 1) { // The request will time out | ||||
scenario.AdvanceTime(expiry - MICROSECOND); | scenario.AdvanceTime(expiry - MICROSECOND); | ||||
scenario.Check(peer, {}, 0, 1, 0, "s7"); | scenario.Check(peer, {}, 0, 1, 0, "s7"); | ||||
scenario.AdvanceTime(MICROSECOND); | scenario.AdvanceTime(MICROSECOND); | ||||
scenario.Check(peer, {}, 0, 0, 0, "s8"); | scenario.Check(peer, {}, 0, 0, 0, "s8"); | ||||
scenario.CheckExpired(peer, txid); | |||||
return; | return; | ||||
} else { | } else { | ||||
scenario.AdvanceTime( | scenario.AdvanceTime( | ||||
std::chrono::microseconds{InsecureRandRange(expiry.count())}); | std::chrono::microseconds{InsecureRandRange(expiry.count())}); | ||||
scenario.Check(peer, {}, 0, 1, 0, "s9"); | scenario.Check(peer, {}, 0, 1, 0, "s9"); | ||||
if ((config >> 3) == | if ((config >> 3) == | ||||
3) { // A response will arrive for the transaction | 3) { // A response will arrive for the transaction | ||||
scenario.ReceivedResponse(peer, txid); | scenario.ReceivedResponse(peer, txid); | ||||
scenario.Check(peer, {}, 0, 0, 0, "s10"); | scenario.Check(peer, {}, 0, 0, 0, "s10"); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (InsecureRandBool()) { | |||||
scenario.AdvanceTime(RandomTime8s()); | |||||
} | |||||
if (config & 4) { | if (config & 4) { | ||||
// The peer will go offline | // The peer will go offline | ||||
scenario.DisconnectedPeer(peer); | scenario.DisconnectedPeer(peer); | ||||
} else { | } else { | ||||
// The transaction is no longer needed | // The transaction is no longer needed | ||||
scenario.ForgetTxId(txid); | scenario.ForgetTxId(txid); | ||||
} | } | ||||
scenario.Check(peer, {}, 0, 0, 0, "s11"); | scenario.Check(peer, {}, 0, 0, 0, "s11"); | ||||
▲ Show 20 Lines • Show All 247 Lines • ▼ Show 20 Lines | void BuildTimeBackwardsTest(Scenario &scenario) { | ||||
scenario.Check(peer1, {}, 0, 1, 0, "r7"); | scenario.Check(peer1, {}, 0, 1, 0, "r7"); | ||||
scenario.Check(peer2, {}, 1, 0, 0, "r8"); | scenario.Check(peer2, {}, 1, 0, 0, "r8"); | ||||
// Expiration passes. | // Expiration passes. | ||||
scenario.AdvanceTime(expiry - scenario.Now()); | scenario.AdvanceTime(expiry - scenario.Now()); | ||||
scenario.Check(peer1, {}, 0, 0, 1, "r9"); | scenario.Check(peer1, {}, 0, 0, 1, "r9"); | ||||
// Request goes back to peer2. | // Request goes back to peer2. | ||||
scenario.Check(peer2, {txid}, 1, 0, 0, "r10"); | scenario.Check(peer2, {txid}, 1, 0, 0, "r10"); | ||||
scenario.CheckExpired(peer1, txid); | |||||
// Going back does not unexpire. | // Going back does not unexpire. | ||||
scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); | scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); | ||||
scenario.Check(peer2, {txid}, 1, 0, 0, "r12", -MICROSECOND); | scenario.Check(peer2, {txid}, 1, 0, 0, "r12", -MICROSECOND); | ||||
// Peer2 goes offline, meaning no viable announcements remain. | // Peer2 goes offline, meaning no viable announcements remain. | ||||
if (InsecureRandBool()) { | if (InsecureRandBool()) { | ||||
scenario.AdvanceTime(RandomTime8s()); | scenario.AdvanceTime(RandomTime8s()); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | void BuildWeirdRequestsTest(Scenario &scenario) { | ||||
scenario.ReceivedInv(peer2, txid1, true, MIN_TIME); | scenario.ReceivedInv(peer2, txid1, true, MIN_TIME); | ||||
scenario.Check(peer1, {}, 0, 1, 0, "q10"); | scenario.Check(peer1, {}, 0, 1, 0, "q10"); | ||||
scenario.Check(peer2, {txid2}, 2, 0, 0, "q11"); | scenario.Check(peer2, {txid2}, 2, 0, 0, "q11"); | ||||
// When reaching expiryA, it expires (not expiryB, which is later). | // When reaching expiryA, it expires (not expiryB, which is later). | ||||
scenario.AdvanceTime(expiryA - scenario.Now()); | scenario.AdvanceTime(expiryA - scenario.Now()); | ||||
scenario.Check(peer1, {}, 0, 0, 1, "q12"); | scenario.Check(peer1, {}, 0, 0, 1, "q12"); | ||||
scenario.Check(peer2, {txid2, txid1}, 2, 0, 0, "q13"); | scenario.Check(peer2, {txid2, txid1}, 2, 0, 0, "q13"); | ||||
scenario.CheckExpired(peer1, txid1); | |||||
// Requesting it yet again from peer1 doesn't do anything, as it's already | // Requesting it yet again from peer1 doesn't do anything, as it's already | ||||
// COMPLETED. | // COMPLETED. | ||||
if (InsecureRandBool()) { | if (InsecureRandBool()) { | ||||
scenario.AdvanceTime(RandomTime8s()); | scenario.AdvanceTime(RandomTime8s()); | ||||
} | } | ||||
scenario.RequestedTx(peer1, txid1, MAX_TIME); | scenario.RequestedTx(peer1, txid1, MAX_TIME); | ||||
scenario.Check(peer1, {}, 0, 0, 1, "q14"); | scenario.Check(peer1, {}, 0, 0, 1, "q14"); | ||||
▲ Show 20 Lines • Show All 77 Lines • ▼ Show 20 Lines | std::stable_sort( | ||||
[](const Action &a1, const Action &a2) { return a1.first < a2.first; }); | [](const Action &a1, const Action &a2) { return a1.first < a2.first; }); | ||||
// Run all actions from all scenarios, in order. | // Run all actions from all scenarios, in order. | ||||
for (auto &action : runner.actions) { | for (auto &action : runner.actions) { | ||||
action.second(); | action.second(); | ||||
} | } | ||||
BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U); | BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U); | ||||
BOOST_CHECK(runner.expired.empty()); | |||||
} | } | ||||
} // namespace | } // namespace | ||||
BOOST_AUTO_TEST_CASE(TxRequestTest) { | BOOST_AUTO_TEST_CASE(TxRequestTest) { | ||||
for (int i = 0; i < 5; ++i) { | for (int i = 0; i < 5; ++i) { | ||||
TestInterleavedScenarios(); | TestInterleavedScenarios(); | ||||
} | } | ||||
} | } | ||||
BOOST_AUTO_TEST_SUITE_END() | BOOST_AUTO_TEST_SUITE_END() |