diff --git a/src/coins.cpp b/src/coins.cpp index 877fb8b26..1c329740b 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -45,7 +45,7 @@ bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return fal bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } -bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; } +CCoinsViewCursor *CCoinsView::Cursor() const { return 0; } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } @@ -54,7 +54,7 @@ bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveC 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); } -bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); } +CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} @@ -300,3 +300,7 @@ CCoinsModifier::~CCoinsModifier() cache.cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); } } + +CCoinsViewCursor::~CCoinsViewCursor() +{ +} diff --git a/src/coins.h b/src/coins.h index d297cae1a..d72f88547 100644 --- a/src/coins.h +++ b/src/coins.h @@ -297,19 +297,26 @@ struct CCoinsCacheEntry typedef boost::unordered_map CCoinsMap; -struct CCoinsStats +/** Cursor for iterating over CoinsView state */ +class CCoinsViewCursor { - int nHeight; - uint256 hashBlock; - uint64_t nTransactions; - uint64_t nTransactionOutputs; - uint64_t nSerializedSize; - uint256 hashSerialized; - CAmount nTotalAmount; +public: + CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {} + virtual ~CCoinsViewCursor(); - CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0), nTotalAmount(0) {} -}; + virtual bool GetKey(uint256 &key) const = 0; + virtual bool GetValue(CCoins &coins) const = 0; + /* Don't care about GetKeySize here */ + virtual unsigned int GetValueSize() const = 0; + virtual bool Valid() const = 0; + virtual void Next() = 0; + + //! Get best block at the time this cursor was created + const uint256 &GetBestBlock() const { return hashBlock; } +private: + uint256 hashBlock; +}; /** Abstract view on the open txout dataset. */ class CCoinsView @@ -329,8 +336,8 @@ public: //! The passed mapCoins can be modified. virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); - //! Calculate statistics about the unspent transaction output set - virtual bool GetStats(CCoinsStats &stats) const; + //! Get a cursor to iterate over the whole state + virtual CCoinsViewCursor *Cursor() const; //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} @@ -350,7 +357,7 @@ public: uint256 GetBestBlock() const; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); - bool GetStats(CCoinsStats &stats) const; + CCoinsViewCursor *Cursor() const; }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 7a01a10b7..b85b2f6b5 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -18,11 +18,14 @@ #include "txmempool.h" #include "util.h" #include "utilstrencodings.h" +#include "hash.h" #include #include +#include // boost::thread::interrupt + using namespace std; extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); @@ -432,6 +435,59 @@ UniValue getblock(const UniValue& params, bool fHelp) return blockToJSON(block, pblockindex); } +struct CCoinsStats +{ + int nHeight; + uint256 hashBlock; + uint64_t nTransactions; + uint64_t nTransactionOutputs; + uint64_t nSerializedSize; + uint256 hashSerialized; + CAmount nTotalAmount; + + CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0), nTotalAmount(0) {} +}; + +//! Calculate statistics about the unspent transaction output set +static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) +{ + boost::scoped_ptr pcursor(view->Cursor()); + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + stats.hashBlock = pcursor->GetBestBlock(); + { + LOCK(cs_main); + stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; + } + ss << stats.hashBlock; + CAmount nTotalAmount = 0; + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + uint256 key; + CCoins coins; + if (pcursor->GetKey(key) && pcursor->GetValue(coins)) { + stats.nTransactions++; + for (unsigned int i=0; iGetValueSize(); + ss << VARINT(0); + } else { + return error("%s: unable to read value", __func__); + } + pcursor->Next(); + } + stats.hashSerialized = ss.GetHash(); + stats.nTotalAmount = nTotalAmount; + return true; +} + UniValue gettxoutsetinfo(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) @@ -458,7 +514,7 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp) CCoinsStats stats; FlushStateToDisk(); - if (pcoinsTip->GetStats(stats)) { + if (GetUTXOStats(pcoinsTip, stats)) { ret.push_back(Pair("height", (int64_t)stats.nHeight)); ret.push_back(Pair("bestblock", stats.hashBlock.GetHex())); ret.push_back(Pair("transactions", (int64_t)stats.nTransactions)); diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 3fe536f91..48e3c8ed8 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -61,8 +61,6 @@ public: hashBestBlock_ = hashBlock; return true; } - - bool GetStats(CCoinsStats& stats) const { return false; } }; class CCoinsViewCacheTest : public CCoinsViewCache diff --git a/src/txdb.cpp b/src/txdb.cpp index f99e11f26..be86cceeb 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -94,50 +94,52 @@ bool CBlockTreeDB::ReadLastBlockFile(int &nFile) { return Read(DB_LAST_BLOCK, nFile); } -bool CCoinsViewDB::GetStats(CCoinsStats &stats) const { +CCoinsViewCursor *CCoinsViewDB::Cursor() const +{ + CCoinsViewDBCursor *i = new CCoinsViewDBCursor(const_cast(&db)->NewIterator(), GetBestBlock()); /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ - boost::scoped_ptr pcursor(const_cast(&db)->NewIterator()); - pcursor->Seek(DB_COINS); + i->pcursor->Seek(DB_COINS); + // Cache key of first record + i->pcursor->GetKey(i->keyTmp); + return i; +} - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - stats.hashBlock = GetBestBlock(); - ss << stats.hashBlock; - CAmount nTotalAmount = 0; - while (pcursor->Valid()) { - boost::this_thread::interruption_point(); - std::pair key; - CCoins coins; - if (pcursor->GetKey(key) && key.first == DB_COINS) { - if (pcursor->GetValue(coins)) { - stats.nTransactions++; - for (unsigned int i=0; iGetValueSize(); - ss << VARINT(0); - } else { - return error("CCoinsViewDB::GetStats() : unable to read value"); - } - } else { - break; - } - pcursor->Next(); +bool CCoinsViewDBCursor::GetKey(uint256 &key) const +{ + // Return cached key + if (keyTmp.first == DB_COINS) { + key = keyTmp.second; + return true; } - { - LOCK(cs_main); - stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; + return false; +} + +bool CCoinsViewDBCursor::GetValue(CCoins &coins) const +{ + return pcursor->GetValue(coins); +} + +unsigned int CCoinsViewDBCursor::GetValueSize() const +{ + return pcursor->GetValueSize(); +} + +bool CCoinsViewDBCursor::Valid() const +{ + return keyTmp.first == DB_COINS; +} + +void CCoinsViewDBCursor::Next() +{ + pcursor->Next(); + if (pcursor->Valid()) { + bool ok = pcursor->GetKey(keyTmp); + assert(ok); // If GetKey fails here something must be wrong with underlying database, we cannot handle that here + } else { + keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false } - stats.hashSerialized = ss.GetHash(); - stats.nTotalAmount = nTotalAmount; - return true; } bool CBlockTreeDB::WriteBatchSync(const std::vector >& fileInfo, int nLastFile, const std::vector& blockinfo) { diff --git a/src/txdb.h b/src/txdb.h index 22e0c5704..749802f0e 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -26,6 +26,8 @@ static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024; //! min. -dbcache in (MiB) static const int64_t nMinDbCache = 4; +class CCoinsViewDBCursor; + /** CCoinsView backed by the coin database (chainstate/) */ class CCoinsViewDB : public CCoinsView { @@ -38,7 +40,29 @@ public: bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); - bool GetStats(CCoinsStats &stats) const; + CCoinsViewCursor *Cursor() const; +}; + +/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ +class CCoinsViewDBCursor: public CCoinsViewCursor +{ +public: + ~CCoinsViewDBCursor() {} + + bool GetKey(uint256 &key) const; + bool GetValue(CCoins &coins) const; + unsigned int GetValueSize() const; + + bool Valid() const; + void Next(); + +private: + CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn): + CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} + boost::scoped_ptr pcursor; + std::pair keyTmp; + + friend class CCoinsViewDB; }; /** Access to the block database (blocks/index/) */