Merge #18410: Docs: Improve commenting for coins.cpp|h

21fa0a44abe8c1b5c452e097eab20cf0ae988805 [docs] use consistent naming for possible_overwrite (John Newbery)
2685c214cce4b07695273503e60350e3f05fe3e2 [tests] small whitespace fixup (John Newbery)
e9936966c08bd8a6ac02828131f619ddaa1ced13 scripted-diff: Rename PRUNED to SPENT in coins tests (John Newbery)
c205979031ff4e8e32a5f05bae813405f233fccd [docs] Improve commenting in coins.cpp|h (John Newbery)

Pull request description:

  - Add full commenting for spentness / DIRTYness / FRESHness and which combinations are valid
  - Remove the 'pruned' terminology, which doesn't make sense since per-txout chainstate db was merged (#10195).
  - Rename `potential_overwrite` to `possible_overwrite` to standardize terminology (there were previously examples of both, which made searching the codebase difficult).
  - Make other minor improvements to the comments

ACKs for top commit:
  jonatack:
    Re-ACK 21fa0a4 per `git diff 98bee55 21fa0a4` the only change since my previous review is the following code commenting diff in `src/coins.cpp::L177-179`;  rebuilt/ran unit tests anyway as a sanity check on the unit test changes.

Tree-SHA512: 391e01588ef5edb417250080cec17361f982c4454bc5f8c6d78bbd528c68a2bb94373297760691295c24660ce1022ad3ef7599762f736c8eed772ce096d38c3d
This commit is contained in:
Wladimir J. van der Laan 2020-04-22 14:23:10 +02:00 committed by Pasta
parent aaf2a17767
commit d0b0f08432
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
4 changed files with 159 additions and 121 deletions

View File

@ -76,8 +76,21 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
}
if (!possible_overwrite) {
if (!it->second.coin.IsSpent()) {
throw std::logic_error("Adding new coin that replaces non-pruned entry");
throw std::logic_error("Attempted to overwrite an unspent coin (when possible_overwrite is false)");
}
// If the coin exists in this cache as a spent coin and is DIRTY, then
// its spentness hasn't been flushed to the parent cache. We're
// re-adding the coin to this cache now but we can't mark it as FRESH.
// If we mark it FRESH and then spend it before the cache is flushed
// we would remove it from this cache and would never flush spentness
// to the parent cache.
//
// Re-adding a spent coin can happen in the case of a re-org (the coin
// is 'spent' when the block adding it is disconnected and then
// re-added when it is also added in a newly connected block).
//
// If the coin doesn't exist in the current cache, or is spent but not
// DIRTY, then it can be marked FRESH.
fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
}
it->second.coin = std::move(coin);
@ -85,12 +98,12 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
}
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check) {
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
bool fCoinbase = tx.IsCoinBase();
const uint256& txid = tx.GetHash();
for (size_t i = 0; i < tx.vout.size(); ++i) {
bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
// Always set the possible_overwrite flag to AddCoin for coinbase txn, in order to correctly
bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
// Coinbase transactions can always be overwritten, in order to correctly
// deal with the pre-BIP30 occurrences of duplicate coinbase transactions.
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
}
@ -151,11 +164,11 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
}
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
if (itUs == cacheCoins.end()) {
// The parent cache does not have an entry, while the child does
// We can ignore it if it's both FRESH and pruned in the child
// The parent cache does not have an entry, while the child cache does.
// We can ignore it if it's both spent and FRESH in the child
if (!(it->second.flags & CCoinsCacheEntry::FRESH && it->second.coin.IsSpent())) {
// Otherwise we will need to create it in the parent
// and move the data up and mark it as dirty
// Create the coin in the parent cache, move the data up
// and mark it as dirty.
CCoinsCacheEntry& entry = cacheCoins[it->first];
entry.coin = std::move(it->second.coin);
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
@ -168,19 +181,18 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
}
}
} else {
// Assert that the child cache entry was not marked FRESH if the
// parent cache entry has unspent outputs. If this ever happens,
// it means the FRESH flag was misapplied and there is a logic
// error in the calling code.
// Found the entry in the parent cache
if ((it->second.flags & CCoinsCacheEntry::FRESH) && !itUs->second.coin.IsSpent()) {
throw std::logic_error("FRESH flag misapplied to cache entry for base transaction with spendable outputs");
// The coin was marked FRESH in the child cache, but the coin
// exists in the parent cache. If this ever happens, it means
// the FRESH flag was misapplied and there is a logic error in
// the calling code.
throw std::logic_error("FRESH flag misapplied to coin that exists in parent cache");
}
// Found the entry in the parent cache
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coin.IsSpent()) {
// The grandparent does not have an entry, and the child is
// modified and being pruned. This means we can just delete
// it from the parent.
// The grandparent cache does not have an entry, and the coin
// has been spent. We can just delete it from the parent cache.
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
cacheCoins.erase(itUs);
} else {
@ -189,11 +201,10 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
itUs->second.coin = std::move(it->second.coin);
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
// NOTE: It is possible the child has a FRESH flag here in
// the event the entry we found in the parent is pruned. But
// we must not copy that FRESH flag to the parent as that
// pruned state likely still needs to be communicated to the
// grandparent.
// NOTE: It isn't safe to mark the coin as FRESH in the parent
// cache. If it already existed and was spent in the parent
// cache then marking it FRESH would prevent that spentness
// from being flushed to the grandparent.
}
}
}

