Skip to content

Commit 810cdf6

Browse files
committed
tests: overhaul deterministic test randomness
The existing code provides two randomness mechanisms for test purposes: - g_insecure_rand_ctx (with its wrappers InsecureRand*), which during tests is initialized using either zeros (SeedRand::ZEROS), or using environment-provided randomness (SeedRand::SEED). - g_mock_deterministic_tests, which controls some (but not all) of the normal randomness output if set, but then makes it extremely predictable (identical output repeatedly). Replace this with a single mechanism, which retains the SeedRand modes to control all randomness. There is a new internal deterministic PRNG inside the random module, which is used in GetRandBytes() when in test mode, and which is also used to initialize g_insecure_rand_ctx. This means that during tests, all random numbers are made deterministic. There is one exception, GetStrongRandBytes(), which even in test mode still uses the normal PRNG state. This probably opens the door to removing a lot of the ad-hoc "deterministic" mode functions littered through the codebase (by simply running relevant tests in SeedRand::ZEROS mode), but this isn't done yet.
1 parent 6cfdc5b commit 810cdf6

13 files changed

+134
-77
lines changed

src/random.cpp

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <array>
2424
#include <cmath>
2525
#include <cstdlib>
26+
#include <optional>
2627
#include <thread>
2728

2829
#ifdef WIN32
@@ -417,6 +418,10 @@ class RNGState {
417418
uint64_t m_counter GUARDED_BY(m_mutex) = 0;
418419
bool m_strongly_seeded GUARDED_BY(m_mutex) = false;
419420

421+
/** If not nullopt, the output of this RNGState is redirected and drawn from here
422+
* (unless always_use_real_rng is passed to MixExtract). */
423+
std::optional<ChaCha20> m_deterministic_prng GUARDED_BY(m_mutex);
424+
420425
Mutex m_events_mutex;
421426
CSHA256 m_events_hasher GUARDED_BY(m_events_mutex);
422427

@@ -457,11 +462,21 @@ class RNGState {
457462
m_events_hasher.Write(events_hash, 32);
458463
}
459464

465+
/** Make the output of MixExtract (unless always_use_real_rng) deterministic, with specified seed. */
466+
void MakeDeterministic(const uint256& seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
467+
{
468+
LOCK(m_mutex);
469+
m_deterministic_prng.emplace(MakeByteSpan(seed));
470+
}
471+
460472
/** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher.
461473
*
462474
* If this function has never been called with strong_seed = true, false is returned.
475+
*
476+
* If always_use_real_rng is false, and MakeDeterministic has been called before, output
477+
* from the deterministic PRNG instead.
463478
*/
464-
bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
479+
bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed, bool always_use_real_rng) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
465480
{
466481
assert(num <= 32);
467482
unsigned char buf[64];
@@ -479,6 +494,13 @@ class RNGState {
479494
hasher.Finalize(buf);
480495
// Store the last 32 bytes of the hash output as new RNG state.
481496
memcpy(m_state, buf + 32, 32);
497+
// Handle requests for deterministic randomness.
498+
if (!always_use_real_rng && m_deterministic_prng.has_value()) [[unlikely]] {
499+
// Overwrite the beginning of buf, which will be used for output.
500+
m_deterministic_prng->Keystream(AsWritableBytes(Span{buf, num}));
501+
// Do not require strong seeding for deterministic output.
502+
ret = true;
503+
}
482504
}
483505
// If desired, copy (up to) the first 32 bytes of the hash output as output.
484506
if (num) {
@@ -552,8 +574,9 @@ static void SeedSlow(CSHA512& hasher, RNGState& rng) noexcept
552574
static void SeedStrengthen(CSHA512& hasher, RNGState& rng, SteadyClock::duration dur) noexcept
553575
{
554576
// Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher.
577+
// Never use the deterministic PRNG for this, as the result is only used internally.
555578
unsigned char strengthen_seed[32];
556-
rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false);
579+
rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false, /*always_use_real_rng=*/true);
557580
// Strengthen the seed, and feed it into hasher.
558581
Strengthen(strengthen_seed, dur, hasher);
559582
}
@@ -604,7 +627,7 @@ enum class RNGLevel {
604627
PERIODIC, //!< Called by RandAddPeriodic()
605628
};
606629

607-
static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept
630+
static void ProcRand(unsigned char* out, int num, RNGLevel level, bool always_use_real_rng) noexcept
608631
{
609632
// Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available).
610633
RNGState& rng = GetRNGState();
@@ -625,24 +648,40 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept
625648
}
626649

627650
// Combine with and update state
628-
if (!rng.MixExtract(out, num, std::move(hasher), false)) {
651+
if (!rng.MixExtract(out, num, std::move(hasher), false, always_use_real_rng)) {
629652
// On the first invocation, also seed with SeedStartup().
630653
CSHA512 startup_hasher;
631654
SeedStartup(startup_hasher, rng);
632-
rng.MixExtract(out, num, std::move(startup_hasher), true);
655+
rng.MixExtract(out, num, std::move(startup_hasher), true, always_use_real_rng);
633656
}
634657
}
635658

636-
void GetRandBytes(Span<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST); }
637-
void GetStrongRandBytes(Span<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW); }
638-
void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); }
639-
void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); }
659+
/** Internal function to set g_determinstic_rng. Only accessed from tests. */
660+
void MakeRandDeterministicDANGEROUS(const uint256& seed) noexcept
661+
{
662+
GetRNGState().MakeDeterministic(seed);
663+
}
640664

