Skip to content

Commit c77f7cd

Browse files
author
MarcoFalke
committed
Merge #16197: net: Use mockable time for tx download
fab3658 [qa] Test that getdata requests work as expected (Suhas Daftuar) fa883ab net: Use mockable time for tx download (MarcoFalke) Pull request description: Two commits: * First commit changes to mockable time for tx download (refactoring, should only have an effect on regtest) * Second commit adds a test that uses mocktime to test tx download ACKs for top commit: laanwj: code review ACK 16197/commits/fab365835639a3da03f8ad9a58a0db6c6c4c2314 jamesob: ACK bitcoin/bitcoin@fab3658 Tree-SHA512: 3a64a3e283ec4bab1f6e506404b11f0a564a5b61d2a7508ae738a61f035e57220484c66e0ae47d847fe9f7e3ff5cc834909d7b34a9bbcea6abe01f8742806908
2 parents 1063fc9 + fab3658 commit c77f7cd

File tree

5 files changed

+218
-32
lines changed

5 files changed

+218
-32
lines changed

src/net_processing.cpp

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100;
6868
/** Maximum number of announced transactions from a peer */
6969
static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ;
7070
/** How many microseconds to delay requesting transactions from inbound peers */
71-
static constexpr int64_t INBOUND_PEER_TX_DELAY = 2 * 1000000; // 2 seconds
71+
static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{std::chrono::seconds{2}};
7272
/** How long to wait (in microseconds) before downloading a transaction from an additional peer */
73-
static constexpr int64_t GETDATA_TX_INTERVAL = 60 * 1000000; // 1 minute
73+
static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seconds{60}};
7474
/** Maximum delay (in microseconds) for transaction requests to avoid biasing some peers over others. */
75-
static constexpr int64_t MAX_GETDATA_RANDOM_DELAY = 2 * 1000000; // 2 seconds
75+
static constexpr std::chrono::microseconds MAX_GETDATA_RANDOM_DELAY{std::chrono::seconds{2}};
7676
/** How long to wait (in microseconds) before expiring an in-flight getdata request to a peer */
77-
static constexpr int64_t TX_EXPIRY_INTERVAL = 10 * GETDATA_TX_INTERVAL;
77+
static constexpr std::chrono::microseconds TX_EXPIRY_INTERVAL{GETDATA_TX_INTERVAL * 10};
7878
static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY,
7979
"To preserve security, MAX_GETDATA_RANDOM_DELAY should not exceed INBOUND_PEER_DELAY");
8080
/** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */
@@ -340,16 +340,16 @@ struct CNodeState {
340340
/* Track when to attempt download of announced transactions (process
341341
* time in micros -> txid)
342342
*/
343-
std::multimap<int64_t, uint256> m_tx_process_time;
343+
std::multimap<std::chrono::microseconds, uint256> m_tx_process_time;
344344

345345
//! Store all the transactions a peer has recently announced
346346
std::set<uint256> m_tx_announced;
347347

348348
//! Store transactions which were requested by us, with timestamp
349-
std::map<uint256, int64_t> m_tx_in_flight;
349+
std::map<uint256, std::chrono::microseconds> m_tx_in_flight;
350350

351351
//! Periodically check for stuck getdata requests
352-
int64_t m_check_expiry_timer{0};
352+
std::chrono::microseconds m_check_expiry_timer{0};
353353
};
354354

355355
TxDownloadState m_tx_download;
@@ -391,7 +391,7 @@ struct CNodeState {
391391
};
392392

393393
// Keeps track of the time (in microseconds) when transactions were requested last time
394-
limitedmap<uint256, int64_t> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ);
394+
limitedmap<uint256, std::chrono::microseconds> g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ);
395395

396396
/** Map maintaining per-node state. */
397397
static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main);
@@ -688,16 +688,16 @@ void EraseTxRequest(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
688688
g_already_asked_for.erase(txid);
689689
}
690690

