Branch data Line data Source code
1 : : // Copyright (c) 2017-present The Bitcoin Core 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 <chain.h>
7 : : #include <chainparams.h>
8 : : #include <common/args.h>
9 : : #include <consensus/amount.h>
10 : : #include <consensus/validation.h>
11 : : #include <crypto/hex_base.h>
12 : : #include <dbwrapper.h>
13 : : #include <flatfile.h>
14 : : #include <index/disktxpos.h>
15 : : #include <index/txindex.h>
16 : : #include <index/txindex_key.h>
17 : : #include <interfaces/chain.h>
18 : : #include <key.h>
19 : : #include <node/blockstorage.h>
20 : : #include <primitives/block.h>
21 : : #include <script/script.h>
22 : : #include <streams.h>
23 : : #include <sync.h>
24 : : #include <test/util/setup_common.h>
25 : : #include <util/byte_units.h>
26 : : #include <util/check.h>
27 : : #include <util/strencodings.h>
28 : : #include <validation.h>
29 : :
30 : : #include <cstdint>
31 : : #include <memory>
32 : : #include <string>
33 : : #include <string_view>
34 : : #include <utility>
35 : : #include <vector>
36 : :
37 : : #include <boost/test/unit_test.hpp>
38 : :
39 : : BOOST_AUTO_TEST_SUITE(txindex_tests)
40 : :
41 : : // Grants tests access to the otherwise non-public txindex database handle.
42 : : class TxIndexTest
43 : : {
44 : : public:
45 [ + - ]: 1 : static CDBWrapper& GetDB(const TxIndex& txindex) { return txindex.GetDB(); }
46 : 2 : static CBlockLocator ReadBestBlock(const TxIndex& txindex) { return txindex.GetDB().ReadBestBlock(); }
47 : 1 : static void WriteBestBlock(const TxIndex& txindex, const CBlockLocator& locator)
48 : : {
49 : 1 : auto& db{txindex.GetDB()};
50 : 1 : CDBBatch batch{db};
51 [ + - ]: 1 : db.WriteBestBlock(batch, locator);
52 [ + - ]: 1 : db.WriteBatch(batch);
53 : 1 : }
54 : : };
55 : :
56 : : namespace {
57 : :
58 : 3 : SipHasher13UJ ReadHasher(const CDBWrapper& db)
59 : : {
60 : 3 : std::pair<uint64_t, uint64_t> salt;
61 [ + - + - ]: 6 : BOOST_REQUIRE(db.Read(txindex::DB_TXID_HASH_SALT, salt));
62 : 3 : return SipHasher13UJ{salt.first, salt.second};
63 : : }
64 : :
65 : 5 : std::vector<txindex::BlockTxPosition> BucketPositions(CDBWrapper& db, txindex::TxHashKeyPrefix prefix)
66 : : {
67 : 5 : std::vector<txindex::BlockTxPosition> positions;
68 [ + - + - ]: 5 : std::unique_ptr<CDBIterator> it{db.NewIterator()};
69 : 5 : txindex::DBKey key{prefix, {}};
70 [ + - + - : 24 : for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {
+ - + - +
- + - +
+ ]
71 [ + - ]: 7 : positions.push_back(key.pos);
72 : : }
73 : 5 : return positions;
74 : 5 : }
75 : :
76 : 2 : FlatFilePos BlockFilePos(const ChainstateManager& chainman, uint32_t height)
77 : : {
78 : 2 : LOCK(cs_main);
79 [ + - + - ]: 2 : const CBlockIndex* block_index{chainman.ActiveChain()[height]};
80 [ + - + - : 4 : BOOST_REQUIRE(block_index);
+ - ]
81 [ + - ]: 2 : return {block_index->nFile, block_index->nDataPos};
82 : 2 : }
83 : :
84 : 116 : uint256 LookupTx(const TxIndex& txindex, const Txid& txid)
85 : : {
86 : 116 : const auto result{txindex.FindTx(txid)};
87 [ + - + - : 232 : BOOST_REQUIRE(result);
+ - ]
88 [ + - - + : 232 : BOOST_CHECK(result->tx->GetHash() == txid);
+ - ]
89 : 116 : return result->block_hash;
90 : 116 : }
91 : :
92 : 2 : void InvalidateBlock(ChainstateManager& chainman, const uint256& block_hash)
93 : : {
94 [ + - + - ]: 6 : CBlockIndex* block_index{WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash))};
95 [ + - + - ]: 4 : BOOST_REQUIRE(block_index);
96 [ + - ]: 2 : BlockValidationState state;
97 [ + - + - : 4 : BOOST_REQUIRE(chainman.ActiveChainstate().InvalidateBlock(state, block_index));
+ - + - ]
98 : 2 : }
99 : :
100 : : } // namespace
101 : :
102 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(txindex_position_encoding)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
103 : : {
104 : 1 : constexpr struct { txindex::BlockTxPosition position; std::string_view encoded; } test_vectors[]{
105 : : {{0, 0}, "00000000"},
106 : : {{1, 2}, "01000002"},
107 : : {{10'000'000, 123}, "83e1ac0000007b"},
108 : : {{456, 3'999'999}, "82483d08ff"},
109 : : };
110 : :
111 [ + + ]: 5 : for (const auto& [position, encoded] : test_vectors) {
112 [ + - - + : 4 : BOOST_CHECK_EQUAL(HexStr(DataStream{} << position), encoded);
+ - + - ]
113 : :
114 : 4 : txindex::BlockTxPosition decoded;
115 [ + - + - : 16 : BOOST_CHECK((DataStream{ParseHex(encoded)} >> decoded).empty());
- + + - ]
116 [ + - + - ]: 12 : BOOST_CHECK(decoded == position);
117 : : }
118 : :
119 : : // Pin the full key encodings, including the type prefixes.
120 [ + - - + : 1 : BOOST_CHECK_EQUAL(HexStr(DataStream{} << txindex::BlockSeqKey{1}), "7301");
+ - + - ]
121 [ + - - + : 1 : BOOST_CHECK_EQUAL(HexStr(DataStream{} << txindex::DBKey{0x0102030405, {1, 2}}),
+ - + - ]
122 : : "78010203040501000002");
123 : :
124 [ + - ]: 1 : BOOST_CHECK_EQUAL(txindex::BLOCK_HEADER_SIZE, GetSerializeSize(CBlockHeader{}));
125 : 1 : }
126 : :
127 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(txindex_hash_prefix)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
128 : : {
129 [ + - ]: 1 : BOOST_CHECK_EQUAL(
130 : : txindex::CreateKeyPrefix(
131 : : SipHasher13UJ{0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL},
132 : : Txid{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}),
133 : : 0xc67d87b08cULL);
134 : 1 : }
135 : :
136 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
137 : : {
138 [ + - ]: 1 : TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/true);
139 [ + - + - : 2 : BOOST_REQUIRE(txindex.Init());
+ - ]
140 : :
141 : : // Transaction should not be found in the index before it is started.
142 [ + + ]: 101 : for (const auto& txn : m_coinbase_txns) {
143 [ + - + - : 200 : BOOST_CHECK(!txindex.FindTx(txn->GetHash()));
+ - ]
144 : : }
145 : :
146 : : // BlockUntilSyncedToCurrentChain should return false before txindex is started.
147 [ + - + - : 2 : BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
148 : :
149 [ + - ]: 1 : txindex.Sync();
150 : :
151 : : // Check that txindex excludes genesis block transactions.
152 [ + - ]: 1 : const CBlock& genesis_block = Params().GenesisBlock();
153 [ + + ]: 2 : for (const auto& txn : genesis_block.vtx) {
154 [ + - + - : 2 : BOOST_CHECK(!txindex.FindTx(txn->GetHash()));
+ - ]
155 : : }
156 : :
157 : : // Check that txindex has all txs that were in the chain before it started.
158 [ + + ]: 101 : for (const auto& txn : m_coinbase_txns) {
159 [ + - ]: 100 : LookupTx(txindex, txn->GetHash());
160 : : }
161 : :
162 : : // Check that new transactions in new blocks make it into the index.
163 [ + + ]: 11 : for (int i = 0; i < 10; i++) {
164 [ + - + - : 10 : CScript coinbase_script_pub_key = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
+ - ]
165 : 10 : std::vector<CMutableTransaction> no_txns;
166 [ + - ]: 10 : const CBlock& block = CreateAndProcessBlock(no_txns, coinbase_script_pub_key);
167 [ + - ]: 10 : const CTransaction& txn = *block.vtx[0];
168 : :
169 [ + - + - : 20 : BOOST_CHECK(txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
170 [ + - ]: 10 : LookupTx(txindex, txn.GetHash());
171 : 10 : }
172 : :
173 : : // shutdown sequence (c.f. Shutdown() in init.cpp)
174 [ + - ]: 1 : txindex.Stop();
175 : 1 : }
176 : :
177 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(txindex_collision_scan_path, TestChain100Setup)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
178 : : {
179 : : // On-disk, so the legacy-entry probe at construction runs against a fresh
180 : : // database, as it would on a node whose index was created by this version.
181 [ + - ]: 1 : TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
182 [ + - + - : 2 : BOOST_REQUIRE(txindex.Init());
+ - + - ]
183 [ + - ]: 1 : txindex.Sync();
184 : :
185 [ + - ]: 1 : CDBWrapper& db{TxIndexTest::GetDB(txindex)};
186 [ + - ]: 1 : const SipHasher13UJ hasher{ReadHasher(db)};
187 : :
188 : : // Lookups scan candidates in descending sequence order, so entries of
189 : : // later-connected blocks are tried first. Forge a colliding entry under the
190 : : // first coinbase's prefix pointing at the last coinbase, so looking up the
191 : : // first tx must scan that false positive first.
192 [ + - ]: 1 : const Txid fake_txid{m_coinbase_txns.back()->GetHash()};
193 [ + - ]: 1 : const Txid target_txid{m_coinbase_txns.front()->GetHash()};
194 : 1 : const auto fake_prefix{txindex::CreateKeyPrefix(hasher, fake_txid)};
195 : 1 : const auto target_prefix{txindex::CreateKeyPrefix(hasher, target_txid)};
196 : : // Distinct prefixes guarantee the target's bucket initially holds only the target.
197 [ + - + - : 2 : BOOST_REQUIRE(fake_prefix != target_prefix);
+ - ]
198 : :
199 : : // Read the last coinbase's encoded position straight from its bucket.
200 [ + - ]: 1 : const auto fake_bucket{BucketPositions(db, fake_prefix)};
201 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(fake_bucket.size(), 1U);
+ - ]
202 [ + - ]: 1 : const txindex::BlockTxPosition fake_pos{fake_bucket.front()};
203 : :
204 [ + - ]: 1 : db.Write(txindex::DBKey{target_prefix, fake_pos}, txindex::EMPTY_VALUE);
205 : :
206 : : // The target's bucket now holds the real target first (lower sequence
207 : : // number), then the forged false positive, which the descending scan tries first.
208 [ + - ]: 1 : const auto target_bucket{BucketPositions(db, target_prefix)};
209 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(target_bucket.size(), 2U);
+ - ]
210 [ + - - + : 2 : BOOST_CHECK(target_bucket[0] != fake_pos);
+ - + - ]
211 [ + - + - : 3 : BOOST_CHECK(target_bucket[1] == fake_pos);
+ - + - ]
212 : :
213 [ + - ]: 1 : LookupTx(txindex, target_txid);
214 : :
215 : : // A database created fresh by this version cannot contain legacy entries, so
216 : : // lookups skip the legacy fallback: drop the last coinbase's hashed entry and
217 : : // re-add it under the old 't' + txid schema (a physical CDiskTxPos), then
218 : : // confirm the lookup misses even though the legacy row exists.
219 : : // BlockTxPosition offsets are from the block start (header included), while
220 : : // the legacy CDiskTxPos.nTxOffset is measured after the header.
221 [ + - + - ]: 1 : const CDiskTxPos fake_physical{BlockFilePos(*m_node.chainman, fake_pos.block_seq + 1), fake_pos.tx_offset_in_block - txindex::BLOCK_HEADER_SIZE};
222 [ + - ]: 1 : db.Erase(txindex::DBKey{fake_prefix, fake_pos});
223 [ + - ]: 1 : db.Write(txindex::LegacyTxKey(fake_txid), fake_physical);
224 [ + - + - : 2 : BOOST_CHECK(!txindex.FindTx(fake_txid));
+ - + - ]
225 : :
226 [ + - ]: 1 : txindex.Stop();
227 : 1 : }
228 : :
229 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(txindex_legacy_fallback, TestChain100Setup)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
230 : : {
231 : : // Seed the on-disk database with a legacy ('t' + txid) entry before the index
232 : : // is opened, as if it had been written by a pre-hashing version.
233 : 1 : const Txid legacy_txid{m_coinbase_txns.front()->GetHash()};
234 : : // The block at height 1 holds only the coinbase, so the tx starts right after
235 : : // the header and the 1-byte tx count.
236 : 1 : const CDiskTxPos legacy_pos{BlockFilePos(*m_node.chainman, 1), 1};
237 : 1 : {
238 [ + - + - : 5 : CDBWrapper db{DBParams{.path = gArgs.GetDataDirNet() / "indexes" / "txindex", .cache_bytes = 1_MiB}};
+ - ]
239 [ + - ]: 1 : db.Write(txindex::LegacyTxKey(legacy_txid), legacy_pos);
240 : 1 : }
241 : :
242 [ + - ]: 1 : TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
243 [ + - + - : 2 : BOOST_REQUIRE(txindex.Init());
+ - + - ]
244 [ + - ]: 1 : txindex.Sync();
245 : :
246 : : // Drop the hashed entries so only the legacy row remains, then confirm the
247 : : // lookup succeeds through the fallback.
248 [ + - ]: 1 : CDBWrapper& db{TxIndexTest::GetDB(txindex)};
249 [ + - ]: 1 : const auto prefix{txindex::CreateKeyPrefix(ReadHasher(db), legacy_txid)};
250 [ + - ]: 1 : const auto bucket{BucketPositions(db, prefix)};
251 [ + - + - ]: 2 : BOOST_REQUIRE(!bucket.empty());
252 [ + - + + ]: 2 : for (const auto& pos : bucket) db.Erase(txindex::DBKey{prefix, pos});
253 : :
254 [ + - ]: 1 : LookupTx(txindex, legacy_txid);
255 : :
256 [ + - ]: 1 : txindex.Stop();
257 : 1 : }
258 : :
259 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(txindex_locator_upgrade, TestChain100Setup)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
260 : : {
261 : 1 : uint256 legacy_hash, new_hash;
262 : 1 : {
263 : 1 : LOCK(cs_main);
264 [ + - - + : 2 : legacy_hash = Assert(m_node.chainman->ActiveChain()[1])->GetBlockHash();
- + ]
265 [ + - - + : 2 : new_hash = Assert(m_node.chainman->ActiveChain().Tip())->GetBlockHash();
- + + - ]
266 : 0 : }
267 [ + - + - ]: 1 : CBlockLocator legacy_locator{{legacy_hash}}, new_locator{{new_hash}};
268 [ + - + - : 5 : { CDBWrapper{DBParams{.path = gArgs.GetDataDirNet() / "indexes" / "txindex", .cache_bytes = 1_MiB}}.Write(uint8_t{'B'}, legacy_locator); }
+ - + - ]
269 : :
270 [ + - + - ]: 1 : TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);
271 [ + - + - : 2 : BOOST_CHECK(TxIndexTest::ReadBestBlock(txindex).vHave == legacy_locator.vHave);
+ - + - ]
272 : :
273 [ + - ]: 1 : TxIndexTest::WriteBestBlock(txindex, new_locator);
274 [ + - + - : 2 : BOOST_CHECK(TxIndexTest::ReadBestBlock(txindex).vHave == new_locator.vHave);
+ - + - ]
275 : :
276 : 1 : CBlockLocator stored_legacy_locator;
277 [ + - + - : 2 : BOOST_REQUIRE(TxIndexTest::GetDB(txindex).Read(uint8_t{'B'}, stored_legacy_locator));
+ - + - +
- ]
278 [ + - + - ]: 2 : BOOST_CHECK(stored_legacy_locator.vHave == legacy_locator.vHave);
279 : 1 : }
280 : :
281 [ + - + - : 7 : BOOST_FIXTURE_TEST_CASE(txindex_reorg_keeps_stale_entries, TestChain100Setup)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
282 : : {
283 [ + - ]: 1 : TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/true);
284 [ + - + - : 2 : BOOST_REQUIRE(txindex.Init());
+ - + - ]
285 [ + - ]: 1 : txindex.Sync();
286 : :
287 [ + - + - : 2 : const CScript coinbase_script{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
+ - ]
288 : :
289 : : // Mine a unique (non-coinbase) transaction into a new block at height 101.
290 [ + - ]: 2 : CMutableTransaction unique_mtx{CreateValidMempoolTransaction(
291 [ + - ]: 1 : /*input_transaction=*/m_coinbase_txns[0],
292 : : /*input_vout=*/0,
293 : : /*input_height=*/1,
294 : : /*input_signing_key=*/coinbaseKey,
295 : 1 : /*output_destination=*/CScript() << OP_TRUE,
296 : : /*output_amount=*/CAmount{1 * COIN},
297 [ + - + - ]: 2 : /*submit=*/false)};
298 [ + - + - ]: 2 : const Txid unique_txid{MakeTransactionRef(unique_mtx)->GetHash()};
299 [ + - + - : 3 : const uint256 stale_block_hash{CreateAndProcessBlock({unique_mtx}, coinbase_script).GetHash()};
+ - + + -
- ]
300 [ + - + - : 2 : BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
301 : :
302 [ + - + - : 3 : BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
+ - + - ]
303 : :
304 [ + - ]: 1 : CDBWrapper& db{TxIndexTest::GetDB(txindex)};
305 [ + - ]: 1 : const auto prefix{txindex::CreateKeyPrefix(ReadHasher(db), unique_txid)};
306 [ + - ]: 1 : const auto original_bucket{BucketPositions(db, prefix)};
307 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(original_bucket.size(), 1U);
+ - ]
308 : :
309 [ + - ]: 1 : ChainstateManager& chainman{*m_node.chainman};
310 : :
311 : : // Invalidate the block holding the unique transaction.
312 [ + - ]: 1 : InvalidateBlock(chainman, stale_block_hash);
313 [ + - + - : 2 : BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
314 : :
315 : : // The disconnected transaction is still found, in the now-stale block.
316 [ + - + - : 3 : BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
+ - + - ]
317 : 1 : {
318 [ + - ]: 1 : LOCK(cs_main);
319 [ + - ]: 1 : const CBlockIndex* stale_index{chainman.m_blockman.LookupBlockIndex(stale_block_hash)};
320 [ + - + - : 2 : BOOST_REQUIRE(stale_index);
+ - ]
321 [ + - + - : 2 : BOOST_CHECK(!chainman.ActiveChain().Contains(*stale_index));
+ - + - ]
322 : 0 : }
323 : :
324 : : // Mine the same transaction into a replacement branch, which gets a later
325 : : // sequence number. The lookup must now return the branch block in the active chain.
326 [ + - + - : 4 : const uint256 branch_block_hash{CreateAndProcessBlock({unique_mtx}, CScript() << OP_TRUE).GetHash()};
+ - + - +
+ - - ]
327 [ + - ]: 2 : CreateAndProcessBlock({}, coinbase_script);
328 [ + - + - : 2 : BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
329 [ + - + - : 3 : BOOST_CHECK(LookupTx(txindex, unique_txid) == branch_block_hash);
+ - + - ]
330 : :
331 : : // Reorg back to the original branch. The original branch block must be
332 : : // now be preferred even though the replacement branch has a later sequence.
333 : 1 : {
334 [ + - ]: 1 : LOCK(cs_main);
335 [ + - + - : 1 : chainman.ActiveChainstate().ResetBlockFailureFlags(chainman.m_blockman.LookupBlockIndex(stale_block_hash));
+ - ]
336 : 0 : }
337 [ + - ]: 1 : InvalidateBlock(chainman, branch_block_hash);
338 : 1 : {
339 [ + - ]: 1 : BlockValidationState state;
340 [ + - + - : 2 : BOOST_REQUIRE(chainman.ActiveChainstate().ActivateBestChain(state));
+ - + - -
+ ]
341 : 0 : }
342 [ + - + - : 2 : BOOST_REQUIRE(txindex.BlockUntilSyncedToCurrentChain());
+ - + - ]
343 [ + - + + : 6 : BOOST_CHECK(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip()->GetBlockHash()) == stale_block_hash);
+ - + - ]
344 : :
345 [ + - + - : 3 : BOOST_CHECK(LookupTx(txindex, unique_txid) == stale_block_hash);
+ - + - ]
346 : :
347 : : // Reconnecting the original block must not create duplicate entries.
348 [ + - ]: 1 : const auto reorg_bucket{BucketPositions(db, prefix)};
349 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(reorg_bucket.size(), 2U);
+ - ]
350 [ + - + - : 3 : BOOST_CHECK(reorg_bucket.front() == original_bucket.front());
+ - + - ]
351 : :
352 [ + - ]: 1 : txindex.Stop();
353 [ + - + - ]: 4 : }
354 : :
355 : : BOOST_AUTO_TEST_SUITE_END()
|