Skip to content

Commit 509cb00

Browse files
committed
txdb: Add Cursor() method to CCoinsView to iterate over UTXO set
Add a method Cursor() to CCoinsView that returns a cursor which can be used to iterate over the whole UTXO set. - rpc: Change gettxoutsetinfo to use new Cursor method - txdb: Remove GetStats method - Now that GetStats is implemented in terms of Cursor, remove it.
1 parent 1b2460b commit 509cb00

File tree

6 files changed

+148
-57
lines changed

6 files changed

+148
-57
lines changed

src/coins.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return fal
4545
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
4646
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
4747
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
48-
bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
48+
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
4949

5050

5151
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
@@ -54,7 +54,7 @@ bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveC
5454
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
5555
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
5656
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
57-
bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); }
57+
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
5858

5959
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
6060

@@ -300,3 +300,7 @@ CCoinsModifier::~CCoinsModifier()
300300
cache.cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
301301
}
302302
}
303+
304+
CCoinsViewCursor::~CCoinsViewCursor()
305+
{
306+
}

src/coins.h

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -297,19 +297,26 @@ struct CCoinsCacheEntry
297297

298298
typedef boost::unordered_map<uint256, CCoinsCacheEntry, CCoinsKeyHasher> CCoinsMap;
299299

300-
struct CCoinsStats
300+
/** Cursor for iterating over CoinsView state */
301+
class CCoinsViewCursor
301302
{
302-
int nHeight;
303-
uint256 hashBlock;
304-
uint64_t nTransactions;
305-
uint64_t nTransactionOutputs;
306-
uint64_t nSerializedSize;
307-
uint256 hashSerialized;
308-
CAmount nTotalAmount;
303+
public:
304+
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
305+
virtual ~CCoinsViewCursor();
309306

310-
CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0), nTotalAmount(0) {}
311-
};
307+
virtual bool GetKey(uint256 &key) const = 0;
308+
virtual bool GetValue(CCoins &coins) const = 0;
309+
/* Don't care about GetKeySize here */
310+
virtual unsigned int GetValueSize() const = 0;
312311

312+
virtual bool Valid() const = 0;
313+
virtual void Next() = 0;
314+
315+
//! Get best block at the time this cursor was created
316+
const uint256 &GetBestBlock() const { return hashBlock; }
317+
private:
318+
uint256 hashBlock;
319+
};
313320

314321
/** Abstract view on the open txout dataset. */
315322
class CCoinsView
@@ -329,8 +336,8 @@ class CCoinsView
329336
//! The passed mapCoins can be modified.
330337
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
331338

332-
//! Calculate statistics about the unspent transaction output set
333-
virtual bool GetStats(CCoinsStats &stats) const;
339+
//! Get a cursor to iterate over the whole state
340+
virtual CCoinsViewCursor *Cursor() const;
334341

335342
//! As we use CCoinsViews polymorphically, have a virtual destructor
336343
virtual ~CCoinsView() {}
@@ -350,7 +357,7 @@ class CCoinsViewBacked : public CCoinsView
350357
uint256 GetBestBlock() const;
351358
void SetBackend(CCoinsView &viewIn);
352359
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
353-
bool GetStats(CCoinsStats &stats) const;
360+
CCoinsViewCursor *Cursor() const;
354361
};
355362

356363

src/rpc/blockchain.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818
#include "txmempool.h"
1919
#include "util.h"
2020
#include "utilstrencodings.h"
21+
#include "hash.h"
2122

2223
#include <stdint.h>
2324

2425
#include <univalue.h>
2526

27+
#include <boost/thread/thread.hpp> // boost::thread::interrupt
28+
2629
using namespace std;
2730

2831
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
@@ -432,6 +435,59 @@ UniValue getblock(const UniValue& params, bool fHelp)
432435
return blockToJSON(block, pblockindex);
433436
}
434437

438+
struct CCoinsStats
439+
{
440+
int nHeight;
441+
uint256 hashBlock;
442+
uint64_t nTransactions;
443+
uint64_t nTransactionOutputs;
444+
uint64_t nSerializedSize;
445+
uint256 hashSerialized;
446+
CAmount nTotalAmount;
447+
448+
CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nSerializedSize(0), nTotalAmount(0) {}
449+
};
450+
451+
//! Calculate statistics about the unspent transaction output set
452+
static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
453+
{
454+
boost::scoped_ptr<CCoinsViewCursor> pcursor(view->Cursor());
455+
456+
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
457+
stats.hashBlock = pcursor->GetBestBlock();
458+
{
459+
LOCK(cs_main);
460+
stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight;
461+
}
462+
ss << stats.hashBlock;
463+
CAmount nTotalAmount = 0;
464+
while (pcursor->Valid()) {
465+
boost::this_thread::interruption_point();
466+
uint256 key;
467+
CCoins coins;
468+
if (pcursor->GetKey(key) && pcursor->GetValue(coins)) {
469+
stats.nTransactions++;
470+
for (unsigned int i=0; i<coins.vout.size(); i++) {
471+
const CTxOut &out = coins.vout[i];
472+
if (!out.IsNull()) {
473+
stats.nTransactionOutputs++;
474+
ss << VARINT(i+1);
475+
ss << out;
476+
nTotalAmount += out.nValue;
477+
}
478+
}
479+
stats.nSerializedSize += 32 + pcursor->GetValueSize();
480+
ss << VARINT(0);
481+
} else {
482+
return error("%s: unable to read value", __func__);
483+
}
484+
pcursor->Next();
485+
}
486+
stats.hashSerialized = ss.GetHash();
487+
stats.nTotalAmount = nTotalAmount;
488+
return true;
489+
}
490+
435491
UniValue gettxoutsetinfo(const UniValue& params, bool fHelp)
436492
{
437493
if (fHelp || params.size() != 0)
@@ -458,7 +514,7 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp)
458514

