Skip to content

Commit 55eea00

Browse files
committed
test: Make blockencodings_tests deterministic
refactor: CBlockHeaderAndShortTxIDs constructor now always takes an explicit nonce. test: Make blockencodings_tests deterministic using fixed seed providing deterministic CBlockHeaderAndShortTxID nonces and dummy transaction IDs. Fixes very rare flaky test failures, where the ShortIDs of test transactions collide, leading to `READ_STATUS_FAILED` from PartiallyDownloadedBlock::InitData and/or `IsTxAvailable` giving `false` when the transaction should actually be available. * Use a new `FastRandomContext` with a fixed seed in each test, to ensure 'random' uint256s used as fake prevouts are deterministic, so in-turn test txids and short IDs are deterministic and don't collide causing very rare but flaky test failures. * Add new test-only/internal initializer for `CBlockHeaderAndShortTxIDs` that takes a specified nonce to further ensure determinism and avoid rare but undesireable short ID collisions. In a test context this nonce is set to a fixed known-good value. Normally it is random, as previously. Flaky test failures can be reproduced with: ```patch diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index 695e8d806a..64d635a97a 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -44,7 +44,8 @@ void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const { uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const { static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids"); - return SipHashUint256(shorttxidk0, shorttxidk1, wtxid) & 0xffffffffffffL; + // return SipHashUint256(shorttxidk0, shorttxidk1, wtxid) & 0xffffffffffffL; + return SipHashUint256(shorttxidk0, shorttxidk1, wtxid) & 0x0f; } ``` to increase the likelihood of a short ID collision; and running ```shell set -e; n=0; while (( n++ < 5000 )); do src/test/test_bitcoin --run_test=blockencodings_tests; done ```
1 parent 4c99301 commit 55eea00

File tree

5 files changed

+38
-27
lines changed

5 files changed

+38
-27
lines changed

src/blockencodings.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
#include <unordered_map>
1919

20-
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) :
21-
nonce(GetRand<uint64_t>()),
20+
CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) :
21+
nonce(nonce),
2222
shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) {
2323
FillShortTxIDSelector();
2424
//TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase

src/blockencodings.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,15 @@ class CBlockHeaderAndShortTxIDs {
106106

107107
CBlockHeader header;
108108

109-
// Dummy for deserialization
109+
/**
110+
* Dummy for deserialization
111+
*/
110112
CBlockHeaderAndShortTxIDs() {}
111113

112-
CBlockHeaderAndShortTxIDs(const CBlock& block);
114+
/**
115+
* @param[in] nonce This should be randomly generated, and is used for the siphash secret key
116+
*/
117+
CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce);
113118

114119
uint64_t GetShortID(const Wtxid& wtxid) const;
115120

src/net_processing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,7 +2151,7 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo
21512151
*/
21522152
void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock)
21532153
{
2154-
auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock);
2154+
auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock, GetRand<uint64_t>());
21552155

21562156
LOCK(cs_main);
21572157

@@ -2549,7 +2549,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv&
25492549
if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) {
25502550
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, *a_recent_compact_block);
25512551
} else {
2552-
CBlockHeaderAndShortTxIDs cmpctblock{*pblock};
2552+
CBlockHeaderAndShortTxIDs cmpctblock{*pblock, GetRand<uint64_t>()};
25532553
MakeAndPushMessage(pfrom, NetMsgType::CMPCTBLOCK, cmpctblock);
25542554
}
25552555
} else {
@@ -6033,7 +6033,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
60336033
CBlock block;
60346034
const bool ret{m_chainman.m_blockman.ReadBlockFromDisk(block, *pBestIndex)};
60356035
assert(ret);
6036-
CBlockHeaderAndShortTxIDs cmpctblock{block};
6036+
CBlockHeaderAndShortTxIDs cmpctblock{block, GetRand<uint64_t>()};
60376037
MakeAndPushMessage(*pto, NetMsgType::CMPCTBLOCK, cmpctblock);
60386038
}
60396039
state.pindexBestHeaderSent = pBestIndex;

