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