Browse Source

Switch CCoinsView and chainstate db from per-txid to per-txout

This patch makes several related changes:
* Changes the CCoinsView virtual methods (GetCoins, HaveCoins, ...)
  to be COutPoint/Coin-based rather than txid/CCoins-based.
* Changes the chainstate db to a new incompatible format that is also
  COutPoint/Coin based.
* Implements reconstruction code for hash_serialized_2.
* Adapts the coins_tests unit tests (thanks to Russell Yanofsky).

A side effect of the new CCoinsView model is that we can no longer
use the (unreliable) test for transaction outputs in the UTXO set
to determine whether we already have a particular transaction.
0.15
Pieter Wuille 7 years ago
parent
commit
5083079688
  1. 112
      src/coins.cpp
  2. 74
      src/coins.h
  3. 4
      src/init.cpp
  4. 5
      src/net_processing.cpp
  5. 7
      src/qt/transactiondesc.cpp
  6. 20
      src/rest.cpp
  7. 47
      src/rpc/blockchain.cpp
  8. 7
      src/rpc/rawtransaction.cpp
  9. 239
      src/test/coins_tests.cpp
  10. 4
      src/test/test_bitcoin_fuzzy.cpp
  11. 66
      src/txdb.cpp
  12. 16
      src/txdb.h
  13. 40
      src/txmempool.cpp
  14. 33
      src/txmempool.h
  15. 54
      src/validation.cpp

112
src/coins.cpp

@ -42,41 +42,40 @@ bool CCoins::Spend(uint32_t nPos)
return true; return true;
} }
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::GetCoins(const COutPoint &outpoint, Coin &coin) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } bool CCoinsView::HaveCoins(const COutPoint &outpoint) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); } uint256 CCoinsView::GetBestBlock() const { return uint256(); }
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; } CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); } bool CCoinsViewBacked::GetCoins(const COutPoint &outpoint, Coin &coin) const { return base->GetCoins(outpoint, coin); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } bool CCoinsViewBacked::HaveCoins(const COutPoint &outpoint) const { return base->HaveCoins(outpoint); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) { } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
size_t CCoinsViewCache::DynamicMemoryUsage() const { size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
} }
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { CCoinsMap::iterator CCoinsViewCache::FetchCoins(const COutPoint &outpoint) const {
CCoinsMap::iterator it = cacheCoins.find(txid); CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end()) if (it != cacheCoins.end())
return it; return it;
CCoins tmp; Coin tmp;
if (!base->GetCoins(txid, tmp)) if (!base->GetCoins(outpoint, tmp))
return cacheCoins.end(); return cacheCoins.end();
CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;
tmp.swap(ret->second.coins);
if (ret->second.coins.IsPruned()) { if (ret->second.coins.IsPruned()) {
// The parent only has an empty entry for this txid; we can consider our // The parent only has an empty entry for this outpoint; we can consider our
// version as fresh. // version as fresh.
ret->second.flags = CCoinsCacheEntry::FRESH; ret->second.flags = CCoinsCacheEntry::FRESH;
} }
@ -84,10 +83,10 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
return ret; return ret;
} }
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewCache::GetCoins(const COutPoint &outpoint, Coin &coin) const {
CCoinsMap::const_iterator it = FetchCoins(txid); CCoinsMap::const_iterator it = FetchCoins(outpoint);
if (it != cacheCoins.end()) { if (it != cacheCoins.end()) {
coins = it->second.coins; coin = it->second.coins;
return true; return true;
} }
return false; return false;
@ -98,23 +97,18 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
if (coin.out.scriptPubKey.IsUnspendable()) return; if (coin.out.scriptPubKey.IsUnspendable()) return;
CCoinsMap::iterator it; CCoinsMap::iterator it;
bool inserted; bool inserted;
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint.hash), std::tuple<>()); std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>());
bool fresh = false; bool fresh = false;
if (!inserted) { if (!inserted) {
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
} }
if (!possible_overwrite) { if (!possible_overwrite) {
if (it->second.coins.IsAvailable(outpoint.n)) { if (!it->second.coins.IsPruned()) {
throw std::logic_error("Adding new coin that replaces non-pruned entry"); throw std::logic_error("Adding new coin that replaces non-pruned entry");
} }
fresh = it->second.coins.IsPruned() && !(it->second.flags & CCoinsCacheEntry::DIRTY); fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
} }
if (it->second.coins.vout.size() <= outpoint.n) { it->second.coins = std::move(coin);
it->second.coins.vout.resize(outpoint.n + 1);
}
it->second.coins.vout[outpoint.n] = std::move(coin.out);
it->second.coins.nHeight = coin.nHeight;
it->second.coins.fCoinBase = coin.fCoinBase;
it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0); it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
} }
@ -130,58 +124,38 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) {
} }
void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) { void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
CCoinsMap::iterator it = FetchCoins(outpoint.hash); CCoinsMap::iterator it = FetchCoins(outpoint);
if (it == cacheCoins.end()) return; if (it == cacheCoins.end()) return;
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
if (moveout && it->second.coins.IsAvailable(outpoint.n)) { if (moveout) {
*moveout = Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); *moveout = std::move(it->second.coins);
} }
it->second.coins.Spend(outpoint.n); // Ignore return value: SpendCoin has no effect if no UTXO found. if (it->second.flags & CCoinsCacheEntry::FRESH) {
if (it->second.coins.IsPruned() && it->second.flags & CCoinsCacheEntry::FRESH) {
cacheCoins.erase(it); cacheCoins.erase(it);
} else { } else {
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
it->second.flags |= CCoinsCacheEntry::DIRTY; it->second.flags |= CCoinsCacheEntry::DIRTY;
} it->second.coins.Clear();
}
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
CCoinsMap::const_iterator it = FetchCoins(txid);
if (it == cacheCoins.end()) {
return NULL;
} else {
return &it->second.coins;
} }
} }
static const Coin coinEmpty; static const Coin coinEmpty;
const Coin CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash); CCoinsMap::const_iterator it = FetchCoins(outpoint);
if (it == cacheCoins.end() || !it->second.coins.IsAvailable(outpoint.n)) { if (it == cacheCoins.end()) {
return coinEmpty; return coinEmpty;
} else { } else {
return Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); return it->second.coins;
} }
} }
bool CCoinsViewCache::HaveCoins(const uint256 &txid) const {
CCoinsMap::const_iterator it = FetchCoins(txid);
// We're using vtx.empty() instead of IsPruned here for performance reasons,
// as we only care about the case where a transaction was replaced entirely
// in a reorganization (which wipes vout entirely, as opposed to spending
// which just cleans individual outputs).
return (it != cacheCoins.end() && !it->second.coins.vout.empty());
}
bool CCoinsViewCache::HaveCoins(const COutPoint &outpoint) const { bool CCoinsViewCache::HaveCoins(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash); CCoinsMap::const_iterator it = FetchCoins(outpoint);
return (it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n)); return (it != cacheCoins.end() && !it->second.coins.IsPruned());
} }
bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const { bool CCoinsViewCache::HaveCoinsInCache(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = cacheCoins.find(txid); CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
return it != cacheCoins.end(); return it != cacheCoins.end();
} }
@ -206,7 +180,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
// Otherwise we will need to create it in the parent // Otherwise we will need to create it in the parent
// and move the data up and mark it as dirty // and move the data up and mark it as dirty
CCoinsCacheEntry& entry = cacheCoins[it->first]; CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coins.swap(it->second.coins); entry.coins = std::move(it->second.coins);
cachedCoinsUsage += entry.coins.DynamicMemoryUsage(); cachedCoinsUsage += entry.coins.DynamicMemoryUsage();
entry.flags = CCoinsCacheEntry::DIRTY; entry.flags = CCoinsCacheEntry::DIRTY;
// We can mark it FRESH in the parent if it was FRESH in the child // We can mark it FRESH in the parent if it was FRESH in the child
@ -233,7 +207,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
} else { } else {
// A normal modification. // A normal modification.
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage();
itUs->second.coins.swap(it->second.coins); itUs->second.coins = std::move(it->second.coins);
cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage(); cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage();
itUs->second.flags |= CCoinsCacheEntry::DIRTY; itUs->second.flags |= CCoinsCacheEntry::DIRTY;
// NOTE: It is possible the child has a FRESH flag here in // NOTE: It is possible the child has a FRESH flag here in
@ -258,7 +232,7 @@ bool CCoinsViewCache::Flush() {
return fOk; return fOk;
} }
void CCoinsViewCache::Uncache(const uint256& hash) void CCoinsViewCache::Uncache(const COutPoint& hash)
{ {
CCoinsMap::iterator it = cacheCoins.find(hash); CCoinsMap::iterator it = cacheCoins.find(hash);
if (it != cacheCoins.end() && it->second.flags == 0) { if (it != cacheCoins.end() && it->second.flags == 0) {
@ -273,9 +247,9 @@ unsigned int CCoinsViewCache::GetCacheSize() const {
const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
{ {
const CCoins* coins = AccessCoins(input.prevout.hash); const Coin& coin = AccessCoin(input.prevout);
assert(coins && coins->IsAvailable(input.prevout.n)); assert(!coin.IsPruned());
return coins->vout[input.prevout.n]; return coin.out;
} }
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
@ -294,9 +268,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{ {
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) { for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout; if (!HaveCoins(tx.vin[i].prevout)) {
const CCoins* coins = AccessCoins(prevout.hash);
if (!coins || !coins->IsAvailable(prevout.n)) {
return false; return false;
} }
} }
@ -304,13 +276,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
return true; return true;
} }
CCoinsViewCursor::~CCoinsViewCursor()
{
}
static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); // TODO: merge with similar definition in undo.h. static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); // TODO: merge with similar definition in undo.h.
const Coin AccessByTxid(const CCoinsViewCache& view, const uint256& txid) const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
{ {
COutPoint iter(txid, 0); COutPoint iter(txid, 0);
while (iter.n < MAX_OUTPUTS_PER_BLOCK) { while (iter.n < MAX_OUTPUTS_PER_BLOCK) {

74
src/coins.h

@ -299,28 +299,28 @@ public:
} }
}; };
class SaltedTxidHasher class SaltedOutpointHasher
{ {
private: private:
/** Salt */ /** Salt */
const uint64_t k0, k1; const uint64_t k0, k1;
public: public:
SaltedTxidHasher(); SaltedOutpointHasher();
/** /**
* This *must* return size_t. With Boost 1.46 on 32-bit systems the * This *must* return size_t. With Boost 1.46 on 32-bit systems the
* unordered_map will behave unpredictably if the custom hasher returns a * unordered_map will behave unpredictably if the custom hasher returns a
* uint64_t, resulting in failures when syncing the chain (#4634). * uint64_t, resulting in failures when syncing the chain (#4634).
*/ */
size_t operator()(const uint256& txid) const { size_t operator()(const COutPoint& id) const {
return SipHashUint256(k0, k1, txid); return SipHashUint256Extra(k0, k1, id.hash, id.n);
} }
}; };
struct CCoinsCacheEntry struct CCoinsCacheEntry
{ {
CCoins coins; // The actual cached data. Coin coins; // The actual cached data.
unsigned char flags; unsigned char flags;
enum Flags { enum Flags {
@ -333,20 +333,21 @@ struct CCoinsCacheEntry
*/ */
}; };
CCoinsCacheEntry() : coins(), flags(0) {} CCoinsCacheEntry() : flags(0) {}
explicit CCoinsCacheEntry(Coin&& coin_) : coins(std::move(coin_)), flags(0) {}
}; };
typedef std::unordered_map<uint256, CCoinsCacheEntry, SaltedTxidHasher> CCoinsMap; typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> CCoinsMap;
/** Cursor for iterating over CoinsView state */ /** Cursor for iterating over CoinsView state */
class CCoinsViewCursor class CCoinsViewCursor
{ {
public: public:
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {} CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
virtual ~CCoinsViewCursor(); virtual ~CCoinsViewCursor() {}
virtual bool GetKey(uint256 &key) const = 0; virtual bool GetKey(COutPoint &key) const = 0;
virtual bool GetValue(CCoins &coins) const = 0; virtual bool GetValue(Coin &coin) const = 0;
virtual unsigned int GetValueSize() const = 0; virtual unsigned int GetValueSize() const = 0;
virtual bool Valid() const = 0; virtual bool Valid() const = 0;
@ -362,17 +363,17 @@ private:
class CCoinsView class CCoinsView
{ {
public: public:
//! Retrieve the CCoins (unspent transaction outputs) for a given txid //! Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; virtual bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
//! Just check whether we have data for a given txid. //! Just check whether we have data for a given outpoint.
//! This may (but cannot always) return true for fully spent transactions //! This may (but cannot always) return true for spent outputs.
virtual bool HaveCoins(const uint256 &txid) const; virtual bool HaveCoins(const COutPoint &outpoint) const;
//! Retrieve the block hash whose state this CCoinsView currently represents //! Retrieve the block hash whose state this CCoinsView currently represents
virtual uint256 GetBestBlock() const; virtual uint256 GetBestBlock() const;
//! Do a bulk modification (multiple CCoins changes + BestBlock change). //! Do a bulk modification (multiple Coin changes + BestBlock change).
//! The passed mapCoins can be modified. //! The passed mapCoins can be modified.
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@ -395,12 +396,12 @@ protected:
public: public:
CCoinsViewBacked(CCoinsView *viewIn); CCoinsViewBacked(CCoinsView *viewIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const; uint256 GetBestBlock() const override;
void SetBackend(CCoinsView &viewIn); void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
CCoinsViewCursor *Cursor() const; CCoinsViewCursor *Cursor() const override;
size_t EstimateSize() const override; size_t EstimateSize() const override;
}; };
@ -416,41 +417,32 @@ protected:
mutable uint256 hashBlock; mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins; mutable CCoinsMap cacheCoins;
/* Cached dynamic memory usage for the inner CCoins objects. */ /* Cached dynamic memory usage for the inner Coin objects. */
mutable size_t cachedCoinsUsage; mutable size_t cachedCoinsUsage;
public: public:
CCoinsViewCache(CCoinsView *baseIn); CCoinsViewCache(CCoinsView *baseIn);
// Standard CCoinsView methods // Standard CCoinsView methods
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
bool HaveCoins(const uint256 &txid) const;
bool HaveCoins(const COutPoint &outpoint) const; bool HaveCoins(const COutPoint &outpoint) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
void SetBestBlock(const uint256 &hashBlock); void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
/** /**
* Check if we have the given tx already loaded in this cache. * Check if we have the given utxo already loaded in this cache.
* The semantics are the same as HaveCoins(), but no calls to * The semantics are the same as HaveCoins(), but no calls to
* the backing CCoinsView are made. * the backing CCoinsView are made.
*/ */
bool HaveCoinsInCache(const uint256 &txid) const; bool HaveCoinsInCache(const COutPoint &outpoint) const;
/**
* Return a pointer to CCoins in the cache, or NULL if not found. This is
* more efficient than GetCoins. Modifications to other cache entries are
* allowed while accessing the returned pointer.
*/
const CCoins* AccessCoins(const uint256 &txid) const;
/** /**
* Return a copy of a Coin in the cache, or a pruned one if not found. This is * Return a reference to Coin in the cache, or a pruned one if not found. This is
* more efficient than GetCoins. Modifications to other cache entries are * more efficient than GetCoins. Modifications to other cache entries are
* allowed while accessing the returned pointer. * allowed while accessing the returned pointer.
* TODO: return a reference to a Coin after changing CCoinsViewCache storage.
*/ */
const Coin AccessCoin(const COutPoint &output) const; const Coin& AccessCoin(const COutPoint &output) const;
/** /**
* Add a coin. Set potential_overwrite to true if a non-pruned version may * Add a coin. Set potential_overwrite to true if a non-pruned version may
@ -473,12 +465,12 @@ public:
bool Flush(); bool Flush();
/** /**
* Removes the transaction with the given hash from the cache, if it is * Removes the UTXO with the given outpoint from the cache, if it is
* not modified. * not modified.
*/ */
void Uncache(const uint256 &txid); void Uncache(const COutPoint &outpoint);
//! Calculate the size of the cache (in number of transactions) //! Calculate the size of the cache (in number of transaction outputs)
unsigned int GetCacheSize() const; unsigned int GetCacheSize() const;
//! Calculate the size of the cache (in bytes) //! Calculate the size of the cache (in bytes)
@ -500,7 +492,7 @@ public:
const CTxOut &GetOutputFor(const CTxIn& input) const; const CTxOut &GetOutputFor(const CTxIn& input) const;
private: private:
CCoinsMap::iterator FetchCoins(const uint256 &txid) const; CCoinsMap::iterator FetchCoins(const COutPoint &outpoint) const;
/** /**
* By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache. * By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache.
@ -515,6 +507,6 @@ private:
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight); void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight);
//! Utility function to find any unspent output with a given txid. //! Utility function to find any unspent output with a given txid.
const Coin AccessByTxid(const CCoinsViewCache& cache, const uint256& txid); const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);
#endif // BITCOIN_COINS_H #endif // BITCOIN_COINS_H

4
src/init.cpp

@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked
{ {
public: public:
CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
bool GetCoins(const uint256 &txid, CCoins &coins) const { bool GetCoins(const COutPoint &outpoint, Coin &coin) const override {
try { try {
return CCoinsViewBacked::GetCoins(txid, coins); return CCoinsViewBacked::GetCoins(outpoint, coin);
} catch(const std::runtime_error& e) { } catch(const std::runtime_error& e) {
uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR);
LogPrintf("Error reading from database: %s\n", e.what()); LogPrintf("Error reading from database: %s\n", e.what());

5
src/net_processing.cpp

@ -911,12 +911,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
recentRejects->reset(); recentRejects->reset();
} }
// Use pcoinsTip->HaveCoinsInCache as a quick approximation to exclude
// requesting or processing some txs which have already been included in a block
return recentRejects->contains(inv.hash) || return recentRejects->contains(inv.hash) ||
mempool.exists(inv.hash) || mempool.exists(inv.hash) ||
mapOrphanTransactions.count(inv.hash) || mapOrphanTransactions.count(inv.hash) ||
pcoinsTip->HaveCoinsInCache(inv.hash); pcoinsTip->HaveCoinsInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1
pcoinsTip->HaveCoinsInCache(COutPoint(inv.hash, 1));
} }
case MSG_BLOCK: case MSG_BLOCK:
case MSG_WITNESS_BLOCK: case MSG_WITNESS_BLOCK:

7
src/qt/transactiondesc.cpp

@ -293,13 +293,12 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
{ {
COutPoint prevout = txin.prevout; COutPoint prevout = txin.prevout;
CCoins prev; Coin prev;
if(pcoinsTip->GetCoins(prevout.hash, prev)) if(pcoinsTip->GetCoins(prevout, prev))
{ {
if (prevout.n < prev.vout.size())
{ {
strHTML += "<li>"; strHTML += "<li>";
const CTxOut &vout = prev.vout[prevout.n]; const CTxOut &vout = prev.out;
CTxDestination address; CTxDestination address;
if (ExtractDestination(vout.scriptPubKey, address)) if (ExtractDestination(vout.scriptPubKey, address))
{ {

20
src/rest.cpp

@ -47,6 +47,9 @@ struct CCoin {
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
CCoin() : nHeight(0) {}
CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) inline void SerializationOp(Stream& s, Operation ser_action)
{ {
@ -509,20 +512,11 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool
for (size_t i = 0; i < vOutPoints.size(); i++) { for (size_t i = 0; i < vOutPoints.size(); i++) {
CCoins coins;
uint256 hash = vOutPoints[i].hash;
bool hit = false; bool hit = false;
if (view.GetCoins(hash, coins)) { Coin coin;
if (coins.IsAvailable(vOutPoints[i].n) && !mempool.isSpent(vOutPoints[i])) { if (view.GetCoins(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
hit = true; hit = true;
// Safe to index into vout here because IsAvailable checked if it's off the end of the array, or if outs.emplace_back(std::move(coin));
// n is valid but points to an already spent output (IsNull).
CCoin coin;
coin.nHeight = coins.nHeight;
coin.out = coins.vout.at(vOutPoints[i].n);
assert(!coin.out.IsNull());
outs.push_back(coin);
}
} }
hits.push_back(hit); hits.push_back(hit);

47
src/rpc/blockchain.cpp

@ -816,24 +816,27 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight;
} }
ss << stats.hashBlock; ss << stats.hashBlock;
uint256 prevkey;
std::map<uint32_t, Coin> outputs;
while (pcursor->Valid()) { while (pcursor->Valid()) {
boost::this_thread::interruption_point(); boost::this_thread::interruption_point();
uint256 key; COutPoint key;
CCoins coins; Coin coin;
if (pcursor->GetKey(key) && pcursor->GetValue(coins)) { if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
std::map<uint32_t, Coin> outputs; if (!outputs.empty() && key.hash != prevkey) {
for (unsigned int i=0; i<coins.vout.size(); i++) { ApplyStats(stats, ss, prevkey, outputs);
CTxOut &out = coins.vout[i]; outputs.clear();
if (!out.IsNull()) {
outputs[i] = Coin(std::move(out), coins.nHeight, coins.fCoinBase);
}
} }
ApplyStats(stats, ss, key, outputs); prevkey = key.hash;
outputs[key.n] = std::move(coin);
} else { } else {
return error("%s: unable to read value", __func__); return error("%s: unable to read value", __func__);
} }
pcursor->Next(); pcursor->Next();
} }
if (!outputs.empty()) {
ApplyStats(stats, ss, prevkey, outputs);
}
stats.hashSerialized = ss.GetHash(); stats.hashSerialized = ss.GetHash();
stats.nDiskSize = view->EstimateSize(); stats.nDiskSize = view->EstimateSize();
return true; return true;
@ -973,35 +976,37 @@ UniValue gettxout(const JSONRPCRequest& request)
std::string strHash = request.params[0].get_str(); std::string strHash = request.params[0].get_str();
uint256 hash(uint256S(strHash)); uint256 hash(uint256S(strHash));
int n = request.params[1].get_int(); int n = request.params[1].get_int();
COutPoint out(hash, n);
bool fMempool = true; bool fMempool = true;
if (request.params.size() > 2) if (request.params.size() > 2)
fMempool = request.params[2].get_bool(); fMempool = request.params[2].get_bool();
CCoins coins; Coin coin;
if (fMempool) { if (fMempool) {
LOCK(mempool.cs); LOCK(mempool.cs);
CCoinsViewMemPool view(pcoinsTip, mempool); CCoinsViewMemPool view(pcoinsTip, mempool);
if (!view.GetCoins(hash, coins) || mempool.isSpent(COutPoint(hash, n))) // TODO: this should be done by the CCoinsViewMemPool if (!view.GetCoins(out, coin) || mempool.isSpent(out)) { // TODO: filtering spent coins should be done by the CCoinsViewMemPool
return NullUniValue; return NullUniValue;
}
} else { } else {
if (!pcoinsTip->GetCoins(hash, coins)) if (!pcoinsTip->GetCoins(out, coin)) {
return NullUniValue; return NullUniValue;
}
} }
if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
return NullUniValue;
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
CBlockIndex *pindex = it->second; CBlockIndex *pindex = it->second;
ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex())); ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex()));
if ((unsigned int)coins.nHeight == MEMPOOL_HEIGHT) if (coin.nHeight == MEMPOOL_HEIGHT) {
ret.push_back(Pair("confirmations", 0)); ret.push_back(Pair("confirmations", 0));
else } else {
ret.push_back(Pair("confirmations", pindex->nHeight - coins.nHeight + 1)); ret.push_back(Pair("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1)));
ret.push_back(Pair("value", ValueFromAmount(coins.vout[n].nValue))); }
ret.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));
UniValue o(UniValue::VOBJ); UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coins.vout[n].scriptPubKey, o, true); ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
ret.push_back(Pair("scriptPubKey", o)); ret.push_back(Pair("scriptPubKey", o));
ret.push_back(Pair("coinbase", coins.fCoinBase)); ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
return ret; return ret;
} }

7
src/rpc/rawtransaction.cpp

@ -219,9 +219,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
pblockindex = mapBlockIndex[hashBlock]; pblockindex = mapBlockIndex[hashBlock];
} else { } else {
CCoins coins; const Coin& coin = AccessByTxid(*pcoinsTip, oneTxid);
if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height()) if (!coin.IsPruned() && coin.nHeight > 0 && coin.nHeight <= chainActive.Height()) {
pblockindex = chainActive[coins.nHeight]; pblockindex = chainActive[coin.nHeight];
}
} }
if (pblockindex == NULL) if (pblockindex == NULL)

239
src/test/coins_tests.cpp

@ -34,27 +34,27 @@ bool operator==(const Coin &a, const Coin &b) {
class CCoinsViewTest : public CCoinsView class CCoinsViewTest : public CCoinsView
{ {
uint256 hashBestBlock_; uint256 hashBestBlock_;
std::map<uint256, CCoins> map_; std::map<COutPoint, Coin> map_;
public: public:
bool GetCoins(const uint256& txid, CCoins& coins) const bool GetCoins(const COutPoint& outpoint, Coin& coin) const
{ {
std::map<uint256, CCoins>::const_iterator it = map_.find(txid); std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
if (it == map_.end()) { if (it == map_.end()) {
return false; return false;
} }
coins = it->second; coin = it->second;
if (coins.IsPruned() && insecure_rand() % 2 == 0) { if (coin.IsPruned() && insecure_rand() % 2 == 0) {
// Randomly return false in case of an empty entry. // Randomly return false in case of an empty entry.
return false; return false;
} }
return true; return true;
} }
bool HaveCoins(const uint256& txid) const bool HaveCoins(const COutPoint& outpoint) const
{ {
CCoins coins; Coin coin;
return GetCoins(txid, coins); return GetCoins(outpoint, coin);
} }
uint256 GetBestBlock() const { return hashBestBlock_; } uint256 GetBestBlock() const { return hashBestBlock_; }
@ -106,7 +106,7 @@ static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
// This is a large randomized insert/remove simulation test on a variable-size // This is a large randomized insert/remove simulation test on a variable-size
// stack of caches on top of CCoinsViewTest. // stack of caches on top of CCoinsViewTest.
// //
// It will randomly create/update/delete CCoins entries to a tip of caches, with // 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 // 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. // new tip is added to the stack of caches, or the tip is flushed and removed.
// //
@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
bool missed_an_entry = false; bool missed_an_entry = false;
// A simple map to track what we expect the cache stack to represent. // A simple map to track what we expect the cache stack to represent.
std::map<uint256, CCoins> result; std::map<COutPoint, Coin> result;
// The cache stack. // The cache stack.
CCoinsViewTest base; // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -142,39 +142,38 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
// Do a random modification. // Do a random modification.
{ {
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration. uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
CCoins& coins = result[txid]; Coin& coin = result[COutPoint(txid, 0)];
const Coin& entry = stack.back()->AccessCoin(COutPoint(txid, 0)); const Coin& entry = stack.back()->AccessCoin(COutPoint(txid, 0));
BOOST_CHECK((entry.IsPruned() && coins.IsPruned()) || entry == Coin(coins.vout[0], coins.nHeight, coins.fCoinBase)); BOOST_CHECK(coin == entry);
if (insecure_rand() % 5 == 0 || coins.IsPruned()) { if (insecure_rand() % 5 == 0 || coin.IsPruned()) {
if (coins.IsPruned()) { if (coin.IsPruned()) {
added_an_entry = true; added_an_entry = true;
} else { } else {
updated_an_entry = true; updated_an_entry = true;
} }
coins.vout.resize(1); coin.out.nValue = insecure_rand();
coins.vout[0].nValue = insecure_rand(); coin.nHeight = 1;
} else { } else {
coins.Clear(); coin.Clear();
removed_an_entry = true; removed_an_entry = true;
} }
if (coins.IsPruned()) { if (coin.IsPruned()) {
stack.back()->SpendCoin(COutPoint(txid, 0)); stack.back()->SpendCoin(COutPoint(txid, 0));
} else { } else {
stack.back()->AddCoin(COutPoint(txid, 0), Coin(coins.vout[0], coins.nHeight, coins.fCoinBase), true); stack.back()->AddCoin(COutPoint(txid, 0), Coin(coin), true);
} }
} }
// Once every 1000 iterations and at the end, verify the full cache. // Once every 1000 iterations and at the end, verify the full cache.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { for (auto it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first); const Coin& coin = stack.back()->AccessCoin(it->first);
if (coins) { BOOST_CHECK(coin == it->second);
BOOST_CHECK(*coins == it->second); if (coin.IsPruned()) {
found_an_entry = true;
} else {
BOOST_CHECK(it->second.IsPruned());
missed_an_entry = true; missed_an_entry = true;
} else {
found_an_entry = true;
} }
} }
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) { BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
@ -229,19 +228,19 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
BOOST_CHECK(missed_an_entry); BOOST_CHECK(missed_an_entry);
} }
typedef std::tuple<CTransaction,CTxUndo,CCoins> TxData;
// Store of all necessary tx and undo data for next test // Store of all necessary tx and undo data for next test
std::map<uint256, TxData> alltxs; typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
UtxoData utxoData;
TxData &FindRandomFrom(const std::set<uint256> &txidset) {
assert(txidset.size()); UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
std::set<uint256>::iterator txIt = txidset.lower_bound(GetRandHash()); assert(utxoSet.size());
if (txIt == txidset.end()) { auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
txIt = txidset.begin(); if (utxoSetIt == utxoSet.end()) {
utxoSetIt = utxoSet.begin();
} }
std::map<uint256, TxData>::iterator txdit = alltxs.find(*txIt); auto utxoDataIt = utxoData.find(*utxoSetIt);
assert(txdit != alltxs.end()); assert(utxoDataIt != utxoData.end());
return txdit->second; return utxoDataIt;
} }
@ -254,7 +253,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
{ {
bool spent_a_duplicate_coinbase = false; bool spent_a_duplicate_coinbase = false;
// A simple map to track what we expect the cache stack to represent. // A simple map to track what we expect the cache stack to represent.
std::map<uint256, CCoins> result; std::map<COutPoint, Coin> result;
// The cache stack. // The cache stack.
CCoinsViewTest base; // A CCoinsViewTest at the bottom. CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -262,10 +261,10 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
// Track the txids we've used in various sets // Track the txids we've used in various sets
std::set<uint256> coinbaseids; std::set<COutPoint> coinbaseids;
std::set<uint256> disconnectedids; std::set<COutPoint> disconnectedids;
std::set<uint256> duplicateids; std::set<COutPoint> duplicateids;
std::set<uint256> utxoset; std::set<COutPoint> utxoset;
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
uint32_t randiter = insecure_rand(); uint32_t randiter = insecure_rand();
@ -277,22 +276,22 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
tx.vout.resize(1); tx.vout.resize(1);
tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
unsigned int height = insecure_rand(); unsigned int height = insecure_rand();
CCoins oldcoins; Coin oldcoins;
// 2/20 times create a new coinbase // 2/20 times create a new coinbase
if (randiter % 20 < 2 || coinbaseids.size() < 10) { if (randiter % 20 < 2 || coinbaseids.size() < 10) {
// 1/10 of those times create a duplicate coinbase // 1/10 of those times create a duplicate coinbase
if (insecure_rand() % 10 == 0 && coinbaseids.size()) { if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
TxData &txd = FindRandomFrom(coinbaseids); auto utxod = FindRandomFrom(coinbaseids);
// Reuse the exact same coinbase // Reuse the exact same coinbase
tx = std::get<0>(txd); tx = std::get<0>(utxod->second);
// shouldn't be available for reconnection if its been duplicated // shouldn't be available for reconnection if its been duplicated
disconnectedids.erase(tx.GetHash()); disconnectedids.erase(utxod->first);
duplicateids.insert(tx.GetHash()); duplicateids.insert(utxod->first);
} }
else { else {
coinbaseids.insert(tx.GetHash()); coinbaseids.insert(COutPoint(tx.GetHash(), 0));
} }
assert(CTransaction(tx).IsCoinBase()); assert(CTransaction(tx).IsCoinBase());
} }
@ -300,85 +299,82 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
// 17/20 times reconnect previous or add a regular tx // 17/20 times reconnect previous or add a regular tx
else { else {
uint256 prevouthash; COutPoint prevout;
// 1/20 times reconnect a previously disconnected tx // 1/20 times reconnect a previously disconnected tx
if (randiter % 20 == 2 && disconnectedids.size()) { if (randiter % 20 == 2 && disconnectedids.size()) {
TxData &txd = FindRandomFrom(disconnectedids); auto utxod = FindRandomFrom(disconnectedids);
tx = std::get<0>(txd); tx = std::get<0>(utxod->second);
prevouthash = tx.vin[0].prevout.hash; prevout = tx.vin[0].prevout;
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevouthash)) { if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
disconnectedids.erase(tx.GetHash()); disconnectedids.erase(utxod->first);
continue; continue;
} }
// If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate
if (utxoset.count(tx.GetHash())) { if (utxoset.count(utxod->first)) {
assert(CTransaction(tx).IsCoinBase()); assert(CTransaction(tx).IsCoinBase());
assert(duplicateids.count(tx.GetHash())); assert(duplicateids.count(utxod->first));
} }
disconnectedids.erase(tx.GetHash()); disconnectedids.erase(utxod->first);
} }
// 16/20 times create a regular tx // 16/20 times create a regular tx
else { else {
TxData &txd = FindRandomFrom(utxoset); auto utxod = FindRandomFrom(utxoset);
prevouthash = std::get<0>(txd).GetHash(); prevout = utxod->first;
// Construct the tx to spend the coins of prevouthash // Construct the tx to spend the coins of prevouthash
tx.vin[0].prevout.hash = prevouthash; tx.vin[0].prevout = prevout;
tx.vin[0].prevout.n = 0;
assert(!CTransaction(tx).IsCoinBase()); assert(!CTransaction(tx).IsCoinBase());
} }
// In this simple test coins only have two states, spent or unspent, save the unspent state to restore // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
oldcoins = result[prevouthash]; oldcoins = result[prevout];
// Update the expected result of prevouthash to know these coins are spent // Update the expected result of prevouthash to know these coins are spent
result[prevouthash].Clear(); result[prevout].Clear();
utxoset.erase(prevouthash); utxoset.erase(prevout);
// The test is designed to ensure spending a duplicate coinbase will work properly // 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 that ever happens and not resurrect the previously overwritten coinbase
if (duplicateids.count(prevouthash)) { if (duplicateids.count(prevout)) {
spent_a_duplicate_coinbase = true; spent_a_duplicate_coinbase = true;
} }
} }
// Update the expected result to know about the new output coins // Update the expected result to know about the new output coins
result[tx.GetHash()].FromTx(tx, height); assert(tx.vout.size() == 1);
const COutPoint outpoint(tx.GetHash(), 0);
result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase());
// Call UpdateCoins on the top cache // Call UpdateCoins on the top cache
CTxUndo undo; CTxUndo undo;
UpdateCoins(tx, *(stack.back()), undo, height); UpdateCoins(tx, *(stack.back()), undo, height);
// Update the utxo set for future spends // Update the utxo set for future spends
utxoset.insert(tx.GetHash()); utxoset.insert(outpoint);
// Track this tx and undo info to use later // Track this tx and undo info to use later
alltxs.insert(std::make_pair(tx.GetHash(),std::make_tuple(tx,undo,oldcoins))); utxoData.emplace(outpoint, std::make_tuple(tx,undo,oldcoins));
} else if (utxoset.size()) { } else if (utxoset.size()) {
//1/20 times undo a previous transaction //1/20 times undo a previous transaction
TxData &txd = FindRandomFrom(utxoset); auto utxod = FindRandomFrom(utxoset);
CTransaction &tx = std::get<0>(txd);
CTxUndo &undo = std::get<1>(txd);
CCoins &origcoins = std::get<2>(txd);
uint256 undohash = tx.GetHash(); CTransaction &tx = std::get<0>(utxod->second);
CTxUndo &undo = std::get<1>(utxod->second);
Coin &origcoins = std::get<2>(utxod->second);
// Update the expected result // Update the expected result
// Remove new outputs // Remove new outputs
result[undohash].Clear(); result[utxod->first].Clear();
// If not coinbase restore prevout // If not coinbase restore prevout
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
result[tx.vin[0].prevout.hash] = origcoins; result[tx.vin[0].prevout] = origcoins;
} }
// Disconnect the tx from the current UTXO // Disconnect the tx from the current UTXO
// See code in DisconnectBlock // See code in DisconnectBlock
// remove outputs // remove outputs
{ stack.back()->SpendCoin(utxod->first);
stack.back()->SpendCoin(COutPoint(undohash, 0));
}
// restore inputs // restore inputs
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
const COutPoint &out = tx.vin[0].prevout; const COutPoint &out = tx.vin[0].prevout;
@ -386,23 +382,19 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
ApplyTxInUndo(std::move(coin), *(stack.back()), out); ApplyTxInUndo(std::move(coin), *(stack.back()), out);
} }
// Store as a candidate for reconnection // Store as a candidate for reconnection
disconnectedids.insert(undohash); disconnectedids.insert(utxod->first);
// Update the utxoset // Update the utxoset
utxoset.erase(undohash); utxoset.erase(utxod->first);
if (!tx.IsCoinBase()) if (!tx.IsCoinBase())
utxoset.insert(tx.vin[0].prevout.hash); utxoset.insert(tx.vin[0].prevout);
} }
// Once every 1000 iterations and at the end, verify the full cache. // Once every 1000 iterations and at the end, verify the full cache.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) { for (auto it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first); const Coin& coin = stack.back()->AccessCoin(it->first);
if (coins) { BOOST_CHECK(coin == it->second);
BOOST_CHECK(*coins == it->second);
} else {
BOOST_CHECK(it->second.IsPruned());
}
} }
} }
@ -443,50 +435,36 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
BOOST_AUTO_TEST_CASE(ccoins_serialization) BOOST_AUTO_TEST_CASE(ccoins_serialization)
{ {
// Good example // Good example
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION); CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
CCoins cc1; Coin cc1;
ss1 >> cc1; ss1 >> cc1;
BOOST_CHECK_EQUAL(cc1.fCoinBase, false); BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
BOOST_CHECK_EQUAL(cc1.nHeight, 203998); BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
BOOST_CHECK_EQUAL(cc1.vout.size(), 2); BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false); BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true);
BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL);
BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example // Good example
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION); CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
CCoins cc2; Coin cc2;
ss2 >> cc2; ss2 >> cc2;
BOOST_CHECK_EQUAL(cc2.fCoinBase, true); BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
BOOST_CHECK_EQUAL(cc2.nHeight, 120891); BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
BOOST_CHECK_EQUAL(cc2.vout.size(), 17); BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
for (int i = 0; i < 17; i++) { BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16);
}
BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952);
BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("61b01caab50f1b8e9c50a5057eb43c2d9563a4ee"))))));
BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397);
BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example // Smallest possible example
CDataStream ssx(SER_DISK, CLIENT_VERSION); CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), ""); Coin cc3;
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
CCoins cc3;
ss3 >> cc3; ss3 >> cc3;
BOOST_CHECK_EQUAL(cc3.fCoinBase, false); BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
BOOST_CHECK_EQUAL(cc3.nHeight, 0); BOOST_CHECK_EQUAL(cc3.nHeight, 0);
BOOST_CHECK_EQUAL(cc3.vout.size(), 1); BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true); BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
// scriptPubKey that ends beyond the end of the stream // scriptPubKey that ends beyond the end of the stream
CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION); CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
try { try {
CCoins cc4; Coin cc4;
ss4 >> cc4; ss4 >> cc4;
BOOST_CHECK_MESSAGE(false, "We should have thrown"); BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) { } catch (const std::ios_base::failure& e) {
@ -497,17 +475,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
uint64_t x = 3000000000ULL; uint64_t x = 3000000000ULL;
tmp << VARINT(x); tmp << VARINT(x);
BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00"); BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION); CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
try { try {
CCoins cc5; Coin cc5;
ss5 >> cc5; ss5 >> cc5;
BOOST_CHECK_MESSAGE(false, "We should have thrown"); BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) { } catch (const std::ios_base::failure& e) {
} }
} }
const static uint256 TXID; const static COutPoint OUTPOINT;
const static COutPoint OUTPOINT = {uint256(), 0};
const static CAmount PRUNED = -1; const static CAmount PRUNED = -1;
const static CAmount ABSENT = -2; const static CAmount ABSENT = -2;
const static CAmount FAIL = -3; const static CAmount FAIL = -3;
@ -522,15 +499,15 @@ const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
const static auto CLEAN_FLAGS = {char(0), FRESH}; const static auto CLEAN_FLAGS = {char(0), FRESH};
const static auto ABSENT_FLAGS = {NO_ENTRY}; const static auto ABSENT_FLAGS = {NO_ENTRY};
void SetCoinsValue(CAmount value, CCoins& coins) void SetCoinsValue(CAmount value, Coin& coin)
{ {
assert(value != ABSENT); assert(value != ABSENT);
coins.Clear(); coin.Clear();
assert(coins.IsPruned()); assert(coin.IsPruned());
if (value != PRUNED) { if (value != PRUNED) {
coins.vout.emplace_back(); coin.out.nValue = value;
coins.vout.back().nValue = value; coin.nHeight = 1;
assert(!coins.IsPruned()); assert(!coin.IsPruned());
} }
} }
@ -544,24 +521,22 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
CCoinsCacheEntry entry; CCoinsCacheEntry entry;
entry.flags = flags; entry.flags = flags;
SetCoinsValue(value, entry.coins); SetCoinsValue(value, entry.coins);
auto inserted = map.emplace(TXID, std::move(entry)); auto inserted = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second); assert(inserted.second);
return inserted.first->second.coins.DynamicMemoryUsage(); return inserted.first->second.coins.DynamicMemoryUsage();
} }
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags) void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
{ {
auto it = map.find(TXID); auto it = map.find(OUTPOINT);
if (it == map.end()) { if (it == map.end()) {
value = ABSENT; value = ABSENT;
flags = NO_ENTRY; flags = NO_ENTRY;
} else { } else {
if (it->second.coins.IsPruned()) { if (it->second.coins.IsPruned()) {
assert(it->second.coins.vout.size() == 0);
value = PRUNED; value = PRUNED;
} else { } else {
assert(it->second.coins.vout.size() == 1); value = it->second.coins.out.nValue;
value = it->second.coins.vout[0].nValue;
} }
flags = it->second.flags; flags = it->second.flags;
assert(flags != NO_ENTRY); assert(flags != NO_ENTRY);

4
src/test/test_bitcoin_fuzzy.cpp

@ -168,8 +168,8 @@ int do_fuzz()
{ {
try try
{ {
CCoins block; Coin coin;
ds >> block; ds >> coin;
} catch (const std::ios_base::failure& e) {return 0;} } catch (const std::ios_base::failure& e) {return 0;}
break; break;
} }

66
src/txdb.cpp

@ -14,6 +14,7 @@
#include <boost/thread.hpp> #include <boost/thread.hpp>
static const char DB_COIN = 'C';
static const char DB_COINS = 'c'; static const char DB_COINS = 'c';
static const char DB_BLOCK_FILES = 'f'; static const char DB_BLOCK_FILES = 'f';
static const char DB_TXINDEX = 't'; static const char DB_TXINDEX = 't';
@ -24,17 +25,40 @@ static const char DB_FLAG = 'F';
static const char DB_REINDEX_FLAG = 'R'; static const char DB_REINDEX_FLAG = 'R';
static const char DB_LAST_BLOCK = 'l'; static const char DB_LAST_BLOCK = 'l';
namespace {
struct CoinsEntry {
COutPoint* outpoint;
char key;
CoinsEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN) {}
template<typename Stream>
void Serialize(Stream &s) const {
s << key;
s << outpoint->hash;
s << VARINT(outpoint->n);
}
template<typename Stream>
void Unserialize(Stream& s) {
s >> key;
s >> outpoint->hash;
s >> VARINT(outpoint->n);
}
};
}
CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true)
{ {
} }
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewDB::GetCoins(const COutPoint &outpoint, Coin &coin) const {
return db.Read(std::make_pair(DB_COINS, txid), coins); return db.Read(CoinsEntry(&outpoint), coin);
} }
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const { bool CCoinsViewDB::HaveCoins(const COutPoint &outpoint) const {
return db.Exists(std::make_pair(DB_COINS, txid)); return db.Exists(CoinsEntry(&outpoint));
} }
uint256 CCoinsViewDB::GetBestBlock() const { uint256 CCoinsViewDB::GetBestBlock() const {
@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
size_t changed = 0; size_t changed = 0;
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { if (it->second.flags & CCoinsCacheEntry::DIRTY) {
CoinsEntry entry(&it->first);
if (it->second.coins.IsPruned()) if (it->second.coins.IsPruned())
batch.Erase(std::make_pair(DB_COINS, it->first)); batch.Erase(entry);
else else
batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins); batch.Write(entry, it->second.coins);
changed++; changed++;
} }
count++; count++;
@ -63,13 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
if (!hashBlock.IsNull()) if (!hashBlock.IsNull())
batch.Write(DB_BEST_BLOCK, hashBlock); batch.Write(DB_BEST_BLOCK, hashBlock);
LogPrint(BCLog::COINDB, "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); bool ret = db.WriteBatch(batch);
return 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 size_t CCoinsViewDB::EstimateSize() const
{ {
return db.EstimateSize(DB_COINS, (char)(DB_COINS+1)); return db.EstimateSize(DB_COIN, (char)(DB_COIN+1));
} }
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) { CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {
@ -101,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const
/* It seems that there are no "const iterators" for LevelDB. Since we /* 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 only need read operations on it, use a const-cast to get around
that restriction. */ that restriction. */
i->pcursor->Seek(DB_COINS); i->pcursor->Seek(DB_COIN);
// Cache key of first record // Cache key of first record
if (i->pcursor->Valid()) { if (i->pcursor->Valid()) {
i->pcursor->GetKey(i->keyTmp); CoinsEntry entry(&i->keyTmp.second);
i->pcursor->GetKey(entry);
i->keyTmp.first = entry.key;
} else { } else {
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
} }
return i; return i;
} }
bool CCoinsViewDBCursor::GetKey(uint256 &key) const bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
{ {
// Return cached key // Return cached key
if (keyTmp.first == DB_COINS) { if (keyTmp.first == DB_COIN) {
key = keyTmp.second; key = keyTmp.second;
return true; return true;
} }
return false; return false;
} }
bool CCoinsViewDBCursor::GetValue(CCoins &coins) const bool CCoinsViewDBCursor::GetValue(Coin &coin) const
{ {
return pcursor->GetValue(coins); return pcursor->GetValue(coin);
} }
unsigned int CCoinsViewDBCursor::GetValueSize() const unsigned int CCoinsViewDBCursor::GetValueSize() const
@ -133,14 +161,18 @@ unsigned int CCoinsViewDBCursor::GetValueSize() const
bool CCoinsViewDBCursor::Valid() const bool CCoinsViewDBCursor::Valid() const
{ {
return keyTmp.first == DB_COINS; return keyTmp.first == DB_COIN;
} }
void CCoinsViewDBCursor::Next() void CCoinsViewDBCursor::Next()
{ {
pcursor->Next(); pcursor->Next();
if (!pcursor->Valid() || !pcursor->GetKey(keyTmp)) CoinsEntry entry(&keyTmp.second);
if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
} 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) { bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {

16
src/txdb.h

@ -73,11 +73,11 @@ protected:
public: public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const; uint256 GetBestBlock() const override;
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
CCoinsViewCursor *Cursor() const; CCoinsViewCursor *Cursor() const override;
size_t EstimateSize() const override; size_t EstimateSize() const override;
}; };
@ -88,8 +88,8 @@ class CCoinsViewDBCursor: public CCoinsViewCursor
public: public:
~CCoinsViewDBCursor() {} ~CCoinsViewDBCursor() {}
bool GetKey(uint256 &key) const; bool GetKey(COutPoint &key) const;
bool GetValue(CCoins &coins) const; bool GetValue(Coin &coin) const;
unsigned int GetValueSize() const; unsigned int GetValueSize() const;
bool Valid() const; bool Valid() const;
@ -99,7 +99,7 @@ private:
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn): CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn):
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
std::unique_ptr<CDBIterator> pcursor; std::unique_ptr<CDBIterator> pcursor;
std::pair<char, uint256> keyTmp; std::pair<char, COutPoint> keyTmp;
friend class CCoinsViewDB; friend class CCoinsViewDB;
}; };

40
src/txmempool.cpp

@ -524,9 +524,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
if (it2 != mapTx.end()) if (it2 != mapTx.end())
continue; continue;
const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash); const Coin &coin = pcoins->AccessCoin(txin.prevout);
if (nCheckFrequency != 0) assert(coins); if (nCheckFrequency != 0) assert(!coin.IsPruned());
if (!coins || (coins->IsCoinBase() && ((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY)) { if (coin.IsPruned() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
txToRemove.insert(it); txToRemove.insert(it);
break; break;
} }
@ -654,8 +654,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
parentSigOpCost += it2->GetSigOpCost(); parentSigOpCost += it2->GetSigOpCost();
} }
} else { } else {
const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash); assert(pcoins->HaveCoins(txin.prevout));
assert(coins && coins->IsAvailable(txin.prevout.n));
} }
// Check whether its inputs are marked in mapNextTx. // Check whether its inputs are marked in mapNextTx.
auto it3 = mapNextTx.find(txin.prevout); auto it3 = mapNextTx.find(txin.prevout);
@ -891,20 +890,24 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const { bool CCoinsViewMemPool::GetCoins(const COutPoint &outpoint, Coin &coin) const {
// If an entry in the mempool exists, always return that one, as it's guaranteed to never // If an entry in the mempool exists, always return that one, as it's guaranteed to never
// conflict with the underlying cache, and it cannot have pruned entries (as it contains full) // conflict with the underlying cache, and it cannot have pruned entries (as it contains full)
// transactions. First checking the underlying cache risks returning a pruned entry instead. // transactions. First checking the underlying cache risks returning a pruned entry instead.
CTransactionRef ptx = mempool.get(txid); CTransactionRef ptx = mempool.get(outpoint.hash);
if (ptx) { if (ptx) {
coins = CCoins(*ptx, MEMPOOL_HEIGHT); if (outpoint.n < ptx->vout.size()) {
return true; coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
return true;
} else {
return false;
}
} }
return (base->GetCoins(txid, coins) && !coins.IsPruned()); return (base->GetCoins(outpoint, coin) && !coin.IsPruned());
} }
bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const { bool CCoinsViewMemPool::HaveCoins(const COutPoint &outpoint) const {
return mempool.exists(txid) || base->HaveCoins(txid); return mempool.exists(outpoint) || base->HaveCoins(outpoint);
} }
size_t CTxMemPool::DynamicMemoryUsage() const { size_t CTxMemPool::DynamicMemoryUsage() const {
@ -1015,7 +1018,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
} }
} }
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining) { void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) {
LOCK(cs); LOCK(cs);
unsigned nTxnRemoved = 0; unsigned nTxnRemoved = 0;
@ -1046,11 +1049,10 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
if (pvNoSpendsRemaining) { if (pvNoSpendsRemaining) {
BOOST_FOREACH(const CTransaction& tx, txn) { BOOST_FOREACH(const CTransaction& tx, txn) {
BOOST_FOREACH(const CTxIn& txin, tx.vin) { BOOST_FOREACH(const CTxIn& txin, tx.vin) {
if (exists(txin.prevout.hash)) if (exists(txin.prevout.hash)) continue;
continue; if (!mapNextTx.count(txin.prevout)) {
auto iter = mapNextTx.lower_bound(COutPoint(txin.prevout.hash, 0)); pvNoSpendsRemaining->push_back(txin.prevout);
if (iter == mapNextTx.end() || iter->first->hash != txin.prevout.hash) }
pvNoSpendsRemaining->push_back(txin.prevout.hash);
} }
} }
} }
@ -1067,3 +1069,5 @@ bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLi
return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit && return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit &&
it->GetCountWithDescendants() < chainLimit); it->GetCountWithDescendants() < chainLimit);
} }
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}

