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 Jun 2, 2017
1 parent 329fc1d commit d58b56d
Show file tree
Hide file tree
Showing 3 changed files with 160 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 @@ -34,6 +34,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.coin.IsSpent())
return;

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

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

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

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::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
CCoinsMap::iterator it = cacheCoins.find(outpoint);
if (it != cacheCoins.end())
Expand Down
31 changes: 31 additions & 0 deletions src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,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 utxo already loaded in this cache.
Expand Down Expand Up @@ -272,13 +273,17 @@ class CCoinsViewCache : public CCoinsViewBacked
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const;

friend class CCoinsModifier;

private:
CCoinsMap::iterator FetchCoin(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.
*/
CCoinsViewCache(const CCoinsViewCache &);

friend class CCoinsViewCacheCursor;
};

//! Utility function to add all of a transaction's outputs to a cache.
Expand All @@ -290,4 +295,30 @@ 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);

/**
* 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(COutPoint &key) const override;
bool GetValue(Coin &coin) 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 @@ -76,8 +76,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(COutPoint &key) const override
{
if (it == test.map_.end())
return false;
key = it->first;
return true;
}

bool GetValue(Coin &coin) const override
{
if (it == test.map_.end())
return false;
coin = 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<COutPoint, Coin>::const_iterator it;
};

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

class CCoinsViewCacheTest : public CCoinsViewCache
{
public:
Expand Down Expand Up @@ -182,6 +227,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<COutPoint, Coin> cursorResult;
for (std::unique_ptr<CCoinsViewCursor> cursor(stack.back()->Cursor()); cursor->Valid(); cursor->Next()) {
COutPoint key;
BOOST_CHECK(cursor->GetKey(key));
Coin value;
BOOST_CHECK(cursor->GetValue(value));
BOOST_CHECK(cursorResult.emplace(key, value).second);
}

auto cursor = cursorResult.begin();
for (auto it = result.begin(); it != result.end(); it++) {
bool have = stack.back()->HaveCoin(it->first);
const Coin& coin = stack.back()->AccessCoin(it->first);
Expand All @@ -193,7 +248,15 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
BOOST_CHECK(stack.back()->HaveCoinInCache(it->first));
found_an_entry = true;
}

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

0 comments on commit d58b56d

Please sign in to comment.