Changeset View
Changeset View
Standalone View
Standalone View
src/index/txindex.cpp
- This file was copied to src/index/base.cpp.
// Copyright (c) 2017-2018 The Bitcoin Core developers | // Copyright (c) 2017-2018 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <config.h> | |||||
#include <index/txindex.h> | #include <index/txindex.h> | ||||
#include <init.h> | |||||
#include <tinyformat.h> | |||||
#include <ui_interface.h> | |||||
#include <util.h> | #include <util.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
#include <warnings.h> | |||||
constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds | |||||
constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds | |||||
std::unique_ptr<TxIndex> g_txindex; | std::unique_ptr<TxIndex> g_txindex; | ||||
template <typename... Args> | |||||
static void FatalError(const char *fmt, const Args &... args) { | |||||
std::string strMessage = tfm::format(fmt, args...); | |||||
SetMiscWarning(strMessage); | |||||
LogPrintf("*** %s\n", strMessage); | |||||
uiInterface.ThreadSafeMessageBox( | |||||
"Error: A fatal internal error occurred, see debug.log for details", "", | |||||
CClientUIInterface::MSG_ERROR); | |||||
StartShutdown(); | |||||
} | |||||
TxIndex::TxIndex(std::unique_ptr<TxIndexDB> db) : m_db(std::move(db)) {} | TxIndex::TxIndex(std::unique_ptr<TxIndexDB> db) : m_db(std::move(db)) {} | ||||
BaseIndex::~BaseIndex() { | |||||
Interrupt(); | |||||
Stop(); | |||||
} | |||||
bool TxIndex::Init() { | bool TxIndex::Init() { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
// Attempt to migrate txindex from the old database to the new one. Even if | // Attempt to migrate txindex from the old database to the new one. Even if | ||||
// chain_tip is null, the node could be reindexing and we still want to | // chain_tip is null, the node could be reindexing and we still want to | ||||
// delete txindex records in the old database. | // delete txindex records in the old database. | ||||
if (!m_db->MigrateData(*pblocktree, chainActive.GetLocator())) { | if (!m_db->MigrateData(*pblocktree, chainActive.GetLocator())) { | ||||
return false; | return false; | ||||
} | } | ||||
return BaseIndex::Init(); | return BaseIndex::Init(); | ||||
} | } | ||||
bool BaseIndex::Init() { | |||||
CBlockLocator locator; | |||||
if (!GetDB().ReadBestBlock(locator)) { | |||||
locator.SetNull(); | |||||
} | |||||
LOCK(cs_main); | |||||
m_best_block_index = FindForkInGlobalIndex(chainActive, locator); | |||||
m_synced = m_best_block_index.load() == chainActive.Tip(); | |||||
return true; | |||||
} | |||||
static const CBlockIndex *NextSyncBlock(const CBlockIndex *pindex_prev) { | |||||
AssertLockHeld(cs_main); | |||||
if (!pindex_prev) { | |||||
return chainActive.Genesis(); | |||||
} | |||||
const CBlockIndex *pindex = chainActive.Next(pindex_prev); | |||||
if (pindex) { | |||||
return pindex; | |||||
} | |||||
return chainActive.Next(chainActive.FindFork(pindex_prev)); | |||||
} | |||||
void BaseIndex::ThreadSync() { | |||||
const CBlockIndex *pindex = m_best_block_index.load(); | |||||
if (!m_synced) { | |||||
auto &config = GetConfig(); | |||||
int64_t last_log_time = 0; | |||||
int64_t last_locator_write_time = 0; | |||||
while (true) { | |||||
if (m_interrupt) { | |||||
WriteBestBlock(pindex); | |||||
return; | |||||
} | |||||
{ | |||||
LOCK(cs_main); | |||||
const CBlockIndex *pindex_next = NextSyncBlock(pindex); | |||||
if (!pindex_next) { | |||||
WriteBestBlock(pindex); | |||||
m_best_block_index = pindex; | |||||
m_synced = true; | |||||
break; | |||||
} | |||||
pindex = pindex_next; | |||||
} | |||||
int64_t current_time = GetTime(); | |||||
if (last_log_time + SYNC_LOG_INTERVAL < current_time) { | |||||
LogPrintf("Syncing %s with block chain from height %d\n", | |||||
GetName(), pindex->nHeight); | |||||
last_log_time = current_time; | |||||
} | |||||
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < | |||||
current_time) { | |||||
WriteBestBlock(pindex); | |||||
last_locator_write_time = current_time; | |||||
} | |||||
CBlock block; | |||||
if (!ReadBlockFromDisk(block, pindex, config)) { | |||||
FatalError("%s: Failed to read block %s from disk", __func__, | |||||
pindex->GetBlockHash().ToString()); | |||||
return; | |||||
} | |||||
if (!WriteBlock(block, pindex)) { | |||||
FatalError("%s: Failed to write block %s to index database", | |||||
__func__, pindex->GetBlockHash().ToString()); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
if (pindex) { | |||||
LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight); | |||||
} else { | |||||
LogPrintf("%s is enabled\n", GetName()); | |||||
} | |||||
} | |||||
bool TxIndex::WriteBlock(const CBlock &block, const CBlockIndex *pindex) { | bool TxIndex::WriteBlock(const CBlock &block, const CBlockIndex *pindex) { | ||||
CDiskTxPos pos(pindex->GetBlockPos(), | CDiskTxPos pos(pindex->GetBlockPos(), | ||||
GetSizeOfCompactSize(block.vtx.size())); | GetSizeOfCompactSize(block.vtx.size())); | ||||
std::vector<std::pair<uint256, CDiskTxPos>> vPos; | std::vector<std::pair<uint256, CDiskTxPos>> vPos; | ||||
vPos.reserve(block.vtx.size()); | vPos.reserve(block.vtx.size()); | ||||
for (const auto &tx : block.vtx) { | for (const auto &tx : block.vtx) { | ||||
vPos.emplace_back(tx->GetHash(), pos); | vPos.emplace_back(tx->GetHash(), pos); | ||||
pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION); | pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION); | ||||
} | } | ||||
return m_db->WriteTxs(vPos); | return m_db->WriteTxs(vPos); | ||||
} | } | ||||
BaseIndexDB &TxIndex::GetDB() const { | BaseIndexDB &TxIndex::GetDB() const { | ||||
return *m_db; | return *m_db; | ||||
} | } | ||||
bool BaseIndex::WriteBestBlock(const CBlockIndex *block_index) { | |||||
LOCK(cs_main); | |||||
if (!GetDB().WriteBestBlock(chainActive.GetLocator(block_index))) { | |||||
return error("%s: Failed to write locator to disk", __func__); | |||||
} | |||||
return true; | |||||
} | |||||
void BaseIndex::BlockConnected( | |||||
const std::shared_ptr<const CBlock> &block, const CBlockIndex *pindex, | |||||
const std::vector<CTransactionRef> &txn_conflicted) { | |||||
if (!m_synced) { | |||||
return; | |||||
} | |||||
const CBlockIndex *best_block_index = m_best_block_index.load(); | |||||
if (!best_block_index) { | |||||
if (pindex->nHeight != 0) { | |||||
FatalError("%s: First block connected is not the genesis block " | |||||
"(height=%d)", | |||||
__func__, pindex->nHeight); | |||||
return; | |||||
} | |||||
} else { | |||||
// Ensure block connects to an ancestor of the current best block. This | |||||
// should be the case most of the time, but may not be immediately after | |||||
// the the sync thread catches up and sets m_synced. Consider the case | |||||
// where there is a reorg and the blocks on the stale branch are in the | |||||
// ValidationInterface queue backlog even after the sync thread has | |||||
// caught up to the new chain tip. In this unlikely event, log a warning | |||||
// and let the queue clear. | |||||
if (best_block_index->GetAncestor(pindex->nHeight - 1) != | |||||
pindex->pprev) { | |||||
LogPrintf("%s: WARNING: Block %s does not connect to an ancestor " | |||||
"of known best chain (tip=%s); not updating index\n", | |||||
__func__, pindex->GetBlockHash().ToString(), | |||||
best_block_index->GetBlockHash().ToString()); | |||||
return; | |||||
} | |||||
} | |||||
if (WriteBlock(*block, pindex)) { | |||||
m_best_block_index = pindex; | |||||
} else { | |||||
FatalError("%s: Failed to write block %s to index", __func__, | |||||
pindex->GetBlockHash().ToString()); | |||||
return; | |||||
} | |||||
} | |||||
void BaseIndex::ChainStateFlushed(const CBlockLocator &locator) { | |||||
if (!m_synced) { | |||||
return; | |||||
} | |||||
const uint256 &locator_tip_hash = locator.vHave.front(); | |||||
const CBlockIndex *locator_tip_index; | |||||
{ | |||||
LOCK(cs_main); | |||||
locator_tip_index = LookupBlockIndex(locator_tip_hash); | |||||
} | |||||
if (!locator_tip_index) { | |||||
FatalError("%s: First block (hash=%s) in locator was not found", | |||||
__func__, locator_tip_hash.ToString()); | |||||
return; | |||||
} | |||||
// This checks that ChainStateFlushed callbacks are received after | |||||
// BlockConnected. The check may fail immediately after the the sync thread | |||||
// catches up and sets m_synced. Consider the case where there is a reorg | |||||
// and the blocks on the stale branch are in the ValidationInterface queue | |||||
// backlog even after the sync thread has caught up to the new chain tip. In | |||||
// this unlikely event, log a warning and let the queue clear. | |||||
const CBlockIndex *best_block_index = m_best_block_index.load(); | |||||
if (best_block_index->GetAncestor(locator_tip_index->nHeight) != | |||||
locator_tip_index) { | |||||
LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known " | |||||
"best chain (tip=%s); not writing index locator\n", | |||||
__func__, locator_tip_hash.ToString(), | |||||
best_block_index->GetBlockHash().ToString()); | |||||
return; | |||||
} | |||||
if (!GetDB().WriteBestBlock(locator)) { | |||||
error("%s: Failed to write locator to disk", __func__); | |||||
} | |||||
} | |||||
bool BaseIndex::BlockUntilSyncedToCurrentChain() { | |||||
AssertLockNotHeld(cs_main); | |||||
if (!m_synced) { | |||||
return false; | |||||
} | |||||
{ | |||||
// Skip the queue-draining stuff if we know we're caught up with | |||||
// chainActive.Tip(). | |||||
LOCK(cs_main); | |||||
const CBlockIndex *chain_tip = chainActive.Tip(); | |||||
const CBlockIndex *best_block_index = m_best_block_index.load(); | |||||
if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) { | |||||
return true; | |||||
} | |||||
} | |||||
LogPrintf("%s: %s is catching up on block notifications\n", __func__, | |||||
GetName()); | |||||
SyncWithValidationInterfaceQueue(); | |||||
return true; | |||||
} | |||||
bool TxIndex::FindTx(const uint256 &tx_hash, uint256 &block_hash, | bool TxIndex::FindTx(const uint256 &tx_hash, uint256 &block_hash, | ||||
CTransactionRef &tx) const { | CTransactionRef &tx) const { | ||||
CDiskTxPos postx; | CDiskTxPos postx; | ||||
if (!m_db->ReadTxPos(tx_hash, postx)) { | if (!m_db->ReadTxPos(tx_hash, postx)) { | ||||
return false; | return false; | ||||
} | } | ||||
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); | CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION); | ||||
Show All 9 Lines | try { | ||||
return error("%s: Deserialize or I/O error - %s", __func__, e.what()); | return error("%s: Deserialize or I/O error - %s", __func__, e.what()); | ||||
} | } | ||||
if (tx->GetHash() != tx_hash) { | if (tx->GetHash() != tx_hash) { | ||||
return error("%s: txid mismatch", __func__); | return error("%s: txid mismatch", __func__); | ||||
} | } | ||||
block_hash = header.GetHash(); | block_hash = header.GetHash(); | ||||
return true; | return true; | ||||
} | } | ||||
void BaseIndex::Interrupt() { | |||||
m_interrupt(); | |||||
} | |||||
void BaseIndex::Start() { | |||||
// Need to register this ValidationInterface before running Init(), so that | |||||
// callbacks are not missed if Init sets m_synced to true. | |||||
RegisterValidationInterface(this); | |||||
if (!Init()) { | |||||
FatalError("%s: %s failed to initialize", __func__, GetName()); | |||||
return; | |||||
} | |||||
m_thread_sync = std::thread(&TraceThread<std::function<void()>>, GetName(), | |||||
std::bind(&BaseIndex::ThreadSync, this)); | |||||
} | |||||
void BaseIndex::Stop() { | |||||
UnregisterValidationInterface(this); | |||||
if (m_thread_sync.joinable()) { | |||||
m_thread_sync.join(); | |||||
} | |||||
} |