459515
CCoinsStats stats;
460516
FlushStateToDisk();
461-
if (pcoinsTip->GetStats(stats)) {
517+
if (GetUTXOStats(pcoinsTip, stats)) {
462518
ret.push_back(Pair("height", (int64_t)stats.nHeight));
463519
ret.push_back(Pair("bestblock", stats.hashBlock.GetHex()));
464520
ret.push_back(Pair("transactions", (int64_t)stats.nTransactions));

src/test/coins_tests.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ class CCoinsViewTest : public CCoinsView
6161
hashBestBlock_ = hashBlock;
6262
return true;
6363
}
64-
65-
bool GetStats(CCoinsStats& stats) const { return false; }
6664
};
6765

6866
class CCoinsViewCacheTest : public CCoinsViewCache

src/txdb.cpp

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -94,50 +94,52 @@ bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {
9494
return Read(DB_LAST_BLOCK, nFile);
9595
}
9696

97-
bool CCoinsViewDB::GetStats(CCoinsStats &stats) const {
97+
CCoinsViewCursor *CCoinsViewDB::Cursor() const
98+
{
99+
CCoinsViewDBCursor *i = new CCoinsViewDBCursor(const_cast<CDBWrapper*>(&db)->NewIterator(), GetBestBlock());
98100
/* It seems that there are no "const iterators" for LevelDB. Since we
99101
only need read operations on it, use a const-cast to get around
100102
that restriction. */
101-
boost::scoped_ptr<CDBIterator> pcursor(const_cast<CDBWrapper*>(&db)->NewIterator());
102-
pcursor->Seek(DB_COINS);
103+
i->pcursor->Seek(DB_COINS);
104+
// Cache key of first record
105+
i->pcursor->GetKey(i->keyTmp);
106+
return i;
107+
}
103108

104-
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
105-
stats.hashBlock = GetBestBlock();
106-
ss << stats.hashBlock;
107-
CAmount nTotalAmount = 0;
108-
while (pcursor->Valid()) {
109-
boost::this_thread::interruption_point();
110-
std::pair<char, uint256> key;
111-
CCoins coins;
112-
if (pcursor->GetKey(key) && key.first == DB_COINS) {
113-
if (pcursor->GetValue(coins)) {
114-
stats.nTransactions++;
115-
for (unsigned int i=0; i<coins.vout.size(); i++) {
116-
const CTxOut &out = coins.vout[i];
117-
if (!out.IsNull()) {
118-
stats.nTransactionOutputs++;
119-
ss << VARINT(i+1);
120-
ss << out;
121-
nTotalAmount += out.nValue;
122-
}
123-
}
124-
stats.nSerializedSize += 32 + pcursor->GetValueSize();
125-
ss << VARINT(0);
126-
} else {
127-
return error("CCoinsViewDB::GetStats() : unable to read value");
128-
}
129-
} else {
130-
break;
131-
}
132-
pcursor->Next();
109+
bool CCoinsViewDBCursor::GetKey(uint256 &key) const
110+
{
111+
// Return cached key
112+
if (keyTmp.first == DB_COINS) {
113+
key = keyTmp.second;
114+
return true;
133115
}
134-
{
135-
LOCK(cs_main);
136-
stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight;
116+
return false;
117+
}
118+
119+
bool CCoinsViewDBCursor::GetValue(CCoins &coins) const
120+
{
121+
return pcursor->GetValue(coins);
122+
}
123+
124+
unsigned int CCoinsViewDBCursor::GetValueSize() const
125+
{
126+
return pcursor->GetValueSize();
127+
}
128+
129+
bool CCoinsViewDBCursor::Valid() const
130+
{
131+
return keyTmp.first == DB_COINS;
132+
}
133+
134+
void CCoinsViewDBCursor::Next()
135+
{
136+
pcursor->Next();
137+
if (pcursor->Valid()) {
138+
bool ok = pcursor->GetKey(keyTmp);
139+
assert(ok); // If GetKey fails here something must be wrong with underlying database, we cannot handle that here
140+
} else {
141+
keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
137142
}
138-
stats.hashSerialized = ss.GetHash();
139-
stats.nTotalAmount = nTotalAmount;
140-
return true;
141143
}
142144

143145
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {

src/txdb.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024;
2626
//! min. -dbcache in (MiB)
2727
static const int64_t nMinDbCache = 4;
2828

29+
class CCoinsViewDBCursor;
30+
2931
/** CCoinsView backed by the coin database (chainstate/) */
3032
class CCoinsViewDB : public CCoinsView
3133
{
@@ -38,7 +40,29 @@ class CCoinsViewDB : public CCoinsView
3840
bool HaveCoins(const uint256 &txid) const;
3941
uint256 GetBestBlock() const;
4042
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
41-
bool GetStats(CCoinsStats &stats) const;
43+
CCoinsViewCursor *Cursor() const;
44+
};
45+
46+
/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
47+
class CCoinsViewDBCursor: public CCoinsViewCursor
48+
{
49+
public:
50+
~CCoinsViewDBCursor() {}
51+
52+
bool GetKey(uint256 &key) const;
53+
bool GetValue(CCoins &coins) const;
54+
unsigned int GetValueSize() const;
55+
56+
bool Valid() const;
57+
void Next();
58+
59+
private:
60+
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn):
61+
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
62+
boost::scoped_ptr<CDBIterator> pcursor;
63+
std::pair<char, uint256> keyTmp;
64+
65+
friend class CCoinsViewDB;
4266
};
4367

4468
/** Access to the block database (blocks/index/) */

0 commit comments

Comments
 (0)