Skip to content

Commit 50dbec5

Browse files
committed
Add unit tests for signals generated by ProcessNewBlock()
After a recent bug discovered in callback ordering in MainSignals, this test checks invariants in ordering of BlockConnected / BlockDisconnected / UpdatedChainTip signals Adaptation of btc@dd435ad40267f5c50ff17533c696f9302829a6a6
1 parent f68251d commit 50dbec5

File tree

6 files changed

+192
-3
lines changed

6 files changed

+192
-3
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ BITCOIN_TESTS =\
101101
test/univalue_tests.cpp \
102102
test/util_tests.cpp \
103103
test/sha256compress_tests.cpp \
104-
test/upgrades_tests.cpp
104+
test/upgrades_tests.cpp \
105+
test/validation_block_tests.cpp
105106

106107
SAPLING_TESTS =\
107108
test/librust/libsapling_utils_tests.cpp \

src/blockassembler.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ class BlockAssembler
6666
BlockAssembler(const CChainParams& chainparams, const bool defaultPrintPriority);
6767
/** Construct a new block template with coinbase to scriptPubKeyIn */
6868
std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn,
69-
CWallet* pwallet,
70-
bool fProofOfStake,
69+
CWallet* pwallet = nullptr,
70+
bool fProofOfStake = false,
7171
std::vector<CStakeableOutput>* availableCoins = nullptr);
7272

7373
private:

src/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ set(BITCOIN_TESTS
133133
${CMAKE_CURRENT_SOURCE_DIR}/validation_tests.cpp
134134
${CMAKE_CURRENT_SOURCE_DIR}/sha256compress_tests.cpp
135135
${CMAKE_CURRENT_SOURCE_DIR}/upgrades_tests.cpp
136+
${CMAKE_CURRENT_SOURCE_DIR}/validation_block_tests.cpp
136137
${CMAKE_CURRENT_SOURCE_DIR}/librust/sapling_rpc_wallet_tests.cpp
137138
${CMAKE_SOURCE_DIR}/src/wallet/test/wallet_tests.cpp
138139
${CMAKE_SOURCE_DIR}/src/wallet/test/crypto_tests.cpp

src/test/test_pivx.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ FastRandomContext insecure_rand_ctx(insecure_rand_seed);
3232
extern bool fPrintToConsole;
3333
extern void noui_connect();
3434

35+
std::ostream& operator<<(std::ostream& os, const uint256& num)
36+
{
37+
os << num.ToString();
38+
return os;
39+
}
40+
3541
BasicTestingSetup::BasicTestingSetup()
3642
{
3743
RandomInit();

src/test/test_pivx.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,7 @@ struct TestMemPoolEntryHelper
117117
TestMemPoolEntryHelper &SigOps(unsigned int _sigops) { sigOpCount = _sigops; return *this; }
118118
};
119119

120+
// define an implicit conversion here so that uint256 may be used directly in BOOST_CHECK_*
121+
std::ostream& operator<<(std::ostream& os, const uint256& num);
122+
120123
#endif
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright (c) 2018 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <boost/test/unit_test.hpp>
6+
7+
#include "blockassembler.h"
8+
#include "chainparams.h"
9+
#include "consensus/merkle.h"
10+
#include "consensus/validation.h"
11+
#include "pow.h"
12+
#include "random.h"
13+
#include "test/test_pivx.h"
14+
#include "validation.h"
15+
#include "validationinterface.h"
16+
17+
struct RegtestingSetup : public TestingSetup {
18+
RegtestingSetup() : TestingSetup() {
19+
SelectParams(CBaseChainParams::REGTEST);
20+
}
21+
};
22+
23+
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup)
24+
25+
struct TestSubscriber : public CValidationInterface {
26+
uint256 m_expected_tip;
27+
28+
TestSubscriber(uint256 tip) : m_expected_tip(std::move(tip)) {}
29+
30+
void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
31+
{
32+
BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash());
33+
}
34+
35+
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex, const std::vector<CTransactionRef>& txnConflicted)
36+
{
37+
BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock);
38+
BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash());
39+
40+
m_expected_tip = block->GetHash();
41+
}
42+
43+
void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const uint256& blockHash, int nBlockHeight, int64_t blockTime)
44+
{
45+
BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash());
46+
47+
m_expected_tip = block->hashPrevBlock;
48+
}
49+
};
50+
51+
std::shared_ptr<CBlock> Block(const uint256& prev_hash)
52+
{
53+
static int i = 0;
54+
static uint64_t time = Params().GenesisBlock().nTime;
55+
56+
CScript pubKey;
57+
pubKey << i++ << OP_TRUE;
58+
59+
auto ptemplate = BlockAssembler(Params(), false).CreateNewBlock(pubKey);
60+
auto pblock = std::make_shared<CBlock>(ptemplate->block);
61+
pblock->hashPrevBlock = prev_hash;
62+
pblock->nTime = ++time;
63+
64+
CMutableTransaction txCoinbase(*pblock->vtx[0]);
65+
txCoinbase.vout.resize(1);
66+
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
67+
68+
return pblock;
69+
}
70+
71+
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock)
72+
{
73+
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
74+
75+
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits)) {
76+
++(pblock->nNonce);
77+
}
78+
79+
return pblock;
80+
}
81+
82+
// construct a valid block
83+
const std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash)
84+
{
85+
return FinalizeBlock(Block(prev_hash));
86+
}
87+
88+
// construct an invalid block (but with a valid header)
89+
const std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash)
90+
{
91+
auto pblock = Block(prev_hash);
92+
93+
CMutableTransaction coinbase_spend;
94+
coinbase_spend.vin.emplace_back(CTxIn(COutPoint(pblock->vtx[0]->GetHash(), 0), CScript(), 0));
95+
coinbase_spend.vout.emplace_back(pblock->vtx[0]->vout[0]);
96+
97+
CTransactionRef tx = MakeTransactionRef(coinbase_spend);
98+
pblock->vtx.emplace_back(tx);
99+
100+
auto ret = FinalizeBlock(pblock);
101+
return ret;
102+
}
103+
104+
void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks)
105+
{
106+
if (height <= 0 || blocks.size() >= max_size) return;
107+
108+
bool gen_invalid = GetRand(100) < invalid_rate;
109+
bool gen_fork = GetRand(100) < branch_rate;
110+
111+
const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root);
112+
blocks.emplace_back(pblock);
113+
if (!gen_invalid) {
114+
BuildChain(pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
115+
}
116+
117+
if (gen_fork) {
118+
blocks.emplace_back(GoodBlock(root));
119+
BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
120+
}
121+
}
122+
123+
BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
124+
{
125+
// build a large-ish chain that's likely to have some forks
126+
std::vector<std::shared_ptr<const CBlock>> blocks;
127+
while (blocks.size() < 50) {
128+
blocks.clear();
129+
BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks);
130+
}
131+
132+
CValidationState state;
133+
// Connect the genesis block and drain any outstanding events
134+
BOOST_CHECK_MESSAGE(ProcessNewBlock(state, nullptr, std::make_shared<CBlock>(Params().GenesisBlock()), nullptr), "Error: genesis not connected");
135+
SyncWithValidationInterfaceQueue();
136+
137+
// subscribe to events (this subscriber will validate event ordering)
138+
const CBlockIndex* initial_tip = WITH_LOCK(cs_main, return chainActive.Tip());
139+
TestSubscriber sub(initial_tip->GetBlockHash());
140+
RegisterValidationInterface(&sub);
141+
142+
// create a bunch of threads that repeatedly process a block generated above at random
143+
// this will create parallelism and randomness inside validation - the ValidationInterface
144+
// will subscribe to events generated during block validation and assert on ordering invariance
145+
boost::thread_group threads;
146+
for (int i = 0; i < 10; i++) {
147+
threads.create_thread([&blocks]() {
148+
CValidationState state;
149+
for (int i = 0; i < 1000; i++) {
150+
auto block = blocks[GetRand(blocks.size() - 1)];
151+
ProcessNewBlock(state, nullptr, block, nullptr);
152+
}
153+
154+
// to make sure that eventually we process the full chain - do it here
155+
for (const auto& block : blocks) {
156+
if (block->vtx.size() == 1) {
157+
bool processed = ProcessNewBlock(state, nullptr, block, nullptr);
158+
// Future to do: "prevblk-not-found" here is the only valid reason to not check processed flag.
159+
if (state.GetRejectReason() == "duplicate" ||
160+
state.GetRejectReason() == "prevblk-not-found" ||
161+
state.GetRejectReason() == "bad-prevblk") continue;
162+
BOOST_ASSERT_MSG(processed, ("Error: " + state.GetRejectReason()).c_str());
163+
}
164+
}
165+
});
166+
}
167+
168+
threads.join_all();
169+
while (GetMainSignals().CallbacksPending() > 0) {
170+
MilliSleep(100);
171+
}
172+
173+
UnregisterValidationInterface(&sub);
174+
175+
BOOST_CHECK_EQUAL(sub.m_expected_tip, WITH_LOCK(cs_main, return chainActive.Tip()->GetBlockHash()));
176+
}
177+
178+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)