641-
bool g_mock_deterministic_tests{false};
665+
void GetRandBytes(Span<unsigned char> bytes) noexcept
666+
{
667+
ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST, /*always_use_real_rng=*/false);
668+
}
669+
670+
void GetStrongRandBytes(Span<unsigned char> bytes) noexcept
671+
{
672+
ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW, /*always_use_real_rng=*/true);
673+
}
674+
675+
void RandAddPeriodic() noexcept
676+
{
677+
ProcRand(nullptr, 0, RNGLevel::PERIODIC, /*always_use_real_rng=*/false);
678+
}
679+
680+
void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); }
642681

643682
uint64_t GetRandInternal(uint64_t nMax) noexcept
644683
{
645-
return FastRandomContext(g_mock_deterministic_tests).randrange(nMax);
684+
return FastRandomContext().randrange(nMax);
646685
}
647686

648687
uint256 GetRandHash() noexcept
@@ -708,7 +747,7 @@ bool Random_SanityCheck()
708747
CSHA512 to_add;
709748
to_add.Write((const unsigned char*)&start, sizeof(start));
710749
to_add.Write((const unsigned char*)&stop, sizeof(stop));
711-
GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false);
750+
GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false, /*always_use_real_rng=*/true);
712751

713752
return true;
714753
}
@@ -734,7 +773,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce
734773
void RandomInit()
735774
{
736775
// Invoke RNG code to trigger initialization (if not already performed)
737-
ProcRand(nullptr, 0, RNGLevel::FAST);
776+
ProcRand(nullptr, 0, RNGLevel::FAST, /*always_use_real_rng=*/true);
738777

739778
ReportHardwareRand();
740779
}

src/random.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@
6363
* When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and
6464
* (up to) the first 32 bytes of H are produced as output, while the last 32 bytes
6565
* become the new RNG state.
66+
*
67+
* During tests, the RNG can be put into a special deterministic mode, in which the output
68+
* of all RNG functions, with the exception of GetStrongRandBytes(), is replaced with the
69+
* output of a deterministic RNG. This deterministic RNG does not gather entropy, and is
70+
* unaffected by RandAddPeriodic() or RandAddEvent(). It produces pseudorandom data that
71+
* only depends on the seed it was initialized with, possibly until it is reinitialized.
6672
*/
6773

6874
/**
@@ -340,6 +346,7 @@ class FastRandomContext : public RandomMixin<FastRandomContext>
340346
void RandomSeed() noexcept;
341347

342348
public:
349+
/** Construct a FastRandomContext with GetRandHash()-based entropy (or zero key if fDeterministic). */
343350
explicit FastRandomContext(bool fDeterministic = false) noexcept;
344351

345352
/** Initialize with explicit seed (only for testing) */

src/test/bloom_tests.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,7 @@ static std::vector<unsigned char> RandomData()
463463

