Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501scenario.Check(peer, {}, 0, 0, 0, "o5");502}504/** Add to scenario a test that verifies behavior related to both txid and wtxid with the same505* hash being announced.506*507* config is an integer in [0, 4) inclusive, and selects the variant of the test used.508*/509void TxRequestTest::BuildWtxidTest(Scenario& scenario, int config)510{511scenario.SetTestName(strprintf("Wtxid(config=%i)", config));513auto peerT = scenario.NewPeer();514auto peerW = scenario.NewPeer();515auto txhash = scenario.NewTxHash();516auto txid{Txid::FromUint256(txhash)};517auto wtxid{Wtxid::FromUint256(txhash)};519auto reqtimeT = m_rng.randbool() ? MIN_TIME : scenario.Now() + RandomTime8s();520auto reqtimeW = m_rng.randbool() ? MIN_TIME : scenario.Now() + RandomTime8s();522// Announce txid first or wtxid first.523if (config & 1) {524scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);525if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());526scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);527} else {528scenario.ReceivedInv(peerW, wtxid, !(config & 2), reqtimeW);529if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());530scenario.ReceivedInv(peerT, txid, config & 2, reqtimeT);531}533// Let time pass if needed, and check that the preferred announcement (txid or wtxid)534// is correctly to-be-requested (and with the correct wtxidness).535auto max_reqtime = std::max(reqtimeT, reqtimeW);536if (max_reqtime > scenario.Now()) scenario.AdvanceTime(max_reqtime - scenario.Now());537if (config & 2) {538scenario.Check(peerT, {txid}, 1, 0, 0, "w1");539scenario.Check(peerW, {}, 1, 0, 0, "w2");540} else {541scenario.Check(peerT, {}, 1, 0, 0, "w3");542scenario.Check(peerW, {wtxid}, 1, 0, 0, "w4");543}545// Let the preferred announcement be requested. It's not going to be delivered.546auto expiry = RandomTime8s();547if (config & 2) {548scenario.RequestedTx(peerT, txid.ToUint256(), scenario.Now() + expiry);549scenario.Check(peerT, {}, 0, 1, 0, "w5");550scenario.Check(peerW, {}, 1, 0, 0, "w6");551} else {552scenario.RequestedTx(peerW, wtxid.ToUint256(), scenario.Now() + expiry);553scenario.Check(peerT, {}, 1, 0, 0, "w7");554scenario.Check(peerW, {}, 0, 1, 0, "w8");555}557// After reaching expiration time of the preferred announcement, verify that the558// remaining one is requestable559scenario.AdvanceTime(expiry);560if (config & 2) {561scenario.Check(peerT, {}, 0, 0, 1, "w9");562scenario.Check(peerW, {wtxid}, 1, 0, 0, "w10");563scenario.CheckExpired(peerT, txid);564} else {565scenario.Check(peerT, {txid}, 1, 0, 0, "w11");566scenario.Check(peerW, {}, 0, 0, 1, "w12");567scenario.CheckExpired(peerW, wtxid);568}570// If a good transaction with either that hash as wtxid or txid arrives, both571// announcements are gone.572if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());573scenario.ForgetTxHash(txhash);574scenario.Check(peerT, {}, 0, 0, 0, "w13");575scenario.Check(peerW, {}, 0, 0, 0, "w14");576}578/** Add to scenario a test that exercises clocks that go backwards. */579void TxRequestTest::BuildTimeBackwardsTest(Scenario& scenario)580{581auto peer1 = scenario.NewPeer();582auto peer2 = scenario.NewPeer();583auto gtxid = scenario.NewGTxid({{peer1, peer2}});585// Announce from peer2.586auto reqtime = scenario.Now() + RandomTime8s();587scenario.ReceivedInv(peer2, gtxid, true, reqtime);588scenario.Check(peer2, {}, 1, 0, 0, "r1");589scenario.AdvanceTime(reqtime - scenario.Now());590scenario.Check(peer2, {gtxid}, 1, 0, 0, "r2");591// Check that if the clock goes backwards by 1us, the transaction would stop being requested.592scenario.Check(peer2, {}, 1, 0, 0, "r3", -MICROSECOND);593// But it reverts to being requested if time goes forward again.594scenario.Check(peer2, {gtxid}, 1, 0, 0, "r4");596// Announce from peer1.597if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());598scenario.ReceivedInv(peer1, gtxid, true, MAX_TIME);599scenario.Check(peer2, {gtxid}, 1, 0, 0, "r5");600scenario.Check(peer1, {}, 1, 0, 0, "r6");602// Request from peer1.603if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());604auto expiry = scenario.Now() + RandomTime8s();605scenario.RequestedTx(peer1, gtxid.ToUint256(), expiry);606scenario.Check(peer1, {}, 0, 1, 0, "r7");607scenario.Check(peer2, {}, 1, 0, 0, "r8");609// Expiration passes.610scenario.AdvanceTime(expiry - scenario.Now());611scenario.Check(peer1, {}, 0, 0, 1, "r9");612scenario.Check(peer2, {gtxid}, 1, 0, 0, "r10"); // Request goes back to peer2.613scenario.CheckExpired(peer1, gtxid);614scenario.Check(peer1, {}, 0, 0, 1, "r11", -MICROSECOND); // Going back does not unexpire.615scenario.Check(peer2, {gtxid}, 1, 0, 0, "r12", -MICROSECOND);617// Peer2 goes offline, meaning no viable announcements remain.618if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());619scenario.DisconnectedPeer(peer2);620scenario.Check(peer1, {}, 0, 0, 0, "r13");621scenario.Check(peer2, {}, 0, 0, 0, "r14");622}624/** Add to scenario a test that involves RequestedTx() calls for txhashes not returned by GetRequestable. */625void TxRequestTest::BuildWeirdRequestsTest(Scenario& scenario)626{627auto peer1 = scenario.NewPeer();628auto peer2 = scenario.NewPeer();629auto gtxid1 = scenario.NewGTxid({{peer1, peer2}});630auto gtxid2 = scenario.NewGTxid({{peer2, peer1}});632// Announce gtxid1 by peer1.633scenario.ReceivedInv(peer1, gtxid1, true, MIN_TIME);634scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q1");636// Announce gtxid2 by peer2.637if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());638scenario.ReceivedInv(peer2, gtxid2, true, MIN_TIME);639scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q2");640scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q3");642// We request gtxid2 from *peer1* - no effect.643if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());644scenario.RequestedTx(peer1, gtxid2.ToUint256(), MAX_TIME);645scenario.Check(peer1, {gtxid1}, 1, 0, 0, "q4");646scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q5");648// Now request gtxid1 from peer1 - marks it as REQUESTED.649if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());650auto expiryA = scenario.Now() + RandomTime8s();651scenario.RequestedTx(peer1, gtxid1.ToUint256(), expiryA);652scenario.Check(peer1, {}, 0, 1, 0, "q6");653scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q7");655// Request it a second time - nothing happens, as it's already REQUESTED.656auto expiryB = expiryA + RandomTime8s();657scenario.RequestedTx(peer1, gtxid1.ToUint256(), expiryB);658scenario.Check(peer1, {}, 0, 1, 0, "q8");659scenario.Check(peer2, {gtxid2}, 1, 0, 0, "q9");661// Also announce gtxid1 from peer2 now, so that the txhash isn't forgotten when the peer1 request expires.662scenario.ReceivedInv(peer2, gtxid1, true, MIN_TIME);663scenario.Check(peer1, {}, 0, 1, 0, "q10");664scenario.Check(peer2, {gtxid2}, 2, 0, 0, "q11");666// When reaching expiryA, it expires (not expiryB, which is later).667scenario.AdvanceTime(expiryA - scenario.Now());668scenario.Check(peer1, {}, 0, 0, 1, "q12");669scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q13");670scenario.CheckExpired(peer1, gtxid1);672// Requesting it yet again from peer1 doesn't do anything, as it's already COMPLETED.673if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());674scenario.RequestedTx(peer1, gtxid1.ToUint256(), MAX_TIME);675scenario.Check(peer1, {}, 0, 0, 1, "q14");676scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q15");678// Now announce gtxid2 from peer1.679if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());680scenario.ReceivedInv(peer1, gtxid2, true, MIN_TIME);681scenario.Check(peer1, {}, 1, 0, 1, "q16");682scenario.Check(peer2, {gtxid2, gtxid1}, 2, 0, 0, "q17");684// And request it from peer1 (weird as peer2 has the preference).685if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());686scenario.RequestedTx(peer1, gtxid2.ToUint256(), MAX_TIME);687scenario.Check(peer1, {}, 0, 1, 1, "q18");688scenario.Check(peer2, {gtxid1}, 2, 0, 0, "q19");690// If peer2 now (normally) requests gtxid2, the existing request by peer1 becomes COMPLETED.691if (m_rng.randbool()) scenario.AdvanceTime(RandomTime8s());692scenario.RequestedTx(peer2, gtxid2.ToUint256(), MAX_TIME);693scenario.Check(peer1, {}, 0, 0, 2, "q20");694scenario.Check(peer2, {gtxid1}, 1, 1, 0, "q21");696// If peer2 goes offline, no viable announcements remain.697scenario.DisconnectedPeer(peer2);698scenario.Check(peer1, {}, 0, 0, 0, "q22");699scenario.Check(peer2, {}, 0, 0, 0, "q23");700}702void TxRequestTest::TestInterleavedScenarios()703{704// Create a list of functions which add tests to scenarios.705std::vector<std::function<void(Scenario&)>> builders;706// Add instances of every test, for every configuration.707for (int n = 0; n < 64; ++n) {708builders.emplace_back([this, n](Scenario& scenario) { BuildWtxidTest(scenario, n); });709builders.emplace_back([this, n](Scenario& scenario) { BuildRequestOrderTest(scenario, n & 3); });710builders.emplace_back([this, n](Scenario& scenario) { BuildSingleTest(scenario, n & 31); });711builders.emplace_back([this, n](Scenario& scenario) { BuildPriorityTest(scenario, n & 31); });712builders.emplace_back([this, n](Scenario& scenario) { BuildBigPriorityTest(scenario, (n & 7) + 1); });713builders.emplace_back([this](Scenario& scenario) { BuildTimeBackwardsTest(scenario); });714builders.emplace_back([this](Scenario& scenario) { BuildWeirdRequestsTest(scenario); });715}716// Randomly shuffle all those functions.717std::shuffle(builders.begin(), builders.end(), m_rng);719Runner runner;720auto starttime = RandomTime1y();721// Construct many scenarios, and run (up to) 10 randomly-chosen tests consecutively in each.722while (builders.size()) {723// Introduce some variation in the start time of each scenario, so they don't all start off724// concurrently, but get a more random interleaving.725auto scenario_start = starttime + RandomTime8s() + RandomTime8s() + RandomTime8s();726Scenario scenario(m_rng, runner, scenario_start);727for (int j = 0; builders.size() && j < 10; ++j) {728builders.back()(scenario);729builders.pop_back();730}731}732// Sort all the actions from all those scenarios chronologically, resulting in the actions from733// distinct scenarios to become interleaved. Use stable_sort so that actions from one scenario734// aren't reordered w.r.t. each other.735std::stable_sort(runner.actions.begin(), runner.actions.end(), [](const Action& a1, const Action& a2) {736return a1.first < a2.first;737});739// Run all actions from all scenarios, in order.740for (auto& action : runner.actions) {741action.second();742}744BOOST_CHECK_EQUAL(runner.txrequest.Size(), 0U);745BOOST_CHECK(runner.expired.empty());746}748} // namespace750BOOST_FIXTURE_TEST_SUITE(txrequest_tests, TxRequestTest)
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.