mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-02-02 11:41:18 +00:00
7a799c9c2b8652e780d1fd5e1bf7d05b026c1c1a index: refactor-only: Reuse CChain ref (Carl Dong) db33cde80fff749c6adff9e91fca5f27f4bb6278 index: Add chainstate member to BaseIndex (Carl Dong) f4a47a1febfa35ab077f2a841fe31a8cd9618250 bench: Use existing chainman in AssembleBlock (Carl Dong) 91226eb91769aad5a63bc671595e1353a2b2247a bench: Use existing NodeContext in DuplicateInputs (Carl Dong) e6b4aa6eb53dc555ecab2922af35e7a2572faf4f miner: Pass in chainman to RegenerateCommitments (Carl Dong) 9ecade14252ad1972f668d2d2e4ef44fdfcb944a rest: Add GetChainman function and use it (Carl Dong) fc1c282845f6b8436d1ea4c68eb3511034c29bea rpc/blockchain: Use existing blockman in gettxoutsetinfo (Carl Dong) Pull request description: Overall PR: #20158 (tree-wide: De-globalize ChainstateManager) The first 2 commits are fixups addressing review for the last bundle: #21391 NEW note: 1. I have opened #21766 which keeps track of potential improvements where the flaws already existed before the de-globalization work, please post on that issue about these improvements, thanks! Note to reviewers: 1. This bundle may _apparently_ introduce usage of `g_chainman` or `::Chain(state|)Active()` globals, but these are resolved later on in the overall PR. [Commits of overall PR](https://github.com/bitcoin/bitcoin/pull/20158/commits) 2. There may be seemingly obvious local references to `ChainstateManager` or other validation objects which are not being used in callers of the current function in question, this is done intentionally to **_keep each commit centered around one function/method_** to ease review and to make the overall change systematic. We don't assume anything about our callers. Rest assured that once we are considering that particular caller in later commits, we will use the obvious local references. [Commits of overall PR](https://github.com/bitcoin/bitcoin/pull/20158/commits) 3. When changing a function/method that has many callers (e.g. `LookupBlockIndex` with 55 callers), it is sometimes easier (and less error-prone) to use a scripted-diff. When doing so, there will be 3 commits in sequence so that every commit compiles like so: 1. Add `new_function`, make `old_function` a wrapper of `new_function`, divert all calls to `old_function` to `new_function` **in the local module only** 2. Scripted-diff to divert all calls to `old_function` to `new_function` **in the rest of the codebase** 3. Remove `old_function` ACKs for top commit: jarolrod: ACK 7a799c9 ariard: Code Review ACK 7a799c9 fjahr: re-ACK 7a799c9c2b8652e780d1fd5e1bf7d05b026c1c1a MarcoFalke: review ACK 7a799c9c2b8652e780d1fd5e1bf7d05b026c1c1a 🌠 ryanofsky: Code review ACK 7a799c9c2b8652e780d1fd5e1bf7d05b026c1c1a. Basically no change since last review except fixed rebase conflicts and a new comment about REST Ensure() jamesob: conditional ACK 7a799c9c2b8652e780d1fd5e1bf7d05b026c1c1a ([`jamesob/ackr/21767.1.dongcarl.bundle_6_n_prune_g_chai`](https://github.com/jamesob/bitcoin/tree/ackr/21767.1.dongcarl.bundle_6_n_prune_g_chai)) Tree-SHA512: 531c00ddcb318817457db2812d9a9d930bc664e58e6f7f1c746350732b031dd624270bfa6b9f49d8056aeb6321d973f0e38e4ff914acd6768edd8602c017d10e
258 lines
9.4 KiB
C++
258 lines
9.4 KiB
C++
// Copyright (c) 2017-2020 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <index/disktxpos.h>
|
|
#include <index/txindex.h>
|
|
#include <node/blockstorage.h>
|
|
#include <node/ui_interface.h>
|
|
#include <shutdown.h>
|
|
#include <util/system.h>
|
|
#include <util/translation.h>
|
|
#include <validation.h>
|
|
|
|
constexpr uint8_t DB_BEST_BLOCK{'B'};
|
|
constexpr uint8_t DB_TXINDEX{'t'};
|
|
constexpr uint8_t DB_TXINDEX_BLOCK{'T'};
|
|
|
|
std::unique_ptr<TxIndex> g_txindex;
|
|
|
|
|
|
/** Access to the txindex database (indexes/txindex/) */
|
|
class TxIndex::DB : public BaseIndex::DB
|
|
{
|
|
public:
|
|
explicit DB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
|
|
|
|
/// Read the disk location of the transaction data with the given hash. Returns false if the
|
|
/// transaction hash is not indexed.
|
|
bool ReadTxPos(const uint256& txid, CDiskTxPos& pos) const;
|
|
|
|
/// Write a batch of transaction positions to the DB.
|
|
bool WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_pos);
|
|
|
|
/// Migrate txindex data from the block tree DB, where it may be for older nodes that have not
|
|
/// been upgraded yet to the new database.
|
|
bool MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& best_locator);
|
|
};
|
|
|
|
TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
|
|
BaseIndex::DB(gArgs.GetDataDirNet() / "indexes" / "txindex", n_cache_size, f_memory, f_wipe)
|
|
{}
|
|
|
|
bool TxIndex::DB::ReadTxPos(const uint256 &txid, CDiskTxPos& pos) const
|
|
{
|
|
return Read(std::make_pair(DB_TXINDEX, txid), pos);
|
|
}
|
|
|
|
bool TxIndex::DB::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);
|
|
}
|
|
|
|
/*
|
|
* 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(CDBWrapper& newdb, CDBWrapper& olddb,
|
|
CDBBatch& batch_newdb, CDBBatch& batch_olddb,
|
|
const std::pair<uint8_t, uint256>& begin_key,
|
|
const std::pair<uint8_t, 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 TxIndex::DB::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.ShowProgress(_("Upgrading txindex database").translated, 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<uint8_t, uint256> key;
|
|
std::pair<uint8_t, uint256> begin_key{DB_TXINDEX, uint256()};
|
|
std::pair<uint8_t, 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()) {
|
|
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").translated, 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;
|
|
}
|
|
|
|
TxIndex::TxIndex(size_t n_cache_size, bool f_memory, bool f_wipe)
|
|
: m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
|
|
{}
|
|
|
|
TxIndex::~TxIndex() {}
|
|
|
|
bool TxIndex::Init()
|
|
{
|
|
LOCK(cs_main);
|
|
|
|
// 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
|
|
// delete txindex records in the old database.
|
|
if (!m_db->MigrateData(*pblocktree, m_chainstate->m_chain.GetLocator())) {
|
|
return false;
|
|
}
|
|
|
|
return BaseIndex::Init();
|
|
}
|
|
|
|
bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
|
|
{
|
|
// Exclude genesis block transaction because outputs are not spendable.
|
|
if (pindex->nHeight == 0) return true;
|
|
|
|
CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
|
|
std::vector<std::pair<uint256, CDiskTxPos>> vPos;
|
|
vPos.reserve(block.vtx.size());
|
|
for (const auto& tx : block.vtx) {
|
|
vPos.emplace_back(tx->GetHash(), pos);
|
|
pos.nTxOffset += ::GetSerializeSize(*tx, CLIENT_VERSION);
|
|
}
|
|
return m_db->WriteTxs(vPos);
|
|
}
|
|
|
|
BaseIndex::DB& TxIndex::GetDB() const { return *m_db; }
|
|
|
|
bool TxIndex::FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRef& tx) const
|
|
{
|
|
CDiskTxPos postx;
|
|
if (!m_db->ReadTxPos(tx_hash, postx)) {
|
|
return false;
|
|
}
|
|
|
|
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
|
|
if (file.IsNull()) {
|
|
return error("%s: OpenBlockFile failed", __func__);
|
|
}
|
|
CBlockHeader header;
|
|
try {
|
|
file >> header;
|
|
if (fseek(file.Get(), postx.nTxOffset, SEEK_CUR)) {
|
|
return error("%s: fseek(...) failed", __func__);
|
|
}
|
|
file >> tx;
|
|
} catch (const std::exception& e) {
|
|
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
|
|
}
|
|
if (tx->GetHash() != tx_hash) {
|
|
return error("%s: txid mismatch", __func__);
|
|
}
|
|
block_hash = header.GetHash();
|
|
return true;
|
|
}
|