View File

@ -111,19 +111,45 @@ public:
}
};
/**
* A Coin in one level of the coins database caching hierarchy.
*
* A coin can either be:
* - unspent or spent (in which case the Coin object will be nulled out - see Coin.Clear())
* - DIRTY or not DIRTY
* - FRESH or not FRESH
*
* Out of these 2^3 = 8 states, only some combinations are valid:
* - unspent, FRESH, DIRTY (e.g. a new coin created in the cache)
* - unspent, not FRESH, DIRTY (e.g. a coin changed in the cache during a reorg)
* - unspent, not FRESH, not DIRTY (e.g. an unspent coin fetched from the parent cache)
* - spent, FRESH, not DIRTY (e.g. a spent coin fetched from the parent cache)
* - spent, not FRESH, DIRTY (e.g. a coin is spent and spentness needs to be flushed to the parent)
*/
struct CCoinsCacheEntry
{
Coin coin; // The actual cached data.
unsigned char flags;
enum Flags {
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
FRESH = (1 << 1), // The parent view does not have this entry (or it is pruned).
/* Note that FRESH is a performance optimization with which we can
* erase coins that are fully spent if we know we do not need to
* flush the changes to the parent cache. It is always safe to
* not mark FRESH if that condition is not guaranteed.
/**
* DIRTY means the CCoinsCacheEntry is potentially different from the
* version in the parent cache. Failure to mark a coin as DIRTY when
* it is potentially different from the parent cache will cause a
* consensus failure, since the coin's state won't get written to the
* parent when the cache is flushed.
*/
DIRTY = (1 << 0),
/**
* FRESH means the parent cache does not have this coin or that it is a
* spent coin in the parent cache. If a FRESH coin in the cache is
* later spent, it can be deleted entirely and doesn't ever need to be
* flushed to the parent. This is a performance optimization. Marking a
* coin as FRESH when it exists unspent in the parent cache will cause a
* consensus failure, since it might not be deleted from the parent
* when this cache is flushed.
*/
FRESH = (1 << 1),
};
CCoinsCacheEntry() : flags(0) {}
@ -248,7 +274,7 @@ public:
bool HaveCoinInCache(const COutPoint &outpoint) const;
/**
* Return a reference to Coin in the cache, or a pruned one if not found. This is
* Return a reference to Coin in the cache, or coinEmpty if not found. This is
* more efficient than GetCoin.
*
* Generally, do not hold the reference returned for more than a short scope.
@ -260,10 +286,10 @@ public:
const Coin& AccessCoin(const COutPoint &output) const;
/**
* Add a coin. Set potential_overwrite to true if a non-pruned version may
* already exist.
* Add a coin. Set possible_overwrite to true if an unspent version may
* already exist in the cache.
*/
void AddCoin(const COutPoint& outpoint, Coin&& coin, bool potential_overwrite);
void AddCoin(const COutPoint& outpoint, Coin&& coin, bool possible_overwrite);
/**
* Spend a coin. Pass moveto in order to get the deleted data.

View File

@ -532,7 +532,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
}
const static COutPoint OUTPOINT;
const static CAmount PRUNED = -1;
const static CAmount SPENT = -1;
const static CAmount ABSENT = -2;
const static CAmount FAIL = -3;
const static CAmount VALUE1 = 100;
@ -551,7 +551,7 @@ static void SetCoinsValue(CAmount value, Coin& coin)
assert(value != ABSENT);
coin.Clear();
assert(coin.IsSpent());
if (value != PRUNED) {
if (value != SPENT) {
coin.out.nValue = value;
coin.nHeight = 1;
assert(!coin.IsSpent());
@ -581,7 +581,7 @@ void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
flags = NO_ENTRY;
} else {
if (it->second.coin.IsSpent()) {
value = PRUNED;
value = SPENT;
} else {
value = it->second.coin.out.nValue;
}
@ -634,28 +634,28 @@ BOOST_AUTO_TEST_CASE(ccoins_access)
* Value Value Value Flags Flags
*/
CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(ABSENT, SPENT , SPENT , 0 , 0 );
CheckAccessCoin(ABSENT, SPENT , SPENT , FRESH , FRESH );
CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
CheckAccessCoin(ABSENT, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(PRUNED, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckAccessCoin(SPENT , SPENT , SPENT , 0 , 0 );
CheckAccessCoin(SPENT , SPENT , SPENT , FRESH , FRESH );
CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY , DIRTY );
CheckAccessCoin(SPENT , SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(SPENT , VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(SPENT , VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY , DIRTY );
CheckAccessCoin(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY , 0 );
CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0 , 0 );
CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH , FRESH );
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(VALUE1, SPENT , SPENT , 0 , 0 );
CheckAccessCoin(VALUE1, SPENT , SPENT , FRESH , FRESH );
CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
CheckAccessCoin(VALUE1, SPENT , SPENT , DIRTY|FRESH, DIRTY|FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0 , 0 );
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH , FRESH );
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY , DIRTY );
@ -685,31 +685,31 @@ BOOST_AUTO_TEST_CASE(ccoins_spend)
* Value Value Value Flags Flags
*/
CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0 , DIRTY );
CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0 , DIRTY );
CheckSpendCoins(ABSENT, SPENT , SPENT , 0 , DIRTY );
CheckSpendCoins(ABSENT, SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(ABSENT, SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(ABSENT, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(ABSENT, VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(ABSENT, VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY );
CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0 , DIRTY );
CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY , DIRTY );
CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY );
CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0 , DIRTY );
CheckSpendCoins(SPENT , ABSENT, ABSENT, NO_ENTRY , NO_ENTRY );
CheckSpendCoins(SPENT , SPENT , SPENT , 0 , DIRTY );
CheckSpendCoins(SPENT , SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(SPENT , VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(SPENT , VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(SPENT , VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(SPENT , VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, ABSENT, SPENT , NO_ENTRY , DIRTY );
CheckSpendCoins(VALUE1, SPENT , SPENT , 0 , DIRTY );
CheckSpendCoins(VALUE1, SPENT , ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY );
CheckSpendCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, NO_ENTRY );
CheckSpendCoins(VALUE1, VALUE2, SPENT , 0 , DIRTY );
CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH , NO_ENTRY );
CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY , DIRTY );
CheckSpendCoins(VALUE1, VALUE2, SPENT , DIRTY , DIRTY );
CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY|FRESH, NO_ENTRY );
}
@ -742,7 +742,7 @@ static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount mo
template <typename... Args>
static void CheckAddCoin(Args&&... args)
{
for (const CAmount base_value : {ABSENT, PRUNED, VALUE1})
for (const CAmount base_value : {ABSENT, SPENT, VALUE1})
CheckAddCoinBase(base_value, std::forward<Args>(args)...);
}
@ -751,21 +751,21 @@ BOOST_AUTO_TEST_CASE(ccoins_add)
/* Check AddCoin behavior, requesting a new coin from a cache view,
* writing a modification to the coin, and then checking the resulting
* entry in the cache after the modification. Verify behavior with the
* with the AddCoin potential_overwrite argument set to false, and to true.
* AddCoin possible_overwrite argument set to false, and to true.
*
* Cache Write Result Cache Result potential_overwrite
* Cache Write Result Cache Result possible_overwrite
* Value Value Value Flags Flags
*/
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY|FRESH, false);
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY|FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , false);
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY , DIRTY , true );
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false);
CheckAddCoin(SPENT , VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
CheckAddCoin(VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
CheckAddCoin(VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false);
@ -805,42 +805,42 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
* Value Value Value Flags Flags Flags
*/
CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY , NO_ENTRY , NO_ENTRY );
CheckWriteCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY , DIRTY , DIRTY );
CheckWriteCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(ABSENT, SPENT , SPENT , NO_ENTRY , DIRTY , DIRTY );
CheckWriteCoins(ABSENT, SPENT , ABSENT, NO_ENTRY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY , DIRTY );
CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY , DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(PRUNED, ABSENT, PRUNED, 0 , NO_ENTRY , 0 );
CheckWriteCoins(PRUNED, ABSENT, PRUNED, FRESH , NO_ENTRY , FRESH );
CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY , NO_ENTRY , DIRTY );
CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY , DIRTY );
CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(SPENT , ABSENT, SPENT , 0 , NO_ENTRY , 0 );
CheckWriteCoins(SPENT , ABSENT, SPENT , FRESH , NO_ENTRY , FRESH );
CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY , NO_ENTRY , DIRTY );
CheckWriteCoins(SPENT , ABSENT, SPENT , DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY , DIRTY );
CheckWriteCoins(SPENT , SPENT , SPENT , 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , ABSENT, FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY , DIRTY );
CheckWriteCoins(SPENT , SPENT , SPENT , DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(SPENT , SPENT , ABSENT, DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY , DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, 0 , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, FRESH , DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY , DIRTY|FRESH, DIRTY );
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY , DIRTY|FRESH);
CheckWriteCoins(SPENT , VALUE2, VALUE2, DIRTY|FRESH, DIRTY|FRESH, DIRTY|FRESH);
CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0 , NO_ENTRY , 0 );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH , NO_ENTRY , FRESH );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY , NO_ENTRY , DIRTY );
CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY|FRESH, NO_ENTRY , DIRTY|FRESH);
CheckWriteCoins(VALUE1, PRUNED, PRUNED, 0 , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, PRUNED, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, PRUNED, FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, PRUNED, FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, PRUNED, FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , SPENT , 0 , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, SPENT , FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , ABSENT, FRESH , DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , FAIL , FRESH , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , SPENT , DIRTY , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , ABSENT, DIRTY|FRESH, DIRTY , NO_ENTRY );
CheckWriteCoins(VALUE1, SPENT , FAIL , DIRTY|FRESH, DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0 , DIRTY , DIRTY );
CheckWriteCoins(VALUE1, VALUE2, FAIL , 0 , DIRTY|FRESH, NO_ENTRY );
CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH , DIRTY , DIRTY|FRESH);
@ -854,8 +854,8 @@ BOOST_AUTO_TEST_CASE(ccoins_write)
// they would be too repetitive (the parent cache is never updated in these
// cases). The loop below covers these cases and makes sure the parent cache
// is always left unchanged.
for (const CAmount parent_value : {ABSENT, PRUNED, VALUE1})
for (const CAmount child_value : {ABSENT, PRUNED, VALUE2})
for (const CAmount parent_value : {ABSENT, SPENT, VALUE1})
for (const CAmount child_value : {ABSENT, SPENT, VALUE2})
for (const char parent_flags : parent_value == ABSENT ? ABSENT_FLAGS : FLAGS)
for (const char child_flags : child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS)
CheckWriteCoins(parent_value, child_value, parent_value, parent_flags, child_flags, parent_flags);

View File

@ -1655,10 +1655,11 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
return DISCONNECT_FAILED; // adding output for transaction without known metadata
}
}
// The potential_overwrite parameter to AddCoin is only allowed to be false if we know for
// sure that the coin did not already exist in the cache. As we have queried for that above
// using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and
// it is an overwrite.
// If the coin already exists as an unspent coin in the cache, then the
// possible_overwrite parameter to AddCoin must be set to true. We have
// already checked whether an unspent coin exists above using HaveCoin, so
// we don't need to guess. When fClean is false, an unspent coin already
// existed and it is an overwrite.
view.AddCoin(out, std::move(undo), !fClean);
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;