691-
int64_t GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
691+
std::chrono::microseconds GetTxRequestTime(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
692692
{
693693
auto it = g_already_asked_for.find(txid);
694694
if (it != g_already_asked_for.end()) {
695695
return it->second;
696696
}
697-
return 0;
697+
return {};
698698
}
699699

700-
void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
700+
void UpdateTxRequestTime(const uint256& txid, std::chrono::microseconds request_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
701701
{
702702
auto it = g_already_asked_for.find(txid);
703703
if (it == g_already_asked_for.end()) {
@@ -707,17 +707,17 @@ void UpdateTxRequestTime(const uint256& txid, int64_t request_time) EXCLUSIVE_LO
707707
}
708708
}
709709

710-
int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
710+
std::chrono::microseconds CalculateTxGetDataTime(const uint256& txid, std::chrono::microseconds current_time, bool use_inbound_delay) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
711711
{
712-
int64_t process_time;
713-
int64_t last_request_time = GetTxRequestTime(txid);
712+
std::chrono::microseconds process_time;
713+
const auto last_request_time = GetTxRequestTime(txid);
714714
// First time requesting this tx
715-
if (last_request_time == 0) {
715+
if (last_request_time.count() == 0) {
716716
process_time = current_time;
717717
} else {
718718
// Randomize the delay to avoid biasing some peers over others (such as due to
719719
// fixed ordering of peer processing in ThreadMessageHandler)
720-
process_time = last_request_time + GETDATA_TX_INTERVAL + GetRand(MAX_GETDATA_RANDOM_DELAY);
720+
process_time = last_request_time + GETDATA_TX_INTERVAL + GetRandMicros(MAX_GETDATA_RANDOM_DELAY);
721721
}
722722

723723
// We delay processing announcements from inbound peers
@@ -726,7 +726,7 @@ int64_t CalculateTxGetDataTime(const uint256& txid, int64_t current_time, bool u
726726
return process_time;
727727
}
728728

729-
void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
729+
void RequestTx(CNodeState* state, const uint256& txid, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
730730
{
731731
CNodeState::TxDownloadState& peer_download_state = state->m_tx_download;
732732
if (peer_download_state.m_tx_announced.size() >= MAX_PEER_TX_ANNOUNCEMENTS ||
@@ -740,7 +740,7 @@ void RequestTx(CNodeState* state, const uint256& txid, int64_t nNow) EXCLUSIVE_L
740740

741741
// Calculate the time to try requesting this transaction. Use
742742
// fPreferredDownload as a proxy for outbound peers.
743-
int64_t process_time = CalculateTxGetDataTime(txid, nNow, !state->fPreferredDownload);
743+
const auto process_time = CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload);
744744

745745
peer_download_state.m_tx_process_time.emplace(process_time, txid);
746746
}
@@ -2223,7 +2223,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
22232223
LOCK(cs_main);
22242224

22252225
uint32_t nFetchFlags = GetFetchFlags(pfrom);
2226-
int64_t nNow = GetTimeMicros();
2226+
const auto current_time = GetTime<std::chrono::microseconds>();
22272227

22282228
for (CInv &inv : vInv)
22292229
{
@@ -2255,7 +2255,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
22552255
if (fBlocksOnly) {
22562256
LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId());
22572257
} else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) {
2258-
RequestTx(State(pfrom->GetId()), inv.hash, nNow);
2258+
RequestTx(State(pfrom->GetId()), inv.hash, current_time);
22592259
}
22602260
}
22612261
}
@@ -2529,12 +2529,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
25292529
}
25302530
if (!fRejectedParents) {
25312531
uint32_t nFetchFlags = GetFetchFlags(pfrom);
2532-
int64_t nNow = GetTimeMicros();
2532+
const auto current_time = GetTime<std::chrono::microseconds>();
25332533

25342534
for (const CTxIn& txin : tx.vin) {
25352535
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
25362536
pfrom->AddInventoryKnown(_inv);
2537-
if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, nNow);
2537+
if (!AlreadyHave(_inv)) RequestTx(State(pfrom->GetId()), _inv.hash, current_time);
25382538
}
25392539
AddOrphanTx(ptx, pfrom->GetId());
25402540

@@ -3906,6 +3906,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39063906
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
39073907

39083908
// Detect whether we're stalling
3909+
const auto current_time = GetTime<std::chrono::microseconds>();
3910+
// nNow is the current system time (GetTimeMicros is not mockable) and
3911+
// should be replaced by the mockable current_time eventually
39093912
nNow = GetTimeMicros();
39103913
if (state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) {
39113914
// Stalling only triggers when the block download window cannot move. During normal steady state,
@@ -3998,9 +4001,9 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
39984001
// were unresponsive in the past.
39994002
// Eventually we should consider disconnecting peers, but this is
40004003
// conservative.
4001-
if (state.m_tx_download.m_check_expiry_timer <= nNow) {
4004+
if (state.m_tx_download.m_check_expiry_timer <= current_time) {
40024005
for (auto it=state.m_tx_download.m_tx_in_flight.begin(); it != state.m_tx_download.m_tx_in_flight.end();) {
4003-
if (it->second <= nNow - TX_EXPIRY_INTERVAL) {
4006+
if (it->second <= current_time - TX_EXPIRY_INTERVAL) {
40044007
LogPrint(BCLog::NET, "timeout of inflight tx %s from peer=%d\n", it->first.ToString(), pto->GetId());
40054008
state.m_tx_download.m_tx_announced.erase(it->first);
40064009
state.m_tx_download.m_tx_in_flight.erase(it++);
@@ -4010,11 +4013,11 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
40104013
}
40114014
// On average, we do this check every TX_EXPIRY_INTERVAL. Randomize
40124015
// so that we're not doing this for all peers at the same time.
4013-
state.m_tx_download.m_check_expiry_timer = nNow + TX_EXPIRY_INTERVAL/2 + GetRand(TX_EXPIRY_INTERVAL);
4016+
state.m_tx_download.m_check_expiry_timer = current_time + TX_EXPIRY_INTERVAL / 2 + GetRandMicros(TX_EXPIRY_INTERVAL);
40144017
}
40154018

40164019
auto& tx_process_time = state.m_tx_download.m_tx_process_time;
4017-
while (!tx_process_time.empty() && tx_process_time.begin()->first <= nNow && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) {
4020+
while (!tx_process_time.empty() && tx_process_time.begin()->first <= current_time && state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) {
40184021
const uint256 txid = tx_process_time.begin()->second;
40194022
// Erase this entry from tx_process_time (it may be added back for
40204023
// processing at a later time, see below)
@@ -4023,22 +4026,22 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
40234026
if (!AlreadyHave(inv)) {
40244027
// If this transaction was last requested more than 1 minute ago,
40254028
// then request.
4026-
int64_t last_request_time = GetTxRequestTime(inv.hash);
4027-
if (last_request_time <= nNow - GETDATA_TX_INTERVAL) {
4029+
const auto last_request_time = GetTxRequestTime(inv.hash);
4030+
if (last_request_time <= current_time - GETDATA_TX_INTERVAL) {
40284031
LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId());
40294032
vGetData.push_back(inv);
40304033
if (vGetData.size() >= MAX_GETDATA_SZ) {
40314034
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
40324035
vGetData.clear();
40334036
}
4034-
UpdateTxRequestTime(inv.hash, nNow);
4035-
state.m_tx_download.m_tx_in_flight.emplace(inv.hash, nNow);
4037+
UpdateTxRequestTime(inv.hash, current_time);
4038+
state.m_tx_download.m_tx_in_flight.emplace(inv.hash, current_time);
40364039
} else {
40374040
// This transaction is in flight from someone else; queue
40384041
// up processing to happen after the download times out
40394042
// (with a slight delay for inbound peers, to prefer
40404043
// requests to outbound peers).
4041-
int64_t next_process_time = CalculateTxGetDataTime(txid, nNow, !state.fPreferredDownload);
4044+
const auto next_process_time = CalculateTxGetDataTime(txid, current_time, !state.fPreferredDownload);
40424045
tx_process_time.emplace(next_process_time, txid);
40434046
}
40444047
} else {

src/random.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,11 @@ uint64_t GetRand(uint64_t nMax) noexcept
667667
return FastRandomContext(g_mock_deterministic_tests).randrange(nMax);
668668
}
669669

670+
std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept
671+
{
672+
return std::chrono::microseconds{GetRand(duration_max.count())};
673+
}
674+
670675
int GetRandInt(int nMax) noexcept
671676
{
672677
return GetRand(nMax);

src/random.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
#include <crypto/common.h>
1111
#include <uint256.h>
1212

13-
#include <stdint.h>
13+
#include <chrono> // For std::chrono::microseconds
14+
#include <cstdint>
1415
#include <limits>
1516

1617
/**
@@ -69,6 +70,7 @@
6970
*/
7071
void GetRandBytes(unsigned char* buf, int num) noexcept;
7172
uint64_t GetRand(uint64_t nMax) noexcept;
73+
std::chrono::microseconds GetRandMicros(std::chrono::microseconds duration_max) noexcept;
7274
int GetRandInt(int nMax) noexcept;
7375
uint256 GetRandHash() noexcept;
7476

0 commit comments

Comments
 (0)