src/test/blockencodings_tests.cpp

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,23 @@ static CMutableTransaction BuildTransactionTestCase() {
2727
return tx;
2828
}
2929

30-
static CBlock BuildBlockTestCase() {
30+
static CBlock BuildBlockTestCase(FastRandomContext& ctx) {
3131
CBlock block;
3232
CMutableTransaction tx = BuildTransactionTestCase();
3333

3434
block.vtx.resize(3);
3535
block.vtx[0] = MakeTransactionRef(tx);
3636
block.nVersion = 42;
37-
block.hashPrevBlock = InsecureRand256();
37+
block.hashPrevBlock = ctx.rand256();
3838
block.nBits = 0x207fffff;
3939

40-
tx.vin[0].prevout.hash = Txid::FromUint256(InsecureRand256());
40+
tx.vin[0].prevout.hash = Txid::FromUint256(ctx.rand256());
4141
tx.vin[0].prevout.n = 0;
4242
block.vtx[1] = MakeTransactionRef(tx);
4343

4444
tx.vin.resize(10);
4545
for (size_t i = 0; i < tx.vin.size(); i++) {
46-
tx.vin[i].prevout.hash = Txid::FromUint256(InsecureRand256());
46+
tx.vin[i].prevout.hash = Txid::FromUint256(ctx.rand256());
4747
tx.vin[i].prevout.n = 0;
4848
}
4949
block.vtx[2] = MakeTransactionRef(tx);
@@ -63,15 +63,16 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
6363
{
6464
CTxMemPool& pool = *Assert(m_node.mempool);
6565
TestMemPoolEntryHelper entry;
66-
CBlock block(BuildBlockTestCase());
66+
auto rand_ctx(FastRandomContext(uint256{42}));
67+
CBlock block(BuildBlockTestCase(rand_ctx));
6768

6869
LOCK2(cs_main, pool.cs);
6970
pool.addUnchecked(entry.FromTx(block.vtx[2]));
7071
BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
7172

7273
// Do a simple ShortTxIDs RT
7374
{
74-
CBlockHeaderAndShortTxIDs shortIDs{block};
75+
CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
7576

7677
DataStream stream{};
7778
stream << shortIDs;
@@ -128,8 +129,8 @@ class TestHeaderAndShortIDs {
128129
stream << orig;
129130
stream >> *this;
130131
}
131-
explicit TestHeaderAndShortIDs(const CBlock& block) :
132-
TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block}) {}
132+
explicit TestHeaderAndShortIDs(const CBlock& block, FastRandomContext& ctx) :
133+
TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block, ctx.rand64()}) {}
133134

