Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F10615282
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
111 KB
Subscribers
None
View Options
diff --git a/src/coins.cpp b/src/coins.cpp
index 6a684ba19..858ecc23c 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -1,358 +1,392 @@
// Copyright (c) 2012-2016 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 <coins.h>
#include <consensus/consensus.h>
#include <logging.h>
#include <random.h>
#include <util/trace.h>
#include <version.h>
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const {
return false;
}
BlockHash CCoinsView::GetBestBlock() const {
return BlockHash();
}
std::vector<BlockHash> CCoinsView::GetHeadBlocks() const {
return std::vector<BlockHash>();
}
-bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) {
+bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase) {
return false;
}
CCoinsViewCursor *CCoinsView::Cursor() const {
return nullptr;
}
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const {
Coin coin;
return GetCoin(outpoint, coin);
}
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) {}
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const {
return base->GetCoin(outpoint, coin);
}
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const {
return base->HaveCoin(outpoint);
}
BlockHash CCoinsViewBacked::GetBestBlock() const {
return base->GetBestBlock();
}
std::vector<BlockHash> CCoinsViewBacked::GetHeadBlocks() const {
return base->GetHeadBlocks();
}
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) {
base = &viewIn;
}
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins,
- const BlockHash &hashBlock) {
- return base->BatchWrite(mapCoins, hashBlock);
+ const BlockHash &hashBlock, bool erase) {
+ return base->BatchWrite(mapCoins, hashBlock, erase);
}
CCoinsViewCursor *CCoinsViewBacked::Cursor() const {
return base->Cursor();
}
size_t CCoinsViewBacked::EstimateSize() const {
return base->EstimateSize();
}
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn)
: CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
}
CCoinsMap::iterator
CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end()) {
return it;
}
Coin tmp;
if (!base->GetCoin(outpoint, tmp)) {
return cacheCoins.end();
}
CCoinsMap::iterator ret =
cacheCoins
.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint),
std::forward_as_tuple(std::move(tmp)))
.first;
if (ret->second.coin.IsSpent()) {
// The parent only has an empty entry for this outpoint; we can consider
// our version as fresh.
ret->second.flags = CCoinsCacheEntry::FRESH;
}
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
return ret;
}
bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
CCoinsMap::const_iterator it = FetchCoin(outpoint);
if (it == cacheCoins.end()) {
return false;
}
coin = it->second.coin;
return !coin.IsSpent();
}
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin coin,
bool possible_overwrite) {
assert(!coin.IsSpent());
if (coin.GetTxOut().scriptPubKey.IsUnspendable()) {
return;
}
CCoinsMap::iterator it;
bool inserted;
std::tie(it, inserted) =
cacheCoins.emplace(std::piecewise_construct,
std::forward_as_tuple(outpoint), std::tuple<>());
bool fresh = false;
if (!inserted) {
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
}
if (!possible_overwrite) {
if (!it->second.coin.IsSpent()) {
throw std::logic_error("Attempted to overwrite an unspent coin "
"(when possible_overwrite is false)");
}
// If the coin exists in this cache as a spent coin and is DIRTY, then
// its spentness hasn't been flushed to the parent cache. We're
// re-adding the coin to this cache now but we can't mark it as FRESH.
// If we mark it FRESH and then spend it before the cache is flushed
// we would remove it from this cache and would never flush spentness
// to the parent cache.
//
// Re-adding a spent coin can happen in the case of a re-org (the coin
// is 'spent' when the block adding it is disconnected and then
// re-added when it is also added in a newly connected block).
//
// If the coin doesn't exist in the current cache, or is spent but not
// DIRTY, then it can be marked FRESH.
fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
}
it->second.coin = std::move(coin);
it->second.flags |=
CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
TRACE5(utxocache, add, outpoint.GetTxId().data(), outpoint.GetN(),
coin.GetHeight(), coin.GetTxOut().nValue.ToString().c_str(),
coin.IsCoinBase());
}
void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint &&outpoint,
Coin &&coin) {
cachedCoinsUsage += coin.DynamicMemoryUsage();
cacheCoins.emplace(
std::piecewise_construct, std::forward_as_tuple(std::move(outpoint)),
std::forward_as_tuple(std::move(coin), CCoinsCacheEntry::DIRTY));
}
void AddCoins(CCoinsViewCache &cache, const CTransaction &tx, int nHeight,
bool check_for_overwrite) {
bool fCoinbase = tx.IsCoinBase();
const TxId txid = tx.GetId();
for (size_t i = 0; i < tx.vout.size(); ++i) {
const COutPoint outpoint(txid, i);
bool overwrite =
check_for_overwrite ? cache.HaveCoin(outpoint) : fCoinbase;
// Coinbase transactions can always be overwritten,
// in order to correctly deal with the pre-BIP30 occurrences of
// duplicate coinbase transactions.
cache.AddCoin(outpoint, Coin(tx.vout[i], nHeight, fCoinbase),
overwrite);
}
}
bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin *moveout) {
CCoinsMap::iterator it = FetchCoin(outpoint);
if (it == cacheCoins.end()) {
return false;
}
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
TRACE5(utxocache, spent, outpoint.GetTxId().data(), outpoint.GetN(),
it->second.coin.GetHeight(),
it->second.coin.GetTxOut().nValue.ToString().c_str(),
it->second.coin.IsCoinBase());
if (moveout) {
*moveout = std::move(it->second.coin);
}
if (it->second.flags & CCoinsCacheEntry::FRESH) {
cacheCoins.erase(it);
} else {
it->second.flags |= CCoinsCacheEntry::DIRTY;
it->second.coin.Clear();
}
return true;
}
static const Coin coinEmpty;
const Coin &CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoin(outpoint);
if (it == cacheCoins.end()) {
return coinEmpty;
}
return it->second.coin;
}
bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoin(outpoint);
return it != cacheCoins.end() && !it->second.coin.IsSpent();
}
bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
}
BlockHash CCoinsViewCache::GetBestBlock() const {
if (hashBlock.IsNull()) {
hashBlock = base->GetBestBlock();
}
return hashBlock;
}
void CCoinsViewCache::SetBestBlock(const BlockHash &hashBlockIn) {
hashBlock = hashBlockIn;
}
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
- const BlockHash &hashBlockIn) {
+ const BlockHash &hashBlockIn, bool erase) {
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();
- it = mapCoins.erase(it)) {
+ it = erase ? mapCoins.erase(it) : std::next(it)) {
// Ignore non-dirty entries (optimization).
if (!(it->second.flags & CCoinsCacheEntry::DIRTY)) {
continue;
}
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
if (itUs == cacheCoins.end()) {
// The parent cache does not have an entry, while the child cache
// does. We can ignore it if it's both spent and FRESH in the child
if (!(it->second.flags & CCoinsCacheEntry::FRESH &&
it->second.coin.IsSpent())) {
// Create the coin in the parent cache, move the data up
// and mark it as dirty.
CCoinsCacheEntry &entry = cacheCoins[it->first];
- entry.coin = std::move(it->second.coin);
+ if (erase) {
+ // The `move` call here is purely an optimization; we rely
+ // on the `mapCoins.erase` call in the `for` expression to
+ // actually remove the entry from the child map.
+ entry.coin = std::move(it->second.coin);
+ } else {
+ entry.coin = it->second.coin;
+ }
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
entry.flags = CCoinsCacheEntry::DIRTY;
// We can mark it FRESH in the parent if it was FRESH in the
// child. Otherwise it might have just been flushed from the
// parent's cache and already exist in the grandparent
if (it->second.flags & CCoinsCacheEntry::FRESH) {
entry.flags |= CCoinsCacheEntry::FRESH;
}
}
} else {
// Found the entry in the parent cache
if ((it->second.flags & CCoinsCacheEntry::FRESH) &&
!itUs->second.coin.IsSpent()) {
// The coin was marked FRESH in the child cache, but the coin
// exists in the parent cache. If this ever happens, it means
// the FRESH flag was misapplied and there is a logic error in
// the calling code.
throw std::logic_error("FRESH flag misapplied to coin that "
"exists in parent cache");
}
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) &&
it->second.coin.IsSpent()) {
// The grandparent cache does not have an entry, and the coin
// has been spent. We can just delete it from the parent cache.
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
cacheCoins.erase(itUs);
} else {
// A normal modification.
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
- itUs->second.coin = std::move(it->second.coin);
+ if (erase) {
+ // The `move` call here is purely an optimization; we rely
+ // on the `mapCoins.erase` call in the `for` expression to
+ // actually remove the entry from the child map.
+ itUs->second.coin = std::move(it->second.coin);
+ } else {
+ itUs->second.coin = it->second.coin;
+ }
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
// NOTE: It isn't safe to mark the coin as FRESH in the parent
// cache. If it already existed and was spent in the parent
// cache then marking it FRESH would prevent that spentness
// from being flushed to the grandparent.
}
}
}
hashBlock = hashBlockIn;
return true;
}
bool CCoinsViewCache::Flush() {
- bool fOk = base->BatchWrite(cacheCoins, hashBlock);
- cacheCoins.clear();
+ bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/true);
+ if (fOk && !cacheCoins.empty()) {
+ /* BatchWrite must erase all cacheCoins elements when erase=true. */
+ throw std::logic_error("Not all cached coins were erased");
+ }
cachedCoinsUsage = 0;
return fOk;
}
+bool CCoinsViewCache::Sync() {
+ bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/false);
+ // Instead of clearing `cacheCoins` as we would in Flush(), just clear the
+ // FRESH/DIRTY flags of any coin that isn't spent.
+ for (auto it = cacheCoins.begin(); it != cacheCoins.end();) {
+ if (it->second.coin.IsSpent()) {
+ cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
+ it = cacheCoins.erase(it);
+ } else {
+ it->second.flags = 0;
+ ++it;
+ }
+ }
+ return fOk;
+}
+
void CCoinsViewCache::Uncache(const COutPoint &outpoint) {
CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end() && it->second.flags == 0) {
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
TRACE5(utxocache, uncache, outpoint.GetTxId().data(), outpoint.GetN(),
it->second.coin.GetHeight(),
it->second.coin.GetTxOut().nValue.ToString().c_str(),
it->second.coin.IsCoinBase());
cacheCoins.erase(it);
}
}
unsigned int CCoinsViewCache::GetCacheSize() const {
return cacheCoins.size();
}
bool CCoinsViewCache::HaveInputs(const CTransaction &tx) const {
if (tx.IsCoinBase()) {
return true;
}
for (size_t i = 0; i < tx.vin.size(); i++) {
if (!HaveCoin(tx.vin[i].prevout)) {
return false;
}
}
return true;
}
void CCoinsViewCache::ReallocateCache() {
// Cache should be empty when we're calling this.
assert(cacheCoins.size() == 0);
cacheCoins.~CCoinsMap();
::new (&cacheCoins) CCoinsMap();
}
// TODO: merge with similar definition in undo.h.
static const size_t MAX_OUTPUTS_PER_TX =
MAX_TX_SIZE / ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION);
const Coin &AccessByTxid(const CCoinsViewCache &view, const TxId &txid) {
for (uint32_t n = 0; n < MAX_OUTPUTS_PER_TX; n++) {
const Coin &alternate = view.AccessCoin(COutPoint(txid, n));
if (!alternate.IsSpent()) {
return alternate;
}
}
return coinEmpty;
}
bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint,
Coin &coin) const {
try {
return CCoinsViewBacked::GetCoin(outpoint, coin);
} catch (const std::runtime_error &e) {
for (auto f : m_err_callbacks) {
f();
}
LogPrintf("Error reading from database: %s\n", e.what());
// Starting the shutdown sequence and returning false to the caller
// would be interpreted as 'entry not found' (as opposed to unable to
// read data), and could lead to invalid interpretation. Just exit
// immediately, as we can't continue anyway, and all writes should be
// atomic.
std::abort();
}
}
diff --git a/src/coins.h b/src/coins.h
index 2d6de4a88..563c253bd 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -1,357 +1,369 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 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 <memusage.h>
#include <primitives/blockhash.h>
#include <serialize.h>
#include <util/hasher.h>
#include <cassert>
#include <cstdint>
#include <functional>
#include <unordered_map>
/**
* A UTXO entry.
*
* Serialized format:
* - VARINT((coinbase ? 1 : 0) | (height << 1))
* - the non-spent CTxOut (via TxOutCompression)
*/
class Coin {
//! Unspent transaction output.
CTxOut out;
//! Whether containing transaction was a coinbase and height at which the
//! transaction was included into a block.
uint32_t nHeightAndIsCoinBase;
public:
//! Empty constructor
Coin() : nHeightAndIsCoinBase(0) {}
//! Constructor from a CTxOut and height/coinbase information.
Coin(CTxOut outIn, uint32_t nHeightIn, bool IsCoinbase)
: out(std::move(outIn)),
nHeightAndIsCoinBase((nHeightIn << 1) | IsCoinbase) {}
uint32_t GetHeight() const { return nHeightAndIsCoinBase >> 1; }
bool IsCoinBase() const { return nHeightAndIsCoinBase & 0x01; }
bool IsSpent() const { return out.IsNull(); }
CTxOut &GetTxOut() { return out; }
const CTxOut &GetTxOut() const { return out; }
void Clear() {
out.SetNull();
nHeightAndIsCoinBase = 0;
}
template <typename Stream> void Serialize(Stream &s) const {
assert(!IsSpent());
::Serialize(s, VARINT(nHeightAndIsCoinBase));
::Serialize(s, Using<TxOutCompression>(out));
}
template <typename Stream> void Unserialize(Stream &s) {
::Unserialize(s, VARINT(nHeightAndIsCoinBase));
::Unserialize(s, Using<TxOutCompression>(out));
}
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 {
// The actual cached data.
Coin coin;
uint8_t 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 coinIn)
: coin(std::move(coinIn)), flags(0) {}
CCoinsCacheEntry(Coin &&coin_, uint8_t flag)
: coin(std::move(coin_)), flags(flag) {}
};
typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher>
CCoinsMap;
/** Cursor for iterating over CoinsView state */
class CCoinsViewCursor {
public:
CCoinsViewCursor(const BlockHash &hashBlockIn) : hashBlock(hashBlockIn) {}
virtual ~CCoinsViewCursor() {}
virtual bool GetKey(COutPoint &key) const = 0;
virtual bool GetValue(Coin &coin) const = 0;
virtual unsigned int GetValueSize() const = 0;
virtual bool Valid() const = 0;
virtual void Next() = 0;
//! Get best block at the time this cursor was created
const BlockHash &GetBestBlock() const { return hashBlock; }
private:
BlockHash 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 BlockHash 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<BlockHash> GetHeadBlocks() const;
//! Do a bulk modification (multiple Coin changes + BestBlock change).
//! The passed mapCoins can be modified.
- virtual bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock);
+ virtual bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase = true);
//! Get a cursor to iterate over the whole state
virtual 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;
BlockHash GetBestBlock() const override;
std::vector<BlockHash> GetHeadBlocks() const override;
void SetBackend(CCoinsView &viewIn);
- bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override;
+ bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase = true) override;
CCoinsViewCursor *Cursor() const override;
size_t EstimateSize() const override;
};
/**
* CCoinsView that adds a memory cache for transactions to another CCoinsView
*/
class CCoinsViewCache : public CCoinsViewBacked {
protected:
/**
* Make mutable so that we can "fill the cache" even from Get-methods
* declared as "const".
*/
mutable BlockHash hashBlock;
mutable CCoinsMap cacheCoins;
/* Cached dynamic memory usage for the inner Coin objects. */
mutable size_t cachedCoinsUsage;
public:
CCoinsViewCache(CCoinsView *baseIn);
/**
* 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;
BlockHash GetBestBlock() const override;
void SetBestBlock(const BlockHash &hashBlock);
- bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override;
+ bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase = true) override;
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.
- * Failure to call this method 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.
+ * 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();
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 TxId &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;
private:
/**
* A list of callbacks to execute upon leveldb read error.
*/
std::vector<std::function<void()>> m_err_callbacks;
};
#endif // BITCOIN_COINS_H
diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp
index 1224689e9..7118a46af 100644
--- a/src/test/coins_tests.cpp
+++ b/src/test/coins_tests.cpp
@@ -1,950 +1,1165 @@
// Copyright (c) 2014-2019 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 <coins.h>
#include <clientversion.h>
#include <script/standard.h>
#include <streams.h>
#include <txdb.h>
#include <undo.h>
#include <util/strencodings.h>
#include <validation.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
#include <map>
#include <vector>
namespace {
//! equality test
bool operator==(const Coin &a, const Coin &b) {
// Empty Coin objects are always equal.
if (a.IsSpent() && b.IsSpent()) {
return true;
}
return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() &&
a.GetTxOut() == b.GetTxOut();
}
class CCoinsViewTest : public CCoinsView {
BlockHash hashBestBlock_;
std::map<COutPoint, Coin> map_;
public:
[[nodiscard]] bool GetCoin(const COutPoint &outpoint,
Coin &coin) const override {
std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
if (it == map_.end()) {
return false;
}
coin = it->second;
if (coin.IsSpent() && InsecureRandBool() == 0) {
// Randomly return false in case of an empty entry.
return false;
}
return true;
}
BlockHash GetBestBlock() const override { return hashBestBlock_; }
- bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override {
- for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
+ bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase = true) override {
+ for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();
+ it = erase ? mapCoins.erase(it) : std::next(it)) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
// Same optimization used in CCoinsViewDB is to only write dirty
// entries.
map_[it->first] = it->second.coin;
if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
// Randomly delete empty entries on write.
map_.erase(it->first);
}
}
- mapCoins.erase(it++);
}
if (!hashBlock.IsNull()) {
hashBestBlock_ = hashBlock;
}
return true;
}
};
class CCoinsViewCacheTest : public CCoinsViewCache {
public:
explicit CCoinsViewCacheTest(CCoinsView *_base) : CCoinsViewCache(_base) {}
void SelfTest() const {
// Manually recompute the dynamic usage of the whole data, and compare
// it.
size_t ret = memusage::DynamicUsage(cacheCoins);
size_t count = 0;
for (const auto &entry : cacheCoins) {
ret += entry.second.coin.DynamicMemoryUsage();
count++;
}
BOOST_CHECK_EQUAL(GetCacheSize(), count);
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
}
CCoinsMap &map() const { return cacheCoins; }
size_t &usage() const { return cachedCoinsUsage; }
};
} // namespace
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
// This is a large randomized insert/remove simulation test on a variable-size
// stack of caches on top of CCoinsViewTest.
//
// It will randomly create/update/delete Coin entries to a tip of caches, with
// txids picked from a limited list of random 256-bit hashes. Occasionally, a
// new tip is added to the stack of caches, or the tip is flushed and removed.
//
// During the process, booleans are kept to make sure that the randomized
// operation hits all branches.
//
// If fake_best_block is true, assign a random BlockHash to mock the recording
// of best block on flush. This is necessary when using CCoinsViewDB as the
// base, otherwise we'll hit an assertion in BatchWrite.
//
void SimulationTest(CCoinsView *base, bool fake_best_block) {
// Various coverage trackers.
bool removed_all_caches = false;
bool reached_4_caches = false;
bool added_an_entry = false;
bool added_an_unspendable_entry = false;
bool removed_an_entry = false;
bool updated_an_entry = false;
bool found_an_entry = false;
bool missed_an_entry = false;
bool uncached_an_entry = false;
+ bool flushed_without_erase = false;
// A simple map to track what we expect the cache stack to represent.
std::map<COutPoint, Coin> result;
// The cache stack.
// A stack of CCoinsViewCaches on top.
std::vector<CCoinsViewCacheTest *> stack;
// Start with one cache.
stack.push_back(new CCoinsViewCacheTest(base));
// Use a limited set of random transaction ids, so we do test overwriting
// entries.
std::vector<TxId> txids;
txids.resize(NUM_SIMULATION_ITERATIONS / 8);
for (size_t i = 0; i < txids.size(); i++) {
txids[i] = TxId(InsecureRand256());
}
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
// Do a random modification.
{
// txid we're going to modify in this iteration.
const TxId txid = txids[InsecureRandRange(txids.size())];
Coin &coin = result[COutPoint(txid, 0)];
// Determine whether to test HaveCoin before or after Access* (or
// both). As these functions can influence each other's behaviour by
// pulling things into the cache, all combinations are tested.
bool test_havecoin_before = InsecureRandBits(2) == 0;
bool test_havecoin_after = InsecureRandBits(2) == 0;
bool result_havecoin =
test_havecoin_before
? stack.back()->HaveCoin(COutPoint(txid, 0))
: false;
// Infrequently, test usage of AccessByTxid instead of AccessCoin -
// the former just delegates to the latter and returns the first
// unspent in a txn.
const Coin &entry =
(InsecureRandRange(500) == 0)
? AccessByTxid(*stack.back(), txid)
: stack.back()->AccessCoin(COutPoint(txid, 0));
BOOST_CHECK(coin == entry);
if (test_havecoin_before) {
BOOST_CHECK(result_havecoin == !entry.IsSpent());
}
if (test_havecoin_after) {
bool ret = stack.back()->HaveCoin(COutPoint(txid, 0));
BOOST_CHECK(ret == !entry.IsSpent());
}
if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
CTxOut txout;
txout.nValue = InsecureRandMoneyAmount();
// Infrequently test adding unspendable coins.
if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
txout.scriptPubKey.assign(1 + InsecureRandBits(6),
OP_RETURN);
BOOST_CHECK(txout.scriptPubKey.IsUnspendable());
added_an_unspendable_entry = true;
} else {
// Random sizes so we can test memory usage accounting
txout.scriptPubKey.assign(InsecureRandBits(6), 0);
(coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
coin = Coin(txout, 1, false);
}
Coin newcoin(txout, 1, false);
bool is_overwrite = !coin.IsSpent() || InsecureRand32() & 1;
stack.back()->AddCoin(COutPoint(txid, 0), newcoin,
is_overwrite);
} else {
// Spend the coin.
removed_an_entry = true;
coin.Clear();
BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0)));
}
}
// Once every 10 iterations, remove a random entry from the cache
if (InsecureRandRange(10) == 0) {
COutPoint out(txids[InsecureRand32() % txids.size()], 0);
int cacheid = InsecureRand32() % stack.size();
stack[cacheid]->Uncache(out);
uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
}
// Once every 1000 iterations and at the end, verify the full cache.
if (InsecureRandRange(1000) == 1 ||
i == NUM_SIMULATION_ITERATIONS - 1) {
for (const auto &entry : result) {
bool have = stack.back()->HaveCoin(entry.first);
const Coin &coin = stack.back()->AccessCoin(entry.first);
BOOST_CHECK(have == !coin.IsSpent());
BOOST_CHECK(coin == entry.second);
if (coin.IsSpent()) {
missed_an_entry = true;
} else {
BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
found_an_entry = true;
}
}
for (const CCoinsViewCacheTest *test : stack) {
test->SelfTest();
}
}
// Every 100 iterations, flush an intermediate cache
if (InsecureRandRange(100) == 0) {
if (stack.size() > 1 && InsecureRandBool() == 0) {
unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
if (fake_best_block) {
stack[flushIndex]->SetBestBlock(
BlockHash(InsecureRand256()));
}
- BOOST_CHECK(stack[flushIndex]->Flush());
+ bool should_erase = InsecureRandRange(4) < 3;
+ BOOST_CHECK(should_erase ? stack[flushIndex]->Flush()
+ : stack[flushIndex]->Sync());
+ flushed_without_erase |= !should_erase;
}
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && InsecureRandBool() == 0) {
// Remove the top cache
if (fake_best_block) {
stack.back()->SetBestBlock(BlockHash(InsecureRand256()));
}
- BOOST_CHECK(stack.back()->Flush());
+
+ bool should_erase = InsecureRandRange(4) < 3;
+ BOOST_CHECK(should_erase ? stack.back()->Flush()
+ : stack.back()->Sync());
+ flushed_without_erase |= !should_erase;
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
// Add a new cache
CCoinsView *tip = base;
if (stack.size() > 0) {
tip = stack.back();
} else {
removed_all_caches = true;
}
stack.push_back(new CCoinsViewCacheTest(tip));
if (stack.size() == 4) {
reached_4_caches = true;
}
}
}
}
// Clean up the stack.
while (stack.size() > 0) {
delete stack.back();
stack.pop_back();
}
// Verify coverage.
BOOST_CHECK(removed_all_caches);
BOOST_CHECK(reached_4_caches);
BOOST_CHECK(added_an_entry);
BOOST_CHECK(added_an_unspendable_entry);
BOOST_CHECK(removed_an_entry);
BOOST_CHECK(updated_an_entry);
BOOST_CHECK(found_an_entry);
BOOST_CHECK(missed_an_entry);
BOOST_CHECK(uncached_an_entry);
+ BOOST_CHECK(flushed_without_erase);
}
// Run the above simulation for multiple base types.
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) {
CCoinsViewTest base;
SimulationTest(&base, false);
CCoinsViewDB db_base{
{.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}};
SimulationTest(&db_base, true);
}
// Store of all necessary tx and undo data for next test
typedef std::map<COutPoint, std::tuple<CTransactionRef, CTxUndo, Coin>>
UtxoData;
UtxoData utxoData;
UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
assert(utxoSet.size());
auto utxoSetIt = utxoSet.lower_bound(COutPoint(TxId(InsecureRand256()), 0));
if (utxoSetIt == utxoSet.end()) {
utxoSetIt = utxoSet.begin();
}
auto utxoDataIt = utxoData.find(*utxoSetIt);
assert(utxoDataIt != utxoData.end());
return utxoDataIt;
}
// This test is similar to the previous test except the emphasis is on testing
// the functionality of UpdateCoins random txs are created and UpdateCoins is
// used to update the cache stack. In particular it is tested that spending a
// duplicate coinbase tx has the expected effect (the other duplicate is
// overwritten at all cache levels)
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) {
SeedInsecureRand(SeedRand::ZEROS);
g_mock_deterministic_tests = true;
bool spent_a_duplicate_coinbase = false;
// A simple map to track what we expect the cache stack to represent.
std::map<COutPoint, Coin> result;
// The cache stack.
// A CCoinsViewTest at the bottom.
CCoinsViewTest base;
// A stack of CCoinsViewCaches on top.
std::vector<CCoinsViewCacheTest *> stack;
// Start with one cache.
stack.push_back(new CCoinsViewCacheTest(&base));
// Track the txids we've used in various sets
std::set<COutPoint> coinbase_coins;
std::set<COutPoint> disconnected_coins;
std::set<COutPoint> duplicate_coins;
std::set<COutPoint> utxoset;
for (int64_t i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
uint32_t randiter = InsecureRand32();
// 19/20 txs add a new transaction
if (randiter % 20 < 19) {
CMutableTransaction tx;
tx.vin.resize(1);
tx.vout.resize(1);
// Keep txs unique unless intended to duplicate.
tx.vout[0].nValue = i * SATOSHI;
// Random sizes so we can test memory usage accounting
tx.vout[0].scriptPubKey.assign(InsecureRand32() & 0x3F, 0);
const int height{int(InsecureRand32()) >> 1};
Coin old_coin;
// 2/20 times create a new coinbase
if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
// 1/10 of those times create a duplicate coinbase
if (InsecureRandRange(10) == 0 && coinbase_coins.size()) {
auto utxod = FindRandomFrom(coinbase_coins);
// Reuse the exact same coinbase
tx = CMutableTransaction{*std::get<0>(utxod->second)};
// shouldn't be available for reconnection if it's been
// duplicated
disconnected_coins.erase(utxod->first);
duplicate_coins.insert(utxod->first);
} else {
coinbase_coins.insert(COutPoint(tx.GetId(), 0));
}
assert(CTransaction(tx).IsCoinBase());
}
// 17/20 times reconnect previous or add a regular tx
else {
COutPoint prevout;
// 1/20 times reconnect a previously disconnected tx
if (randiter % 20 == 2 && disconnected_coins.size()) {
auto utxod = FindRandomFrom(disconnected_coins);
tx = CMutableTransaction{*std::get<0>(utxod->second)};
prevout = tx.vin[0].prevout;
if (!CTransaction(tx).IsCoinBase() &&
!utxoset.count(prevout)) {
disconnected_coins.erase(utxod->first);
continue;
}
// If this tx is already IN the UTXO, then it must be a
// coinbase, and it must be a duplicate
if (utxoset.count(utxod->first)) {
assert(CTransaction(tx).IsCoinBase());
assert(duplicate_coins.count(utxod->first));
}
disconnected_coins.erase(utxod->first);
}
// 16/20 times create a regular tx
else {
auto utxod = FindRandomFrom(utxoset);
prevout = utxod->first;
// Construct the tx to spend the coins of prevouthash
tx.vin[0].prevout = COutPoint(prevout.GetTxId(), 0);
assert(!CTransaction(tx).IsCoinBase());
}
// In this simple test coins only have two states, spent or
// unspent, save the unspent state to restore
old_coin = result[prevout];
// Update the expected result of prevouthash to know these coins
// are spent
result[prevout].Clear();
utxoset.erase(prevout);
// The test is designed to ensure spending a duplicate coinbase
// will work properly if that ever happens and not resurrect the
// previously overwritten coinbase
if (duplicate_coins.count(prevout)) {
spent_a_duplicate_coinbase = true;
}
}
// Update the expected result to know about the new output coins
assert(tx.vout.size() == 1);
const COutPoint outpoint(tx.GetId(), 0);
result[outpoint] =
Coin(tx.vout[0], height, CTransaction{tx}.IsCoinBase());
// Call UpdateCoins on the top cache
CTxUndo undo;
UpdateCoins(*(stack.back()), CTransaction{tx}, undo, height);
// Update the utxo set for future spends
utxoset.insert(outpoint);
// Track this tx and undo info to use later
utxoData.emplace(outpoint, std::make_tuple(MakeTransactionRef(tx),
undo, old_coin));
}
// 1/20 times undo a previous transaction
else if (utxoset.size()) {
auto utxod = FindRandomFrom(utxoset);
const CTransactionRef &tx = std::get<0>(utxod->second);
CTxUndo &undo = std::get<1>(utxod->second);
Coin &orig_coin = std::get<2>(utxod->second);
// Update the expected result
// Remove new outputs
result[utxod->first].Clear();
// If not coinbase restore prevout
if (!tx->IsCoinBase()) {
result[tx->vin[0].prevout] = orig_coin;
}
// Disconnect the tx from the current UTXO
// See code in DisconnectBlock
// remove outputs
BOOST_CHECK(stack.back()->SpendCoin(utxod->first));
// restore inputs
if (!tx->IsCoinBase()) {
const COutPoint &out = tx->vin[0].prevout;
Coin coin = undo.vprevout[0];
UndoCoinSpend(std::move(coin), *(stack.back()), out);
}
// Store as a candidate for reconnection
disconnected_coins.insert(utxod->first);
// Update the utxoset
utxoset.erase(utxod->first);
if (!tx->IsCoinBase()) {
utxoset.insert(tx->vin[0].prevout);
}
}
// Once every 1000 iterations and at the end, verify the full cache.
if (InsecureRandRange(1000) == 1 ||
i == NUM_SIMULATION_ITERATIONS - 1) {
for (const auto &entry : result) {
bool have = stack.back()->HaveCoin(entry.first);
const Coin &coin = stack.back()->AccessCoin(entry.first);
BOOST_CHECK(have == !coin.IsSpent());
BOOST_CHECK(coin == entry.second);
}
}
// One every 10 iterations, remove a random entry from the cache
if (utxoset.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(utxoset)->first);
}
if (disconnected_coins.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(disconnected_coins)->first);
}
if (duplicate_coins.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(duplicate_coins)->first);
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, flush an intermediate cache
if (stack.size() > 1 && InsecureRandBool() == 0) {
unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
BOOST_CHECK(stack[flushIndex]->Flush());
}
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && InsecureRandBool() == 0) {
BOOST_CHECK(stack.back()->Flush());
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
CCoinsView *tip = &base;
if (stack.size() > 0) {
tip = stack.back();
}
stack.push_back(new CCoinsViewCacheTest(tip));
}
}
}
// Clean up the stack.
while (stack.size() > 0) {
delete stack.back();
stack.pop_back();
}
// Verify coverage.
BOOST_CHECK(spent_a_duplicate_coinbase);
g_mock_deterministic_tests = false;
}
BOOST_AUTO_TEST_CASE(coin_serialization) {
// Good example
CDataStream ss1(
ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"),
SER_DISK, CLIENT_VERSION);
Coin c1;
ss1 >> c1;
BOOST_CHECK_EQUAL(c1.IsCoinBase(), false);
BOOST_CHECK_EQUAL(c1.GetHeight(), 203998U);
BOOST_CHECK_EQUAL(c1.GetTxOut().nValue, int64_t(60000000000) * SATOSHI);
BOOST_CHECK_EQUAL(HexStr(c1.GetTxOut().scriptPubKey),
HexStr(GetScriptForDestination(PKHash(uint160(ParseHex(
"816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example
CDataStream ss2(
ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"),
SER_DISK, CLIENT_VERSION);
Coin c2;
ss2 >> c2;
BOOST_CHECK_EQUAL(c2.IsCoinBase(), true);
BOOST_CHECK_EQUAL(c2.GetHeight(), 120891U);
BOOST_CHECK_EQUAL(c2.GetTxOut().nValue, 110397 * SATOSHI);
BOOST_CHECK_EQUAL(HexStr(c2.GetTxOut().scriptPubKey),
HexStr(GetScriptForDestination(PKHash(uint160(ParseHex(
"8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
Coin c3;
ss3 >> c3;
BOOST_CHECK_EQUAL(c3.IsCoinBase(), false);
BOOST_CHECK_EQUAL(c3.GetHeight(), 0U);
BOOST_CHECK_EQUAL(c3.GetTxOut().nValue, Amount::zero());
BOOST_CHECK_EQUAL(c3.GetTxOut().scriptPubKey.size(), 0U);
// scriptPubKey that ends beyond the end of the stream
CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
try {
Coin c4;
ss4 >> c4;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure &) {
}
// Very large scriptPubKey (3*10^9 bytes) past the end of the stream
CDataStream tmp(SER_DISK, CLIENT_VERSION);
uint64_t x = 3000000000ULL;
tmp << VARINT(x);
BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");
CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
try {
Coin c5;
ss5 >> c5;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure &) {
}
}
static const COutPoint OUTPOINT;
static const Amount SPENT(-1 * SATOSHI);
static const Amount ABSENT(-2 * SATOSHI);
static const Amount FAIL(-3 * SATOSHI);
static const Amount VALUE1(100 * SATOSHI);
static const Amount VALUE2(200 * SATOSHI);
static const Amount VALUE3(300 * SATOSHI);
static const char DIRTY = CCoinsCacheEntry::DIRTY;
static const char FRESH = CCoinsCacheEntry::FRESH;
static const char NO_ENTRY = -1;
static const auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
static const auto CLEAN_FLAGS = {char(0), FRESH};
static const auto ABSENT_FLAGS = {NO_ENTRY};
static void SetCoinValue(const Amount value, Coin &coin) {
assert(value != ABSENT);
coin.Clear();
assert(coin.IsSpent());
if (value != SPENT) {
CTxOut out;
out.nValue = value;
coin = Coin(std::move(out), 1, false);
assert(!coin.IsSpent());
}
}
static size_t InsertCoinMapEntry(CCoinsMap &map, const Amount value,
char flags) {
if (value == ABSENT) {
assert(flags == NO_ENTRY);
return 0;
}
assert(flags != NO_ENTRY);
CCoinsCacheEntry entry;
entry.flags = flags;
SetCoinValue(value, entry.coin);
auto inserted = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second);
return inserted.first->second.coin.DynamicMemoryUsage();
}
-void GetCoinMapEntry(const CCoinsMap &map, Amount &value, char &flags) {
- auto it = map.find(OUTPOINT);
+void GetCoinMapEntry(const CCoinsMap &map, Amount &value, char &flags,
+ const COutPoint &outp = OUTPOINT) {
+ auto it = map.find(outp);
if (it == map.end()) {
value = ABSENT;
flags = NO_ENTRY;
} else {
if (it->second.coin.IsSpent()) {
value = SPENT;
} else {
value = it->second.coin.GetTxOut().nValue;
}
flags = it->second.flags;
assert(flags != NO_ENTRY);
}
}
void WriteCoinViewEntry(CCoinsView &view, const Amount value, char flags) {
CCoinsMap map;
InsertCoinMapEntry(map, value, flags);
BOOST_CHECK(view.BatchWrite(map, BlockHash()));
}
class SingleEntryCacheTest {
public:
SingleEntryCacheTest(const Amount base_value, const Amount cache_value,
char cache_flags) {
WriteCoinViewEntry(base, base_value,
base_value == ABSENT ? NO_ENTRY : DIRTY);
cache.usage() +=
InsertCoinMapEntry(cache.map(), cache_value, cache_flags);
}
CCoinsView root;
CCoinsViewCacheTest base{&root};
CCoinsViewCacheTest cache{&base};
};
static void CheckAccessCoin(const Amount base_value, const Amount cache_value,
const Amount expected_value, char cache_flags,
char expected_flags) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
test.cache.AccessCoin(OUTPOINT);
test.cache.SelfTest();
Amount result_value;
char result_flags;
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
BOOST_AUTO_TEST_CASE(coin_access) {
/* Check AccessCoin behavior, requesting a coin from a cache view layered on
* top of a base view, and checking the resulting entry in the cache after
* the access.
*
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/
CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckAccessCoin(ABSENT, SPENT, SPENT, 0, 0);
CheckAccessCoin(ABSENT, SPENT, SPENT, FRESH, FRESH);
CheckAccessCoin(ABSENT, SPENT, SPENT, DIRTY, DIRTY);
CheckAccessCoin(ABSENT, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(SPENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckAccessCoin(SPENT, SPENT, SPENT, 0, 0);
CheckAccessCoin(SPENT, SPENT, SPENT, FRESH, FRESH);
CheckAccessCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY);
CheckAccessCoin(SPENT, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(SPENT, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(SPENT, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY, 0);
CheckAccessCoin(VALUE1, SPENT, SPENT, 0, 0);
CheckAccessCoin(VALUE1, SPENT, SPENT, FRESH, FRESH);
CheckAccessCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY);
CheckAccessCoin(VALUE1, SPENT, SPENT, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
}
static void CheckSpendCoin(Amount base_value, Amount cache_value,
Amount expected_value, char cache_flags,
char expected_flags) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
test.cache.SpendCoin(OUTPOINT);
test.cache.SelfTest();
Amount result_value;
char result_flags;
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
};
BOOST_AUTO_TEST_CASE(coin_spend) {
/**
* Check SpendCoin behavior, requesting a coin from a cache view layered on
* top of a base view, spending, and then checking the resulting entry in
* the cache after the modification.
*
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/
CheckSpendCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckSpendCoin(ABSENT, SPENT, SPENT, 0, DIRTY);
CheckSpendCoin(ABSENT, SPENT, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, SPENT, SPENT, DIRTY, DIRTY);
CheckSpendCoin(ABSENT, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, VALUE2, SPENT, 0, DIRTY);
CheckSpendCoin(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, VALUE2, SPENT, DIRTY, DIRTY);
CheckSpendCoin(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(SPENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckSpendCoin(SPENT, SPENT, SPENT, 0, DIRTY);
CheckSpendCoin(SPENT, SPENT, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY);
CheckSpendCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(SPENT, VALUE2, SPENT, 0, DIRTY);
CheckSpendCoin(SPENT, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(SPENT, VALUE2, SPENT, DIRTY, DIRTY);
CheckSpendCoin(SPENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, ABSENT, SPENT, NO_ENTRY, DIRTY);
CheckSpendCoin(VALUE1, SPENT, SPENT, 0, DIRTY);
CheckSpendCoin(VALUE1, SPENT, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY);
CheckSpendCoin(VALUE1, SPENT, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, VALUE2, SPENT, 0, DIRTY);
CheckSpendCoin(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, VALUE2, SPENT, DIRTY, DIRTY);
CheckSpendCoin(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
}
static void CheckAddCoinBase(Amount base_value, Amount cache_value,
Amount modify_value, Amount expected_value,
char cache_flags, char expected_flags,
bool coinbase) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
Amount result_value;
char result_flags;
try {
CTxOut output;
output.nValue = modify_value;
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase),
coinbase);
test.cache.SelfTest();
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
} catch (std::logic_error &) {
result_value = FAIL;
result_flags = NO_ENTRY;
}
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
// Simple wrapper for CheckAddCoinBase function above that loops through
// different possible base_values, making sure each one gives the same results.
// This wrapper lets the coin_add test below be shorter and less repetitive,
// while still verifying that the CoinsViewCache::AddCoin implementation ignores
// base values.
template <typename... Args> static void CheckAddCoin(Args &&...args) {
for (const Amount &base_value : {ABSENT, SPENT, VALUE1}) {
CheckAddCoinBase(base_value, std::forward<Args>(args)...);
}
}
BOOST_AUTO_TEST_CASE(coin_add) {
/**
* Check AddCoin behavior, requesting a new coin from a cache view, writing
* a modification to the coin, and then checking the resulting entry in the
* cache after the modification. Verify behavior with the AddCoin
* possible_overwrite argument set to false, and to true.
*
* Cache Write Result Cache Result possible_overwrite
* Value Value Value Flags Flags
*/
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH, false);
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY, true);
CheckAddCoin(SPENT, VALUE3, VALUE3, 0, DIRTY | FRESH, false);
CheckAddCoin(SPENT, VALUE3, VALUE3, 0, DIRTY, true);
CheckAddCoin(SPENT, VALUE3, VALUE3, FRESH, DIRTY | FRESH, false);
CheckAddCoin(SPENT, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true);
CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY, DIRTY, false);
CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY, DIRTY, true);
CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, false);
CheckAddCoin(SPENT, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, 0, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0, DIRTY, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, FRESH, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY, DIRTY, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY | FRESH, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true);
}
void CheckWriteCoin(Amount parent_value, Amount child_value,
Amount expected_value, char parent_flags, char child_flags,
char expected_flags) {
SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
Amount result_value;
char result_flags;
try {
WriteCoinViewEntry(test.cache, child_value, child_flags);
test.cache.SelfTest();
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
} catch (std::logic_error &) {
result_value = FAIL;
result_flags = NO_ENTRY;
}
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
BOOST_AUTO_TEST_CASE(coin_write) {
/* Check BatchWrite behavior, flushing one entry from a child cache to a
* parent cache, and checking the resulting entry in the parent cache
* after the write.
*
* Parent Child Result Parent Child Result
* Value Value Value Flags Flags Flags
*/
CheckWriteCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY);
CheckWriteCoin(ABSENT, SPENT, SPENT, NO_ENTRY, DIRTY, DIRTY);
CheckWriteCoin(ABSENT, SPENT, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY);
CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH,
DIRTY | FRESH);
CheckWriteCoin(SPENT, ABSENT, SPENT, 0, NO_ENTRY, 0);
CheckWriteCoin(SPENT, ABSENT, SPENT, FRESH, NO_ENTRY, FRESH);
CheckWriteCoin(SPENT, ABSENT, SPENT, DIRTY, NO_ENTRY, DIRTY);
CheckWriteCoin(SPENT, ABSENT, SPENT, DIRTY | FRESH, NO_ENTRY,
DIRTY | FRESH);
CheckWriteCoin(SPENT, SPENT, SPENT, 0, DIRTY, DIRTY);
CheckWriteCoin(SPENT, SPENT, SPENT, 0, DIRTY | FRESH, DIRTY);
CheckWriteCoin(SPENT, SPENT, ABSENT, FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(SPENT, SPENT, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(SPENT, SPENT, SPENT, DIRTY, DIRTY | FRESH, DIRTY);
CheckWriteCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(SPENT, SPENT, ABSENT, DIRTY | FRESH, DIRTY | FRESH,
NO_ENTRY);
CheckWriteCoin(SPENT, VALUE2, VALUE2, 0, DIRTY, DIRTY);
CheckWriteCoin(SPENT, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY);
CheckWriteCoin(SPENT, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(SPENT, VALUE2, VALUE2, FRESH, DIRTY | FRESH, DIRTY | FRESH);
CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY);
CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(SPENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH,
DIRTY | FRESH);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY,
DIRTY | FRESH);
CheckWriteCoin(VALUE1, SPENT, SPENT, 0, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, SPENT, FAIL, 0, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, SPENT, ABSENT, FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(VALUE1, SPENT, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, SPENT, SPENT, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, SPENT, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, SPENT, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(VALUE1, SPENT, FAIL, DIRTY | FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH,
NO_ENTRY);
// The checks above omit cases where the child flags are not DIRTY, since
// they would be too repetitive (the parent cache is never updated in these
// cases). The loop below covers these cases and makes sure the parent cache
// is always left unchanged.
for (const Amount &parent_value : {ABSENT, SPENT, VALUE1}) {
for (const Amount &child_value : {ABSENT, SPENT, VALUE2}) {
for (const char parent_flags :
parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) {
for (const char child_flags :
child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) {
CheckWriteCoin(parent_value, child_value, parent_value,
parent_flags, child_flags, parent_flags);
}
}
}
}
}
+Coin MakeCoin() {
+ CScript scriptPubKey;
+ scriptPubKey.assign(uint32_t{56}, 1);
+ Coin coin{CTxOut{InsecureRandMoneyAmount(), std::move(scriptPubKey)},
+ /*nHeightIn=*/static_cast<uint32_t>(InsecureRandRange(4096)),
+ /*IsCoinbase=*/false};
+ return coin;
+}
+
+//! For CCoinsViewCache instances backed by either another cache instance or
+//! leveldb, test cache behavior and flag state (DIRTY/FRESH) by
+//!
+//! 1. Adding a random coin to the child-most cache,
+//! 2. Flushing all caches (without erasing),
+//! 3. Ensure the entry still exists in the cache and has been written to
+//! parent,
+//! 4. (if `do_erasing_flush`) Flushing the caches again (with erasing),
+//! 5. (if `do_erasing_flush`) Ensure the entry has been written to the parent
+//! and is no longer in the cache,
+//! 6. Spend the coin, ensure it no longer exists in the parent.
+//!
+void TestFlushBehavior(CCoinsViewCacheTest *view, CCoinsViewDB &base,
+ std::vector<CCoinsViewCacheTest *> &all_caches,
+ bool do_erasing_flush) {
+ Amount value;
+ char flags;
+ size_t cache_usage;
+
+ auto flush_all = [&all_caches](bool erase) {
+ // Flush in reverse order to ensure that flushes happen from children
+ // up.
+ for (auto i = all_caches.rbegin(); i != all_caches.rend(); ++i) {
+ auto cache = *i;
+ // hashBlock must be filled before flushing to disk; value is
+ // unimportant here. This is normally done during connect/disconnect
+ // block.
+ cache->SetBestBlock(BlockHash{InsecureRand256()});
+ erase ? cache->Flush() : cache->Sync();
+ }
+ };
+
+ TxId txid{InsecureRand256()};
+ COutPoint outp = COutPoint(txid, 0);
+ Coin coin = MakeCoin();
+ // Ensure the coins views haven't seen this coin before.
+ BOOST_CHECK(!base.HaveCoin(outp));
+ BOOST_CHECK(!view->HaveCoin(outp));
+
+ // --- 1. Adding a random coin to the child cache
+ //
+ view->AddCoin(outp, Coin(coin), false);
+
+ cache_usage = view->DynamicMemoryUsage();
+ // `base` shouldn't have coin (no flush yet) but `view` should have cached
+ // it.
+ BOOST_CHECK(!base.HaveCoin(outp));
+ BOOST_CHECK(view->HaveCoin(outp));
+
+ GetCoinMapEntry(view->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, coin.GetTxOut().nValue);
+ BOOST_CHECK_EQUAL(flags, DIRTY | FRESH);
+
+ // --- 2. Flushing all caches (without erasing)
+ //
+ flush_all(/*erase=*/false);
+
+ // CoinsMap usage should be unchanged since we didn't erase anything.
+ BOOST_CHECK_EQUAL(cache_usage, view->DynamicMemoryUsage());
+
+ // --- 3. Ensuring the entry still exists in the cache and has been written
+ // to parent
+ //
+ GetCoinMapEntry(view->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, coin.GetTxOut().nValue);
+ BOOST_CHECK_EQUAL(flags, 0); // Flags should have been wiped.
+
+ // Both views should now have the coin.
+ BOOST_CHECK(base.HaveCoin(outp));
+ BOOST_CHECK(view->HaveCoin(outp));
+
+ if (do_erasing_flush) {
+ // --- 4. Flushing the caches again (with erasing)
+ //
+ flush_all(/*erase=*/true);
+
+ // Memory usage should have gone down.
+ BOOST_CHECK(view->DynamicMemoryUsage() < cache_usage);
+
+ // --- 5. Ensuring the entry is no longer in the cache
+ //
+ GetCoinMapEntry(view->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, ABSENT);
+ BOOST_CHECK_EQUAL(flags, NO_ENTRY);
+
+ view->AccessCoin(outp);
+ GetCoinMapEntry(view->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, coin.GetTxOut().nValue);
+ BOOST_CHECK_EQUAL(flags, 0);
+ }
+
+ // Can't overwrite an entry without specifying that an overwrite is
+ // expected.
+ BOOST_CHECK_THROW(
+ view->AddCoin(outp, Coin(coin), /*possible_overwrite=*/false),
+ std::logic_error);
+
+ // --- 6. Spend the coin.
+ //
+ BOOST_CHECK(view->SpendCoin(outp));
+
+ // The coin should be in the cache, but spent and marked dirty.
+ GetCoinMapEntry(view->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, SPENT);
+ BOOST_CHECK_EQUAL(flags, DIRTY);
+ BOOST_CHECK(
+ !view->HaveCoin(outp)); // Coin should be considered spent in `view`.
+ BOOST_CHECK(
+ base.HaveCoin(outp)); // But coin should still be unspent in `base`.
+
+ flush_all(/*erase=*/false);
+
+ // Coin should be considered spent in both views.
+ BOOST_CHECK(!view->HaveCoin(outp));
+ BOOST_CHECK(!base.HaveCoin(outp));
+
+ // Spent coin should not be spendable.
+ BOOST_CHECK(!view->SpendCoin(outp));
+
+ // --- Bonus check: ensure that a coin added to the base view via one cache
+ // can be spent by another cache which has never seen it.
+ //
+ txid = TxId{InsecureRand256()};
+ outp = COutPoint(txid, 0);
+ coin = MakeCoin();
+ BOOST_CHECK(!base.HaveCoin(outp));
+ BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+
+ all_caches[0]->AddCoin(outp, std::move(coin), false);
+ all_caches[0]->Sync();
+ BOOST_CHECK(base.HaveCoin(outp));
+ BOOST_CHECK(all_caches[0]->HaveCoin(outp));
+ BOOST_CHECK(!all_caches[1]->HaveCoinInCache(outp));
+
+ BOOST_CHECK(all_caches[1]->SpendCoin(outp));
+ flush_all(/*erase=*/false);
+ BOOST_CHECK(!base.HaveCoin(outp));
+ BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+
+ flush_all(/*erase=*/true); // Erase all cache content.
+
+ // --- Bonus check 2: ensure that a FRESH, spent coin is deleted by Sync()
+ //
+ txid = TxId{InsecureRand256()};
+ outp = COutPoint(txid, 0);
+ coin = MakeCoin();
+ Amount coin_val = coin.GetTxOut().nValue;
+ BOOST_CHECK(!base.HaveCoin(outp));
+ BOOST_CHECK(!all_caches[0]->HaveCoin(outp));
+ BOOST_CHECK(!all_caches[1]->HaveCoin(outp));
+
+ // Add and spend from same cache without flushing.
+ all_caches[0]->AddCoin(outp, std::move(coin), false);
+
+ // Coin should be FRESH in the cache.
+ GetCoinMapEntry(all_caches[0]->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, coin_val);
+ BOOST_CHECK_EQUAL(flags, DIRTY | FRESH);
+
+ // Base shouldn't have seen coin.
+ BOOST_CHECK(!base.HaveCoin(outp));
+
+ BOOST_CHECK(all_caches[0]->SpendCoin(outp));
+ all_caches[0]->Sync();
+
+ // Ensure there is no sign of the coin after spend/flush.
+ GetCoinMapEntry(all_caches[0]->map(), value, flags, outp);
+ BOOST_CHECK_EQUAL(value, ABSENT);
+ BOOST_CHECK_EQUAL(flags, NO_ENTRY);
+ BOOST_CHECK(!all_caches[0]->HaveCoinInCache(outp));
+ BOOST_CHECK(!base.HaveCoin(outp));
+}
+
+BOOST_AUTO_TEST_CASE(ccoins_flush_behavior) {
+ // Create two in-memory caches atop a leveldb view.
+ CCoinsViewDB base{
+ {.path = "test", .cache_bytes = 1 << 23, .memory_only = true}, {}};
+ std::vector<CCoinsViewCacheTest *> caches;
+ caches.push_back(new CCoinsViewCacheTest(&base));
+ caches.push_back(new CCoinsViewCacheTest(caches.back()));
+
+ for (CCoinsViewCacheTest *view : caches) {
+ TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/false);
+ TestFlushBehavior(view, base, caches, /*do_erasing_flush=*/true);
+ }
+
+ // Clean up the caches.
+ while (caches.size() > 0) {
+ delete caches.back();
+ caches.pop_back();
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
index 71482f655..eba3c2bf9 100644
--- a/src/test/fuzz/coins_view.cpp
+++ b/src/test/fuzz/coins_view.cpp
@@ -1,287 +1,288 @@
// Copyright (c) 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 <chainparams.h>
#include <chainparamsbase.h>
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <key.h>
#include <policy/policy.h>
#include <primitives/blockhash.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <validation.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
#include <vector>
namespace {
const TestingSetup *g_setup;
const Coin EMPTY_COIN{};
bool operator==(const Coin &a, const Coin &b) {
if (a.IsSpent() && b.IsSpent()) {
return true;
}
return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() &&
a.GetTxOut() == b.GetTxOut();
}
} // namespace
void initialize_coins_view() {
static const auto testing_setup =
MakeNoLogFileContext<const TestingSetup>();
g_setup = testing_setup.get();
}
FUZZ_TARGET_INIT(coins_view, initialize_coins_view) {
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CCoinsView backend_coins_view;
CCoinsViewCache coins_view_cache{&backend_coins_view};
COutPoint random_out_point;
Coin random_coin;
CMutableTransaction random_mutable_transaction;
while (fuzzed_data_provider.ConsumeBool()) {
CallOneOf(
fuzzed_data_provider,
[&] {
if (random_coin.IsSpent()) {
return;
}
Coin coin = random_coin;
bool expected_code_path = false;
const bool possible_overwrite =
fuzzed_data_provider.ConsumeBool();
try {
coins_view_cache.AddCoin(random_out_point, std::move(coin),
possible_overwrite);
expected_code_path = true;
} catch (const std::logic_error &e) {
if (e.what() ==
std::string{"Attempted to overwrite an unspent coin "
"(when possible_overwrite is false)"}) {
assert(!possible_overwrite);
expected_code_path = true;
}
}
assert(expected_code_path);
},
[&] { (void)coins_view_cache.Flush(); },
+ [&] { (void)coins_view_cache.Sync(); },
[&] {
coins_view_cache.SetBestBlock(
BlockHash{ConsumeUInt256(fuzzed_data_provider)});
},
[&] {
Coin move_to;
(void)coins_view_cache.SpendCoin(
random_out_point,
fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
},
[&] { coins_view_cache.Uncache(random_out_point); },
[&] {
if (fuzzed_data_provider.ConsumeBool()) {
backend_coins_view = CCoinsView{};
}
coins_view_cache.SetBackend(backend_coins_view);
},
[&] {
const std::optional<COutPoint> opt_out_point =
ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
if (!opt_out_point) {
return;
}
random_out_point = *opt_out_point;
},
[&] {
const std::optional<Coin> opt_coin =
ConsumeDeserializable<Coin>(fuzzed_data_provider);
if (!opt_coin) {
return;
}
random_coin = *opt_coin;
},
[&] {
const std::optional<CMutableTransaction>
opt_mutable_transaction =
ConsumeDeserializable<CMutableTransaction>(
fuzzed_data_provider);
if (!opt_mutable_transaction) {
return;
}
random_mutable_transaction = *opt_mutable_transaction;
},
[&] {
CCoinsMap coins_map;
while (fuzzed_data_provider.ConsumeBool()) {
CCoinsCacheEntry coins_cache_entry;
coins_cache_entry.flags =
fuzzed_data_provider.ConsumeIntegral<uint8_t>();
if (fuzzed_data_provider.ConsumeBool()) {
coins_cache_entry.coin = random_coin;
} else {
const std::optional<Coin> opt_coin =
ConsumeDeserializable<Coin>(fuzzed_data_provider);
if (!opt_coin) {
return;
}
coins_cache_entry.coin = *opt_coin;
}
coins_map.emplace(random_out_point,
std::move(coins_cache_entry));
}
bool expected_code_path = false;
try {
coins_view_cache.BatchWrite(
coins_map,
fuzzed_data_provider.ConsumeBool()
? BlockHash{ConsumeUInt256(fuzzed_data_provider)}
: coins_view_cache.GetBestBlock());
expected_code_path = true;
} catch (const std::logic_error &e) {
if (e.what() ==
std::string{"FRESH flag misapplied to coin that exists "
"in parent cache"}) {
expected_code_path = true;
}
}
assert(expected_code_path);
});
}
{
const Coin &coin_using_access_coin =
coins_view_cache.AccessCoin(random_out_point);
const bool exists_using_access_coin =
!(coin_using_access_coin == EMPTY_COIN);
const bool exists_using_have_coin =
coins_view_cache.HaveCoin(random_out_point);
const bool exists_using_have_coin_in_cache =
coins_view_cache.HaveCoinInCache(random_out_point);
Coin coin_using_get_coin;
const bool exists_using_get_coin =
coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
if (exists_using_get_coin) {
assert(coin_using_get_coin == coin_using_access_coin);
}
assert((exists_using_access_coin && exists_using_have_coin_in_cache &&
exists_using_have_coin && exists_using_get_coin) ||
(!exists_using_access_coin && !exists_using_have_coin_in_cache &&
!exists_using_have_coin && !exists_using_get_coin));
const bool exists_using_have_coin_in_backend =
backend_coins_view.HaveCoin(random_out_point);
if (exists_using_have_coin_in_backend) {
assert(exists_using_have_coin);
}
Coin coin_using_backend_get_coin;
if (backend_coins_view.GetCoin(random_out_point,
coin_using_backend_get_coin)) {
assert(exists_using_have_coin_in_backend);
assert(coin_using_get_coin == coin_using_backend_get_coin);
} else {
assert(!exists_using_have_coin_in_backend);
}
}
{
bool expected_code_path = false;
try {
(void)coins_view_cache.Cursor();
} catch (const std::logic_error &) {
expected_code_path = true;
}
assert(expected_code_path);
(void)coins_view_cache.DynamicMemoryUsage();
(void)coins_view_cache.EstimateSize();
(void)coins_view_cache.GetBestBlock();
(void)coins_view_cache.GetCacheSize();
(void)coins_view_cache.GetHeadBlocks();
(void)coins_view_cache.HaveInputs(
CTransaction{random_mutable_transaction});
}
{
const CCoinsViewCursor *coins_view_cursor = backend_coins_view.Cursor();
assert(coins_view_cursor == nullptr);
(void)backend_coins_view.EstimateSize();
(void)backend_coins_view.GetBestBlock();
(void)backend_coins_view.GetHeadBlocks();
}
if (fuzzed_data_provider.ConsumeBool()) {
CallOneOf(
fuzzed_data_provider,
[&] {
const CTransaction transaction{random_mutable_transaction};
bool is_spent = false;
for (const CTxOut &tx_out : transaction.vout) {
if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
is_spent = true;
}
}
if (is_spent) {
// Avoid:
// coins.cpp:69: void CCoinsViewCache::AddCoin(const
// COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()'
// failed.
return;
}
bool expected_code_path = false;
const int height{
int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
const bool possible_overwrite =
fuzzed_data_provider.ConsumeBool();
try {
AddCoins(coins_view_cache, transaction, height,
possible_overwrite);
expected_code_path = true;
} catch (const std::logic_error &e) {
if (e.what() ==
std::string{"Attempted to overwrite an unspent coin "
"(when possible_overwrite is false)"}) {
assert(!possible_overwrite);
expected_code_path = true;
}
}
assert(expected_code_path);
},
[&] {
uint32_t flags =
fuzzed_data_provider.ConsumeIntegral<uint32_t>();
(void)AreInputsStandard(
CTransaction{random_mutable_transaction}, coins_view_cache,
flags);
},
[&] {
TxValidationState state;
Amount tx_fee_out;
const CTransaction transaction{random_mutable_transaction};
if (ContainsSpentInput(transaction, coins_view_cache)) {
// Avoid:
// consensus/tx_verify.cpp:171: bool
// Consensus::CheckTxInputs(const CTransaction &,
// TxValidationState &, const CCoinsViewCache &, int,
// CAmount &): Assertion `!coin.IsSpent()' failed.
}
try {
(void)Consensus::CheckTxInputs(
transaction, state, coins_view_cache,
fuzzed_data_provider.ConsumeIntegralInRange<int>(
0, std::numeric_limits<int>::max()),
tx_fee_out);
assert(MoneyRange(tx_fee_out));
} catch (const std::runtime_error &) {
}
});
}
}
diff --git a/src/txdb.cpp b/src/txdb.cpp
index 72741303c..7802e8e0b 100644
--- a/src/txdb.cpp
+++ b/src/txdb.cpp
@@ -1,522 +1,522 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 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 <chain.h>
#include <logging.h>
#include <node/ui_interface.h>
#include <pow/pow.h>
#include <random.h>
#include <shutdown.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/vector.h>
#include <version.h>
#include <cstdint>
#include <memory>
static constexpr uint8_t DB_COIN{'C'};
static constexpr uint8_t DB_COINS{'c'};
static constexpr uint8_t DB_BLOCK_FILES{'f'};
static constexpr uint8_t DB_BLOCK_INDEX{'b'};
static constexpr uint8_t DB_BEST_BLOCK{'B'};
static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
static constexpr uint8_t DB_FLAG{'F'};
static constexpr uint8_t DB_REINDEX_FLAG{'R'};
static constexpr uint8_t DB_LAST_BLOCK{'l'};
// Keys used in previous version that might still be found in the DB:
static constexpr uint8_t DB_TXINDEX_BLOCK{'T'};
// uint8_t DB_TXINDEX{'t'}
util::Result<void> CheckLegacyTxindex(CBlockTreeDB &block_tree_db) {
CBlockLocator ignored{};
if (block_tree_db.Read(DB_TXINDEX_BLOCK, ignored)) {
return util::Error{
_("The -txindex upgrade started by a previous version can not "
"be completed. Restart with the previous version or run a "
"full -reindex.")};
}
bool txindex_legacy_flag{false};
block_tree_db.ReadFlag("txindex", txindex_legacy_flag);
if (txindex_legacy_flag) {
// Disable legacy txindex and warn once about occupied disk space
if (!block_tree_db.WriteFlag("txindex", false)) {
return util::Error{Untranslated(
"Failed to write block index db flag 'txindex'='0'")};
}
return util::Error{
_("The block index db contains a legacy 'txindex'. To clear the "
"occupied disk space, run a full -reindex, otherwise ignore "
"this error. This error message will not be displayed again.")};
}
return {};
}
namespace {
struct CoinEntry {
COutPoint *outpoint;
uint8_t key;
explicit CoinEntry(const COutPoint *ptr)
: outpoint(const_cast<COutPoint *>(ptr)), key(DB_COIN) {}
SERIALIZE_METHODS(CoinEntry, obj) {
TxId id = obj.outpoint->GetTxId();
uint32_t n = obj.outpoint->GetN();
READWRITE(obj.key, id, VARINT(n));
SER_READ(obj, *obj.outpoint = COutPoint(id, 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);
}
}
bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
return m_db->Read(CoinEntry(&outpoint), coin);
}
bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
return m_db->Exists(CoinEntry(&outpoint));
}
BlockHash CCoinsViewDB::GetBestBlock() const {
BlockHash hashBestChain;
if (!m_db->Read(DB_BEST_BLOCK, hashBestChain)) {
return BlockHash();
}
return hashBestChain;
}
std::vector<BlockHash> CCoinsViewDB::GetHeadBlocks() const {
std::vector<BlockHash> vhashHeadBlocks;
if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
return std::vector<BlockHash>();
}
return vhashHeadBlocks;
}
-bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) {
+bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase) {
CDBBatch batch(*m_db);
size_t count = 0;
size_t changed = 0;
assert(!hashBlock.IsNull());
BlockHash old_tip = GetBestBlock();
if (old_tip.IsNull()) {
// We may be in the middle of replaying.
std::vector<BlockHash> old_heads = GetHeadBlocks();
if (old_heads.size() == 2) {
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 (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
CoinEntry entry(&it->first);
if (it->second.coin.IsSpent()) {
batch.Erase(entry);
} else {
batch.Write(entry, it->second.coin);
}
changed++;
}
count++;
- CCoinsMap::iterator itOld = it++;
- mapCoins.erase(itOld);
+ it = erase ? mapCoins.erase(it) : std::next(it);
if (batch.SizeEstimate() > m_options.batch_write_bytes) {
LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n",
batch.SizeEstimate() * (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);
LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n",
batch.SizeEstimate() * (1.0 / 1048576.0));
bool ret = m_db->WriteBatch(batch);
LogPrint(BCLog::COINDB,
"Committed %u changed transaction outputs (out of "
"%u) to coin database...\n",
(unsigned int)changed, (unsigned int)count);
return ret;
}
size_t CCoinsViewDB::EstimateSize() const {
return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
}
bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {
return Read(std::make_pair(DB_BLOCK_FILES, nFile), info);
}
bool CBlockTreeDB::WriteReindexing(bool fReindexing) {
if (fReindexing) {
return Write(DB_REINDEX_FLAG, uint8_t{'1'});
} else {
return Erase(DB_REINDEX_FLAG);
}
}
bool CBlockTreeDB::IsReindexing() const {
return Exists(DB_REINDEX_FLAG);
}
bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {
return Read(DB_LAST_BLOCK, nFile);
}
CCoinsViewCursor *CCoinsViewDB::Cursor() const {
CCoinsViewDBCursor *i = new 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 {
// Make sure Valid() and GetKey() return false
i->keyTmp.first = 0;
}
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);
}
unsigned int CCoinsViewDBCursor::GetValueSize() const {
return pcursor->GetValueSize();
}
bool CCoinsViewDBCursor::Valid() const {
return keyTmp.first == DB_COIN;
}
void CCoinsViewDBCursor::Next() {
pcursor->Next();
CoinEntry entry(&keyTmp.second);
if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
// Invalidate cached key after last record so that Valid() and GetKey()
// return false
keyTmp.first = 0;
} else {
keyTmp.first = entry.key;
}
}
bool CBlockTreeDB::WriteBatchSync(
const std::vector<std::pair<int, const CBlockFileInfo *>> &fileInfo,
int nLastFile, const std::vector<const CBlockIndex *> &blockinfo) {
CDBBatch batch(*this);
for (std::vector<std::pair<int, const CBlockFileInfo *>>::const_iterator
it = fileInfo.begin();
it != fileInfo.end(); it++) {
batch.Write(std::make_pair(DB_BLOCK_FILES, it->first), *it->second);
}
batch.Write(DB_LAST_BLOCK, nLastFile);
for (std::vector<const CBlockIndex *>::const_iterator it =
blockinfo.begin();
it != blockinfo.end(); it++) {
batch.Write(std::make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash()),
CDiskBlockIndex(*it));
}
return WriteBatch(batch, true);
}
bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) {
return Write(std::make_pair(DB_FLAG, name),
fValue ? uint8_t{'1'} : uint8_t{'0'});
}
bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) {
uint8_t ch;
if (!Read(std::make_pair(DB_FLAG, name), ch)) {
return false;
}
fValue = ch == uint8_t{'1'};
return true;
}
bool CBlockTreeDB::LoadBlockIndexGuts(
const Consensus::Params ¶ms,
std::function<CBlockIndex *(const BlockHash &)> insertBlockIndex) {
AssertLockHeld(::cs_main);
std::unique_ptr<CDBIterator> pcursor(NewIterator());
uint64_t version = 0;
pcursor->Seek("version");
if (pcursor->Valid()) {
pcursor->GetValue(version);
}
if (version != CLIENT_VERSION) {
return error("%s: Invalid block index database version: %s", __func__,
version);
}
pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256()));
// Load m_block_index
while (pcursor->Valid()) {
if (ShutdownRequested()) {
return false;
}
std::pair<uint8_t, uint256> key;
if (!pcursor->GetKey(key) || key.first != DB_BLOCK_INDEX) {
break;
}
CDiskBlockIndex diskindex;
if (!pcursor->GetValue(diskindex)) {
return error("%s : failed to read value", __func__);
}
// Construct block index object
CBlockIndex *pindexNew =
insertBlockIndex(diskindex.ConstructBlockHash());
pindexNew->pprev = insertBlockIndex(diskindex.hashPrev);
pindexNew->nHeight = diskindex.nHeight;
pindexNew->nFile = diskindex.nFile;
pindexNew->nDataPos = diskindex.nDataPos;
pindexNew->nUndoPos = diskindex.nUndoPos;
pindexNew->nVersion = diskindex.nVersion;
pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce;
pindexNew->nStatus = diskindex.nStatus;
pindexNew->nTx = diskindex.nTx;
if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits,
params)) {
return error("%s: CheckProofOfWork failed: %s", __func__,
pindexNew->ToString());
}
pcursor->Next();
}
return true;
}
namespace {
//! Legacy class to deserialize pre-pertxout database entries without reindex.
class CCoins {
public:
//! whether transaction is a coinbase
bool fCoinBase;
//! unspent transaction outputs; spent outputs are .IsNull(); spent outputs
//! at the end of the array are dropped
std::vector<CTxOut> vout;
//! at which height this transaction was included in the active block chain
int nHeight;
//! empty constructor
CCoins() : fCoinBase(false), vout(0), nHeight(0) {}
template <typename Stream> void Unserialize(Stream &s) {
uint32_t nCode = 0;
// version
unsigned int nVersionDummy = 0;
::Unserialize(s, VARINT(nVersionDummy));
// header code
::Unserialize(s, VARINT(nCode));
fCoinBase = nCode & 1;
std::vector<bool> vAvail(2, false);
vAvail[0] = (nCode & 2) != 0;
vAvail[1] = (nCode & 4) != 0;
uint32_t nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1);
// spentness bitmask
while (nMaskCode > 0) {
uint8_t chAvail = 0;
::Unserialize(s, chAvail);
for (unsigned int p = 0; p < 8; p++) {
bool f = (chAvail & (1 << p)) != 0;
vAvail.push_back(f);
}
if (chAvail != 0) {
nMaskCode--;
}
}
// txouts themself
vout.assign(vAvail.size(), CTxOut());
for (size_t i = 0; i < vAvail.size(); i++) {
if (vAvail[i]) {
::Unserialize(s, Using<TxOutCompression>(vout[i]));
}
}
// coinbase height
::Unserialize(s, VARINT_MODE(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
}
};
} // namespace
/**
* Upgrade the database from older formats.
*
* Currently implemented: from the per-tx utxo model (0.8..0.14.x) to per-txout.
*/
bool CCoinsViewDB::Upgrade() {
std::unique_ptr<CDBIterator> pcursor(m_db->NewIterator());
pcursor->Seek(std::make_pair(DB_COINS, uint256()));
if (!pcursor->Valid()) {
return true;
}
int64_t count = 0;
LogPrintf("Upgrading utxo-set database...\n");
size_t batch_size = 1 << 24;
CDBBatch batch(*m_db);
int reportDone = -1;
std::pair<uint8_t, uint256> key;
std::pair<uint8_t, uint256> prev_key = {DB_COINS, uint256()};
while (pcursor->Valid()) {
if (ShutdownRequested()) {
break;
}
if (!pcursor->GetKey(key) || key.first != DB_COINS) {
break;
}
if (count++ % 256 == 0) {
uint32_t high =
0x100 * *key.second.begin() + *(key.second.begin() + 1);
int percentageDone = (int)(high * 100.0 / 65536.0 + 0.5);
uiInterface.ShowProgress(_("Upgrading UTXO database").translated,
percentageDone, true);
if (reportDone < percentageDone / 10) {
// report max. every 10% step
LogPrintfToBeContinued("[%d%%]...", percentageDone);
reportDone = percentageDone / 10;
}
}
CCoins old_coins;
if (!pcursor->GetValue(old_coins)) {
return error("%s: cannot parse CCoins record", __func__);
}
const TxId id(key.second);
for (size_t i = 0; i < old_coins.vout.size(); ++i) {
if (!old_coins.vout[i].IsNull() &&
!old_coins.vout[i].scriptPubKey.IsUnspendable()) {
Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight,
old_coins.fCoinBase);
COutPoint outpoint(id, i);
CoinEntry entry(&outpoint);
batch.Write(entry, newcoin);
}
}
batch.Erase(key);
if (batch.SizeEstimate() > batch_size) {
m_db->WriteBatch(batch);
batch.Clear();
m_db->CompactRange(prev_key, key);
prev_key = key;
}
pcursor->Next();
}
m_db->WriteBatch(batch);
m_db->CompactRange({DB_COINS, uint256()}, key);
uiInterface.ShowProgress("", 100, false);
LogPrintf("[%s].\n", ShutdownRequested() ? "CANCELLED" : "DONE");
return !ShutdownRequested();
}
bool CBlockTreeDB::Upgrade() {
// This method used to add the block size to pre-0.22.8 block index
// databases. This is no longer supported as of 0.25.5, but the method is
// kept to update the version number in the database.
std::unique_ptr<CDBIterator> pcursor(NewIterator());
uint64_t version = 0;
pcursor->Seek("version");
if (pcursor->Valid()) {
pcursor->GetValue(version);
}
if (version >= CLIENT_VERSION) {
// The DB is already up to date.
return true;
}
pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256()));
// The DB is not empty, and the version is either non-existent or too old.
// The node requires a reindex.
if (pcursor->Valid() && version < CDiskBlockIndex::TRACK_SIZE_VERSION) {
LogPrintf(
"\nThe database is too old. The block index cannot be upgraded "
"and reindexing is required.\n");
return false;
}
// The DB is empty or recent enough.
// Just write the new version number and consider the upgrade done.
CDBBatch batch(*this);
LogPrintf("Updating the block index database version to %d\n",
CLIENT_VERSION);
batch.Write("version", uint64_t(CLIENT_VERSION));
return WriteBatch(batch);
}
diff --git a/src/txdb.h b/src/txdb.h
index 9f801b4ee..f95d979ab 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -1,142 +1,143 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 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_TXDB_H
#define BITCOIN_TXDB_H
#include <blockfileinfo.h>
#include <coins.h>
#include <dbwrapper.h>
#include <flatfile.h>
#include <kernel/cs_main.h>
#include <util/fs.h>
#include <util/result.h>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
struct BlockHash;
class CBlockFileInfo;
class CBlockIndex;
class COutPoint;
namespace Consensus {
struct Params;
};
//! min. -dbcache (MiB)
static constexpr int64_t MIN_DB_CACHE_MB = 4;
//! max. -dbcache (MiB)
static constexpr int64_t MAX_DB_CACHE_MB = sizeof(void *) > 4 ? 16384 : 1024;
//! -dbcache default (MiB)
static constexpr int64_t DEFAULT_DB_CACHE_MB = 1024;
//! -dbbatchsize default (bytes)
static constexpr int64_t DEFAULT_DB_BATCH_SIZE = 16 << 20;
//! Max memory allocated to block tree DB specific cache, if no -txindex (MiB)
static constexpr int64_t MAX_BLOCK_DB_CACHE_MB = 2;
//! Max memory allocated to block tree DB specific cache, if -txindex (MiB)
// Unlike for the UTXO database, for the txindex scenario the leveldb cache make
// a meaningful difference:
// https://github.com/bitcoin/bitcoin/pull/8273#issuecomment-229601991
static constexpr int64_t MAX_TX_INDEX_CACHE_MB = 1024;
//! Max memory allocated to all block filter index caches combined in MiB.
static constexpr int64_t MAX_FILTER_INDEX_CACHE_MB = 1024;
//! Max memory allocated to coin DB specific cache (MiB)
static constexpr int64_t MAX_COINS_DB_CACHE_MB = 8;
//! User-controlled performance and debug options.
struct CoinsViewOptions {
//! Maximum database write batch size in bytes.
size_t batch_write_bytes = DEFAULT_DB_BATCH_SIZE;
//! If non-zero, randomly exit when the database is flushed with (1/ratio)
//! probability.
int simulate_crash_ratio = 0;
};
/** CCoinsView backed by the coin database (chainstate/) */
class CCoinsViewDB final : public CCoinsView {
protected:
DBParams m_db_params;
CoinsViewOptions m_options;
std::unique_ptr<CDBWrapper> m_db;
public:
explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options);
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoin(const COutPoint &outpoint) const override;
BlockHash GetBestBlock() const override;
std::vector<BlockHash> GetHeadBlocks() const override;
- bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override;
+ bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock,
+ bool erase = true) override;
CCoinsViewCursor *Cursor() const override;
//! Attempt to update from an older database format.
//! Returns whether an error occurred.
bool Upgrade();
size_t EstimateSize() const override;
//! Dynamically alter the underlying leveldb cache size.
void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! @returns filesystem path to on-disk storage or std::nullopt if in
//! memory.
std::optional<fs::path> StoragePath() { return m_db->StoragePath(); }
};
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
class CCoinsViewDBCursor : public CCoinsViewCursor {
public:
~CCoinsViewDBCursor() {}
bool GetKey(COutPoint &key) const override;
bool GetValue(Coin &coin) const override;
unsigned int GetValueSize() const override;
bool Valid() const override;
void Next() override;
private:
CCoinsViewDBCursor(CDBIterator *pcursorIn, const BlockHash &hashBlockIn)
: CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
std::unique_ptr<CDBIterator> pcursor;
std::pair<char, COutPoint> keyTmp;
friend class CCoinsViewDB;
};
/** Access to the block database (blocks/index/) */
class CBlockTreeDB : public CDBWrapper {
public:
using CDBWrapper::CDBWrapper;
bool WriteBatchSync(
const std::vector<std::pair<int, const CBlockFileInfo *>> &fileInfo,
int nLastFile, const std::vector<const CBlockIndex *> &blockinfo);
bool ReadBlockFileInfo(int nFile, CBlockFileInfo &info);
bool ReadLastBlockFile(int &nFile);
bool WriteReindexing(bool fReindexing);
bool IsReindexing() const;
bool WriteFlag(const std::string &name, bool fValue);
bool ReadFlag(const std::string &name, bool &fValue);
bool LoadBlockIndexGuts(
const Consensus::Params ¶ms,
std::function<CBlockIndex *(const BlockHash &)> insertBlockIndex)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
;
//! Attempt to update from an older database format.
//! Returns whether an error occurred.
bool Upgrade();
};
[[nodiscard]] util::Result<void>
CheckLegacyTxindex(CBlockTreeDB &block_tree_db);
#endif // BITCOIN_TXDB_H
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 10:03 (1 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4559231
Default Alt Text
(111 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment