Skip to content

Commit

Permalink
Make CCoinsViewCache::Cursor() return latest data
Browse files Browse the repository at this point in the history
Change CCoinsViewCache::Cursor() method to return a cursor yielding the latest
CCoins entries, instead of just previous entries prior to the last cache flush.

The CCoinsViewCache::Cursor method is not currently used. This change just
enables new features that rely on scanning the UXTO set to work correctly (for
example bitcoin#9152, which adds a
sweepprivkeys RPC, and bitcoin#9137, which
improves handling of imported keys for nodes with pruning enabled.)
  • Loading branch information
ryanofsky committed Dec 9, 2016
1 parent 43e8150 commit 6494980
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,72 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
}

CCoinsViewCacheCursor::CCoinsViewCacheCursor(const CCoinsViewCache& cache)
: CCoinsViewCursor(cache.GetBestBlock()), cache(cache), base(cache.base->Cursor()),
it(cache.cacheCoins.begin())
{
AdvanceToNonPruned();
}

void CCoinsViewCacheCursor::AdvanceToNonPruned()
{
// Skip non-dirty cache entries and dirty but pruned entries.
for (; it != cache.cacheCoins.end(); ++it)
if (it->second.flags & CCoinsCacheEntry::DIRTY && !it->second.coins.IsPruned())
return;

// Skip base entries overridden by dirty cache entries.
uint256 txid;
CCoinsMap::iterator match;
for (; base->Valid(); base->Next())
if (!base->GetKey(txid) ||
(match = cache.cacheCoins.find(txid)) == cache.cacheCoins.end() ||
!(match->second.flags & CCoinsCacheEntry::DIRTY))
return;
}

bool CCoinsViewCacheCursor::GetKey(uint256& key) const
{
if (it != cache.cacheCoins.end()) {
key = it->first;
return true;
}
return base->GetKey(key);
}

bool CCoinsViewCacheCursor::GetValue(CCoins& coins) const
{
if (it != cache.cacheCoins.end()) {
coins = it->second.coins;
return true;
}
return base->GetValue(coins);
}

unsigned int CCoinsViewCacheCursor::GetValueSize() const
{
return it != cache.cacheCoins.end() ? 0 : base->GetValueSize();
}

bool CCoinsViewCacheCursor::Valid() const
{
return it != cache.cacheCoins.end() || base->Valid();
}

void CCoinsViewCacheCursor::Next()
{
if (it != cache.cacheCoins.end())
++it;
else
base->Next();
AdvanceToNonPruned();
}

CCoinsViewCursor* CCoinsViewCache::Cursor() const
{
return new CCoinsViewCacheCursor(*this);
}

CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
CCoinsMap::iterator it = cacheCoins.find(txid);
if (it != cacheCoins.end())
Expand Down
28 changes: 28 additions & 0 deletions src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ class CCoinsViewCache : public CCoinsViewBacked
uint256 GetBestBlock() const;
void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
CCoinsViewCursor* Cursor() const override;

/**
* Check if we have the given tx already loaded in this cache.
Expand Down Expand Up @@ -464,6 +465,7 @@ class CCoinsViewCache : public CCoinsViewBacked

const CTxOut &GetOutputFor(const CTxIn& input) const;

friend class CCoinsViewCacheCursor;
friend class CCoinsModifier;

private:
Expand All @@ -476,4 +478,30 @@ class CCoinsViewCache : public CCoinsViewBacked
CCoinsViewCache(const CCoinsViewCache &);
};

/**
* Cursor view of cache. First returns dirty, non-pruned rows in cache, then
* returns rows from the underlying base cursor.
*/
class CCoinsViewCacheCursor : public CCoinsViewCursor
{
public:
CCoinsViewCacheCursor(const CCoinsViewCache& cache);
void AdvanceToNonPruned();
bool GetKey(uint256& key) const override;
bool GetValue(CCoins& coins) const override;
unsigned int GetValueSize() const override;
bool Valid() const override;
void Next() override;

private:
const CCoinsViewCache& cache;
std::unique_ptr<CCoinsViewCursor> base;

/**
* Current cache entry during the initial scan of the cache, before
* resorting to underlying base cursor.
*/
CCoinsMap::iterator it;
};

#endif // BITCOIN_COINS_H
63 changes: 63 additions & 0 deletions src/test/coins_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,53 @@ class CCoinsViewTest : public CCoinsView
hashBestBlock_ = hashBlock;
return true;
}

CCoinsViewCursor* Cursor() const override;

friend class CCoinsViewTestCursor;
};

class CCoinsViewTestCursor : public CCoinsViewCursor
{
public:
CCoinsViewTestCursor(const CCoinsViewTest& test) : CCoinsViewCursor(test.GetBestBlock()), test(test), it(test.map_.begin()) {}

bool GetKey(uint256& key) const override
{
if (it == test.map_.end())
return false;
key = it->first;
return true;
}

bool GetValue(CCoins& coins) const override
{
if (it == test.map_.end())
return false;
coins = it->second;
return true;
}

unsigned int GetValueSize() const override { return 0; }

bool Valid() const override { return it != test.map_.end(); }

void Next() override
{
if (it != test.map_.end())
++it;
}

private:
const CCoinsViewTest& test;
std::map<uint256, CCoins>::const_iterator it;
};

CCoinsViewCursor* CCoinsViewTest::Cursor() const
{
return new CCoinsViewTestCursor(*this);
}

class CCoinsViewCacheTest : public CCoinsViewCache
{
public:
Expand Down Expand Up @@ -149,6 +194,16 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)

// Once every 1000 iterations and at the end, verify the full cache.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
std::map<uint256, CCoins> cursorResult;
for (std::unique_ptr<CCoinsViewCursor> cursor(stack.back()->Cursor()); cursor->Valid(); cursor->Next()) {
uint256 key;
BOOST_CHECK(cursor->GetKey(key));
CCoins value;
BOOST_CHECK(cursor->GetValue(value));
BOOST_CHECK(cursorResult.emplace(key, value).second);
}

std::map<uint256, CCoins>::iterator cursor = cursorResult.begin();
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first);
if (coins) {
Expand All @@ -158,7 +213,15 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
BOOST_CHECK(it->second.IsPruned());
missed_an_entry = true;
}

if (cursor != cursorResult.end() && cursor->first == it->first) {
BOOST_CHECK(cursor->second == it->second);
++cursor;
} else {
BOOST_CHECK(it->second.IsPruned());
}
}
BOOST_CHECK(cursor == cursorResult.end());
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
test->SelfTest();
}
Expand Down

0 comments on commit 6494980

Please sign in to comment.