Auto merge of #1928 - ebfull:fix-anchor-cache-bug, r=str4d

Fix anchor cache bug

Fixes #1912.

If an anchor is removed from the cache, but didn't exist in it beforehand, it will insert a blank tree. If it's reinserted in a child cache, when the child cache flushes it will mark the treestate as entered but won't bring the valid tree with it.

Thankfully, we assert when connecting blocks so that this inconsistency won't cause us to build on a blank tree after a reorg.
This commit is contained in:
zkbot 2016-12-08 21:22:23 +00:00
commit cdbd851417
2 changed files with 87 additions and 3 deletions

View File

@ -176,11 +176,20 @@ void CCoinsViewCache::PopAnchor(const uint256 &newrt) {
// case restoring the "old" anchor during a reorg must
// have no effect.
if (currentRoot != newrt) {
CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(currentRoot, CAnchorsCacheEntry())).first;
// Bring the current best anchor into our local cache
// so that its tree exists in memory.
{
ZCIncrementalMerkleTree tree;
assert(GetAnchorAt(currentRoot, tree));
}
ret->second.entered = false;
ret->second.flags = CAnchorsCacheEntry::DIRTY;
// Mark the anchor as unentered, removing it from view
cacheAnchors[currentRoot].entered = false;
// Mark the cache entry as dirty so it's propagated
cacheAnchors[currentRoot].flags = CAnchorsCacheEntry::DIRTY;
// Mark the new root as the best anchor
hashAnchor = newrt;
}
}

View File

@ -249,6 +249,81 @@ BOOST_AUTO_TEST_CASE(nullifier_regression_test)
}
}
BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
{
// Correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Create dummy anchor/commitment
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
// Add the anchor
cache1.PushAnchor(tree);
cache1.Flush();
// Remove the anchor
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root());
cache1.Flush();
// Add the anchor back
cache1.PushAnchor(tree);
cache1.Flush();
// The base contains the anchor, of course!
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root());
}
}
// Previously incorrect behavior
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Create dummy anchor/commitment
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
// Add the anchor and flush to disk
cache1.PushAnchor(tree);
cache1.Flush();
// Remove the anchor, but don't flush yet!
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root());
{
CCoinsViewCacheTest cache2(&cache1); // Build cache on top
cache2.PushAnchor(tree); // Put the same anchor back!
cache2.Flush(); // Flush to cache1
}
// cache2's flush kinda worked, i.e. cache1 thinks the
// tree is there, but it didn't bring down the correct
// treestate...
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
}
// Flushing cache won't help either, just makes the inconsistency
// permanent.
cache1.Flush();
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
}
}
}
BOOST_AUTO_TEST_CASE(anchor_regression_test)
{
// Correct behavior: