mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-12-18 00:23:38 +01:00
743abbcbderefactor: inline constant return value of `BlockTreeDB::WriteBatchSync` and `BlockManager::WriteBlockIndexDB` and `BlockTreeDB::WriteFlag` (Lőrinc)e030240e90refactor: inline constant return value of `CDBWrapper::Erase` and `BlockTreeDB::WriteReindexing` (Lőrinc)cdab9480e9refactor: inline constant return value of `CDBWrapper::Write` (Lőrinc)d1847cf5b5refactor: inline constant return value of `TxIndex::DB::WriteTxs` (Lőrinc)50b63a5698refactor: inline constant return value of `CDBWrapper::WriteBatch` (Lőrinc) Pull request description: Related to https://github.com/bitcoin/bitcoin/pull/31144#discussion_r2223587480 ### Summary `WriteBatch` always returns `true` - the errors are handled by throwing `dbwrapper_error` instead. ### Context This boolean return value of the `Write` methods is confusing because it's inconsistent with `CDBWrapper::Read`, which catches exceptions and returns a boolean to indicate success/failure. It's bad that `Read` returns and `Write` throws - but it's a lot worse that `Write` advertises a return value when it actually communicates errors through exceptions. ### Solution This PR removes the constant return values from write methods and inlines `true` at their call sites. Many upstream methods had boolean return values only because they were propagating these constants - those have been cleaned up as well. Methods that returned a constant `true` value that now return `void`: - `CDBWrapper::WriteBatch`, `CDBWrapper::Write`, `CDBWrapper::Erase` - `TxIndex::DB::WriteTxs` - `BlockTreeDB::WriteReindexing`, `BlockTreeDB::WriteBatchSync`, `BlockTreeDB::WriteFlag` - `BlockManager::WriteBlockIndexDB` ### Note `CCoinsView::BatchWrite` (and transitively `CCoinsViewCache::Flush` & `CCoinsViewCache::Sync`) were intentionally not changed here. While all implementations return `true`, the base `CCoinsView::BatchWrite` returns `false`. Changing this would cause `coins_view` tests to fail with: > terminating due to uncaught exception of type std::logic_error: Not all unspent flagged entries were cleared We can fix that in a follow-up PR. ACKs for top commit: achow101: ACK743abbcbdejanb84: ACK743abbcbdeTheCharlatan: ACK743abbcbdesipa: ACK743abbcbdeTree-SHA512: b2a550bff066216f1958d2dd9a7ef6a9949de518cc636f8ab9c670e0b7a330c1eb8c838e458a8629acb8ac980cea6616955cd84436a7b8ab9096f6d648073b1e
234 lines
7.4 KiB
C++
234 lines
7.4 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2022 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 <txdb.h>
|
|
|
|
#include <coins.h>
|
|
#include <dbwrapper.h>
|
|
#include <logging.h>
|
|
#include <primitives/transaction.h>
|
|
#include <random.h>
|
|
#include <serialize.h>
|
|
#include <uint256.h>
|
|
#include <util/vector.h>
|
|
|
|
#include <cassert>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <utility>
|
|
|
|
static constexpr uint8_t DB_COIN{'C'};
|
|
static constexpr uint8_t DB_BEST_BLOCK{'B'};
|
|
static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
|
|
// Keys used in previous version that might still be found in the DB:
|
|
static constexpr uint8_t DB_COINS{'c'};
|
|
|
|
bool CCoinsViewDB::NeedsUpgrade()
|
|
{
|
|
std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
|
|
// DB_COINS was deprecated in v0.15.0, commit
|
|
// 1088b02f0ccd7358d2b7076bb9e122d59d502d02
|
|
cursor->Seek(std::make_pair(DB_COINS, uint256{}));
|
|
return cursor->Valid();
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct CoinEntry {
|
|
COutPoint* outpoint;
|
|
uint8_t key{DB_COIN};
|
|
explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)) {}
|
|
|
|
SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
CCoinsViewDB::CCoinsViewDB(DBParams db_params, CoinsViewOptions options) :
|
|
m_db_params{std::move(db_params)},
|
|
m_options{std::move(options)},
|
|
m_db{std::make_unique<CDBWrapper>(m_db_params)} { }
|
|
|
|
void CCoinsViewDB::ResizeCache(size_t new_cache_size)
|
|
{
|
|
// We can't do this operation with an in-memory DB since we'll lose all the coins upon
|
|
// reset.
|
|
if (!m_db_params.memory_only) {
|
|
// Have to do a reset first to get the original `m_db` state to release its
|
|
// filesystem lock.
|
|
m_db.reset();
|
|
m_db_params.cache_bytes = new_cache_size;
|
|
m_db_params.wipe_data = false;
|
|
m_db = std::make_unique<CDBWrapper>(m_db_params);
|
|
}
|
|
}
|
|
|
|
std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
|
|
{
|
|
if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin;
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
|
|
return m_db->Exists(CoinEntry(&outpoint));
|
|
}
|
|
|
|
uint256 CCoinsViewDB::GetBestBlock() const {
|
|
uint256 hashBestChain;
|
|
if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
|
|
return uint256();
|
|
return hashBestChain;
|
|
}
|
|
|
|
std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
|
|
std::vector<uint256> vhashHeadBlocks;
|
|
if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
|
|
return std::vector<uint256>();
|
|
}
|
|
return vhashHeadBlocks;
|
|
}
|
|
|
|
bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) {
|
|
CDBBatch batch(*m_db);
|
|
size_t count = 0;
|
|
size_t changed = 0;
|
|
assert(!hashBlock.IsNull());
|
|
|
|
uint256 old_tip = GetBestBlock();
|
|
if (old_tip.IsNull()) {
|
|
// We may be in the middle of replaying.
|
|
std::vector<uint256> old_heads = GetHeadBlocks();
|
|
if (old_heads.size() == 2) {
|
|
if (old_heads[0] != hashBlock) {
|
|
LogPrintLevel(BCLog::COINDB, BCLog::Level::Error, "The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart bitcoind with the -reindex-chainstate or -reindex configuration option.\n");
|
|
}
|
|
assert(old_heads[0] == hashBlock);
|
|
old_tip = old_heads[1];
|
|
}
|
|
}
|
|
|
|
// In the first batch, mark the database as being in the middle of a
|
|
// transition from old_tip to hashBlock.
|
|
// A vector is used for future extensibility, as we may want to support
|
|
// interrupting after partial writes from multiple independent reorgs.
|
|
batch.Erase(DB_BEST_BLOCK);
|
|
batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
|
|
|
|
for (auto it{cursor.Begin()}; it != cursor.End();) {
|
|
if (it->second.IsDirty()) {
|
|
CoinEntry entry(&it->first);
|
|
if (it->second.coin.IsSpent()) {
|
|
batch.Erase(entry);
|
|
} else {
|
|
batch.Write(entry, it->second.coin);
|
|
}
|
|
|
|
changed++;
|
|
}
|
|
count++;
|
|
it = cursor.NextAndMaybeErase(*it);
|
|
if (batch.ApproximateSize() > m_options.batch_write_bytes) {
|
|
LogDebug(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0));
|
|
|
|
m_db->WriteBatch(batch);
|
|
batch.Clear();
|
|
if (m_options.simulate_crash_ratio) {
|
|
static FastRandomContext rng;
|
|
if (rng.randrange(m_options.simulate_crash_ratio) == 0) {
|
|
LogPrintf("Simulating a crash. Goodbye.\n");
|
|
_Exit(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// In the last batch, mark the database as consistent with hashBlock again.
|
|
batch.Erase(DB_HEAD_BLOCKS);
|
|
batch.Write(DB_BEST_BLOCK, hashBlock);
|
|
|
|
LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.ApproximateSize() * (1.0 / 1048576.0));
|
|
m_db->WriteBatch(batch);
|
|
LogDebug(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
|
|
return true;
|
|
}
|
|
|
|
size_t CCoinsViewDB::EstimateSize() const
|
|
{
|
|
return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
|
|
}
|
|
|
|
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
|
|
class CCoinsViewDBCursor: public CCoinsViewCursor
|
|
{
|
|
public:
|
|
// Prefer using CCoinsViewDB::Cursor() since we want to perform some
|
|
// cache warmup on instantiation.
|
|
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
|
|
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
|
|
~CCoinsViewDBCursor() = default;
|
|
|
|
bool GetKey(COutPoint &key) const override;
|
|
bool GetValue(Coin &coin) const override;
|
|
|
|
bool Valid() const override;
|
|
void Next() override;
|
|
|
|
private:
|
|
std::unique_ptr<CDBIterator> pcursor;
|
|
std::pair<char, COutPoint> keyTmp;
|
|
|
|
friend class CCoinsViewDB;
|
|
};
|
|
|
|
std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
|
|
{
|
|
auto i = std::make_unique<CCoinsViewDBCursor>(
|
|
const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
|
|
/* It seems that there are no "const iterators" for LevelDB. Since we
|
|
only need read operations on it, use a const-cast to get around
|
|
that restriction. */
|
|
i->pcursor->Seek(DB_COIN);
|
|
// Cache key of first record
|
|
if (i->pcursor->Valid()) {
|
|
CoinEntry entry(&i->keyTmp.second);
|
|
i->pcursor->GetKey(entry);
|
|
i->keyTmp.first = entry.key;
|
|
} else {
|
|
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
|
|
}
|
|
return i;
|
|
}
|
|
|
|
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
|
|
{
|
|
// Return cached key
|
|
if (keyTmp.first == DB_COIN) {
|
|
key = keyTmp.second;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CCoinsViewDBCursor::GetValue(Coin &coin) const
|
|
{
|
|
return pcursor->GetValue(coin);
|
|
}
|
|
|
|
bool CCoinsViewDBCursor::Valid() const
|
|
{
|
|
return keyTmp.first == DB_COIN;
|
|
}
|
|
|
|
void CCoinsViewDBCursor::Next()
|
|
{
|
|
pcursor->Next();
|
|
CoinEntry entry(&keyTmp.second);
|
|
if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
|
|
keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
|
|
} else {
|
|
keyTmp.first = entry.key;
|
|
}
|
|
}
|