Changeset View
Changeset View
Standalone View
Standalone View
src/txdb.cpp
Show All 15 Lines | |||||
#include <boost/thread.hpp> // boost::this_thread::interruption_point() (mingw) | #include <boost/thread.hpp> // boost::this_thread::interruption_point() (mingw) | ||||
#include <cstdint> | #include <cstdint> | ||||
static const char DB_COIN = 'C'; | static const char DB_COIN = 'C'; | ||||
static const char DB_COINS = 'c'; | static const char DB_COINS = 'c'; | ||||
static const char DB_BLOCK_FILES = 'f'; | static const char DB_BLOCK_FILES = 'f'; | ||||
static const char DB_TXINDEX = 't'; | |||||
static const char DB_TXINDEX_BLOCK = 'T'; | |||||
static const char DB_BLOCK_INDEX = 'b'; | static const char DB_BLOCK_INDEX = 'b'; | ||||
static const char DB_BEST_BLOCK = 'B'; | static const char DB_BEST_BLOCK = 'B'; | ||||
static const char DB_HEAD_BLOCKS = 'H'; | static const char DB_HEAD_BLOCKS = 'H'; | ||||
static const char DB_FLAG = 'F'; | static const char DB_FLAG = 'F'; | ||||
static const char DB_REINDEX_FLAG = 'R'; | static const char DB_REINDEX_FLAG = 'R'; | ||||
static const char DB_LAST_BLOCK = 'l'; | static const char DB_LAST_BLOCK = 'l'; | ||||
▲ Show 20 Lines • Show All 404 Lines • ▼ Show 20 Lines | bool CCoinsViewDB::Upgrade() { | ||||
} | } | ||||
db.WriteBatch(batch); | db.WriteBatch(batch); | ||||
db.CompactRange({DB_COINS, uint256()}, key); | db.CompactRange({DB_COINS, uint256()}, key); | ||||
uiInterface.ShowProgress("", 100, false); | uiInterface.ShowProgress("", 100, false); | ||||
LogPrintf("[%s].\n", ShutdownRequested() ? "CANCELLED" : "DONE"); | LogPrintf("[%s].\n", ShutdownRequested() ? "CANCELLED" : "DONE"); | ||||
return !ShutdownRequested(); | return !ShutdownRequested(); | ||||
} | } | ||||
BaseIndexDB::BaseIndexDB(const fs::path &path, size_t n_cache_size, | |||||
bool f_memory, bool f_wipe, bool f_obfuscate) | |||||
: CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) {} | |||||
TxIndexDB::TxIndexDB(size_t n_cache_size, bool f_memory, bool f_wipe) | |||||
: BaseIndexDB(GetDataDir() / "indexes" / "txindex", n_cache_size, f_memory, | |||||
f_wipe) {} | |||||
bool TxIndexDB::ReadTxPos(const uint256 &txid, CDiskTxPos &pos) const { | |||||
return Read(std::make_pair(DB_TXINDEX, txid), pos); | |||||
} | |||||
bool TxIndexDB::WriteTxs( | |||||
const std::vector<std::pair<uint256, CDiskTxPos>> &v_pos) { | |||||
CDBBatch batch(*this); | |||||
for (const auto &tuple : v_pos) { | |||||
batch.Write(std::make_pair(DB_TXINDEX, tuple.first), tuple.second); | |||||
} | |||||
return WriteBatch(batch); | |||||
} | |||||
bool BaseIndexDB::ReadBestBlock(CBlockLocator &locator) const { | |||||
bool success = Read(DB_BEST_BLOCK, locator); | |||||
if (!success) { | |||||
locator.SetNull(); | |||||
} | |||||
return success; | |||||
} | |||||
bool BaseIndexDB::WriteBestBlock(const CBlockLocator &locator) { | |||||
return Write(DB_BEST_BLOCK, locator); | |||||
} | |||||
/* | |||||
* Safely persist a transfer of data from the old txindex database to the new | |||||
* one, and compact the range of keys updated. This is used internally by | |||||
* MigrateData. | |||||
*/ | |||||
static void | |||||
WriteTxIndexMigrationBatches(TxIndexDB &newdb, CBlockTreeDB &olddb, | |||||
CDBBatch &batch_newdb, CDBBatch &batch_olddb, | |||||
const std::pair<unsigned char, uint256> &begin_key, | |||||
const std::pair<unsigned char, uint256> &end_key) { | |||||
// Sync new DB changes to disk before deleting from old DB. | |||||
newdb.WriteBatch(batch_newdb, /*fSync=*/true); | |||||
olddb.WriteBatch(batch_olddb); | |||||
olddb.CompactRange(begin_key, end_key); | |||||
batch_newdb.Clear(); | |||||
batch_olddb.Clear(); | |||||
} | |||||
bool TxIndexDB::MigrateData(CBlockTreeDB &block_tree_db, | |||||
const CBlockLocator &best_locator) { | |||||
// The prior implementation of txindex was always in sync with block index | |||||
// and presence was indicated with a boolean DB flag. If the flag is set, | |||||
// this means the txindex from a previous version is valid and in sync with | |||||
// the chain tip. The first step of the migration is to unset the flag and | |||||
// write the chain hash to a separate key, DB_TXINDEX_BLOCK. After that, the | |||||
// index entries are copied over in batches to the new database. Finally, | |||||
// DB_TXINDEX_BLOCK is erased from the old database and the block hash is | |||||
// written to the new database. | |||||
// | |||||
// Unsetting the boolean flag ensures that if the node is downgraded to a | |||||
// previous version, it will not see a corrupted, partially migrated index | |||||
// -- it will see that the txindex is disabled. When the node is upgraded | |||||
// again, the migration will pick up where it left off and sync to the block | |||||
// with hash DB_TXINDEX_BLOCK. | |||||
bool f_legacy_flag = false; | |||||
block_tree_db.ReadFlag("txindex", f_legacy_flag); | |||||
if (f_legacy_flag) { | |||||
if (!block_tree_db.Write(DB_TXINDEX_BLOCK, best_locator)) { | |||||
return error("%s: cannot write block indicator", __func__); | |||||
} | |||||
if (!block_tree_db.WriteFlag("txindex", false)) { | |||||
return error("%s: cannot write block index db flag", __func__); | |||||
} | |||||
} | |||||
CBlockLocator locator; | |||||
if (!block_tree_db.Read(DB_TXINDEX_BLOCK, locator)) { | |||||
return true; | |||||
} | |||||
int64_t count = 0; | |||||
LogPrintf("Upgrading txindex database... [0%%]\n"); | |||||
uiInterface.InitMessage(_("Upgrading txindex database")); | |||||
uiInterface.ShowProgress(_("Upgrading txindex database"), 0, true); | |||||
int report_done = 0; | |||||
const size_t batch_size = 1 << 24; // 16 MiB | |||||
CDBBatch batch_newdb(*this); | |||||
CDBBatch batch_olddb(block_tree_db); | |||||
std::pair<unsigned char, uint256> key; | |||||
std::pair<unsigned char, uint256> begin_key{DB_TXINDEX, uint256()}; | |||||
std::pair<unsigned char, uint256> prev_key = begin_key; | |||||
bool interrupted = false; | |||||
std::unique_ptr<CDBIterator> cursor(block_tree_db.NewIterator()); | |||||
for (cursor->Seek(begin_key); cursor->Valid(); cursor->Next()) { | |||||
boost::this_thread::interruption_point(); | |||||
if (ShutdownRequested()) { | |||||
interrupted = true; | |||||
break; | |||||
} | |||||
if (!cursor->GetKey(key)) { | |||||
return error("%s: cannot get key from valid cursor", __func__); | |||||
} | |||||
if (key.first != DB_TXINDEX) { | |||||
break; | |||||
} | |||||
// Log progress every 10%. | |||||
if (++count % 256 == 0) { | |||||
// Since txids are uniformly random and traversed in increasing | |||||
// order, the high 16 bits of the hash can be used to estimate the | |||||
// current progress. | |||||
const uint256 &txid = key.second; | |||||
uint32_t high_nibble = | |||||
(static_cast<uint32_t>(*(txid.begin() + 0)) << 8) + | |||||
(static_cast<uint32_t>(*(txid.begin() + 1)) << 0); | |||||
int percentage_done = (int)(high_nibble * 100.0 / 65536.0 + 0.5); | |||||
uiInterface.ShowProgress(_("Upgrading txindex database"), | |||||
percentage_done, true); | |||||
if (report_done < percentage_done / 10) { | |||||
LogPrintf("Upgrading txindex database... [%d%%]\n", | |||||
percentage_done); | |||||
report_done = percentage_done / 10; | |||||
} | |||||
} | |||||
CDiskTxPos value; | |||||
if (!cursor->GetValue(value)) { | |||||
return error("%s: cannot parse txindex record", __func__); | |||||
} | |||||
batch_newdb.Write(key, value); | |||||
batch_olddb.Erase(key); | |||||
if (batch_newdb.SizeEstimate() > batch_size || | |||||
batch_olddb.SizeEstimate() > batch_size) { | |||||
// NOTE: it's OK to delete the key pointed at by the current DB | |||||
// cursor while iterating because LevelDB iterators are guaranteed | |||||
// to provide a consistent view of the underlying data, like a | |||||
// lightweight snapshot. | |||||
WriteTxIndexMigrationBatches(*this, block_tree_db, batch_newdb, | |||||
batch_olddb, prev_key, key); | |||||
prev_key = key; | |||||
} | |||||
} | |||||
// If these final DB batches complete the migration, write the best block | |||||
// hash marker to the new database and delete from the old one. This signals | |||||
// that the former is fully caught up to that point in the blockchain and | |||||
// that all txindex entries have been removed from the latter. | |||||
if (!interrupted) { | |||||
batch_olddb.Erase(DB_TXINDEX_BLOCK); | |||||
batch_newdb.Write(DB_BEST_BLOCK, locator); | |||||
} | |||||
WriteTxIndexMigrationBatches(*this, block_tree_db, batch_newdb, batch_olddb, | |||||
begin_key, key); | |||||
if (interrupted) { | |||||
LogPrintf("[CANCELLED].\n"); | |||||
return false; | |||||
} | |||||
uiInterface.ShowProgress("", 100, false); | |||||
LogPrintf("[DONE].\n"); | |||||
return true; | |||||
} |