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.
pull/1/head
Pieter Wuille 6 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) @@ -42,41 +42,40 @@ bool CCoins::Spend(uint32_t nPos)
return true;
}
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
bool CCoinsView::GetCoins(const COutPoint &outpoint, Coin &coin) const { return false; }
bool CCoinsView::HaveCoins(const COutPoint &outpoint) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
bool CCoinsViewBacked::GetCoins(const COutPoint &outpoint, Coin &coin) const { return base->GetCoins(outpoint, coin); }
bool CCoinsViewBacked::HaveCoins(const COutPoint &outpoint) const { return base->HaveCoins(outpoint); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
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 {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
}
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
CCoinsMap::iterator it = cacheCoins.find(txid);
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const COutPoint &outpoint) const {
CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end())
return it;
CCoins tmp;
if (!base->GetCoins(txid, tmp))
Coin tmp;
if (!base->GetCoins(outpoint, tmp))
return cacheCoins.end();
CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first;
tmp.swap(ret->second.coins);
CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;
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.
ret->second.flags = CCoinsCacheEntry::FRESH;
}
@ -84,10 +83,10 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { @@ -84,10 +83,10 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
return ret;
}
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
CCoinsMap::const_iterator it = FetchCoins(txid);
bool CCoinsViewCache::GetCoins(const COutPoint &outpoint, Coin &coin) const {
CCoinsMap::const_iterator it = FetchCoins(outpoint);
if (it != cacheCoins.end()) {
coins = it->second.coins;
coin = it->second.coins;
return true;
}
return false;
@ -98,23 +97,18 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi @@ -98,23 +97,18 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
if (coin.out.scriptPubKey.IsUnspendable()) return;
CCoinsMap::iterator it;
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;
if (!inserted) {
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
}
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");
}
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.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.coins = std::move(coin);
it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
}
@ -130,58 +124,38 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) { @@ -130,58 +124,38 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) {
}
void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
CCoinsMap::iterator it = FetchCoins(outpoint.hash);
CCoinsMap::iterator it = FetchCoins(outpoint);
if (it == cacheCoins.end()) return;
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
if (moveout && it->second.coins.IsAvailable(outpoint.n)) {
*moveout = Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase);
if (moveout) {
*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.coins.IsPruned() && it->second.flags & CCoinsCacheEntry::FRESH) {
if (it->second.flags & CCoinsCacheEntry::FRESH) {
cacheCoins.erase(it);
} else {
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
it->second.flags |= CCoinsCacheEntry::DIRTY;
}
}
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;
it->second.coins.Clear();
}
}
static const Coin coinEmpty;
const Coin CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash);
if (it == cacheCoins.end() || !it->second.coins.IsAvailable(outpoint.n)) {
const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoins(outpoint);
if (it == cacheCoins.end()) {
return coinEmpty;
} 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 {
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash);
return (it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n));
CCoinsMap::const_iterator it = FetchCoins(outpoint);
return (it != cacheCoins.end() && !it->second.coins.IsPruned());
}
bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const {
CCoinsMap::const_iterator it = cacheCoins.find(txid);
bool CCoinsViewCache::HaveCoinsInCache(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
return it != cacheCoins.end();
}
@ -206,7 +180,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn @@ -206,7 +180,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
// Otherwise we will need to create it in the parent
// and move the data up and mark it as dirty
CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coins.swap(it->second.coins);
entry.coins = std::move(it->second.coins);
cachedCoinsUsage += entry.coins.DynamicMemoryUsage();
entry.flags = CCoinsCacheEntry::DIRTY;
// 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 @@ -233,7 +207,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
} else {
// A normal modification.
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();
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
// NOTE: It is possible the child has a FRESH flag here in
@ -258,7 +232,7 @@ bool CCoinsViewCache::Flush() { @@ -258,7 +232,7 @@ bool CCoinsViewCache::Flush() {
return fOk;
}
void CCoinsViewCache::Uncache(const uint256& hash)
void CCoinsViewCache::Uncache(const COutPoint& hash)
{
CCoinsMap::iterator it = cacheCoins.find(hash);
if (it != cacheCoins.end() && it->second.flags == 0) {
@ -273,9 +247,9 @@ unsigned int CCoinsViewCache::GetCacheSize() const { @@ -273,9 +247,9 @@ unsigned int CCoinsViewCache::GetCacheSize() const {
const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
{
const CCoins* coins = AccessCoins(input.prevout.hash);
assert(coins && coins->IsAvailable(input.prevout.n));
return coins->vout[input.prevout.n];
const Coin& coin = AccessCoin(input.prevout);
assert(!coin.IsPruned());
return coin.out;
}
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
@ -294,9 +268,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const @@ -294,9 +268,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
if (!tx.IsCoinBase()) {
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins* coins = AccessCoins(prevout.hash);
if (!coins || !coins->IsAvailable(prevout.n)) {
if (!HaveCoins(tx.vin[i].prevout)) {
return false;
}
}
@ -304,13 +276,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const @@ -304,13 +276,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
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.
const Coin AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
{
COutPoint iter(txid, 0);
while (iter.n < MAX_OUTPUTS_PER_BLOCK) {

74
src/coins.h

@ -299,28 +299,28 @@ public: @@ -299,28 +299,28 @@ public:
}
};
class SaltedTxidHasher
class SaltedOutpointHasher
{
private:
/** Salt */
const uint64_t k0, k1;
public:
SaltedTxidHasher();
SaltedOutpointHasher();
/**
* 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
* uint64_t, resulting in failures when syncing the chain (#4634).
*/
size_t operator()(const uint256& txid) const {
return SipHashUint256(k0, k1, txid);
size_t operator()(const COutPoint& id) const {
return SipHashUint256Extra(k0, k1, id.hash, id.n);
}
};
struct CCoinsCacheEntry
{
CCoins coins; // The actual cached data.
Coin coins; // The actual cached data.
unsigned char flags;
enum Flags {
@ -333,20 +333,21 @@ struct CCoinsCacheEntry @@ -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 */
class CCoinsViewCursor
{
public:
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
virtual ~CCoinsViewCursor();
virtual ~CCoinsViewCursor() {}
virtual bool GetKey(uint256 &key) const = 0;
virtual bool GetValue(CCoins &coins) const = 0;
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;
@ -362,17 +363,17 @@ private: @@ -362,17 +363,17 @@ private:
class CCoinsView
{
public:
//! Retrieve the CCoins (unspent transaction outputs) for a given txid
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
//! Just check whether we have data for a given txid.
//! This may (but cannot always) return true for fully spent transactions
virtual bool HaveCoins(const uint256 &txid) const;
//! Just check whether we have data for a given outpoint.
//! This may (but cannot always) return true for spent outputs.
virtual bool HaveCoins(const COutPoint &outpoint) const;
//! Retrieve the block hash whose state this CCoinsView currently represents
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.
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
@ -395,12 +396,12 @@ protected: @@ -395,12 +396,12 @@ protected:
public:
CCoinsViewBacked(CCoinsView *viewIn);
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const;
bool GetCoins(const COutPoint &outpoint, Coin &coin) const override;
bool HaveCoins(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const override;
void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
CCoinsViewCursor *Cursor() const;
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
CCoinsViewCursor *Cursor() const override;
size_t EstimateSize() const override;
};
@ -416,41 +417,32 @@ protected: @@ -416,41 +417,32 @@ protected:
mutable uint256 hashBlock;
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;
public:
CCoinsViewCache(CCoinsView *baseIn);
// Standard CCoinsView methods
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
bool HaveCoins(const COutPoint &outpoint) const;
uint256 GetBestBlock() const;
void SetBestBlock(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 backing CCoinsView are made.
*/
bool HaveCoinsInCache(const uint256 &txid) 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;
bool HaveCoinsInCache(const COutPoint &outpoint) 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
* 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
@ -473,12 +465,12 @@ public: @@ -473,12 +465,12 @@ public:
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.
*/
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;
//! Calculate the size of the cache (in bytes)
@ -500,7 +492,7 @@ public: @@ -500,7 +492,7 @@ public:
const CTxOut &GetOutputFor(const CTxIn& input) const;
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.
@ -515,6 +507,6 @@ private: @@ -515,6 +507,6 @@ private:
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight);
//! 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

4
src/init.cpp

@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked @@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked
{
public:
CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
bool GetCoins(const uint256 &txid, CCoins &coins) const {
bool GetCoins(const COutPoint &outpoint, Coin &coin) const override {
try {
return CCoinsViewBacked::GetCoins(txid, coins);
return CCoinsViewBacked::GetCoins(outpoint, coin);
} catch(const std::runtime_error& e) {
uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR);
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) @@ -911,12 +911,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
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) ||
mempool.exists(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_WITNESS_BLOCK:

7
src/qt/transactiondesc.cpp

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

20
src/rest.cpp

@ -47,6 +47,9 @@ struct CCoin { @@ -47,6 +47,9 @@ struct CCoin {
ADD_SERIALIZE_METHODS;
CCoin() : nHeight(0) {}
CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
@ -509,20 +512,11 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart) @@ -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
for (size_t i = 0; i < vOutPoints.size(); i++) {
CCoins coins;
uint256 hash = vOutPoints[i].hash;
bool hit = false;
if (view.GetCoins(hash, coins)) {
if (coins.IsAvailable(vOutPoints[i].n) && !mempool.isSpent(vOutPoints[i])) {
hit = true;
// Safe to index into vout here because IsAvailable checked if it's off the end of the array, or if
// 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);
}
Coin coin;
if (view.GetCoins(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
hit = true;
outs.emplace_back(std::move(coin));
}
hits.push_back(hit);

47
src/rpc/blockchain.cpp

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

7
src/rpc/rawtransaction.cpp

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

239
src/test/coins_tests.cpp

@ -34,27 +34,27 @@ bool operator==(const Coin &a, const Coin &b) { @@ -34,27 +34,27 @@ bool operator==(const Coin &a, const Coin &b) {
class CCoinsViewTest : public CCoinsView
{
uint256 hashBestBlock_;
std::map<uint256, CCoins> map_;
std::map<COutPoint, Coin> map_;
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()) {
return false;
}
coins = it->second;
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
coin = it->second;
if (coin.IsPruned() && insecure_rand() % 2 == 0) {
// Randomly return false in case of an empty entry.
return false;
}
return true;
}
bool HaveCoins(const uint256& txid) const
bool HaveCoins(const COutPoint& outpoint) const
{
CCoins coins;
return GetCoins(txid, coins);
Coin coin;
return GetCoins(outpoint, coin);
}
uint256 GetBestBlock() const { return hashBestBlock_; }
@ -106,7 +106,7 @@ static const unsigned int NUM_SIMULATION_ITERATIONS = 40000; @@ -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
// 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
// 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) @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
bool missed_an_entry = false;
// 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.
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -142,39 +142,38 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) @@ -142,39 +142,38 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
// Do a random modification.
{
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));
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 (coins.IsPruned()) {
if (insecure_rand() % 5 == 0 || coin.IsPruned()) {
if (coin.IsPruned()) {
added_an_entry = true;
} else {
updated_an_entry = true;
}
coins.vout.resize(1);
coins.vout[0].nValue = insecure_rand();
coin.out.nValue = insecure_rand();
coin.nHeight = 1;
} else {
coins.Clear();
coin.Clear();
removed_an_entry = true;
}
if (coins.IsPruned()) {
if (coin.IsPruned()) {
stack.back()->SpendCoin(COutPoint(txid, 0));
} 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.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first);
if (coins) {
BOOST_CHECK(*coins == it->second);
found_an_entry = true;
} else {
BOOST_CHECK(it->second.IsPruned());
for (auto it = result.begin(); it != result.end(); it++) {
const Coin& coin = stack.back()->AccessCoin(it->first);
BOOST_CHECK(coin == it->second);
if (coin.IsPruned()) {
missed_an_entry = true;
} else {
found_an_entry = true;
}
}
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
@ -229,19 +228,19 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) @@ -229,19 +228,19 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
BOOST_CHECK(missed_an_entry);
}
typedef std::tuple<CTransaction,CTxUndo,CCoins> TxData;
// Store of all necessary tx and undo data for next test
std::map<uint256, TxData> alltxs;
TxData &FindRandomFrom(const std::set<uint256> &txidset) {
assert(txidset.size());
std::set<uint256>::iterator txIt = txidset.lower_bound(GetRandHash());
if (txIt == txidset.end()) {
txIt = txidset.begin();
typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
UtxoData utxoData;
UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
assert(utxoSet.size());
auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
if (utxoSetIt == utxoSet.end()) {
utxoSetIt = utxoSet.begin();
}
std::map<uint256, TxData>::iterator txdit = alltxs.find(*txIt);
assert(txdit != alltxs.end());
return txdit->second;
auto utxoDataIt = utxoData.find(*utxoSetIt);
assert(utxoDataIt != utxoData.end());
return utxoDataIt;
}
@ -254,7 +253,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -254,7 +253,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
{
bool spent_a_duplicate_coinbase = false;
// 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.
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
@ -262,10 +261,10 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -262,10 +261,10 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
// Track the txids we've used in various sets
std::set<uint256> coinbaseids;
std::set<uint256> disconnectedids;
std::set<uint256> duplicateids;
std::set<uint256> utxoset;
std::set<COutPoint> coinbaseids;
std::set<COutPoint> disconnectedids;
std::set<COutPoint> duplicateids;
std::set<COutPoint> utxoset;
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
uint32_t randiter = insecure_rand();
@ -277,22 +276,22 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -277,22 +276,22 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
tx.vout.resize(1);
tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
unsigned int height = insecure_rand();
CCoins oldcoins;
Coin oldcoins;
// 2/20 times create a new coinbase
if (randiter % 20 < 2 || coinbaseids.size() < 10) {
// 1/10 of those times create a duplicate coinbase
if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
TxData &txd = FindRandomFrom(coinbaseids);
auto utxod = FindRandomFrom(coinbaseids);
// 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
disconnectedids.erase(tx.GetHash());
disconnectedids.erase(utxod->first);
duplicateids.insert(tx.GetHash());
duplicateids.insert(utxod->first);
}
else {
coinbaseids.insert(tx.GetHash());
coinbaseids.insert(COutPoint(tx.GetHash(), 0));
}
assert(CTransaction(tx).IsCoinBase());
}
@ -300,85 +299,82 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -300,85 +299,82 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
// 17/20 times reconnect previous or add a regular tx
else {
uint256 prevouthash;
COutPoint prevout;
// 1/20 times reconnect a previously disconnected tx
if (randiter % 20 == 2 && disconnectedids.size()) {
TxData &txd = FindRandomFrom(disconnectedids);
tx = std::get<0>(txd);
prevouthash = tx.vin[0].prevout.hash;
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevouthash)) {
disconnectedids.erase(tx.GetHash());
auto utxod = FindRandomFrom(disconnectedids);
tx = std::get<0>(utxod->second);
prevout = tx.vin[0].prevout;
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
disconnectedids.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(tx.GetHash())) {
if (utxoset.count(utxod->first)) {
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
else {
TxData &txd = FindRandomFrom(utxoset);
prevouthash = std::get<0>(txd).GetHash();
auto utxod = FindRandomFrom(utxoset);
prevout = utxod->first;
// Construct the tx to spend the coins of prevouthash
tx.vin[0].prevout.hash = prevouthash;
tx.vin[0].prevout.n = 0;
tx.vin[0].prevout = prevout;
assert(!CTransaction(tx).IsCoinBase());
}
// 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
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
// if that ever happens and not resurrect the previously overwritten coinbase
if (duplicateids.count(prevouthash)) {
if (duplicateids.count(prevout)) {
spent_a_duplicate_coinbase = true;
}
}
// 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
CTxUndo undo;
UpdateCoins(tx, *(stack.back()), undo, height);
// Update the utxo set for future spends
utxoset.insert(tx.GetHash());
utxoset.insert(outpoint);
// 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()) {
//1/20 times undo a previous transaction
TxData &txd = FindRandomFrom(utxoset);
CTransaction &tx = std::get<0>(txd);
CTxUndo &undo = std::get<1>(txd);
CCoins &origcoins = std::get<2>(txd);
auto utxod = FindRandomFrom(utxoset);
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
// Remove new outputs
result[undohash].Clear();
result[utxod->first].Clear();
// If not coinbase restore prevout
if (!tx.IsCoinBase()) {
result[tx.vin[0].prevout.hash] = origcoins;
result[tx.vin[0].prevout] = origcoins;
}
// Disconnect the tx from the current UTXO
// See code in DisconnectBlock
// remove outputs
{
stack.back()->SpendCoin(COutPoint(undohash, 0));
}
stack.back()->SpendCoin(utxod->first);
// restore inputs
if (!tx.IsCoinBase()) {
const COutPoint &out = tx.vin[0].prevout;
@ -386,23 +382,19 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -386,23 +382,19 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
ApplyTxInUndo(std::move(coin), *(stack.back()), out);
}
// Store as a candidate for reconnection
disconnectedids.insert(undohash);
disconnectedids.insert(utxod->first);
// Update the utxoset
utxoset.erase(undohash);
utxoset.erase(utxod->first);
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.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first);
if (coins) {
BOOST_CHECK(*coins == it->second);
} else {
BOOST_CHECK(it->second.IsPruned());
}
for (auto it = result.begin(); it != result.end(); it++) {
const Coin& coin = stack.back()->AccessCoin(it->first);
BOOST_CHECK(coin == it->second);
}
}
@ -443,50 +435,36 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) @@ -443,50 +435,36 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
BOOST_AUTO_TEST_CASE(ccoins_serialization)
{
// Good example
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION);
CCoins cc1;
CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
Coin cc1;
ss1 >> cc1;
BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
BOOST_CHECK_EQUAL(cc1.vout.size(), 2);
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false);
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"))))));
BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION);
CCoins cc2;
CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
Coin cc2;
ss2 >> cc2;
BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
BOOST_CHECK_EQUAL(cc2.vout.size(), 17);
for (int i = 0; i < 17; i++) {
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"))))));
BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example
CDataStream ssx(SER_DISK, CLIENT_VERSION);
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), "");
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
CCoins cc3;
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
Coin cc3;
ss3 >> cc3;
BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
BOOST_CHECK_EQUAL(cc3.nHeight, 0);
BOOST_CHECK_EQUAL(cc3.vout.size(), 1);
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true);
BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
// 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 {
CCoins cc4;
Coin cc4;
ss4 >> cc4;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) {
@ -497,17 +475,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization) @@ -497,17 +475,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
uint64_t x = 3000000000ULL;
tmp << VARINT(x);
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 {
CCoins cc5;
Coin cc5;
ss5 >> cc5;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) {
}
}
const static uint256 TXID;
const static COutPoint OUTPOINT = {uint256(), 0};
const static COutPoint OUTPOINT;
const static CAmount PRUNED = -1;
const static CAmount ABSENT = -2;
const static CAmount FAIL = -3;
@ -522,15 +499,15 @@ const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)}; @@ -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 ABSENT_FLAGS = {NO_ENTRY};
void SetCoinsValue(CAmount value, CCoins& coins)
void SetCoinsValue(CAmount value, Coin& coin)
{
assert(value != ABSENT);
coins.Clear();
assert(coins.IsPruned());
coin.Clear();
assert(coin.IsPruned());
if (value != PRUNED) {
coins.vout.emplace_back();
coins.vout.back().nValue = value;
assert(!coins.IsPruned());
coin.out.nValue = value;
coin.nHeight = 1;
assert(!coin.IsPruned());
}
}
@ -544,24 +521,22 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags) @@ -544,24 +521,22 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
CCoinsCacheEntry entry;
entry.flags = flags;
SetCoinsValue(value, entry.coins);
auto inserted = map.emplace(TXID, std::move(entry));
auto inserted = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second);
return inserted.first->second.coins.DynamicMemoryUsage();
}
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
{
auto it = map.find(TXID);
auto it = map.find(OUTPOINT);
if (it == map.end()) {
value = ABSENT;
flags = NO_ENTRY;
} else {
if (it->second.coins.IsPruned()) {
assert(it->second.coins.vout.size() == 0);
value = PRUNED;
} else {
assert(it->second.coins.vout.size() == 1);
value = it->second.coins.vout[0].nValue;
value = it->second.coins.out.nValue;
}
flags = it->second.flags;
assert(flags != NO_ENTRY);

4
src/test/test_bitcoin_fuzzy.cpp

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

66
src/txdb.cpp

@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
#include <boost/thread.hpp>
static const char DB_COIN = 'C';
static const char DB_COINS = 'c';
static const char DB_BLOCK_FILES = 'f';
static const char DB_TXINDEX = 't';
@ -24,17 +25,40 @@ static const char DB_FLAG = 'F'; @@ -24,17 +25,40 @@ static const char DB_FLAG = 'F';
static const char DB_REINDEX_FLAG = 'R';
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)
{
}
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const {
return db.Read(std::make_pair(DB_COINS, txid), coins);
bool CCoinsViewDB::GetCoins(const COutPoint &outpoint, Coin &coin) const {
return db.Read(CoinsEntry(&outpoint), coin);
}
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const {
return db.Exists(std::make_pair(DB_COINS, txid));
bool CCoinsViewDB::HaveCoins(const COutPoint &outpoint) const {
return db.Exists(CoinsEntry(&outpoint));
}
uint256 CCoinsViewDB::GetBestBlock() const {
@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { @@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
size_t changed = 0;
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
CoinsEntry entry(&it->first);
if (it->second.coins.IsPruned())
batch.Erase(std::make_pair(DB_COINS, it->first));
batch.Erase(entry);
else
batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins);
batch.Write(entry, it->second.coins);
changed++;
}
count++;
@ -63,13 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { @@ -63,13 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
if (!hashBlock.IsNull())
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);
return db.WriteBatch(batch);
bool ret = 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 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) {
@ -101,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const @@ -101,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const
/* 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_COINS);
i->pcursor->Seek(DB_COIN);
// Cache key of first record
if (i->pcursor->Valid()) {
i->pcursor->GetKey(i->keyTmp);
CoinsEntry entry(&i->keyTmp.second);
i->pcursor->GetKey(entry);
i->keyTmp.first = entry.key;
} else {
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
}
return i;
}
bool CCoinsViewDBCursor::GetKey(uint256 &key) const
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
{
// Return cached key
if (keyTmp.first == DB_COINS) {
if (keyTmp.first == DB_COIN) {
key = keyTmp.second;
return true;
}
return false;
}