Skip to content

Commit bcc8b35

Browse files
authored
Merge pull request #4178 from UdjinM6/backports-0.17-pr30
Merge bitcoin#13033: Build txindex in parallel with validation
2 parents 639f42a + 7ff6515 commit bcc8b35

28 files changed

+817
-105
lines changed

doc/files.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* evodb/*: special txes and quorums database
1313
* fee_estimates.dat: stores statistics used to estimate minimum transaction fees and priorities required for confirmation
1414
* governance.dat: stores data for governance obgects
15+
* indexes/txindex/*: optional transaction index database (LevelDB); since 0.17.0
1516
* llmq/*: quorum signatures database
1617
* mempool.dat: dump of the mempool's transactions
1718
* mncache.dat: stores data for masternode list

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ BITCOIN_CORE_H = \
173173
fs.h \
174174
httprpc.h \
175175
httpserver.h \
176+
index/txindex.h \
176177
indirectmap.h \
177178
init.h \
178179
interfaces/handler.h \
@@ -316,6 +317,7 @@ libdash_server_a_SOURCES = \
316317
evo/specialtx.cpp \
317318
httprpc.cpp \
318319
httpserver.cpp \
320+
index/txindex.cpp \
319321
init.cpp \
320322
dbwrapper.cpp \
321323
governance/governance.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ BITCOIN_TESTS =\
104104
test/timedata_tests.cpp \
105105
test/torcontrol_tests.cpp \
106106
test/transaction_tests.cpp \
107+
test/txindex_tests.cpp \
107108
test/txvalidation_tests.cpp \
108109
test/txvalidationcache_tests.cpp \
109110
test/versionbits_tests.cpp \

src/dbwrapper.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,9 @@ class CDBWrapper
245245
CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false);
246246
~CDBWrapper();
247247

248+
CDBWrapper(const CDBWrapper&) = delete;
249+
CDBWrapper& operator=(const CDBWrapper&) = delete;
250+
248251
template <typename K>
249252
bool ReadDataStream(const K& key, CDataStream& ssValue) const
250253
{

src/index/txindex.cpp

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// Copyright (c) 2017-2018 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 <chainparams.h>
6+
#include <index/txindex.h>
7+
#include <init.h>
8+
#include <tinyformat.h>
9+
#include <ui_interface.h>
10+
#include <util.h>
11+
#include <validation.h>
12+
#include <warnings.h>
13+
14+
constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds
15+
constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds
16+
17+
std::unique_ptr<TxIndex> g_txindex;
18+
19+
template<typename... Args>
20+
static void FatalError(const char* fmt, const Args&... args)
21+
{
22+
std::string strMessage = tfm::format(fmt, args...);
23+
SetMiscWarning(strMessage);
24+
LogPrintf("*** %s\n", strMessage);
25+
uiInterface.ThreadSafeMessageBox(
26+
"Error: A fatal internal error occurred, see debug.log for details",
27+
"", CClientUIInterface::MSG_ERROR);
28+
StartShutdown();
29+
}
30+
31+
TxIndex::TxIndex(std::unique_ptr<TxIndexDB> db) :
32+
m_db(std::move(db)), m_synced(false), m_best_block_index(nullptr)
33+
{}
34+
35+
TxIndex::~TxIndex()
36+
{
37+
Interrupt();
38+
Stop();
39+
}
40+
41+
bool TxIndex::Init()
42+
{
43+
LOCK(cs_main);
44+
45+
// Attempt to migrate txindex from the old database to the new one. Even if
46+
// chain_tip is null, the node could be reindexing and we still want to
47+
// delete txindex records in the old database.
48+
if (!m_db->MigrateData(*pblocktree, chainActive.GetLocator())) {
49+
return false;
50+
}
51+
52+
CBlockLocator locator;
53+
if (!m_db->ReadBestBlock(locator)) {
54+
locator.SetNull();
55+
}
56+
57+
m_best_block_index = FindForkInGlobalIndex(chainActive, locator);
58+
m_synced = m_best_block_index.load() == chainActive.Tip();
59+
return true;
60+
}
61+
62+
static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev)
63+
{
64+
AssertLockHeld(cs_main);
65+
66+
if (!pindex_prev) {
67+
return chainActive.Genesis();
68+
}
69+
70+
const CBlockIndex* pindex = chainActive.Next(pindex_prev);
71+
if (pindex) {
72+
return pindex;
73+
}
74+
75+
return chainActive.Next(chainActive.FindFork(pindex_prev));
76+
}
77+
78+
void TxIndex::ThreadSync()
79+
{
80+
const CBlockIndex* pindex = m_best_block_index.load();
81+
if (!m_synced) {
82+
auto& consensus_params = Params().GetConsensus();
83+
84+
int64_t last_log_time = 0;
85+
int64_t last_locator_write_time = 0;
86+
while (true) {
87+
if (m_interrupt) {
88+
WriteBestBlock(pindex);
89+
return;
90+
}
91+
92+
{
93+
LOCK(cs_main);
94+
const CBlockIndex* pindex_next = NextSyncBlock(pindex);
95+
if (!pindex_next) {
96+
WriteBestBlock(pindex);
97+
m_best_block_index = pindex;
98+
m_synced = true;
99+
break;
100+
}
101+
pindex = pindex_next;
102+
}
103+
104+
int64_t current_time = GetTime();
105+
if (last_log_time + SYNC_LOG_INTERVAL < current_time) {
106+
LogPrintf("Syncing txindex with block chain from height %d\n", pindex->nHeight);
107+
last_log_time = current_time;
108+
}
109+
110+
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
111+
WriteBestBlock(pindex);
112+
last_locator_write_time = current_time;
113+
}
114+
115+
CBlock block;
116+
if (!ReadBlockFromDisk(block, pindex, consensus_params)) {
117+
FatalError("%s: Failed to read block %s from disk",
118+
__func__, pindex->GetBlockHash().ToString());
119+
return;
120+
}
121+
if (!WriteBlock(block, pindex)) {
122+
FatalError("%s: Failed to write block %s to tx index database",
123+
__func__, pindex->GetBlockHash().ToString());
124+
return;
125+
}
126+
}
127+
}
128+
129+
if (pindex) {
130+
LogPrintf("txindex is enabled at height %d\n", pindex->nHeight);
131+
} else {
132+
LogPrintf("txindex is enabled\n");
133+
}
134+
}
135+
136+
bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
137+
{
138+
CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
139+
std::vector<std::pair<uint256, CDiskTxPos>> vPos;
140+
vPos.reserve(block.vtx.size());
141+
for (const auto& tx : block.vtx) {
142+
vPos.emplace_back(tx->GetHash(), pos);
143+
pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION);
144+
}
145+
return m_db->WriteTxs(vPos);
146+
}
147+
148+
bool TxIndex::WriteBestBlock(const CBlockIndex* block_index)
149+
{
150+
LOCK(cs_main);
151+
if (!m_db->WriteBestBlock(chainActive.GetLocator(block_index))) {
152+
return error("%s: Failed to write locator to disk", __func__);
153+
}
154+
return true;
155+
}
156+
157+
void TxIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex,
158+
const std::vector<CTransactionRef>& txn_conflicted)
159+
{
160+
if (!m_synced) {
161+
return;
162+
}
163+
164+
const CBlockIndex* best_block_index = m_best_block_index.load();
165+
if (!best_block_index) {
166+
if (pindex->nHeight != 0) {
167+
FatalError("%s: First block connected is not the genesis block (height=%d)",
168+
__func__, pindex->nHeight);
169+
return;
170+
}
171+
} else {
172+
// Ensure block connects to an ancestor of the current best block. This should be the case
173+
// most of the time, but may not be immediately after the the sync thread catches up and sets
174+
// m_synced. Consider the case where there is a reorg and the blocks on the stale branch are
175+
// in the ValidationInterface queue backlog even after the sync thread has caught up to the
176+
// new chain tip. In this unlikely event, log a warning and let the queue clear.
177+
if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) {
178+
LogPrintf("%s: WARNING: Block %s does not connect to an ancestor of " /* Continued */
179+
"known best chain (tip=%s); not updating txindex\n",
180+
__func__, pindex->GetBlockHash().ToString(),
181+
best_block_index->GetBlockHash().ToString());
182+
return;
183+
}
184+
}
185+
186+
if (WriteBlock(*block, pindex)) {
187+
m_best_block_index = pindex;
188+
} else {
189+
FatalError("%s: Failed to write block %s to txindex",
190+
__func__, pindex->GetBlockHash().ToString());
191+
return;
192+
}
193+
}
194+
195+
void TxIndex::SetBestChain(const CBlockLocator& locator)
196+
{
197+
if (!m_synced) {
198+
return;
199+
}
200+
201+
const uint256& locator_tip_hash = locator.vHave.front();
202+
const CBlockIndex* locator_tip_index;
203+
{
204+
LOCK(cs_main);
205+
locator_tip_index = LookupBlockIndex(locator_tip_hash);
206+
}
207+
208+
if (!locator_tip_index) {
209+
FatalError("%s: First block (hash=%s) in locator was not found",
210+
__func__, locator_tip_hash.ToString());
211+
return;
212+
}
213+
214+
// This checks that SetBestChain callbacks are received after BlockConnected. The check may fail
215+
// immediately after the the sync thread catches up and sets m_synced. Consider the case where
216+
// there is a reorg and the blocks on the stale branch are in the ValidationInterface queue
217+
// backlog even after the sync thread has caught up to the new chain tip. In this unlikely
218+
// event, log a warning and let the queue clear.
219+
const CBlockIndex* best_block_index = m_best_block_index.load();
220+
if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) {
221+
LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known best " /* Continued */
222+
"chain (tip=%s); not writing txindex locator\n",
223+
__func__, locator_tip_hash.ToString(),
224+
best_block_index->GetBlockHash().ToString());
225+
return;
226+
}
227+
228+
if (!m_db->WriteBestBlock(locator)) {
229+
error("%s: Failed to write locator to disk", __func__);
230+
}
231+
}
232+
233+
bool TxIndex::BlockUntilSyncedToCurrentChain()
234+
{
235+
AssertLockNotHeld(cs_main);
236+
237+
if (!m_synced) {
238+
return false;
239+
}
240+
241+
{
242+
// Skip the queue-draining stuff if we know we're caught up with
243+
// chainActive.Tip().
244+
LOCK(cs_main);
245+
const CBlockIndex* chain_tip = chainActive.Tip();
246+
const CBlockIndex* best_block_index = m_best_block_index.load();
247+
if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
248+
return true;
249+
}
250+
}
251+
252+
LogPrintf("%s: txindex is catching up on block notifications\n", __func__);
253+
SyncWithValidationInterfaceQueue();
254+
return true;
255+
}
256+
257+
bool TxIndex::FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRef& tx) const
258+
{
259+
CDiskTxPos postx;
260+
if (!m_db->ReadTxPos(tx_hash, postx)) {
261+
return false;
262+
}
263+
264+
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
265+
if (file.IsNull()) {
266+
return error("%s: OpenBlockFile failed", __func__);
267+
}
268+
CBlockHeader header;
269+
try {
270+
file >> header;
271+
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
272+
file >> tx;
273+
} catch (const std::exception& e) {
274+
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
275+
}
276+
if (tx->GetHash() != tx_hash) {
277+
return error("%s: txid mismatch", __func__);
278+
}
279+
block_hash = header.GetHash();
280+
return true;
281+
}
282+
283+
bool TxIndex::HasTx(const uint256& tx_hash) const
284+
{
285+
CDiskTxPos postx;
286+
return m_db->ReadTxPos(tx_hash, postx);
287+
}
288+
289+
void TxIndex::Interrupt()
290+
{
291+
m_interrupt();
292+
}
293+
294+
void TxIndex::Start()
295+
{
296+
// Need to register this ValidationInterface before running Init(), so that
297+
// callbacks are not missed if Init sets m_synced to true.
298+
RegisterValidationInterface(this);
299+
if (!Init()) {
300+
FatalError("%s: txindex failed to initialize", __func__);
301+
return;
302+
}
303+
304+
m_thread_sync = std::thread(&TraceThread<std::function<void()>>, "txindex",
305+
std::bind(&TxIndex::ThreadSync, this));
306+
}
307+
308+
void TxIndex::Stop()
309+
{
310+
UnregisterValidationInterface(this);
311+
312+
if (m_thread_sync.joinable()) {
313+
m_thread_sync.join();
314+
}
315+
}

0 commit comments

Comments
 (0)