33
src/txmempool.h

@ -30,8 +30,8 @@
class CAutoFile; class CAutoFile;
class CBlockIndex; class CBlockIndex;
/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF; static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
struct LockPoints struct LockPoints
{ {
@ -321,6 +321,20 @@ enum class MemPoolRemovalReason {
REPLACED //! Removed for replacement REPLACED //! Removed for replacement
}; };
class SaltedTxidHasher
{
private:
/** Salt */
const uint64_t k0, k1;
public:
SaltedTxidHasher();
size_t operator()(const uint256& txid) const {
return SipHashUint256(k0, k1, txid);
}
};
/** /**
* CTxMemPool stores valid-according-to-the-current-best-chain transactions * CTxMemPool stores valid-according-to-the-current-best-chain transactions
* that may be included in the next block. * that may be included in the next block.
@ -570,10 +584,10 @@ public:
CFeeRate GetMinFee(size_t sizelimit) const; CFeeRate GetMinFee(size_t sizelimit) const;
/** Remove transactions from the mempool until its dynamic size is <= sizelimit. /** Remove transactions from the mempool until its dynamic size is <= sizelimit.
* pvNoSpendsRemaining, if set, will be populated with the list of transactions * pvNoSpendsRemaining, if set, will be populated with the list of outpoints
* which are not in mempool which no longer have any spends in this mempool. * which are not in mempool which no longer have any spends in this mempool.
*/ */
void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining=NULL); void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining=NULL);
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */ /** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
int Expire(int64_t time); int Expire(int64_t time);
@ -599,6 +613,13 @@ public:
return (mapTx.count(hash) != 0); return (mapTx.count(hash) != 0);
} }
bool exists(const COutPoint& outpoint) const
{
LOCK(cs);
auto it = mapTx.find(outpoint.hash);
return (it != mapTx.end() && outpoint.n < it->GetTx().vout.size());
}
CTransactionRef get(const uint256& hash) const; CTransactionRef get(const uint256& hash) const;
TxMempoolInfo info(const uint256& hash) const; TxMempoolInfo info(const uint256& hash) const;
std::vector<TxMempoolInfo> infoAll() const; std::vector<TxMempoolInfo> infoAll() const;
@ -658,8 +679,8 @@ protected:
public: public:
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const COutPoint &outpoint) const;
}; };
#endif // BITCOIN_TXMEMPOOL_H #endif // BITCOIN_TXMEMPOOL_H

54
src/validation.cpp

@ -268,15 +268,15 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool
prevheights.resize(tx.vin.size()); prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex]; const CTxIn& txin = tx.vin[txinIndex];
CCoins coins; Coin coin;
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) { if (!viewMemPool.GetCoins(txin.prevout, coin)) {
return error("%s: Missing input", __func__); return error("%s: Missing input", __func__);
} }
if (coins.nHeight == MEMPOOL_HEIGHT) { if (coin.nHeight == MEMPOOL_HEIGHT) {
// Assume all mempool transaction confirm in the next block // Assume all mempool transaction confirm in the next block
prevheights[txinIndex] = tip->nHeight + 1; prevheights[txinIndex] = tip->nHeight + 1;
} else { } else {
prevheights[txinIndex] = coins.nHeight; prevheights[txinIndex] = coin.nHeight;
} }
} }
lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index); lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
@ -315,9 +315,9 @@ void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired);
} }
std::vector<uint256> vNoSpendsRemaining; std::vector<COutPoint> vNoSpendsRemaining;
pool.TrimToSize(limit, &vNoSpendsRemaining); pool.TrimToSize(limit, &vNoSpendsRemaining);
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining) BOOST_FOREACH(const COutPoint& removed, vNoSpendsRemaining)
pcoinsTip->Uncache(removed); pcoinsTip->Uncache(removed);
} }
@ -344,7 +344,7 @@ static bool IsCurrentForFeeEstimation()
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree, bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree,
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<uint256>& vHashTxnToUncache) bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<COutPoint>& vHashTxnToUncache)
{ {
const CTransaction& tx = *ptx; const CTransaction& tx = *ptx;
const uint256 hash = tx.GetHash(); const uint256 hash = tx.GetHash();
@ -437,30 +437,30 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
view.SetBackend(viewMemPool); view.SetBackend(viewMemPool);
// do we already have it? // do we already have it?
bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(hash); for (size_t out = 0; out < tx.vout.size(); out++) {
if (view.HaveCoins(hash)) { COutPoint outpoint(hash, out);
if (!fHadTxInCache) bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(outpoint);
vHashTxnToUncache.push_back(hash); if (view.HaveCoins(outpoint)) {
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known"); if (!fHadTxInCache) {
vHashTxnToUncache.push_back(outpoint);
}
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
}
} }
// do all inputs exist? // do all inputs exist?
// Note that this does not check for the presence of actual outputs (see the next check for that),
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
BOOST_FOREACH(const CTxIn txin, tx.vin) { BOOST_FOREACH(const CTxIn txin, tx.vin) {
if (!pcoinsTip->HaveCoinsInCache(txin.prevout.hash)) if (!pcoinsTip->HaveCoinsInCache(txin.prevout)) {
vHashTxnToUncache.push_back(txin.prevout.hash); vHashTxnToUncache.push_back(txin.prevout);
if (!view.HaveCoins(txin.prevout.hash)) { }
if (pfMissingInputs) if (!view.HaveCoins(txin.prevout)) {
if (pfMissingInputs) {
*pfMissingInputs = true; *pfMissingInputs = true;
}
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid() return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
} }
} }
// are the actual inputs available?
if (!view.HaveInputs(tx))
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
// Bring the best block into scope // Bring the best block into scope
view.GetBestBlock(); view.GetBestBlock();
@ -763,10 +763,10 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced, bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool fOverrideMempoolLimit, const CAmount nAbsurdFee) bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
{ {
std::vector<uint256> vHashTxToUncache; std::vector<COutPoint> vHashTxToUncache;
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache); bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
if (!res) { if (!res) {
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache) BOOST_FOREACH(const COutPoint& hashTx, vHashTxToUncache)
pcoinsTip->Uncache(hashTx); pcoinsTip->Uncache(hashTx);
} }
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
@ -1762,12 +1762,12 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
} }
// Flush best chain related state. This can only be done if the blocks / block index write was also done. // Flush best chain related state. This can only be done if the blocks / block index write was also done.
if (fDoFullFlush) { if (fDoFullFlush) {
// Typical CCoins structures on disk are around 128 bytes in size. // Typical Coin structures on disk are around 48 bytes in size.
// Pushing a new one to the database can cause it to be written // Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is already // twice (once in the log, and once in the tables). This is already
// an overestimation, as most will delete an existing entry or // an overestimation, as most will delete an existing entry or
// overwrite one. Still, use a conservative safety factor of 2. // overwrite one. Still, use a conservative safety factor of 2.
if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize())) if (!CheckDiskSpace(48 * 2 * 2 * pcoinsTip->GetCacheSize()))
return state.Error("out of disk space"); return state.Error("out of disk space");
// Flush the chainstate (which may refer to block index entries). // Flush the chainstate (which may refer to block index entries).
if (!pcoinsTip->Flush()) if (!pcoinsTip->Flush())
@ -1848,7 +1848,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
} }
} }
} }
LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utx)", __func__, LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__,
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), chainActive.Tip()->nVersion, chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), chainActive.Tip()->nVersion,
log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx, log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),

Loading…
Cancel
Save