Bitcoin Core 28.0.0
P2P Digital Currency
Loading...
Searching...
No Matches
txrequest_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2020-2021 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>
11
12#include <algorithm>
13#include <functional>
14#include <vector>
15
16#include <boost/test/unit_test.hpp>
17
18BOOST_FIXTURE_TEST_SUITE(txrequest_tests, BasicTestingSetup)
19
20namespace {
21
22constexpr std::chrono::microseconds MIN_TIME = std::chrono::microseconds::min();
23constexpr std::chrono::microseconds MAX_TIME = std::chrono::microseconds::max();
24constexpr std::chrono::microseconds MICROSECOND = std::chrono::microseconds{1};
25constexpr std::chrono::microseconds NO_TIME = std::chrono::microseconds{0};
26
28using Action = std::pair<std::chrono::microseconds, std::function<void()>>;
29
34struct Runner
35{
37 TxRequestTracker txrequest;
38
40 std::vector<Action> actions;
41
43 std::set<NodeId> peerset;
44
46 std::set<uint256> txhashset;
47
51 std::multiset<std::pair<NodeId, GenTxid>> expired;
52};
53
54std::chrono::microseconds RandomTime8s() { return std::chrono::microseconds{1 + InsecureRandBits(23)}; }
55std::chrono::microseconds RandomTime1y() { return std::chrono::microseconds{1 + InsecureRandBits(45)}; }
56
66class Scenario
67{
68 Runner& m_runner;
69 std::chrono::microseconds m_now;
70 std::string m_testname;
71
72public:
73 Scenario(Runner& runner, std::chrono::microseconds starttime) : m_runner(runner), m_now(starttime) {}
74
76 void SetTestName(std::string testname)
77 {
78 m_testname = std::move(testname);
79 }
80
82 void AdvanceTime(std::chrono::microseconds amount)
83 {
84 assert(amount.count() >= 0);
85 m_now += amount;
86 }
87
89 void ForgetTxHash(const uint256& txhash)
90 {
91 auto& runner = m_runner;
92 runner.actions.emplace_back(m_now, [=,&runner]() {
93 runner.txrequest.ForgetTxHash(txhash);
94 runner.txrequest.SanityCheck();
95 });
96 }
97
99 void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool pref, std::chrono::microseconds reqtime)
100 {
101 auto& runner = m_runner;
102 runner.actions.emplace_back(m_now, [=,&runner]() {
103 runner.txrequest.ReceivedInv(peer, gtxid, pref, reqtime);
104 runner.txrequest.SanityCheck();
105 });
106 }
107
109 void DisconnectedPeer(NodeId peer)
110 {
111 auto& runner = m_runner;
112 runner.actions.emplace_back(m_now, [=,&runner]() {
113 runner.txrequest.DisconnectedPeer(peer);
114 runner.txrequest.SanityCheck();
115 });
116 }
117
119 void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds exptime)
120 {
121 auto& runner = m_runner;
122 runner.actions.emplace_back(m_now, [=,&runner]() {
123 runner.txrequest.RequestedTx(peer, txhash, exptime);
124 runner.txrequest.SanityCheck();
125 });
126 }
127
129 void ReceivedResponse(NodeId peer, const uint256& txhash)
130 {
131 auto& runner = m_runner;
132 runner.actions.emplace_back(m_now, [=,&runner]() {
133 runner.txrequest.ReceivedResponse(peer, txhash);
134 runner.txrequest.SanityCheck();
135 });
136 }
137
149 void Check(NodeId peer, const std::vector<GenTxid>& expected, size_t candidates, size_t inflight,
150 size_t completed, const std::string& checkname,
151 std::chrono::microseconds offset = std::chrono::microseconds{0})
152 {
153 const auto comment = m_testname + " " + checkname;
154 auto& runner = m_runner;
155 const auto now = m_now;
156 assert(offset.count() <= 0);
157 runner.actions.emplace_back(m_now, [=,&runner]() {
158 std::vector<std::pair<NodeId, GenTxid>> expired_now;
159 auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);
160 for (const auto& entry : expired_now) runner.expired.insert(entry);
161 runner.txrequest.SanityCheck();
162 runner.txrequest.PostGetRequestableSanityCheck(now + offset);
163 size_t total = candidates + inflight + completed;
164 size_t real_total = runner.txrequest.Count(peer);
165 size_t real_candidates = runner.txrequest.CountCandidates(peer);
166 size_t real_inflight = runner.txrequest.CountInFlight(peer);
167 BOOST_CHECK_MESSAGE(real_total == total, strprintf("[" + comment + "] total %i (%i expected)", real_total, total));
168 BOOST_CHECK_MESSAGE(real_inflight == inflight, strprintf("[" + comment + "] inflight %i (%i expected)", real_inflight, inflight));
169 BOOST_CHECK_MESSAGE(real_candidates == candidates, strprintf("[" + comment + "] candidates %i (%i expected)", real_candidates, candidates));
170 BOOST_CHECK_MESSAGE(ret == expected, "[" + comment + "] mismatching requestables");
171 });
172 }
173
178 void CheckExpired(NodeId peer, GenTxid gtxid)
179 {
180 const auto& testname = m_testname;
181 auto& runner = m_runner;
182 runner.actions.emplace_back(m_now, [=,&runner]() {
183 auto it = runner.expired.find(std::pair<NodeId, GenTxid>{peer, gtxid});
184 BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");
185 if (it != runner.expired.end()) runner.expired.erase(it);
186 });
187 }
188
197 uint256 NewTxHash(const std::vector<std::vector<NodeId>>& orders = {})
198 {
199 uint256 ret;
200 bool ok;
201 do {
203 ok = true;
204 for (const auto& order : orders) {
205 for (size_t pos = 1; pos < order.size(); ++pos) {
206 uint64_t prio_prev = m_runner.txrequest.ComputePriority(ret, order[pos - 1], true);
207 uint64_t prio_cur = m_runner.txrequest.ComputePriority(ret, order[pos], true);
208 if (prio_prev <= prio_cur) {
209 ok = false;
210 break;
211 }
212 }
213 if (!ok) break;
214 }
215 if (ok) {
216 ok = m_runner.txhashset.insert(ret).second;
217 }
218 } while(!ok);
219 return ret;
220 }
221
223 GenTxid NewGTxid(const std::vector<std::vector<NodeId>>& orders = {})
224 {
225 return InsecureRandBool() ? GenTxid::Wtxid(NewTxHash(orders)) : GenTxid::Txid(NewTxHash(orders));
226 }
227
230 NodeId NewPeer()
231 {
232 bool ok;
233 NodeId ret;
234 do {
235 ret = InsecureRandBits(63);
236 ok = m_runner.peerset.insert(ret).second;
237 } while(!ok);
238 return ret;
239 }
240
241 std::chrono::microseconds Now() const { return m_now; }
242};
243
248void BuildSingleTest(Scenario& scenario, int config)
249{
250 auto peer = scenario.NewPeer();
251 auto gtxid = scenario.NewGTxid();
252 bool immediate = config & 1;
253 bool preferred = config & 2;
254 auto delay = immediate ? NO_TIME : RandomTime8s();
255
256 scenario.SetTestName(strprintf("Single(config=%i)", config));
257
258 // Receive an announcement, either immediately requestable or delayed.
259 scenario.ReceivedInv(peer, gtxid, preferred, immediate ? MIN_TIME : scenario.Now() + delay);
260 if (immediate) {
261 scenario.Check(peer, {gtxid}, 1, 0, 0, "s1");
262 } else {
263 scenario.Check(peer, {}, 1, 0, 0, "s2");
264 scenario.AdvanceTime(delay - MICROSECOND);
265 scenario.Check(peer, {}, 1, 0, 0, "s3");
266 scenario.AdvanceTime(MICROSECOND);
267 scenario.Check(peer, {gtxid}, 1, 0, 0, "s4");
268 }
269
270 if (config >> 3) { // We'll request the transaction
271 scenario.AdvanceTime(RandomTime8s());
272 auto expiry = RandomTime8s();
273 scenario.Check(peer, {gtxid}, 1, 0, 0, "s5");
274 scenario.RequestedTx(peer, gtxid.GetHash(), scenario.Now() + expiry);
275 scenario.Check(peer, {}, 0, 1, 0, "s6");
276
277 if ((config >> 3) == 1) { // The request will time out
278 scenario.AdvanceTime(expiry - MICROSECOND);
279 scenario.Check(peer, {}, 0, 1, 0, "s7");
280 scenario.AdvanceTime(MICROSECOND);
281 scenario.Check(peer, {}, 0, 0, 0, "s8");
282 scenario.CheckExpired(peer, gtxid);
283 return;
284 } else {
285 scenario.AdvanceTime(std::chrono::microseconds{InsecureRandRange(expiry.count())});
286 scenario.Check(peer, {}, 0, 1, 0, "s9");
287 if ((config >> 3) == 3) { // A response will arrive for the transaction
288 scenario.ReceivedResponse(peer, gtxid.GetHash());
289 scenario.Check(peer, {}, 0, 0, 0, "s10");
290 return;
291 }
292 }
293 }
294
295 if (config & 4) { // The peer will go offline
296 scenario.DisconnectedPeer(peer);
297 } else { // The transaction is no longer needed
298 scenario.ForgetTxHash(gtxid.GetHash());
299 }
300 scenario.Check(peer, {}, 0, 0, 0, "s11");
301}
302
308void BuildPriorityTest(Scenario& scenario, int config)
309{
310 scenario.SetTestName(strprintf("Priority(config=%i)", config));
311
312 // Two peers. They will announce in order {peer1, peer2}.
313 auto peer1 = scenario.NewPeer(), peer2 = scenario.NewPeer();
314 // Construct a transaction that under random rules would be preferred by peer2 or peer1,
315 // depending on configuration.
316 bool prio1 = config & 1;
317 auto gtxid = prio1 ? scenario.NewGTxid({{peer1, peer2}}) : scenario.NewGTxid({{peer2, peer1}});
318 bool pref1 = config & 2, pref2 = config & 4;
319
320 scenario.ReceivedInv(peer1, gtxid, pref1, MIN_TIME);
321 scenario.Check(peer1, {gtxid}, 1, 0, 0, "p1");
322 if (InsecureRandBool()) {
323 scenario.AdvanceTime(RandomTime8s());
324 scenario.Check(peer1, {gtxid}, 1, 0, 0, "p2");
325 }
326
327 scenario.ReceivedInv(peer2, gtxid, pref2, MIN_TIME);
328 bool stage2_prio =
329 // At this point, peer2 will be given priority if:
330 // - It is preferred and peer1 is not
331 (pref2 && !pref1) ||
332 // - They're in the same preference class,
333 // and the randomized priority favors peer2 over peer1.
334 (pref1 == pref2 && !prio1);
335 NodeId priopeer = stage2_prio ? peer2 : peer1, otherpeer = stage2_prio ? peer1 : peer2;
336 scenario.Check(otherpeer, {}, 1, 0, 0, "p3");
337 scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p4");
338 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
339 scenario.Check(otherpeer, {}, 1, 0, 0, "p5");
340 scenario.Check(priopeer, {gtxid}, 1, 0, 0, "p6");
341
342 // We possibly request from the selected peer.
343 if (config & 8) {
344 scenario.RequestedTx(priopeer, gtxid.GetHash(), MAX_TIME);
345 scenario.Check(priopeer, {}, 0, 1, 0, "p7");
346 scenario.Check(otherpeer, {}, 1, 0, 0, "p8");
347 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
348 }
349
350 // The peer which was selected (or requested from) now goes offline, or a NOTFOUND is received from them.
351 if (config & 16) {
352 scenario.DisconnectedPeer(priopeer);
353 } else {
354 scenario.ReceivedResponse(priopeer, gtxid.GetHash());
355 }
356 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
357 scenario.Check(priopeer, {}, 0, 0, !(config & 16), "p8");
358 scenario.Check(otherpeer, {gtxid}, 1, 0, 0, "p9");
359 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
360
361 // Now the other peer goes offline.
362 scenario.DisconnectedPeer(otherpeer);
363 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
364 scenario.Check(peer1, {}, 0, 0, 0, "p10");
365 scenario.Check(peer2, {}, 0, 0, 0, "p11");
366}
367
370void BuildBigPriorityTest(Scenario& scenario, int peers)
371{
372 scenario.SetTestName(strprintf("BigPriority(peers=%i)", peers));
373
374 // We will have N peers announce the same transaction.
375 std::map<NodeId, bool> preferred;
376 std::vector<NodeId> pref_peers, npref_peers;
377 int num_pref = InsecureRandRange(peers + 1) ; // Some preferred, ...
378 int num_npref = peers - num_pref; // some not preferred.
379 for (int i = 0; i < num_pref; ++i) {
380 pref_peers.push_back(scenario.NewPeer());
381 preferred[pref_peers.back()] = true;
382 }
383 for (int i = 0; i < num_npref; ++i) {
384 npref_peers.push_back(scenario.NewPeer());
385 preferred[npref_peers.back()] = false;
386 }
387 // Make a list of all peers, in order of intended request order (concatenation of pref_peers and npref_peers).
388 std::vector<NodeId> request_order;
389 request_order.reserve(num_pref + num_npref);
390 for (int i = 0; i < num_pref; ++i) request_order.push_back(pref_peers[i]);
391 for (int i = 0; i < num_npref; ++i) request_order.push_back(npref_peers[i]);
392
393 // Determine the announcement order randomly.
394 std::vector<NodeId> announce_order = request_order;
395 std::shuffle(announce_order.begin(), announce_order.end(), g_insecure_rand_ctx);
396
397 // Find a gtxid whose txhash prioritization is consistent with the required ordering within pref_peers and
398 // within npref_peers.
399 auto gtxid = scenario.NewGTxid({pref_peers, npref_peers});
400
401 // Decide reqtimes in opposite order of the expected request order. This means that as time passes we expect the
402 // to-be-requested-from-peer will change every time a subsequent reqtime is passed.
403 std::map<NodeId, std::chrono::microseconds> reqtimes;
404 auto reqtime = scenario.Now();
405 for (int i = peers - 1; i >= 0; --i) {
406 reqtime += RandomTime8s();
407 reqtimes[request_order[i]] = reqtime;
408 }
409
410 // Actually announce from all peers simultaneously (but in announce_order).
411 for (const auto peer : announce_order) {
412 scenario.ReceivedInv(peer, gtxid, preferred[peer], reqtimes[peer]);
413 }
414 for (const auto peer : announce_order) {
415 scenario.Check(peer, {}, 1, 0, 0, "b1");
416 }
417
418 // Let time pass and observe the to-be-requested-from peer change, from nonpreferred to preferred, and from
419 // high priority to low priority within each class.
420 for (int i = peers - 1; i >= 0; --i) {
421 scenario.AdvanceTime(reqtimes[request_order[i]] - scenario.Now() - MICROSECOND);
422 scenario.Check(request_order[i], {}, 1, 0, 0, "b2");
423 scenario.AdvanceTime(MICROSECOND);
424 scenario.Check(request_order[i], {gtxid}, 1, 0, 0, "b3");
425 }
426
427 // Peers now in random order go offline, or send NOTFOUNDs. At every point in time the new to-be-requested-from
428 // peer should be the best remaining one, so verify this after every response.
429 for (int i = 0; i < peers; ++i) {
430 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
431 const int pos = InsecureRandRange(request_order.size());
432 const auto peer = request_order[pos];
433 request_order.erase(request_order.begin() + pos);
434 if (InsecureRandBool()) {
435 scenario.DisconnectedPeer(peer);
436 scenario.Check(peer, {}, 0, 0, 0, "b4");
437 } else {
438 scenario.ReceivedResponse(peer, gtxid.GetHash());
439 scenario.Check(peer, {}, 0, 0, request_order.size() > 0, "b5");
440 }
441 if (request_order.size()) {
442 scenario.Check(request_order[0], {gtxid}, 1, 0, 0, "b6");
443 }
444 }
445
446 // Everything is gone in the end.
447 for (const auto peer : announce_order) {
448 scenario.Check(peer, {}, 0, 0, 0, "b7");
449 }
450}
451
457void BuildRequestOrderTest(Scenario& scenario, int config)
458{
459 scenario.SetTestName(strprintf("RequestOrder(config=%i)", config));
460
461 auto peer = scenario.NewPeer();
462 auto gtxid1 = scenario.NewGTxid();
463 auto gtxid2 = scenario.NewGTxid();
464
465 auto reqtime2 = scenario.Now() + RandomTime8s();
466 auto reqtime1 = reqtime2 + RandomTime8s();
467
468 scenario.ReceivedInv(peer, gtxid1, config & 1, reqtime1);
469 // Simulate time going backwards by giving the second announcement an earlier reqtime.
470 scenario.ReceivedInv(peer, gtxid2, config & 2, reqtime2);
471
472 scenario.AdvanceTime(reqtime2 - MICROSECOND - scenario.Now());
473 scenario.Check(peer, {}, 2, 0, 0, "o1");
474 scenario.AdvanceTime(MICROSECOND);
475 scenario.Check(peer, {gtxid2}, 2, 0, 0, "o2");
476 scenario.AdvanceTime(reqtime1 - MICROSECOND - scenario.Now());
477 scenario.Check(peer, {gtxid2}, 2, 0, 0, "o3");
478 scenario.AdvanceTime(MICROSECOND);
479 // Even with time going backwards in between announcements, the return value of GetRequestable is in
480 // announcement order.
481 scenario.Check(peer, {gtxid1, gtxid2}, 2, 0, 0, "o4");
482
483 scenario.DisconnectedPeer(peer);
484 scenario.Check(peer, {}, 0, 0, 0, "o5");
485}
486
492void BuildWtxidTest(Scenario& scenario, int config)
493{
494 scenario.SetTestName(strprintf("Wtxid(config=%i)", config));
495
496 auto peerT = scenario.NewPeer();
497 auto peerW = scenario.NewPeer();
498 auto txhash = scenario.NewTxHash();
499 auto txid{GenTxid::Txid(txhash)};
500 auto wtxid{GenTxid::Wtxid(txhash)};
501
502 auto reqtimeT = InsecureRandBool() ? MIN_TIME : scenario.Now() + RandomTime8s();
503 auto reqtimeW = InsecureRandBool() ? MIN_TIME : scenario.Now() + RandomTime8s();
504
505 // Announce txid first or wtxid first.
506 if (config & 1) {
507 scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
508 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
509 scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
510 } else {
511 scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);
512 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
513 scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);
514 }
515
516 // Let time pass if needed, and check that the preferred announcement (txid or wtxid)
517 // is correctly to-be-requested (and with the correct wtxidness).
518 auto max_reqtime = std::max(reqtimeT, reqtimeW);
519 if (max_reqtime > scenario.Now()) scenario.AdvanceTime(max_reqtime - scenario.Now());
520 if (config & 2) {
521 scenario.Check(peerT, {txid}, 1, 0, 0, "w1");
522 scenario.Check(peerW, {}, 1, 0, 0, "w2");
523 } else {
524 scenario.Check(peerT, {}, 1, 0, 0, "w3");
525 scenario.Check(peerW, {wtxid}, 1, 0, 0, "w4");
526 }
527
528 // Let the preferred announcement be requested. It's not going to be delivered.
529 auto expiry = RandomTime8s();
530 if (config & 2) {
531 scenario.RequestedTx(peerT, txid.GetHash(), scenario.Now() + expiry);
532 scenario.Check(peerT, {}, 0, 1, 0, "w5");
533 scenario.Check(peerW, {}, 1, 0, 0, "w6");
534 } else {
535 scenario.RequestedTx(peerW, wtxid.GetHash(), scenario.Now() + expiry);
536 scenario.Check(peerT, {}, 1, 0, 0, "w7");
537 scenario.Check(peerW, {}, 0, 1, 0, "w8");
538 }
539
540 // After reaching expiration time of the preferred announcement, verify that the
541 // remaining one is requestable
542 scenario.AdvanceTime(expiry);
543 if (config & 2) {
544 scenario.Check(peerT, {}, 0, 0, 1, "w9");
545 scenario.Check(peerW, {wtxid}, 1, 0, 0, "w10");
546 scenario.CheckExpired(peerT, txid);
547 } else {
548 scenario.Check(peerT, {txid}, 1, 0, 0, "w11");
549 scenario.Check(peerW, {}, 0, 0, 1, "w12");
550 scenario.CheckExpired(peerW, wtxid);
551 }
552
553 // If a good transaction with either that hash as wtxid or txid arrives, both
554 // announcements are gone.
555 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
556 scenario.ForgetTxHash(txhash);
557 scenario.Check(peerT, {}, 0, 0, 0, "w13");
558 scenario.Check(peerW, {}, 0, 0, 0, "w14");
559}
560
562void BuildTimeBackwardsTest(Scenario& scenario)
563{
564 auto peer1 = scenario.NewPeer();
565 auto peer2 = scenario.NewPeer();
566 auto gtxid = scenario.NewGTxid({{peer1, peer2}});
567
568 // Announce from peer2.
569 auto reqtime = scenario.Now() + RandomTime8s();
570 scenario.ReceivedInv(peer2, gtxid, true, reqtime);
571 scenario.Check(peer2, {}, 1, 0, 0, "r1");
572 scenario.AdvanceTime(reqtime - scenario.Now());
573 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r2");
574 // Check that if the clock goes backwards by 1us, the transaction would stop being requested.
575 scenario.Check(peer2, {}, 1, 0, 0, "r3", -MICROSECOND);
576 // But it reverts to being requested if time goes forward again.
577 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r4");
578
579 // Announce from peer1.
580 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
581 scenario.ReceivedInv(peer1, gtxid, true, MAX_TIME);
582 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r5");
583 scenario.Check(peer1, {}, 1, 0, 0, "r6");
584
585 // Request from peer1.
586 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
587 auto expiry = scenario.Now() + RandomTime8s();
588 scenario.RequestedTx(peer1, gtxid.GetHash(), expiry);
589 scenario.Check(peer1, {}, 0, 1, 0, "r7");
590 scenario.Check(peer2, {}, 1, 0, 0, "r8");
591
592 // Expiration passes.
593 scenario.AdvanceTime(expiry - scenario.Now());
594 scenario.Check(peer1, {}, 0, 0, 1, "r9");
595 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r10"); // Request goes back to peer2.
596 scenario.CheckExpired(peer1, gtxid);
597 scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); // Going back does not unexpire.
598 scenario.Check(peer2, {gtxid}, 1, 0, 0, "r12", -MICROSECOND);
599
600 // Peer2 goes offline, meaning no viable announcements remain.
601 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
602 scenario.DisconnectedPeer(peer2);
603 scenario.Check(peer1, {}, 0, 0, 0, "r13");
604 scenario.Check(peer2, {}, 0, 0, 0, "r14");
605}
606
608void BuildWeirdRequestsTest(Scenario& scenario)
609{
610 auto peer1 = scenario.NewPeer();
611 auto peer2 = scenario.NewPeer();
612 auto gtxid1 = scenario.NewGTxid({{peer1, peer2}});
613 auto gtxid2 = scenario.NewGTxid({{peer2, peer1}});
614
615 // Announce gtxid1 by peer1.
616 scenario.ReceivedInv(peer1, gtxid1, true, MIN_TIME);
617 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q1");
618
619 // Announce gtxid2 by peer2.
620 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
621 scenario.ReceivedInv(peer2, gtxid2, true, MIN_TIME);
622 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q2");
623 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q3");
624
625 // We request gtxid2 from *peer1* - no effect.
626 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
627 scenario.RequestedTx(peer1, gtxid2.GetHash(), MAX_TIME);
628 scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q4");
629 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q5");
630
631 // Now request gtxid1 from peer1 - marks it as REQUESTED.
632 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
633 auto expiryA = scenario.Now() + RandomTime8s();
634 scenario.RequestedTx(peer1, gtxid1.GetHash(), expiryA);
635 scenario.Check(peer1, {}, 0, 1, 0, "q6");
636 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q7");
637
638 // Request it a second time - nothing happens, as it's already REQUESTED.
639 auto expiryB = expiryA + RandomTime8s();
640 scenario.RequestedTx(peer1, gtxid1.GetHash(), expiryB);
641 scenario.Check(peer1, {}, 0, 1, 0, "q8");
642 scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q9");
643
644 // Also announce gtxid1 from peer2 now, so that the txhash isn't forgotten when the peer1 request expires.
645 scenario.ReceivedInv(peer2, gtxid1, true, MIN_TIME);
646 scenario.Check(peer1, {}, 0, 1, 0, "q10");
647 scenario.Check(peer2, {gtxid2}, 2, 0, 0, "q11");
648
649 // When reaching expiryA, it expires (not expiryB, which is later).
650 scenario.AdvanceTime(expiryA - scenario.Now());
651 scenario.Check(peer1, {}, 0, 0, 1, "q12");
652 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q13");
653 scenario.CheckExpired(peer1, gtxid1);
654
655 // Requesting it yet again from peer1 doesn't do anything, as it's already COMPLETED.
656 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
657 scenario.RequestedTx(peer1, gtxid1.GetHash(), MAX_TIME);
658 scenario.Check(peer1, {}, 0, 0, 1, "q14");
659 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q15");
660
661 // Now announce gtxid2 from peer1.
662 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
663 scenario.ReceivedInv(peer1, gtxid2, true, MIN_TIME);
664 scenario.Check(peer1, {}, 1, 0, 1, "q16");
665 scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q17");
666
667 // And request it from peer1 (weird as peer2 has the preference).
668 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
669 scenario.RequestedTx(peer1, gtxid2.GetHash(), MAX_TIME);
670 scenario.Check(peer1, {}, 0, 1, 1, "q18");
671 scenario.Check(peer2, {gtxid1}, 2, 0, 0, "q19");
672
673 // If peer2 now (normally) requests gtxid2, the existing request by peer1 becomes COMPLETED.
674 if (InsecureRandBool()) scenario.AdvanceTime(RandomTime8s());
675 scenario.RequestedTx(peer2, gtxid2.GetHash(), MAX_TIME);
676 scenario.Check(peer1, {}, 0, 0, 2, "q20");
677 scenario.Check(peer2, {gtxid1}, 1, 1, 0, "q21");
678
679 // If peer2 goes offline, no viable announcements remain.
680 scenario.DisconnectedPeer(peer2);
681 scenario.Check(peer1, {}, 0, 0, 0, "q22");
682 scenario.Check(peer2, {}, 0, 0, 0, "q23");
683}
684
685void TestInterleavedScenarios()
686{
687 // Create a list of functions which add tests to scenarios.
688 std::vector<std::function<void(Scenario&)>> builders;
689 // Add instances of every test, for every configuration.
690 for (int n = 0; n < 64; ++n) {
691 builders.emplace_back([n](Scenario& scenario){ BuildWtxidTest(scenario, n); });
692 builders.emplace_back([n](Scenario& scenario){ BuildRequestOrderTest(scenario, n & 3); });
693 builders.emplace_back([n](Scenario& scenario){ BuildSingleTest(scenario, n & 31); });
694 builders.emplace_back([n](Scenario& scenario){ BuildPriorityTest(scenario, n & 31); });
695 builders.emplace_back([n](Scenario& scenario){ BuildBigPriorityTest(scenario, (n & 7) + 1); });
696 builders.emplace_back([](Scenario& scenario){ BuildTimeBackwardsTest(scenario); });
697 builders.emplace_back([](Scenario& scenario){ BuildWeirdRequestsTest(scenario); });
698 }
699 // Randomly shuffle all those functions.
700 std::shuffle(builders.begin(), builders.end(), g_insecure_rand_ctx);
701
702 Runner runner;
703 auto starttime = RandomTime1y();
704 // Construct many scenarios, and run (up to) 10 randomly-chosen tests consecutively in each.
705 while (builders.size()) {
706 // Introduce some variation in the start time of each scenario, so they don't all start off
707 // concurrently, but get a more random interleaving.
708 auto scenario_start = starttime + RandomTime8s() + RandomTime8s() + RandomTime8s();
709 Scenario scenario(runner, scenario_start);
710 for (int j = 0; builders.size() && j < 10; ++j) {
711 builders.back()(scenario);
712 builders.pop_back();
713 }
714 }
715 // Sort all the actions from all those scenarios chronologically, resulting in the actions from
716 // distinct scenarios to become interleaved. Use stable_sort so that actions from one scenario
717 // aren't reordered w.r.t. each other.
718 std::stable_sort(runner.actions.begin(), runner.actions.end(), [](const Action& a1, const Action& a2) {
719 return a1.first < a2.first;
720 });
721
722 // Run all actions from all scenarios, in order.
723 for (auto& action : runner.actions) {
724 action.second();
725 }
726
727 BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U);
728 BOOST_CHECK(runner.expired.empty());
729}
730
731} // namespace
732
734{
735 for (int i = 0; i < 5; ++i) {
736 TestInterleavedScenarios();
737 }
738}
739
int ret
A generic txid reference (txid or wtxid).
static GenTxid Wtxid(const uint256 &hash)
const uint256 & GetHash() const LIFETIMEBOUND
static GenTxid Txid(const uint256 &hash)
Data structure to keep track of, and schedule, transaction downloads from peers.
Definition txrequest.h:96
uint64_t ComputePriority(const uint256 &txhash, NodeId peer, bool preferred) const
Access to the internal priority computation (testing only)
size_t Size() const
Count how many announcements are being tracked in total across all peers and transaction hashes.
256-bit opaque blob.
Definition uint256.h:178
BOOST_AUTO_TEST_SUITE_END()
int64_t NodeId
Definition net.h:97
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:18
#define BOOST_CHECK(expr)
Definition object.cpp:17
Basic testing setup.
FastRandomContext g_insecure_rand_ctx
This global and the helpers that use it are not thread-safe.
Definition random.cpp:14
static uint64_t InsecureRandRange(uint64_t range)
Definition random.h:45
static uint256 InsecureRand256()
Definition random.h:35
static uint64_t InsecureRandBits(int bits)
Definition random.h:40
static bool InsecureRandBool()
Definition random.h:50
T Now()
Return the current time point cast to the given precision.
Definition time.h:91
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
BOOST_AUTO_TEST_CASE(TxRequestTest)
assert(!tx.IsCoinBase())