Skip to content

Commit 746d466

Browse files
committed
Introduce a Shuffle for FastRandomContext and use it in wallet
1 parent 1cdf124 commit 746d466

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

src/random.h

+23
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,29 @@ class FastRandomContext {
139139
inline uint64_t operator()() { return rand64(); }
140140
};
141141

142+
/** More efficient than using std::shuffle on a FastRandomContext.
143+
*
144+
* This is more efficient as std::shuffle will consume entropy in groups of
145+
* 64 bits at the time and throw away most.
146+
*
147+
* This also works around a bug in libstdc++ std::shuffle that may cause
148+
* type::operator=(type&&) to be invoked on itself, which the library's
149+
* debug mode detects and panics on. This is a known issue, see
150+
* https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
151+
*/
152+
template<typename I, typename R>
153+
void Shuffle(I first, I last, R&& rng)
154+
{
155+
while (first != last) {
156+
size_t j = rng.randrange(last - first);
157+
if (j) {
158+
using std::swap;
159+
swap(*first, *(first + j));
160+
}
161+
++first;
162+
}
163+
}
164+
142165
/* Number of random bytes returned by GetOSRand.
143166
* When changing this constant make sure to change all call sites, and make
144167
* sure that the underlying OS APIs for all platforms support the number.

src/test/random_tests.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,42 @@ BOOST_AUTO_TEST_CASE(stdrandom_test)
8282
for (int j = 1; j <= 10; ++j) {
8383
BOOST_CHECK(std::find(test.begin(), test.end(), j) != test.end());
8484
}
85+
Shuffle(test.begin(), test.end(), ctx);
86+
for (int j = 1; j <= 10; ++j) {
87+
BOOST_CHECK(std::find(test.begin(), test.end(), j) != test.end());
88+
}
8589
}
8690

8791
}
8892

93+
/** Test that Shuffle reaches every permutation with equal probability. */
94+
BOOST_AUTO_TEST_CASE(shuffle_stat_test)
95+
{
96+
FastRandomContext ctx(true);
97+
uint32_t counts[5 * 5 * 5 * 5 * 5] = {0};
98+
for (int i = 0; i < 12000; ++i) {
99+
int data[5] = {0, 1, 2, 3, 4};
100+
Shuffle(std::begin(data), std::end(data), ctx);
101+
int pos = data[0] + data[1] * 5 + data[2] * 25 + data[3] * 125 + data[4] * 625;
102+
++counts[pos];
103+
}
104+
unsigned int sum = 0;
105+
double chi_score = 0.0;
106+
for (int i = 0; i < 5 * 5 * 5 * 5 * 5; ++i) {
107+
int i1 = i % 5, i2 = (i / 5) % 5, i3 = (i / 25) % 5, i4 = (i / 125) % 5, i5 = i / 625;
108+
uint32_t count = counts[i];
109+
if (i1 == i2 || i1 == i3 || i1 == i4 || i1 == i5 || i2 == i3 || i2 == i4 || i2 == i5 || i3 == i4 || i3 == i5 || i4 == i5) {
110+
BOOST_CHECK(count == 0);
111+
} else {
112+
chi_score += ((count - 100.0) * (count - 100.0)) / 100.0;
113+
BOOST_CHECK(count > 50);
114+
BOOST_CHECK(count < 150);
115+
sum += count;
116+
}
117+
}
118+
BOOST_CHECK(chi_score > 58.1411); // 99.9999% confidence interval
119+
BOOST_CHECK(chi_score < 210.275);
120+
BOOST_CHECK_EQUAL(sum, 12000);
121+
}
122+
89123
BOOST_AUTO_TEST_SUITE_END()

src/wallet/wallet.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2731,7 +2731,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
27312731
std::vector<std::pair<CAmount, std::pair<const CWalletTx*, unsigned int> > > vValue;
27322732
CAmount nTotalLower = 0;
27332733

2734-
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
2734+
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
27352735

27362736
for (const COutput& output : vCoins) {
27372737
if (!output.fSpendable)

0 commit comments

Comments
 (0)