mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-11 04:12:36 -03:00
04265ba937
ed52e71176
Periodically check disk space to avoid corruption (Aurèle Oulès)7fe537f7a4
Implement CCoinsViewErrorCatcher::HaveCoin (Aurèle Oulès) Pull request description: Attempt to fix #26112. As suggested by sipa in https://github.com/bitcoin/bitcoin/issues/26112#issuecomment-1249683401: > CCoinsViewErrorCatcher, the wrapper class used around CCoinsViewDB that's supposed to detect these problems and forcefully exit the application, has an override for GetCoins. But in CheckTxInputs, HaveInputs is first invoked, which on its turn calls HaveCoin. HaveCoin is implemented in CCoinsViewDB, but not in CCoinsViewErrorCatcher, and thus the disk read exception escapes. > A solution may be to just add an override for HaveCoin in CCoinsViewErrorCatcher. I implemented `CCoinsViewErrorCatcher::HaveCoin` and also added a periodic disk space check that shutdowns the node if there is not enough space left on disk, the minimum here is 50MB. For reviewers, it's possible to saturate disk space to test the PR by creating large files with `fallocate -l 50G test.bin` ACKs for top commit: achow101: ACKed52e71176
w0xlt: Code Review ACKed52e71176
sipa: utACKed52e71176
Tree-SHA512: 456aa7b996023df42b4fbb5158ee429d9abf7374b7b1ec129b21aea1188ad19be8da4ae8e0edd90b85b7a3042b8e44e17d3742e33808a4234d5ddbe9bcef1b78
394 lines
14 KiB
C++
394 lines
14 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.
|
|
|
|
#ifndef BITCOIN_COINS_H
|
|
#define BITCOIN_COINS_H
|
|
|
|
#include <compressor.h>
|
|
#include <core_memusage.h>
|
|
#include <memusage.h>
|
|
#include <primitives/transaction.h>
|
|
#include <serialize.h>
|
|
#include <support/allocators/pool.h>
|
|
#include <uint256.h>
|
|
#include <util/hasher.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
|
|
#include <functional>
|
|
#include <unordered_map>
|
|
|
|
/**
|
|
* A UTXO entry.
|
|
*
|
|
* Serialized format:
|
|
* - VARINT((coinbase ? 1 : 0) | (height << 1))
|
|
* - the non-spent CTxOut (via TxOutCompression)
|
|
*/
|
|
class Coin
|
|
{
|
|
public:
|
|
//! unspent transaction output
|
|
CTxOut out;
|
|
|
|
//! whether containing transaction was a coinbase
|
|
unsigned int fCoinBase : 1;
|
|
|
|
//! at which height this containing transaction was included in the active block chain
|
|
uint32_t nHeight : 31;
|
|
|
|
//! construct a Coin from a CTxOut and height/coinbase information.
|
|
Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
|
|
Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}
|
|
|
|
void Clear() {
|
|
out.SetNull();
|
|
fCoinBase = false;
|
|
nHeight = 0;
|
|
}
|
|
|
|
//! empty constructor
|
|
Coin() : fCoinBase(false), nHeight(0) { }
|
|
|
|
bool IsCoinBase() const {
|
|
return fCoinBase;
|
|
}
|
|
|
|
template<typename Stream>
|
|
void Serialize(Stream &s) const {
|
|
assert(!IsSpent());
|
|
uint32_t code = nHeight * uint32_t{2} + fCoinBase;
|
|
::Serialize(s, VARINT(code));
|
|
::Serialize(s, Using<TxOutCompression>(out));
|
|
}
|
|
|
|
template<typename Stream>
|
|
void Unserialize(Stream &s) {
|
|
uint32_t code = 0;
|
|
::Unserialize(s, VARINT(code));
|
|
nHeight = code >> 1;
|
|
fCoinBase = code & 1;
|
|
::Unserialize(s, Using<TxOutCompression>(out));
|
|
}
|
|
|
|
/** Either this coin never existed (see e.g. coinEmpty in coins.cpp), or it
|
|
* did exist and has been spent.
|
|
*/
|
|
bool IsSpent() const {
|
|
return out.IsNull();
|
|
}
|
|
|
|
size_t DynamicMemoryUsage() const {
|
|
return memusage::DynamicUsage(out.scriptPubKey);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A Coin in one level of the coins database caching hierarchy.
|
|
*
|
|
* A coin can either be:
|
|
* - unspent or spent (in which case the Coin object will be nulled out - see Coin.Clear())
|
|
* - DIRTY or not DIRTY
|
|
* - FRESH or not FRESH
|
|
*
|
|
* Out of these 2^3 = 8 states, only some combinations are valid:
|
|
* - unspent, FRESH, DIRTY (e.g. a new coin created in the cache)
|
|
* - unspent, not FRESH, DIRTY (e.g. a coin changed in the cache during a reorg)
|
|
* - unspent, not FRESH, not DIRTY (e.g. an unspent coin fetched from the parent cache)
|
|
* - spent, FRESH, not DIRTY (e.g. a spent coin fetched from the parent cache)
|
|
* - spent, not FRESH, DIRTY (e.g. a coin is spent and spentness needs to be flushed to the parent)
|
|
*/
|
|
struct CCoinsCacheEntry
|
|
{
|
|
Coin coin; // The actual cached data.
|
|
unsigned char flags;
|
|
|
|
enum Flags {
|
|
/**
|
|
* DIRTY means the CCoinsCacheEntry is potentially different from the
|
|
* version in the parent cache. Failure to mark a coin as DIRTY when
|
|
* it is potentially different from the parent cache will cause a
|
|
* consensus failure, since the coin's state won't get written to the
|
|
* parent when the cache is flushed.
|
|
*/
|
|
DIRTY = (1 << 0),
|
|
/**
|
|
* FRESH means the parent cache does not have this coin or that it is a
|
|
* spent coin in the parent cache. If a FRESH coin in the cache is
|
|
* later spent, it can be deleted entirely and doesn't ever need to be
|
|
* flushed to the parent. This is a performance optimization. Marking a
|
|
* coin as FRESH when it exists unspent in the parent cache will cause a
|
|
* consensus failure, since it might not be deleted from the parent
|
|
* when this cache is flushed.
|
|
*/
|
|
FRESH = (1 << 1),
|
|
};
|
|
|
|
CCoinsCacheEntry() : flags(0) {}
|
|
explicit CCoinsCacheEntry(Coin&& coin_) : coin(std::move(coin_)), flags(0) {}
|
|
CCoinsCacheEntry(Coin&& coin_, unsigned char flag) : coin(std::move(coin_)), flags(flag) {}
|
|
};
|
|
|
|
/**
|
|
* PoolAllocator's MAX_BLOCK_SIZE_BYTES parameter here uses sizeof the data, and adds the size
|
|
* of 4 pointers. We do not know the exact node size used in the std::unordered_node implementation
|
|
* because it is implementation defined. Most implementations have an overhead of 1 or 2 pointers,
|
|
* so nodes can be connected in a linked list, and in some cases the hash value is stored as well.
|
|
* Using an additional sizeof(void*)*4 for MAX_BLOCK_SIZE_BYTES should thus be sufficient so that
|
|
* all implementations can allocate the nodes from the PoolAllocator.
|
|
*/
|
|
using CCoinsMap = std::unordered_map<COutPoint,
|
|
CCoinsCacheEntry,
|
|
SaltedOutpointHasher,
|
|
std::equal_to<COutPoint>,
|
|
PoolAllocator<std::pair<const COutPoint, CCoinsCacheEntry>,
|
|
sizeof(std::pair<const COutPoint, CCoinsCacheEntry>) + sizeof(void*) * 4,
|
|
alignof(void*)>>;
|
|
|
|
using CCoinsMapMemoryResource = CCoinsMap::allocator_type::ResourceType;
|
|
|
|
/** Cursor for iterating over CoinsView state */
|
|
class CCoinsViewCursor
|
|
{
|
|
public:
|
|
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
|
|
virtual ~CCoinsViewCursor() {}
|
|
|
|
virtual bool GetKey(COutPoint &key) const = 0;
|
|
virtual bool GetValue(Coin &coin) const = 0;
|
|
|
|
virtual bool Valid() const = 0;
|
|
virtual void Next() = 0;
|
|
|
|
//! Get best block at the time this cursor was created
|
|
const uint256 &GetBestBlock() const { return hashBlock; }
|
|
private:
|
|
uint256 hashBlock;
|
|
};
|
|
|
|
/** Abstract view on the open txout dataset. */
|
|
class CCoinsView
|
|
{
|
|
public:
|
|
/** Retrieve the Coin (unspent transaction output) for a given outpoint.
|
|
* Returns true only when an unspent coin was found, which is returned in coin.
|
|
* When false is returned, coin's value is unspecified.
|
|
*/
|
|
virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
|
|
|
|
//! Just check whether a given outpoint is unspent.
|
|
virtual bool HaveCoin(const COutPoint &outpoint) const;
|
|
|
|
//! Retrieve the block hash whose state this CCoinsView currently represents
|
|
virtual uint256 GetBestBlock() const;
|
|
|
|
//! Retrieve the range of blocks that may have been only partially written.
|
|
//! If the database is in a consistent state, the result is the empty vector.
|
|
//! Otherwise, a two-element vector is returned consisting of the new and
|
|
//! the old block hash, in that order.
|
|
virtual std::vector<uint256> GetHeadBlocks() const;
|
|
|
|
//! Do a bulk modification (multiple Coin changes + BestBlock change).
|
|
//! The passed mapCoins can be modified.
|
|
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true);
|
|
|
|
//! Get a cursor to iterate over the whole state
|
|
virtual std::unique_ptr<CCoinsViewCursor> Cursor() const;
|
|
|
|
//! As we use CCoinsViews polymorphically, have a virtual destructor
|
|
virtual ~CCoinsView() {}
|
|
|
|
//! Estimate database size (0 if not implemented)
|
|
virtual size_t EstimateSize() const { return 0; }
|
|
};
|
|
|
|
|
|
/** CCoinsView backed by another CCoinsView */
|
|
class CCoinsViewBacked : public CCoinsView
|
|
{
|
|
protected:
|
|
CCoinsView *base;
|
|
|
|
public:
|
|
CCoinsViewBacked(CCoinsView *viewIn);
|
|
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
|
uint256 GetBestBlock() const override;
|
|
std::vector<uint256> GetHeadBlocks() const override;
|
|
void SetBackend(CCoinsView &viewIn);
|
|
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
|
|
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
|
|
size_t EstimateSize() const override;
|
|
};
|
|
|
|
|
|
/** CCoinsView that adds a memory cache for transactions to another CCoinsView */
|
|
class CCoinsViewCache : public CCoinsViewBacked
|
|
{
|
|
private:
|
|
const bool m_deterministic;
|
|
|
|
protected:
|
|
/**
|
|
* Make mutable so that we can "fill the cache" even from Get-methods
|
|
* declared as "const".
|
|
*/
|
|
mutable uint256 hashBlock;
|
|
mutable CCoinsMapMemoryResource m_cache_coins_memory_resource{};
|
|
mutable CCoinsMap cacheCoins;
|
|
|
|
/* Cached dynamic memory usage for the inner Coin objects. */
|
|
mutable size_t cachedCoinsUsage{0};
|
|
|
|
public:
|
|
CCoinsViewCache(CCoinsView *baseIn, bool deterministic = false);
|
|
|
|
/**
|
|
* By deleting the copy constructor, we prevent accidentally using it when one intends to create a cache on top of a base cache.
|
|
*/
|
|
CCoinsViewCache(const CCoinsViewCache &) = delete;
|
|
|
|
// Standard CCoinsView methods
|
|
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
|
uint256 GetBestBlock() const override;
|
|
void SetBestBlock(const uint256 &hashBlock);
|
|
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
|
|
std::unique_ptr<CCoinsViewCursor> Cursor() const override {
|
|
throw std::logic_error("CCoinsViewCache cursor iteration not supported.");
|
|
}
|
|
|
|
/**
|
|
* Check if we have the given utxo already loaded in this cache.
|
|
* The semantics are the same as HaveCoin(), but no calls to
|
|
* the backing CCoinsView are made.
|
|
*/
|
|
bool HaveCoinInCache(const COutPoint &outpoint) const;
|
|
|
|
/**
|
|
* Return a reference to Coin in the cache, or coinEmpty if not found. This is
|
|
* more efficient than GetCoin.
|
|
*
|
|
* Generally, do not hold the reference returned for more than a short scope.
|
|
* While the current implementation allows for modifications to the contents
|
|
* of the cache while holding the reference, this behavior should not be relied
|
|
* on! To be safe, best to not hold the returned reference through any other
|
|
* calls to this cache.
|
|
*/
|
|
const Coin& AccessCoin(const COutPoint &output) const;
|
|
|
|
/**
|
|
* Add a coin. Set possible_overwrite to true if an unspent version may
|
|
* already exist in the cache.
|
|
*/
|
|
void AddCoin(const COutPoint& outpoint, Coin&& coin, bool possible_overwrite);
|
|
|
|
/**
|
|
* Emplace a coin into cacheCoins without performing any checks, marking
|
|
* the emplaced coin as dirty.
|
|
*
|
|
* NOT FOR GENERAL USE. Used only when loading coins from a UTXO snapshot.
|
|
* @sa ChainstateManager::PopulateAndValidateSnapshot()
|
|
*/
|
|
void EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin);
|
|
|
|
/**
|
|
* Spend a coin. Pass moveto in order to get the deleted data.
|
|
* If no unspent output exists for the passed outpoint, this call
|
|
* has no effect.
|
|
*/
|
|
bool SpendCoin(const COutPoint &outpoint, Coin* moveto = nullptr);
|
|
|
|
/**
|
|
* Push the modifications applied to this cache to its base and wipe local state.
|
|
* Failure to call this method or Sync() before destruction will cause the changes
|
|
* to be forgotten.
|
|
* If false is returned, the state of this cache (and its backing view) will be undefined.
|
|
*/
|
|
bool Flush();
|
|
|
|
/**
|
|
* Push the modifications applied to this cache to its base while retaining
|
|
* the contents of this cache (except for spent coins, which we erase).
|
|
* Failure to call this method or Flush() before destruction will cause the changes
|
|
* to be forgotten.
|
|
* If false is returned, the state of this cache (and its backing view) will be undefined.
|
|
*/
|
|
bool Sync();
|
|
|
|
/**
|
|
* Removes the UTXO with the given outpoint from the cache, if it is
|
|
* not modified.
|
|
*/
|
|
void Uncache(const COutPoint &outpoint);
|
|
|
|
//! Calculate the size of the cache (in number of transaction outputs)
|
|
unsigned int GetCacheSize() const;
|
|
|
|
//! Calculate the size of the cache (in bytes)
|
|
size_t DynamicMemoryUsage() const;
|
|
|
|
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
|
|
bool HaveInputs(const CTransaction& tx) const;
|
|
|
|
//! Force a reallocation of the cache map. This is required when downsizing
|
|
//! the cache because the map's allocator may be hanging onto a lot of
|
|
//! memory despite having called .clear().
|
|
//!
|
|
//! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory
|
|
void ReallocateCache();
|
|
|
|
//! Run an internal sanity check on the cache data structure. */
|
|
void SanityCheck() const;
|
|
|
|
private:
|
|
/**
|
|
* @note this is marked const, but may actually append to `cacheCoins`, increasing
|
|
* memory usage.
|
|
*/
|
|
CCoinsMap::iterator FetchCoin(const COutPoint &outpoint) const;
|
|
};
|
|
|
|
//! Utility function to add all of a transaction's outputs to a cache.
|
|
//! When check is false, this assumes that overwrites are only possible for coinbase transactions.
|
|
//! When check is true, the underlying view may be queried to determine whether an addition is
|
|
//! an overwrite.
|
|
// TODO: pass in a boolean to limit these possible overwrites to known
|
|
// (pre-BIP34) cases.
|
|
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool check = false);
|
|
|
|
//! Utility function to find any unspent output with a given txid.
|
|
//! This function can be quite expensive because in the event of a transaction
|
|
//! which is not found in the cache, it can cause up to MAX_OUTPUTS_PER_BLOCK
|
|
//! lookups to database, so it should be used with care.
|
|
const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);
|
|
|
|
/**
|
|
* This is a minimally invasive approach to shutdown on LevelDB read errors from the
|
|
* chainstate, while keeping user interface out of the common library, which is shared
|
|
* between bitcoind, and bitcoin-qt and non-server tools.
|
|
*
|
|
* Writes do not need similar protection, as failure to write is handled by the caller.
|
|
*/
|
|
class CCoinsViewErrorCatcher final : public CCoinsViewBacked
|
|
{
|
|
public:
|
|
explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
|
|
|
|
void AddReadErrCallback(std::function<void()> f) {
|
|
m_err_callbacks.emplace_back(std::move(f));
|
|
}
|
|
|
|
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
|
|
|
private:
|
|
/** A list of callbacks to execute upon leveldb read error. */
|
|
std::vector<std::function<void()>> m_err_callbacks;
|
|
|
|
};
|
|
|
|
#endif // BITCOIN_COINS_H
|