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