464464
BOOST_AUTO_TEST_CASE(rolling_bloom)
465465
{
466-
SeedInsecureRand(SeedRand::ZEROS);
467-
g_mock_deterministic_tests = true;
466+
SeedRandomForTest(SeedRand::ZEROS);
468467

469468
// last-100-entry, 1% false positive:
470469
CRollingBloomFilter rb1(100, 0.01);
@@ -491,7 +490,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
491490
++nHits;
492491
}
493492
// Expect about 100 hits
494-
BOOST_CHECK_EQUAL(nHits, 75U);
493+
BOOST_CHECK_EQUAL(nHits, 71U);
495494

496495
BOOST_CHECK(rb1.contains(data[DATASIZE-1]));
497496
rb1.reset();
@@ -519,7 +518,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
519518
++nHits;
520519
}
521520
// Expect about 5 false positives
522-
BOOST_CHECK_EQUAL(nHits, 6U);
521+
BOOST_CHECK_EQUAL(nHits, 3U);
523522

524523
// last-1000-entry, 0.01% false positive:
525524
CRollingBloomFilter rb2(1000, 0.001);
@@ -530,7 +529,6 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
530529
for (int i = 0; i < DATASIZE; i++) {
531530
BOOST_CHECK(rb2.contains(data[i]));
532531
}
533-
g_mock_deterministic_tests = false;
534532
}
535533

536534
BOOST_AUTO_TEST_SUITE_END()

src/test/coins_tests.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,7 @@ UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
307307
// has the expected effect (the other duplicate is overwritten at all cache levels)
308308
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
309309
{
310-
SeedInsecureRand(SeedRand::ZEROS);
311-
g_mock_deterministic_tests = true;
310+
SeedRandomForTest(SeedRand::ZEROS);
312311

313312
bool spent_a_duplicate_coinbase = false;
314313
// A simple map to track what we expect the cache stack to represent.
@@ -496,8 +495,6 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
496495

497496
// Verify coverage.
498497
BOOST_CHECK(spent_a_duplicate_coinbase);
499-
500-
g_mock_deterministic_tests = false;
501498
}
502499

503500
BOOST_AUTO_TEST_CASE(ccoins_serialization)

