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.
1// Copyright (c) 2020-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.6#include <txrequest.h>7#include <uint256.h>9#include <test/util/random.h>10#include <test/util/setup_common.h>12#include <algorithm>13#include <functional>14#include <vector>16#include <boost/test/unit_test.hpp>18namespace {20class Scenario;22struct TxRequestTest : BasicTestingSetup {23std::chrono::microseconds RandomTime8s();24std::chrono::microseconds RandomTime1y();25void BuildSingleTest(Scenario& scenario, int config);26void BuildPriorityTest(Scenario& scenario, int config);27void BuildBigPriorityTest(Scenario& scenario, int peers);28void BuildRequestOrderTest(Scenario& scenario, int config);29void BuildWtxidTest(Scenario& scenario, int config);30void BuildTimeBackwardsTest(Scenario& scenario);31void BuildWeirdRequestsTest(Scenario& scenario);32void TestInterleavedScenarios();33};35constexpr std::chrono::microseconds MIN_TIME = std::chrono::microseconds::min();36constexpr std::chrono::microseconds MAX_TIME = std::chrono::microseconds::max();37constexpr std::chrono::microseconds MICROSECOND = std::chrono::microseconds{1};38constexpr std::chrono::microseconds NO_TIME = std::chrono::microseconds{0};40/** An Action is a function to call at a particular (simulated) timestamp. */41using Action = std::pair<std::chrono::microseconds, std::function<void()>>;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*/47struct Runner48{49/** The TxRequestTracker being tested. */50TxRequestTracker txrequest;52/** List of actions to be executed (in order of increasing timestamp). */53std::vector<Action> actions;55/** Which node ids have been assigned already (to prevent reuse). */56std::set<NodeId> peerset;58/** Which txhashes have been assigned already (to prevent reuse). */59std::set<uint256> txhashset;61/** Which (peer, gtxid) combinations are known to be expired. These need to be accumulated here instead of62* checked directly in the GetRequestable return value to avoid introducing a dependency between the various63* parallel tests. */64std::multiset<std::pair<NodeId, GenTxid>> expired;65};67std::chrono::microseconds TxRequestTest::RandomTime8s() { return std::chrono::microseconds{1 + m_rng.randbits(23)}; }68std::chrono::microseconds TxRequestTest::RandomTime1y() { return std::chrono::microseconds{1 + m_rng.randbits(45)}; }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 a73* Runner. The actions from multiple scenarios are then run concurrently, resulting in these tests being performed74* against a TxRequestTracker in parallel. Every test has its own unique txhashes and NodeIds which are not75* reused in other tests, and thus they should be independent from each other. Running them in parallel however76* means that we verify the behavior (w.r.t. one test's txhashes and NodeIds) even when the state of the data77* structure is more complicated due to the presence of other tests.78*/79class Scenario80{81FastRandomContext& m_rng;82Runner& m_runner;83std::chrono::microseconds m_now;84std::string m_testname;86public:87Scenario(FastRandomContext& rng, Runner& runner, std::chrono::microseconds starttime) : m_rng(rng), m_runner(runner), m_now(starttime) {}89/** Set a name for the current test, to give more clear error messages. */90void SetTestName(std::string testname)91{92m_testname = std::move(testname);93}95/** Advance this Scenario's time; this affects the timestamps newly scheduled events get. */96void AdvanceTime(std::chrono::microseconds amount)97{98assert(amount.count() >= 0);99m_now += amount;100}102/** Schedule a ForgetTxHash call at the Scheduler's current time. */103void ForgetTxHash(const uint256& txhash)104{105auto& runner = m_runner;106runner.actions.emplace_back(m_now, [=, &runner]() {107runner.txrequest.ForgetTxHash(txhash);108runner.txrequest.SanityCheck();109});110}112/** Schedule a ReceivedInv call at the Scheduler's current time. */113void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool pref, std::chrono::microseconds reqtime)114{115auto& runner = m_runner;116runner.actions.emplace_back(m_now, [=, &runner]() {117runner.txrequest.ReceivedInv(peer, gtxid, pref, reqtime);118runner.txrequest.SanityCheck();119});120}122/** Schedule a DisconnectedPeer call at the Scheduler's current time. */123void DisconnectedPeer(NodeId peer)124{125auto& runner = m_runner;126runner.actions.emplace_back(m_now, [=, &runner]() {127runner.txrequest.DisconnectedPeer(peer);128runner.txrequest.SanityCheck();129});130}132/** Schedule a RequestedTx call at the Scheduler's current time. */133void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds exptime)134{135auto& runner = m_runner;136runner.actions.emplace_back(m_now, [=, &runner]() {137runner.txrequest.RequestedTx(peer, txhash, exptime);138runner.txrequest.SanityCheck();139});140}142/** Schedule a ReceivedResponse call at the Scheduler's current time. */143void ReceivedResponse(NodeId peer, const uint256& txhash)144{145auto& runner = m_runner;146runner.actions.emplace_back(m_now, [=, &runner]() {147runner.txrequest.ReceivedResponse(peer, txhash);148runner.txrequest.SanityCheck();149});150}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 going161* backwards (but note that the ordering of this event only follows the scenario's m_now.162*/163void Check(NodeId peer, const std::vector<GenTxid>& expected, size_t candidates, size_t inflight,164size_t completed, const std::string& checkname,165std::chrono::microseconds offset = std::chrono::microseconds{0})166{167const auto comment = m_testname + " " + checkname;168auto& runner = m_runner;169const auto now = m_now;170assert(offset.count() <= 0);171runner.actions.emplace_back(m_now, [=, &runner]() {172std::vector<std::pair<NodeId, GenTxid>> expired_now;173auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);174for (const auto& entry : expired_now) {175runner.expired.insert(entry);176}177runner.txrequest.SanityCheck();178runner.txrequest.PostGetRequestableSanityCheck(now + offset);179size_t total = candidates + inflight + completed;180size_t real_total = runner.txrequest.Count(peer);181size_t real_candidates = runner.txrequest.CountCandidates(peer);182size_t real_inflight = runner.txrequest.CountInFlight(peer);183BOOST_CHECK_MESSAGE(real_total == total, strprintf("[%s] total %i (%i expected)", comment, real_total, total));184BOOST_CHECK_MESSAGE(real_inflight == inflight, strprintf("[%s] inflight %i (%i expected)", comment, real_inflight, inflight));185BOOST_CHECK_MESSAGE(real_candidates == candidates, strprintf("[%s] candidates %i (%i expected)", comment, real_candidates, candidates));186BOOST_CHECK_MESSAGE(ret == expected, strprintf("[%s] mismatching requestables", comment));187});188}190/** Verify that an announcement for gtxid by peer has expired some time before this check is scheduled.191*192* Every expected expiration should be accounted for through exactly one call to this function.193*/194void CheckExpired(NodeId peer, GenTxid gtxid)195{196const auto& testname = m_testname;197auto& runner = m_runner;198runner.actions.emplace_back(m_now, [=, &runner]() {199auto it = runner.expired.find(std::pair<NodeId, GenTxid>{peer, gtxid});200BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");201if (it != runner.expired.end()) runner.expired.erase(it);202});203}205/** Generate a random txhash, whose priorities for certain peers are constrained.206*207* For example, NewTxHash({{p1,p2,p3},{p2,p4,p5}}) will generate a txhash T such that both:208* - priority(p1,T) > priority(p2,T) > priority(p3,T)209* - priority(p2,T) > priority(p4,T) > priority(p5,T)210* where priority is the predicted internal TxRequestTracker's priority, assuming all announcements211* are within the same preferredness class.212*/213uint256 NewTxHash(const std::vector<std::vector<NodeId>>& orders = {})214{215uint256 ret;216bool ok;217do {218ret = m_rng.rand256();219ok = true;220for (const auto& order : orders) {221for (size_t pos = 1; pos < order.size(); ++pos) {222uint64_t prio_prev = m_runner.txrequest.ComputePriority(ret, order[pos - 1], true);223uint64_t prio_cur = m_runner.txrequest.ComputePriority(ret, order[pos], true);224if (prio_prev <= prio_cur) {225ok = false;226break;227}228}229if (!ok) break;230}231if (ok) {232ok = m_runner.txhashset.insert(ret).second;233}234} while(!ok);235return ret;236}238/** Generate a random GenTxid; the txhash follows NewTxHash; the transaction identifier is random. */239GenTxid NewGTxid(const std::vector<std::vector<NodeId>>& orders = {})240{241const uint256 txhash{NewTxHash(orders)};242return m_rng.randbool() ? GenTxid{Wtxid::FromUint256(txhash)} : GenTxid{Txid::FromUint256(txhash)};243}245/** Generate a new random NodeId to use as peer. The same NodeId is never returned twice246* (across all Scenarios combined). */247NodeId NewPeer()248{249bool ok;250NodeId ret;
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.