txrequest_tests.cpp raw
1 // Copyright (c) 2020-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
6 #include <txrequest.h>
7 #include <uint256.h>
8
9 #include <test/util/random.h>
10 #include <test/util/setup_common.h>
11
12 #include <algorithm>
13 #include <functional>
14 #include <vector>
15
16 #include <boost/test/unit_test.hpp>
17
18 namespace {
19
20 class Scenario;
21
22 struct TxRequestTest : BasicTestingSetup {
23 std::chrono::microseconds RandomTime8s();
24 std::chrono::microseconds RandomTime1y();
25 void BuildSingleTest(Scenario& scenario, int config);
26 void BuildPriorityTest(Scenario& scenario, int config);
27 void BuildBigPriorityTest(Scenario& scenario, int peers);
28 void BuildRequestOrderTest(Scenario& scenario, int config);
29 void BuildWtxidTest(Scenario& scenario, int config);
30 void BuildTimeBackwardsTest(Scenario& scenario);
31 void BuildWeirdRequestsTest(Scenario& scenario);
32 void TestInterleavedScenarios();
33 };
34
35 constexpr std::chrono::microseconds MIN_TIME = std::chrono::microseconds::min();
36 constexpr std::chrono::microseconds MAX_TIME = std::chrono::microseconds::max();
37 constexpr std::chrono::microseconds MICROSECOND = std::chrono::microseconds{1};
38 constexpr std::chrono::microseconds NO_TIME = std::chrono::microseconds{0};
39
40 /** An Action is a function to call at a particular (simulated) timestamp. */
41 using Action = std::pair<std::chrono::microseconds, std::function<void()>>;
42
43 /** Object that stores actions from multiple interleaved scenarios, and data shared across them.
44 *
45 * The Scenario below is used to fill this.
46 */
47 struct Runner
48 {
49 /** The TxRequestTracker being tested. */
50 TxRequestTracker txrequest;
51
52 /** List of actions to be executed (in order of increasing timestamp). */
53 std::vector<Action> actions;
54
55 /** Which node ids have been assigned already (to prevent reuse). */
56 std::set<NodeId> peerset;
57
58 /** Which txhashes have been assigned already (to prevent reuse). */
59 std::set<uint256> txhashset;
60
61 /** Which (peer, gtxid) combinations are known to be expired. These need to be accumulated here instead of
62 * checked directly in the GetRequestable return value to avoid introducing a dependency between the various
63 * parallel tests. */
64 std::multiset<std::pair<NodeId, GenTxid>> expired;
65 };
66
67 std::chrono::microseconds TxRequestTest::RandomTime8s() { return std::chrono::microseconds{1 + m_rng.randbits(23)}; }
68 std::chrono::microseconds TxRequestTest::RandomTime1y() { return std::chrono::microseconds{1 + m_rng.randbits(45)}; }
69
70 /** A proxy for a Runner that helps build a sequence of consecutive test actions on a TxRequestTracker.
71 *
72 * Each Scenario is a proxy through which actions for the (sequential) execution of various tests are added to a
73 * Runner. The actions from multiple scenarios are then run concurrently, resulting in these tests being performed
74 * against a TxRequestTracker in parallel. Every test has its own unique txhashes and NodeIds which are not
75 * reused in other tests, and thus they should be independent from each other. Running them in parallel however
76 * means that we verify the behavior (w.r.t. one test's txhashes and NodeIds) even when the state of the data
77 * structure is more complicated due to the presence of other tests.
78 */
79 class Scenario
80 {
81 FastRandomContext& m_rng;
82 Runner& m_runner;
83 std::chrono::microseconds m_now;
84 std::string m_testname;
85
86 public:
87 Scenario(FastRandomContext& rng, Runner& runner, std::chrono::microseconds starttime) : m_rng(rng), m_runner(runner), m_now(starttime) {}
88
89 /** Set a name for the current test, to give more clear error messages. */
90 void SetTestName(std::string testname)
91 {
92 m_testname = std::move(testname);
93 }
94
95 /** Advance this Scenario's time; this affects the timestamps newly scheduled events get. */
96 void AdvanceTime(std::chrono::microseconds amount)
97 {
98 assert(amount.count() >= 0);
99 m_now += amount;
100 }
101
102 /** Schedule a ForgetTxHash call at the Scheduler's current time. */
103 void ForgetTxHash(const uint256& txhash)
104 {
105 auto& runner = m_runner;
106 runner.actions.emplace_back(m_now, [=, &runner]() {
107 runner.txrequest.ForgetTxHash(txhash);
108 runner.txrequest.SanityCheck();
109 });
110 }
111
112 /** Schedule a ReceivedInv call at the Scheduler's current time. */
113 void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool pref, std::chrono::microseconds reqtime)
114 {
115 auto& runner = m_runner;
116 runner.actions.emplace_back(m_now, [=, &runner]() {
117 runner.txrequest.ReceivedInv(peer, gtxid, pref, reqtime);
118 runner.txrequest.SanityCheck();
119 });
120 }
121
122 /** Schedule a DisconnectedPeer call at the Scheduler's current time. */
123 void DisconnectedPeer(NodeId peer)
124 {
125 auto& runner = m_runner;
126 runner.actions.emplace_back(m_now, [=, &runner]() {
127 runner.txrequest.DisconnectedPeer(peer);
128 runner.txrequest.SanityCheck();
129 });
130 }
131
132 /** Schedule a RequestedTx call at the Scheduler's current time. */
133 void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds exptime)
134 {
135 auto& runner = m_runner;
136 runner.actions.emplace_back(m_now, [=, &runner]() {
137 runner.txrequest.RequestedTx(peer, txhash, exptime);
138 runner.txrequest.SanityCheck();
139 });
140 }
141
142 /** Schedule a ReceivedResponse call at the Scheduler's current time. */
143 void ReceivedResponse(NodeId peer, const uint256& txhash)
144 {
145 auto& runner = m_runner;
146 runner.actions.emplace_back(m_now, [=, &runner]() {
147 runner.txrequest.ReceivedResponse(peer, txhash);
148 runner.txrequest.SanityCheck();
149 });
150 }
151
152 /** Schedule calls to verify the TxRequestTracker's state at the Scheduler's current time.
153 *
154 * @param peer The peer whose state will be inspected.
155 * @param expected The expected return value for GetRequestable(peer)
156 * @param candidates The expected return value CountCandidates(peer)
157 * @param inflight The expected return value CountInFlight(peer)
158 * @param completed The expected return value of Count(peer), minus candidates and inflight.
159 * @param checkname An arbitrary string to include in error messages, for test identificatrion.
160 * @param offset Offset with the current time to use (must be <= 0). This allows simulations of time going
161 * backwards (but note that the ordering of this event only follows the scenario's m_now.
162 */
163 void Check(NodeId peer, const std::vector<GenTxid>& expected, size_t candidates, size_t inflight,
164 size_t completed, const std::string& checkname,
165 std::chrono::microseconds offset = std::chrono::microseconds{0})
166 {
167 const auto comment = m_testname + " " + checkname;
168 auto& runner = m_runner;
169 const auto now = m_now;
170 assert(offset.count() <= 0);
171 runner.actions.emplace_back(m_now, [=, &runner]() {
172 std::vector<std::pair<NodeId, GenTxid>> expired_now;
173 auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);
174 for (const auto& entry : expired_now) {
175 runner.expired.insert(entry);
176 }
177 runner.txrequest.SanityCheck();
178 runner.txrequest.PostGetRequestableSanityCheck(now + offset);
179 size_t total = candidates + inflight + completed;
180 size_t real_total = runner.txrequest.Count(peer);
181 size_t real_candidates = runner.txrequest.CountCandidates(peer);
182 size_t real_inflight = runner.txrequest.CountInFlight(peer);
183 BOOST_CHECK_MESSAGE(real_total == total, strprintf("[%s] total %i (%i expected)", comment, real_total, total));
184 BOOST_CHECK_MESSAGE(real_inflight == inflight, strprintf("[%s] inflight %i (%i expected)", comment, real_inflight, inflight));
185 BOOST_CHECK_MESSAGE(real_candidates == candidates, strprintf("[%s] candidates %i (%i expected)", comment, real_candidates, candidates));
186 BOOST_CHECK_MESSAGE(ret == expected, strprintf("[%s] mismatching requestables", comment));
187 });
188 }
189
190 /** Verify that an announcement for gtxid by peer has expired some time before this check is scheduled.
191 *
192 * Every expected expiration should be accounted for through exactly one call to this function.
193 */
194 void CheckExpired(NodeId peer, GenTxid gtxid)
195 {
196 const auto& testname = m_testname;
197 auto& runner = m_runner;
198 runner.actions.emplace_back(m_now, [=, &runner]() {
199 auto it = runner.expired.find(std::pair<NodeId, GenTxid>{peer, gtxid});
200 BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");
201 if (it != runner.expired.end()) runner.expired.erase(it);
202 });
203 }
204
205 /** Generate a random txhash, whose priorities for certain peers are constrained.
206 *
207 * For example, NewTxHash({{p1,p2,p3},{p2,p4,p5}}) will generate a txhash T such that both:
208 * - priority(p1,T) > priority(p2,T) > priority(p3,T)
209 * - priority(p2,T) > priority(p4,T) > priority(p5,T)
210 * where priority is the predicted internal TxRequestTracker's priority, assuming all announcements
211 * are within the same preferredness class.
212 */
213 uint256 NewTxHash(const std::vector<std::vector<NodeId>>& orders = {})
214 {
215 uint256 ret;
216 bool ok;
217 do {
218 ret = m_rng.rand256();
219 ok = true;
220 for (const auto& order : orders) {
221 for (size_t pos = 1; pos < order.size(); ++pos) {
222 uint64_t prio_prev = m_runner.txrequest.ComputePriority(ret, order[pos - 1], true);
223 uint64_t prio_cur = m_runner.txrequest.ComputePriority(ret, order[pos], true);
224 if (prio_prev <= prio_cur) {
225 ok = false;
226 break;
227 }
228 }
229 if (!ok) break;
230 }
231 if (ok) {
232 ok = m_runner.txhashset.insert(ret).second;
233 }
234 } while(!ok);
235 return ret;
236 }
237
238 /** Generate a random GenTxid; the txhash follows NewTxHash; the transaction identifier is random. */
239 GenTxid NewGTxid(const std::vector<std::vector<NodeId>>& orders = {})
240 {
241 const uint256 txhash{NewTxHash(orders)};
242 return m_rng.randbool() ? GenTxid{Wtxid::FromUint256(txhash)} : GenTxid{Txid::FromUint256(txhash)};
243 }
244
245 /** Generate a new random NodeId to use as peer. The same NodeId is never returned twice
246 * (across all Scenarios combined). */
247 NodeId NewPeer()
248 {
249 bool ok;
250 NodeId ret;
251 do {
252 ret = m_rng.randbits(63);
253 ok = m_runner.peerset.insert(ret).second;
254 } while(!ok);
255 return ret;
256 }
257
258 std::chrono::microseconds Now() const { return m_now; }
259 };
260
261 /** Add to scenario a test with a single tx announced by a single peer.
262 *
263 * config is an integer in [0, 32), which controls which variant of the test is used.
264 */
265 void TxRequestTest::BuildSingleTest(Scenario& scenario, int config)
266 {
267 auto peer = scenario.NewPeer();
268 auto gtxid = scenario.NewGTxid();
269 bool immediate = config & 1;
270 bool preferred = config & 2;
271 auto delay = immediate ? NO_TIME : RandomTime8s();
272
273 scenario.SetTestName(strprintf("Single(config=%i)", config));
274
275 // Receive an announcement, either immediately requestable or delayed.
276 scenario.ReceivedInv(peer, gtxid, preferred, immediate ? MIN_TIME : scenario.Now() + delay);
277 if (immediate) {
278 scenario.Check(peer, {gtxid}, 1, 0, 0, "s1");
279 } else {
280 scenario.Check(peer, {}, 1, 0, 0, "s2");
281 scenario.AdvanceTime(delay - MICROSECOND);
282 scenario.Check(peer, {}, 1, 0, 0, "s3");
283 scenario.AdvanceTime(MICROSECOND);
284 scenario.Check(peer, {gtxid}, 1, 0, 0, "s4");
285 }
286
287 if (config >> 3) { // We'll request the transaction
288 scenario.AdvanceTime(RandomTime8s());
289 auto expiry = RandomTime8s();
290 scenario.Check(peer, {gtxid}, 1, 0, 0, "s5");
291 scenario.RequestedTx(peer, gtxid.ToUint256(), scenario.Now() + expiry);
292 scenario.Check(peer, {}, 0, 1, 0, "s6");
293
294 if ((config >> 3) == 1) { // The request will time out
295 scenario.AdvanceTime(expiry - MICROSECOND);
296 scenario.Check(peer, {}, 0, 1, 0, "s7");
297 scenario.AdvanceTime(MICROSECOND);
298 scenario.Check(peer, {}, 0, 0, 0, "s8");
299 scenario.CheckExpired(peer, gtxid);
300 return;
301 } else {
302 scenario.AdvanceTime(std::chrono::microseconds{m_rng.randrange(expiry.count())});
303 scenario.Check(peer, {}, 0, 1, 0, "s9");
304 if ((config >> 3) == 3) { // A response will arrive for the transaction
305 scenario.ReceivedResponse(peer, gtxid.ToUint256());
306 scenario.Check(peer, {}, 0, 0, 0, "s10");
307 return;
308 }
309 }
310 }
311
312 if (config & 4) { // The peer will go offline
313 scenario.DisconnectedPeer(peer);
314 } else { // The transaction is no longer needed
315 scenario.ForgetTxHash(gtxid.ToUint256());
316 }
317 scenario.Check(peer, {}, 0, 0, 0, "s11");
318 }
319
320 /** Add to scenario a test with a single tx announced by two peers, to verify the
321 * right peer is selected for requests.
322 *
323 * config is an integer in [0, 32), which controls which variant of the test is used.
324 */
325 void TxRequestTest::BuildPriorityTest(Scenario& scenario, int config)
326 {
327 scenario.SetTestName(strprintf("Priority(config=%i)", config));
328
329 // Two peers. They will announce in order {peer1, peer2}.
330 auto peer1 = scenario.NewPeer(), peer2 = scenario.NewPeer();
331 // Construct a transaction that under random rules would be preferred by peer2 or peer1,
332 // depending on configuration.
333 bool prio1 = config & 1;
334 auto gtxid = prio1 ? scenario.NewGTxid({{peer1, peer2}}) : scenario.NewGTxid({{peer2, peer1}});
335 bool pref1 = config & 2, pref2 = config & 4;
336
337 scenario.ReceivedInv(peer1, gtxid, pref1, MIN_TIME);
338 scenario.Check(peer1, {gtxid}, 1, 0, 0, "p1");
339 if (m_rng.randbool()) {
340 scenario.AdvanceTime(RandomTime8s());
341 scenario.Check(peer1, {gtxid}, 1, 0, 0, "p2");
342 }
343
344 scenario.ReceivedInv(peer2, gtxid, pref2, MIN_TIME);
345 bool stage2_prio =
346 // At this point, peer2 will be given priority if:
347 // - It is preferred and peer1 is not
348 (pref2 && !pref1) ||
349 // - They're in the same preference class,
350 // and the randomized priority favors peer2 over peer1.
351 (pref1 == pref2 && !prio1);
352 NodeId priopeer = stage2_prio ? peer2 : peer1, otherpeer = stage2_prio ? peer1 : peer2;
353 scenario.Check(otherpeer, {}, 1, 0, 0, "p3");
354 scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p4");
355 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
356 scenario.Check(otherpeer, {}, 1, 0, 0, "p5");
357 scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p6");
358
359 // We possibly request from the selected peer.
360 if (config & 8) {
361 scenario.RequestedTx(priopeer, gtxid.ToUint256(), MAX_TIME);
362 scenario.Check(priopeer, {}, 0, 1, 0, "p7");
363 scenario.Check(otherpeer, {}, 1, 0, 0, "p8");
364 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
365 }
366
367 // The peer which was selected (or requested from) now goes offline, or a NOTFOUND is received from them.
368 if (config & 16) {
369 scenario.DisconnectedPeer(priopeer);
370 } else {
371 scenario.ReceivedResponse(priopeer, gtxid.ToUint256());
372 }
373 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
374 scenario.Check(priopeer, {}, 0, 0, !(config & 16), "p8");
375 scenario.Check(otherpeer, {gtxid}, 1, 0, 0, "p9");
376 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
377
378 // Now the other peer goes offline.
379 scenario.DisconnectedPeer(otherpeer);
380 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
381 scenario.Check(peer1, {}, 0, 0, 0, "p10");
382 scenario.Check(peer2, {}, 0, 0, 0, "p11");
383 }
384
385 /** Add to scenario a randomized test in which N peers announce the same transaction, to verify
386 * the order in which they are requested. */
387 void TxRequestTest::BuildBigPriorityTest(Scenario& scenario, int peers)
388 {
389 scenario.SetTestName(strprintf("BigPriority(peers=%i)", peers));
390
391 // We will have N peers announce the same transaction.
392 std::map<NodeId, bool> preferred;
393 std::vector<NodeId> pref_peers, npref_peers;
394 int num_pref = m_rng.randrange(peers + 1) ; // Some preferred, ...
395 int num_npref = peers - num_pref; // some not preferred.
396 for (int i = 0; i < num_pref; ++i) {
397 pref_peers.push_back(scenario.NewPeer());
398 preferred[pref_peers.back()] = true;
399 }
400 for (int i = 0; i < num_npref; ++i) {
401 npref_peers.push_back(scenario.NewPeer());
402 preferred[npref_peers.back()] = false;
403 }
404 // Make a list of all peers, in order of intended request order (concatenation of pref_peers and npref_peers).
405 std::vector<NodeId> request_order;
406 request_order.reserve(num_pref + num_npref);
407 for (int i = 0; i < num_pref; ++i) request_order.push_back(pref_peers[i]);
408 for (int i = 0; i < num_npref; ++i) request_order.push_back(npref_peers[i]);
409
410 // Determine the announcement order randomly.
411 std::vector<NodeId> announce_order = request_order;
412 std::shuffle(announce_order.begin(), announce_order.end(), m_rng);
413
414 // Find a gtxid whose txhash prioritization is consistent with the required ordering within pref_peers and
415 // within npref_peers.
416 auto gtxid = scenario.NewGTxid({pref_peers, npref_peers});
417
418 // Decide reqtimes in opposite order of the expected request order. This means that as time passes we expect the
419 // to-be-requested-from-peer will change every time a subsequent reqtime is passed.
420 std::map<NodeId, std::chrono::microseconds> reqtimes;
421 auto reqtime = scenario.Now();
422 for (int i = peers - 1; i >= 0; --i) {
423 reqtime += RandomTime8s();
424 reqtimes[request_order[i]] = reqtime;
425 }
426
427 // Actually announce from all peers simultaneously (but in announce_order).
428 for (const auto peer : announce_order) {
429 scenario.ReceivedInv(peer, gtxid, preferred[peer], reqtimes[peer]);
430 }
431 for (const auto peer : announce_order) {
432 scenario.Check(peer, {}, 1, 0, 0, "b1");
433 }
434
435 // Let time pass and observe the to-be-requested-from peer change, from nonpreferred to preferred, and from
436 // high priority to low priority within each class.
437 for (int i = peers - 1; i >= 0; --i) {
438 scenario.AdvanceTime(reqtimes[request_order[i]] - scenario.Now() - MICROSECOND);
439 scenario.Check(request_order[i], {}, 1, 0, 0, "b2");
440 scenario.AdvanceTime(MICROSECOND);
441 scenario.Check(request_order[i], {gtxid}, 1, 0, 0, "b3");
442 }
443
444 // Peers now in random order go offline, or send NOTFOUNDs. At every point in time the new to-be-requested-from
445 // peer should be the best remaining one, so verify this after every response.
446 for (int i = 0; i < peers; ++i) {
447 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
448 const int pos = m_rng.randrange(request_order.size());
449 const auto peer = request_order[pos];
450 request_order.erase(request_order.begin() + pos);
451 if (m_rng.randbool()) {
452 scenario.DisconnectedPeer(peer);
453 scenario.Check(peer, {}, 0, 0, 0, "b4");
454 } else {
455 scenario.ReceivedResponse(peer, gtxid.ToUint256());
456 scenario.Check(peer, {}, 0, 0, request_order.size() > 0, "b5");
457 }
458 if (request_order.size()) {
459 scenario.Check(request_order[0], {gtxid}, 1, 0, 0, "b6");
460 }
461 }
462
463 // Everything is gone in the end.
464 for (const auto peer : announce_order) {
465 scenario.Check(peer, {}, 0, 0, 0, "b7");
466 }
467 }
468
469 /** Add to scenario a test with one peer announcing two transactions, to verify they are
470 * fetched in announcement order.
471 *
472 * config is an integer in [0, 4) inclusive, and selects the variant of the test.
473 */
474 void TxRequestTest::BuildRequestOrderTest(Scenario& scenario, int config)
475 {
476 scenario.SetTestName(strprintf("RequestOrder(config=%i)", config));
477
478 auto peer = scenario.NewPeer();
479 auto gtxid1 = scenario.NewGTxid();
480 auto gtxid2 = scenario.NewGTxid();
481
482 auto reqtime2 = scenario.Now() + RandomTime8s();
483 auto reqtime1 = reqtime2 + RandomTime8s();
484
485 scenario.ReceivedInv(peer, gtxid1, config & 1, reqtime1);
486 // Simulate time going backwards by giving the second announcement an earlier reqtime.
487 scenario.ReceivedInv(peer, gtxid2, config & 2, reqtime2);
488
489 scenario.AdvanceTime(reqtime2 - MICROSECOND - scenario.Now());
490 scenario.Check(peer, {}, 2, 0, 0, "o1");
491 scenario.AdvanceTime(MICROSECOND);
492 scenario.Check(peer, {gtxid2}, 2, 0, 0, "o2");
493 scenario.AdvanceTime(reqtime1 - MICROSECOND - scenario.Now());
494 scenario.Check(peer, {gtxid2}, 2, 0, 0, "o3");
495 scenario.AdvanceTime(MICROSECOND);
496 // Even with time going backwards in between announcements, the return value of GetRequestable is in
497 // announcement order.
498 scenario.Check(peer, {gtxid1, gtxid2}, 2, 0, 0, "o4");
499
500 scenario.DisconnectedPeer(peer);
501 scenario.Check(peer, {}, 0, 0, 0, "o5");
502 }
503
504 /** Add to scenario a test that verifies behavior related to both txid and wtxid with the same
505 * hash being announced.
506 *
507 * config is an integer in [0, 4) inclusive, and selects the variant of the test used.
508 */
509 void TxRequestTest::BuildWtxidTest(Scenario& scenario, int config)
510 {
511 scenario.SetTestName(strprintf("Wtxid(config=%i)", config));
512
513 auto peerT = scenario.NewPeer();
514 auto peerW = scenario.NewPeer();
515 auto txhash = scenario.NewTxHash();
516 auto txid{Txid::FromUint256(txhash)};
517 auto wtxid{Wtxid::FromUint256(txhash)};
518
519 auto reqtimeT = m_rng.randbool() ? MIN_TIME : scenario.Now() + RandomTime8s();
520 auto reqtimeW = m_rng.randbool() ? MIN_TIME : scenario.Now() + RandomTime8s();
521
522 // Announce txid first or wtxid first.
523 if (config & 1) {
524 scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
525 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
526 scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
527 } else {
528 scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
529 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
530 scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
531 }
532
533 // Let time pass if needed, and check that the preferred announcement (txid or wtxid)
534 // is correctly to-be-requested (and with the correct wtxidness).
535 auto max_reqtime = std::max(reqtimeT, reqtimeW);
536 if (max_reqtime > scenario.Now()) scenario.AdvanceTime(max_reqtime - scenario.Now());
537 if (config & 2) {
538 scenario.Check(peerT, {txid}, 1, 0, 0, "w1");
539 scenario.Check(peerW, {}, 1, 0, 0, "w2");
540 } else {
541 scenario.Check(peerT, {}, 1, 0, 0, "w3");
542 scenario.Check(peerW, {wtxid}, 1, 0, 0, "w4");
543 }
544
545 // Let the preferred announcement be requested. It's not going to be delivered.
546 auto expiry = RandomTime8s();
547 if (config & 2) {
548 scenario.RequestedTx(peerT, txid.ToUint256(), scenario.Now() + expiry);
549 scenario.Check(peerT, {}, 0, 1, 0, "w5");
550 scenario.Check(peerW, {}, 1, 0, 0, "w6");
551 } else {
552 scenario.RequestedTx(peerW, wtxid.ToUint256(), scenario.Now() + expiry);
553 scenario.Check(peerT, {}, 1, 0, 0, "w7");
554 scenario.Check(peerW, {}, 0, 1, 0, "w8");
555 }
556
557 // After reaching expiration time of the preferred announcement, verify that the
558 // remaining one is requestable
559 scenario.AdvanceTime(expiry);
560 if (config & 2) {
561 scenario.Check(peerT, {}, 0, 0, 1, "w9");
562 scenario.Check(peerW, {wtxid}, 1, 0, 0, "w10");
563 scenario.CheckExpired(peerT, txid);
564 } else {
565 scenario.Check(peerT, {txid}, 1, 0, 0, "w11");
566 scenario.Check(peerW, {}, 0, 0, 1, "w12");
567 scenario.CheckExpired(peerW, wtxid);
568 }
569
570 // If a good transaction with either that hash as wtxid or txid arrives, both
571 // announcements are gone.
572 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
573 scenario.ForgetTxHash(txhash);
574 scenario.Check(peerT, {}, 0, 0, 0, "w13");
575 scenario.Check(peerW, {}, 0, 0, 0, "w14");
576 }
577
578 /** Add to scenario a test that exercises clocks that go backwards. */
579 void TxRequestTest::BuildTimeBackwardsTest(Scenario& scenario)
580 {
581 auto peer1 = scenario.NewPeer();
582 auto peer2 = scenario.NewPeer();
583 auto gtxid = scenario.NewGTxid({{peer1, peer2}});
584
585 // Announce from peer2.
586 auto reqtime = scenario.Now() + RandomTime8s();
587 scenario.ReceivedInv(peer2, gtxid, true, reqtime);
588 scenario.Check(peer2, {}, 1, 0, 0, "r1");
589 scenario.AdvanceTime(reqtime - scenario.Now());
590 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r2");
591 // Check that if the clock goes backwards by 1us, the transaction would stop being requested.
592 scenario.Check(peer2, {}, 1, 0, 0, "r3", -MICROSECOND);
593 // But it reverts to being requested if time goes forward again.
594 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r4");
595
596 // Announce from peer1.
597 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
598 scenario.ReceivedInv(peer1, gtxid, true, MAX_TIME);
599 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r5");
600 scenario.Check(peer1, {}, 1, 0, 0, "r6");
601
602 // Request from peer1.
603 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
604 auto expiry = scenario.Now() + RandomTime8s();
605 scenario.RequestedTx(peer1, gtxid.ToUint256(), expiry);
606 scenario.Check(peer1, {}, 0, 1, 0, "r7");
607 scenario.Check(peer2, {}, 1, 0, 0, "r8");
608
609 // Expiration passes.
610 scenario.AdvanceTime(expiry - scenario.Now());
611 scenario.Check(peer1, {}, 0, 0, 1, "r9");
612 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r10"); // Request goes back to peer2.
613 scenario.CheckExpired(peer1, gtxid);
614 scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); // Going back does not unexpire.
615 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r12", -MICROSECOND);
616
617 // Peer2 goes offline, meaning no viable announcements remain.
618 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
619 scenario.DisconnectedPeer(peer2);
620 scenario.Check(peer1, {}, 0, 0, 0, "r13");
621 scenario.Check(peer2, {}, 0, 0, 0, "r14");
622 }
623
624 /** Add to scenario a test that involves RequestedTx() calls for txhashes not returned by GetRequestable. */
625 void TxRequestTest::BuildWeirdRequestsTest(Scenario& scenario)
626 {
627 auto peer1 = scenario.NewPeer();
628 auto peer2 = scenario.NewPeer();
629 auto gtxid1 = scenario.NewGTxid({{peer1, peer2}});
630 auto gtxid2 = scenario.NewGTxid({{peer2, peer1}});
631
632 // Announce gtxid1 by peer1.
633 scenario.ReceivedInv(peer1, gtxid1, true, MIN_TIME);
634 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q1");
635
636 // Announce gtxid2 by peer2.
637 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
638 scenario.ReceivedInv(peer2, gtxid2, true, MIN_TIME);
639 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q2");
640 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q3");
641
642 // We request gtxid2 from *peer1* - no effect.
643 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
644 scenario.RequestedTx(peer1, gtxid2.ToUint256(), MAX_TIME);
645 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q4");
646 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q5");
647
648 // Now request gtxid1 from peer1 - marks it as REQUESTED.
649 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
650 auto expiryA = scenario.Now() + RandomTime8s();
651 scenario.RequestedTx(peer1, gtxid1.ToUint256(), expiryA);
652 scenario.Check(peer1, {}, 0, 1, 0, "q6");
653 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q7");
654
655 // Request it a second time - nothing happens, as it's already REQUESTED.
656 auto expiryB = expiryA + RandomTime8s();
657 scenario.RequestedTx(peer1, gtxid1.ToUint256(), expiryB);
658 scenario.Check(peer1, {}, 0, 1, 0, "q8");
659 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q9");
660
661 // Also announce gtxid1 from peer2 now, so that the txhash isn't forgotten when the peer1 request expires.
662 scenario.ReceivedInv(peer2, gtxid1, true, MIN_TIME);
663 scenario.Check(peer1, {}, 0, 1, 0, "q10");
664 scenario.Check(peer2, {gtxid2}, 2, 0, 0, "q11");
665
666 // When reaching expiryA, it expires (not expiryB, which is later).
667 scenario.AdvanceTime(expiryA - scenario.Now());
668 scenario.Check(peer1, {}, 0, 0, 1, "q12");
669 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q13");
670 scenario.CheckExpired(peer1, gtxid1);
671
672 // Requesting it yet again from peer1 doesn't do anything, as it's already COMPLETED.
673 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
674 scenario.RequestedTx(peer1, gtxid1.ToUint256(), MAX_TIME);
675 scenario.Check(peer1, {}, 0, 0, 1, "q14");
676 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q15");
677
678 // Now announce gtxid2 from peer1.
679 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
680 scenario.ReceivedInv(peer1, gtxid2, true, MIN_TIME);
681 scenario.Check(peer1, {}, 1, 0, 1, "q16");
682 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q17");
683
684 // And request it from peer1 (weird as peer2 has the preference).
685 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
686 scenario.RequestedTx(peer1, gtxid2.ToUint256(), MAX_TIME);
687 scenario.Check(peer1, {}, 0, 1, 1, "q18");
688 scenario.Check(peer2, {gtxid1}, 2, 0, 0, "q19");
689
690 // If peer2 now (normally) requests gtxid2, the existing request by peer1 becomes COMPLETED.
691 if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());
692 scenario.RequestedTx(peer2, gtxid2.ToUint256(), MAX_TIME);
693 scenario.Check(peer1, {}, 0, 0, 2, "q20");
694 scenario.Check(peer2, {gtxid1}, 1, 1, 0, "q21");
695
696 // If peer2 goes offline, no viable announcements remain.
697 scenario.DisconnectedPeer(peer2);
698 scenario.Check(peer1, {}, 0, 0, 0, "q22");
699 scenario.Check(peer2, {}, 0, 0, 0, "q23");
700 }
701
702 void TxRequestTest::TestInterleavedScenarios()
703 {
704 // Create a list of functions which add tests to scenarios.
705 std::vector<std::function<void(Scenario&)>> builders;
706 // Add instances of every test, for every configuration.
707 for (int n = 0; n < 64; ++n) {
708 builders.emplace_back([this, n](Scenario& scenario) { BuildWtxidTest(scenario, n); });
709 builders.emplace_back([this, n](Scenario& scenario) { BuildRequestOrderTest(scenario, n & 3); });
710 builders.emplace_back([this, n](Scenario& scenario) { BuildSingleTest(scenario, n & 31); });
711 builders.emplace_back([this, n](Scenario& scenario) { BuildPriorityTest(scenario, n & 31); });
712 builders.emplace_back([this, n](Scenario& scenario) { BuildBigPriorityTest(scenario, (n & 7) + 1); });
713 builders.emplace_back([this](Scenario& scenario) { BuildTimeBackwardsTest(scenario); });
714 builders.emplace_back([this](Scenario& scenario) { BuildWeirdRequestsTest(scenario); });
715 }
716 // Randomly shuffle all those functions.
717 std::shuffle(builders.begin(), builders.end(), m_rng);
718
719 Runner runner;
720 auto starttime = RandomTime1y();
721 // Construct many scenarios, and run (up to) 10 randomly-chosen tests consecutively in each.
722 while (builders.size()) {
723 // Introduce some variation in the start time of each scenario, so they don't all start off
724 // concurrently, but get a more random interleaving.
725 auto scenario_start = starttime + RandomTime8s() + RandomTime8s() + RandomTime8s();
726 Scenario scenario(m_rng, runner, scenario_start);
727 for (int j = 0; builders.size() && j < 10; ++j) {
728 builders.back()(scenario);
729 builders.pop_back();
730 }
731 }
732 // Sort all the actions from all those scenarios chronologically, resulting in the actions from
733 // distinct scenarios to become interleaved. Use stable_sort so that actions from one scenario
734 // aren't reordered w.r.t. each other.
735 std::stable_sort(runner.actions.begin(), runner.actions.end(), [](const Action& a1, const Action& a2) {
736 return a1.first < a2.first;
737 });
738
739 // Run all actions from all scenarios, in order.
740 for (auto& action : runner.actions) {
741 action.second();
742 }
743
744 BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U);
745 BOOST_CHECK(runner.expired.empty());
746 }
747
748 } // namespace
749
750 BOOST_FIXTURE_TEST_SUITE(txrequest_tests, TxRequestTest)
751
752 BOOST_AUTO_TEST_CASE(TxRequestTest)
753 {
754 for (int i = 0; i < 5; ++i) {
755 TestInterleavedScenarios();
756 }
757 }
758
759 BOOST_AUTO_TEST_SUITE_END()
760