src/test/cuckoocache_tests.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests);
3737
*/
3838
BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes)
3939
{
40-
SeedInsecureRand(SeedRand::ZEROS);
40+
SeedRandomForTest(SeedRand::ZEROS);
4141
CuckooCache::cache<uint256, SignatureCacheHasher> cc{};
4242
size_t megabytes = 4;
4343
cc.setup_bytes(megabytes << 20);
@@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes)
5555
template <typename Cache>
5656
static double test_cache(size_t megabytes, double load)
5757
{
58-
SeedInsecureRand(SeedRand::ZEROS);
58+
SeedRandomForTest(SeedRand::ZEROS);
5959
std::vector<uint256> hashes;
6060
Cache set{};
6161
size_t bytes = megabytes * (1 << 20);
@@ -126,7 +126,7 @@ template <typename Cache>
126126
static void test_cache_erase(size_t megabytes)
127127
{
128128
double load = 1;
129-
SeedInsecureRand(SeedRand::ZEROS);
129+
SeedRandomForTest(SeedRand::ZEROS);
130130
std::vector<uint256> hashes;
131131
Cache set{};
132132
size_t bytes = megabytes * (1 << 20);
@@ -189,7 +189,7 @@ template <typename Cache>
189189
static void test_cache_erase_parallel(size_t megabytes)
190190
{
191191
double load = 1;
192-
SeedInsecureRand(SeedRand::ZEROS);
192+
SeedRandomForTest(SeedRand::ZEROS);
193193
std::vector<uint256> hashes;
194194
Cache set{};
195195
size_t bytes = megabytes * (1 << 20);
@@ -293,7 +293,7 @@ static void test_cache_generations()
293293
// iterations with non-deterministic values, so it isn't "overfit" to the
294294
// specific entropy in FastRandomContext(true) and implementation of the
295295
// cache.
296-
SeedInsecureRand(SeedRand::ZEROS);
296+
SeedRandomForTest(SeedRand::ZEROS);
297297

298298
// block_activity models a chunk of network activity. n_insert elements are
299299
// added to the cache. The first and last n/4 are stored for removal later

src/test/fuzz/fuzz.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <netaddress.h>
88
#include <netbase.h>
9+
#include <test/util/random.h>
910
#include <test/util/setup_common.h>
1011
#include <util/check.h>
1112
#include <util/fs.h>

src/test/prevector_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ class prevector_tester {
210210
}
211211

212212
prevector_tester() {
213-
SeedInsecureRand();
213+
SeedRandomForTest();
214214
rand_seed = InsecureRand256();
215215
rand_cache = FastRandomContext(rand_seed);
216216
}

src/test/random_tests.cpp

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,30 @@ BOOST_AUTO_TEST_CASE(osrandom_tests)
2020
BOOST_CHECK(Random_SanityCheck());
2121
}
2222

23-
BOOST_AUTO_TEST_CASE(fastrandom_tests)
23+
BOOST_AUTO_TEST_CASE(fastrandom_tests_deterministic)
2424
{
2525
// Check that deterministic FastRandomContexts are deterministic
26-
g_mock_deterministic_tests = true;
27-
FastRandomContext ctx1(true);
28-
FastRandomContext ctx2(true);
29-
30-
for (int i = 10; i > 0; --i) {
31-
BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10393729187455219830U});
32-
BOOST_CHECK_EQUAL(GetRand<int>(), int{769702006});
33-
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654);
34-
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374);
26+
SeedRandomForTest(SeedRand::ZEROS);
27+
FastRandomContext ctx1{true};
28+
FastRandomContext ctx2{true};
29+
30+
{
31+
BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{9330418229102544152u});
32+
BOOST_CHECK_EQUAL(GetRand<int>(), int{618925161});
33+
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 1271170921);
34+
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2803534);
35+
36+
BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10170981140880778086u});
37+
BOOST_CHECK_EQUAL(GetRand<int>(), int{1689082725});
38+
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2464643716);
39+
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2312205);
40+
41+
BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{5689404004456455543u});
42+
BOOST_CHECK_EQUAL(GetRand<int>(), int{785839937});
43+
BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 93558804);
44+
BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 507022);
3545
}
46+
3647
{
3748
constexpr SteadySeconds time_point{1s};
3849
FastRandomContext ctx{true};
@@ -65,15 +76,28 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests)
6576
// Check with time-point type
6677
BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count());
6778
}
79+
}
6880

81+
BOOST_AUTO_TEST_CASE(fastrandom_tests_nondeterministic)
82+
{
6983
// Check that a nondeterministic ones are not
70-
g_mock_deterministic_tests = false;
71-
for (int i = 10; i > 0; --i) {
72-
BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10393729187455219830U});
73-
BOOST_CHECK(GetRand<int>() != int{769702006});
74-
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654});
75-
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374});
84+
{
85+
BOOST_CHECK(GetRand<uint64_t>() != uint64_t{9330418229102544152u});
86+
BOOST_CHECK(GetRand<int>() != int{618925161});
87+
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 1271170921);
88+
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2803534);
89+
90+
BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10170981140880778086u});
91+
BOOST_CHECK(GetRand<int>() != int{1689082725});
92+
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 2464643716);
93+
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 2312205);
94+
95+
BOOST_CHECK(GetRand<uint64_t>() != uint64_t{5689404004456455543u});
96+
BOOST_CHECK(GetRand<int>() != int{785839937});
97+
BOOST_CHECK(GetRandMicros(std::chrono::hours{1}).count() != 93558804);
98+
BOOST_CHECK(GetRandMillis(std::chrono::hours{1}).count() != 507022);
7699
}
100+
77101
{
78102
FastRandomContext ctx3, ctx4;
79103
BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal

src/test/streams_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_skip)
436436
BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
437437
{
438438
// Make this test deterministic.
439-
SeedInsecureRand(SeedRand::ZEROS);
439+
SeedRandomForTest(SeedRand::ZEROS);
440440

441441
fs::path streams_test_filename = m_args.GetDataDirBase() / "streams_test_tmp";
442442
for (int rep = 0; rep < 50; ++rep) {

0 commit comments

Comments
 (0)