blockfilter_index_tests.cpp raw
1 // Copyright (c) 2017-2022 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <addresstype.h>
6 #include <blockfilter.h>
7 #include <chainparams.h>
8 #include <consensus/merkle.h>
9 #include <consensus/validation.h>
10 #include <index/blockfilterindex.h>
11 #include <interfaces/chain.h>
12 #include <node/miner.h>
13 #include <pow.h>
14 #include <test/util/blockfilter.h>
15 #include <test/util/index.h>
16 #include <test/util/setup_common.h>
17 #include <validation.h>
18
19 #include <boost/test/unit_test.hpp>
20
21 using node::BlockAssembler;
22 using node::BlockManager;
23 using node::CBlockTemplate;
24
25 BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
26
27 struct BuildChainTestingSetup : public TestChain100Setup {
28 CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey);
29 bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
30 };
31
32 static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
33 uint256& last_header, const BlockManager& blockman)
34 {
35 BlockFilter expected_filter;
36 if (!ComputeFilter(filter_index.GetFilterType(), *block_index, expected_filter, blockman)) {
37 BOOST_ERROR("ComputeFilter failed on block " << block_index->nHeight);
38 return false;
39 }
40
41 BlockFilter filter;
42 uint256 filter_header;
43 std::vector<BlockFilter> filters;
44 std::vector<uint256> filter_hashes;
45
46 BOOST_CHECK(filter_index.LookupFilter(block_index, filter));
47 BOOST_CHECK(filter_index.LookupFilterHeader(block_index, filter_header));
48 BOOST_CHECK(filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
49 BOOST_CHECK(filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
50 filter_hashes));
51
52 BOOST_CHECK_EQUAL(filters.size(), 1U);
53 BOOST_CHECK_EQUAL(filter_hashes.size(), 1U);
54
55 BOOST_CHECK_EQUAL(filter.GetHash(), expected_filter.GetHash());
56 BOOST_CHECK_EQUAL(filter_header, expected_filter.ComputeHeader(last_header));
57 BOOST_CHECK_EQUAL(filters[0].GetHash(), expected_filter.GetHash());
58 BOOST_CHECK_EQUAL(filter_hashes[0], expected_filter.GetHash());
59
60 filters.clear();
61 filter_hashes.clear();
62 last_header = filter_header;
63 return true;
64 }
65
66 CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
67 const std::vector<CMutableTransaction>& txns,
68 const CScript& scriptPubKey)
69 {
70 BlockAssembler::Options options;
71 options.coinbase_output_script = scriptPubKey;
72 auto pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options, m_node}.CreateNewBlock();
73 CBlock& block = pblocktemplate->block;
74 block.hashPrevBlock = prev->GetBlockHash();
75 block.nTime = prev->nTime + 1;
76
77 // Replace mempool-selected txns with just coinbase plus passed-in txns:
78 block.vtx.resize(1);
79 for (const CMutableTransaction& tx : txns) {
80 block.vtx.push_back(MakeTransactionRef(tx));
81 }
82 {
83 CMutableTransaction tx_coinbase{*block.vtx.at(0)};
84 tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
85 block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
86 block.hashMerkleRoot = BlockMerkleRoot(block);
87 }
88
89 while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
90
91 return block;
92 }
93
94 bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
95 const CScript& coinbase_script_pub_key,
96 size_t length,
97 std::vector<std::shared_ptr<CBlock>>& chain)
98 {
99 std::vector<CMutableTransaction> no_txns;
100
101 chain.resize(length);
102 for (auto& block : chain) {
103 block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
104 CBlockHeader header = block->GetBlockHeader();
105
106 BlockValidationState state;
107 if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{header}}, true, state, &pindex)) {
108 return false;
109 }
110 }
111
112 return true;
113 }
114
115 BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
116 {
117 BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true);
118 BOOST_REQUIRE(filter_index.Init());
119
120 uint256 last_header;
121
122 // Filter should not be found in the index before it is started.
123 {
124 LOCK(cs_main);
125
126 BlockFilter filter;
127 uint256 filter_header;
128 std::vector<BlockFilter> filters;
129 std::vector<uint256> filter_hashes;
130
131 for (const CBlockIndex* block_index = m_node.chainman->ActiveChain().Genesis();
132 block_index != nullptr;
133 block_index = m_node.chainman->ActiveChain().Next(block_index)) {
134 BOOST_CHECK(!filter_index.LookupFilter(block_index, filter));
135 BOOST_CHECK(!filter_index.LookupFilterHeader(block_index, filter_header));
136 BOOST_CHECK(!filter_index.LookupFilterRange(block_index->nHeight, block_index, filters));
137 BOOST_CHECK(!filter_index.LookupFilterHashRange(block_index->nHeight, block_index,
138 filter_hashes));
139 }
140 }
141
142 // BlockUntilSyncedToCurrentChain should return false before index is started.
143 BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
144
145 BOOST_REQUIRE(filter_index.StartBackgroundSync());
146
147 // Allow filter index to catch up with the block index.
148 IndexWaitSynced(filter_index, *Assert(m_node.shutdown_signal));
149
150 // Check that filter index has all blocks that were in the chain before it started.
151 {
152 LOCK(cs_main);
153 const CBlockIndex* block_index;
154 for (block_index = m_node.chainman->ActiveChain().Genesis();
155 block_index != nullptr;
156 block_index = m_node.chainman->ActiveChain().Next(block_index)) {
157 CheckFilterLookups(filter_index, block_index, last_header, m_node.chainman->m_blockman);
158 }
159 }
160
161 // Create two forks.
162 const CBlockIndex* tip;
163 {
164 LOCK(cs_main);
165 tip = m_node.chainman->ActiveChain().Tip();
166 }
167 CKey coinbase_key_A = GenerateRandomKey();
168 CKey coinbase_key_B = GenerateRandomKey();
169 CScript coinbase_script_pub_key_A = GetScriptForDestination(PKHash(coinbase_key_A.GetPubKey()));
170 CScript coinbase_script_pub_key_B = GetScriptForDestination(PKHash(coinbase_key_B.GetPubKey()));
171 std::vector<std::shared_ptr<CBlock>> chainA, chainB;
172 BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_A, 10, chainA));
173 BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_B, 10, chainB));
174
175 // Check that new blocks on chain A get indexed.
176 uint256 chainA_last_header = last_header;
177 for (size_t i = 0; i < 2; i++) {
178 const auto& block = chainA[i];
179 BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
180 }
181 for (size_t i = 0; i < 2; i++) {
182 const auto& block = chainA[i];
183 const CBlockIndex* block_index;
184 {
185 LOCK(cs_main);
186 block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
187 }
188
189 BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
190 CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
191 }
192
193 // Reorg to chain B.
194 uint256 chainB_last_header = last_header;
195 for (size_t i = 0; i < 3; i++) {
196 const auto& block = chainB[i];
197 BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
198 }
199 for (size_t i = 0; i < 3; i++) {
200 const auto& block = chainB[i];
201 const CBlockIndex* block_index;
202 {
203 LOCK(cs_main);
204 block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
205 }
206
207 BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
208 CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
209 }
210
211 // Check that filters for stale blocks on A can be retrieved.
212 chainA_last_header = last_header;
213 for (size_t i = 0; i < 2; i++) {
214 const auto& block = chainA[i];
215 const CBlockIndex* block_index;
216 {
217 LOCK(cs_main);
218 block_index = m_node.chainman->m_blockman.LookupBlockIndex(block->GetHash());
219 }
220
221 BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
222 CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
223 }
224
225 // Reorg back to chain A.
226 for (size_t i = 2; i < 4; i++) {
227 const auto& block = chainA[i];
228 BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
229 }
230
231 // Check that chain A and B blocks can be retrieved.
232 chainA_last_header = last_header;
233 chainB_last_header = last_header;
234 for (size_t i = 0; i < 3; i++) {
235 const CBlockIndex* block_index;
236
237 {
238 LOCK(cs_main);
239 block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainA[i]->GetHash());
240 }
241 BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
242 CheckFilterLookups(filter_index, block_index, chainA_last_header, m_node.chainman->m_blockman);
243
244 {
245 LOCK(cs_main);
246 block_index = m_node.chainman->m_blockman.LookupBlockIndex(chainB[i]->GetHash());
247 }
248 BOOST_CHECK(filter_index.BlockUntilSyncedToCurrentChain());
249 CheckFilterLookups(filter_index, block_index, chainB_last_header, m_node.chainman->m_blockman);
250 }
251
252 // Test lookups for a range of filters/hashes.
253 std::vector<BlockFilter> filters;
254 std::vector<uint256> filter_hashes;
255
256 {
257 LOCK(cs_main);
258 tip = m_node.chainman->ActiveChain().Tip();
259 }
260 BOOST_CHECK(filter_index.LookupFilterRange(0, tip, filters));
261 BOOST_CHECK(filter_index.LookupFilterHashRange(0, tip, filter_hashes));
262
263 assert(tip->nHeight >= 0);
264 BOOST_CHECK_EQUAL(filters.size(), tip->nHeight + 1U);
265 BOOST_CHECK_EQUAL(filter_hashes.size(), tip->nHeight + 1U);
266
267 filters.clear();
268 filter_hashes.clear();
269
270 filter_index.Interrupt();
271 filter_index.Stop();
272 }
273
274 BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
275 {
276 BlockFilterIndex* filter_index;
277
278 filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
279 BOOST_CHECK(filter_index == nullptr);
280
281 BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
282
283 filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
284 BOOST_CHECK(filter_index != nullptr);
285 BOOST_CHECK(filter_index->GetFilterType() == BlockFilterType::BASIC);
286
287 // Initialize returns false if index already exists.
288 BOOST_CHECK(!InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
289
290 int iter_count = 0;
291 ForEachBlockFilterIndex([&iter_count](BlockFilterIndex& _index) { iter_count++; });
292 BOOST_CHECK_EQUAL(iter_count, 1);
293
294 BOOST_CHECK(DestroyBlockFilterIndex(BlockFilterType::BASIC));
295
296 // Destroy returns false because index was already destroyed.
297 BOOST_CHECK(!DestroyBlockFilterIndex(BlockFilterType::BASIC));
298
299 filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
300 BOOST_CHECK(filter_index == nullptr);
301
302 // Reinitialize index.
303 BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false));
304
305 DestroyAllBlockFilterIndexes();
306
307 filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
308 BOOST_CHECK(filter_index == nullptr);
309 }
310
311 BOOST_AUTO_TEST_SUITE_END()
312