random: convert GetRand{Micros,Millis} into randrange

There are only a few call sites of these throughout the codebase, so
move the functionality into FastRandomContext, and rewrite all call sites.

This requires the callers to explicit construct FastRandomContext objects,
which do add to the verbosity, but also make potentially apparent locations
where the code can be improved by reusing a FastRandomContext object (see
further commit).
This commit is contained in:
Pieter Wuille 2024-03-11 10:17:20 -04:00
parent 82de1b80d9
commit 4eaa239dc3
4 changed files with 36 additions and 30 deletions

View file

@ -3475,7 +3475,8 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
// nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days,
// max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference
// in terms of the freshness of the response.
cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMicros(std::chrono::hours(6));
cache_entry.m_cache_entry_expiration = current_time +
21h + FastRandomContext().randrange<std::chrono::microseconds>(6h);
}
return cache_entry.m_addrs_response_cache;
}

View file

@ -1698,7 +1698,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler)
// Schedule next run for 10-15 minutes in the future.
// We add randomness on every cycle to avoid the possibility of P2P fingerprinting.
const std::chrono::milliseconds delta = 10min + GetRandMillis(5min);
const auto delta = 10min + FastRandomContext().randrange<std::chrono::milliseconds>(5min);
scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta);
}
@ -2050,7 +2050,7 @@ void PeerManagerImpl::StartScheduledTasks(CScheduler& scheduler)
scheduler.scheduleEvery([this] { this->CheckForStaleTipAndEvictPeers(); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL});
// schedule next run for 10-15 minutes in the future
const std::chrono::milliseconds delta = 10min + GetRandMillis(5min);
const auto delta = 10min + FastRandomContext().randrange<std::chrono::milliseconds>(5min);
scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta);
}
@ -5753,7 +5753,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_next_send_feefilter &&
(currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) {
peer.m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY);
peer.m_next_send_feefilter = current_time + FastRandomContext().randrange<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY);
}
}

View file

@ -132,6 +132,13 @@ concept RandomNumberGenerator = requires(T& rng, Span<std::byte> s) {
requires std::derived_from<std::remove_reference_t<T>, RandomMixin<std::remove_reference_t<T>>>;
};
/** A concept for C++ std::chrono durations. */
template<typename T>
concept StdChronoDuration = requires {
[]<class Rep, class Period>(std::type_identity<std::chrono::duration<Rep, Period>>){}(
std::type_identity<T>());
};
/** Mixin class that provides helper randomness functions.
*
* Intended to be used through CRTP: https://en.cppreference.com/w/cpp/language/crtp.
@ -300,7 +307,7 @@ public:
}
/** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */
template <typename Chrono>
template <typename Chrono> requires StdChronoDuration<typename Chrono::duration>
typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept
{
using Dur = typename Chrono::duration;
@ -309,6 +316,17 @@ public:
/* interval [0..0] */ Dur{0};
};
/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */
template <StdChronoDuration Dur>
Dur randrange(typename std::common_type_t<Dur> range) noexcept
// Having the compiler infer the template argument from the function argument
// is dangerous, because the desired return value generally has a different
// type than the function argument. So std::common_type is used to force the
// call site to specify the type of the return value.
{
return Dur{Impl().randrange(range.count())};
}
// Compatibility with the UniformRandomBitGenerator concept
typedef uint64_t result_type;
static constexpr uint64_t min() noexcept { return 0; }
@ -426,19 +444,6 @@ void Shuffle(I first, I last, R&& rng)
}
}
/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */
template <typename D>
D GetRandomDuration(typename std::common_type<D>::type max) noexcept
// Having the compiler infer the template argument from the function argument
// is dangerous, because the desired return value generally has a different
// type than the function argument. So std::common_type is used to force the
// call site to specify the type of the return value.
{
return D{FastRandomContext().randrange(max.count())};
};
constexpr auto GetRandMicros = GetRandomDuration<std::chrono::microseconds>;
constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>;
/* Number of random bytes returned by GetOSRand.
* When changing this constant make sure to change all call sites, and make
* sure that the underlying OS APIs for all platforms support the number.

View file

@ -30,18 +30,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic)
{
BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{9330418229102544152u});
BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{618925161});
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921);
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 1271170921);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 2803534);
BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{10170981140880778086u});
BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{1689082725});
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716);
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 2464643716);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 2312205);
BOOST_CHECK_EQUAL(FastRandomContext().rand<uint64_t>(), uint64_t{5689404004456455543u});
BOOST_CHECK_EQUAL(FastRandomContext().rand<int>(), int{785839937});
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804);
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::microseconds>(1h).count(), 93558804);
BOOST_CHECK_EQUAL(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count(), 507022);
}
{
@ -84,18 +84,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic)
{
BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{9330418229102544152u});
BOOST_CHECK(FastRandomContext().rand<int>() != int{618925161});
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921);
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 1271170921);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 2803534);
BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{10170981140880778086u});
BOOST_CHECK(FastRandomContext().rand<int>() != int{1689082725});
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716);
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 2464643716);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 2312205);
BOOST_CHECK(FastRandomContext().rand<uint64_t>() != uint64_t{5689404004456455543u});
BOOST_CHECK(FastRandomContext().rand<int>() != int{785839937});
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804);
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::microseconds>(1h).count() != 93558804);
BOOST_CHECK(FastRandomContext().randrange<std::chrono::milliseconds>(1h).count() != 507022);
}
{