134135
uint64_t GetShortID(const Wtxid& txhash) const {
135136
DataStream stream{};
@@ -146,7 +147,8 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
146147
{
147148
CTxMemPool& pool = *Assert(m_node.mempool);
148149
TestMemPoolEntryHelper entry;
149-
CBlock block(BuildBlockTestCase());
150+
auto rand_ctx(FastRandomContext(uint256{42}));
151+
CBlock block(BuildBlockTestCase(rand_ctx));
150152

151153
LOCK2(cs_main, pool.cs);
152154
pool.addUnchecked(entry.FromTx(block.vtx[2]));
@@ -156,7 +158,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
156158

157159
// Test with pre-forwarding tx 1, but not coinbase
158160
{
159-
TestHeaderAndShortIDs shortIDs(block);
161+
TestHeaderAndShortIDs shortIDs(block, rand_ctx);
160162
shortIDs.prefilledtxn.resize(1);
161163
shortIDs.prefilledtxn[0] = {1, block.vtx[1]};
162164
shortIDs.shorttxids.resize(2);
@@ -216,7 +218,8 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
216218
{
217219
CTxMemPool& pool = *Assert(m_node.mempool);
218220
TestMemPoolEntryHelper entry;
219-
CBlock block(BuildBlockTestCase());
221+
auto rand_ctx(FastRandomContext(uint256{42}));
222+
CBlock block(BuildBlockTestCase(rand_ctx));
220223

221224
LOCK2(cs_main, pool.cs);
222225
pool.addUnchecked(entry.FromTx(block.vtx[1]));
@@ -226,7 +229,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
226229

227230
// Test with pre-forwarding coinbase + tx 2 with tx 1 in mempool
228231
{
229-
TestHeaderAndShortIDs shortIDs(block);
232+
TestHeaderAndShortIDs shortIDs(block, rand_ctx);
230233
shortIDs.prefilledtxn.resize(2);
231234
shortIDs.prefilledtxn[0] = {0, block.vtx[0]};
232235
shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
@@ -269,10 +272,11 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
269272
CMutableTransaction coinbase = BuildTransactionTestCase();
270273

271274
CBlock block;
275+
auto rand_ctx(FastRandomContext(uint256{42}));
272276
block.vtx.resize(1);
273277
block.vtx[0] = MakeTransactionRef(std::move(coinbase));
274278
block.nVersion = 42;
275-
block.hashPrevBlock = InsecureRand256();
279+
block.hashPrevBlock = rand_ctx.rand256();
276280
block.nBits = 0x207fffff;
277281

278282
bool mutated;
@@ -282,7 +286,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
282286

283287
// Test simple header round-trip with only coinbase
284288
{
285-
CBlockHeaderAndShortTxIDs shortIDs{block};
289+
CBlockHeaderAndShortTxIDs shortIDs{block, rand_ctx.rand64()};
286290

287291
DataStream stream{};
288292
stream << shortIDs;
@@ -306,15 +310,17 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
306310
BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
307311
CTxMemPool& pool = *Assert(m_node.mempool);
308312
TestMemPoolEntryHelper entry;
309-
const CBlock block(BuildBlockTestCase());
310-
std::vector<CTransactionRef> extra_txn;
311-
extra_txn.resize(10);
313+
auto rand_ctx(FastRandomContext(uint256{42}));
312314

313315
CMutableTransaction mtx = BuildTransactionTestCase();
314-
mtx.vin[0].prevout.hash = Txid::FromUint256(InsecureRand256());
316+
mtx.vin[0].prevout.hash = Txid::FromUint256(rand_ctx.rand256());
315317
mtx.vin[0].prevout.n = 0;
316318
const CTransactionRef non_block_tx = MakeTransactionRef(std::move(mtx));
317319

320+
CBlock block(BuildBlockTestCase(rand_ctx));
321+
std::vector<CTransactionRef> extra_txn;
322+
extra_txn.resize(10);
323+
318324
LOCK2(cs_main, pool.cs);
319325
pool.addUnchecked(entry.FromTx(block.vtx[2]));
320326
BOOST_CHECK_EQUAL(pool.get(block.vtx[2]->GetHash()).use_count(), SHARED_TX_OFFSET + 0);
@@ -326,7 +332,7 @@ BOOST_AUTO_TEST_CASE(ReceiveWithExtraTransactions) {
326332
BOOST_CHECK_EQUAL(pool.get(block.vtx[1]->GetHash()), nullptr);
327333

328334
{
329-
const CBlockHeaderAndShortTxIDs cmpctblock{block};
335+
const CBlockHeaderAndShortTxIDs cmpctblock{block, rand_ctx.rand64()};
330336
PartiallyDownloadedBlock partial_block(&pool);
331337
PartiallyDownloadedBlock partial_block_with_extra(&pool);
332338

src/test/fuzz/partially_downloaded_block.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb)
5050
return;
5151
}
5252

53-
CBlockHeaderAndShortTxIDs cmpctblock{*block};
53+
CBlockHeaderAndShortTxIDs cmpctblock{*block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
5454

5555
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)};
5656
PartiallyDownloadedBlock pdb{&pool};

0 commit comments

Comments
 (0)