@@ -77,8 +77,21 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
7777 }
7878 if (!possible_overwrite) {
7979 if (!it->second .coin .IsSpent ()) {
80- throw std::logic_error (" Adding new coin that replaces non-pruned entry " );
80+ throw std::logic_error (" Attempted to overwrite an unspent coin (when possible_overwrite is false) " );
8181 }
82+ // If the coin exists in this cache as a spent coin and is DIRTY, then
83+ // its spentness hasn't been flushed to the parent cache. We're
84+ // re-adding the coin to this cache now but we can't mark it as FRESH.
85+ // If we mark it FRESH and then spend it before the cache is flushed
86+ // we would remove it from this cache and would never flush spentness
87+ // to the parent cache.
88+ //
89+ // Re-adding a spent coin can happen in the case of a re-org (the coin
90+ // is 'spent' when the block adding it is disconnected and then
91+ // re-added when it is also added in a newly connected block).
92+ //
93+ // If the coin doesn't exist in the current cache, or is spent but not
94+ // DIRTY, then it can be marked FRESH.
8295 fresh = !(it->second .flags & CCoinsCacheEntry::DIRTY);
8396 }
8497 it->second .coin = std::move (coin);
@@ -152,11 +165,11 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
152165 }
153166 CCoinsMap::iterator itUs = cacheCoins.find (it->first );
154167 if (itUs == cacheCoins.end ()) {
155- // The parent cache does not have an entry, while the child does
156- // We can ignore it if it's both FRESH and pruned in the child
168+ // The parent cache does not have an entry, while the child cache does.
169+ // We can ignore it if it's both spent and FRESH in the child
157170 if (!(it->second .flags & CCoinsCacheEntry::FRESH && it->second .coin .IsSpent ())) {
158- // Otherwise we will need to create it in the parent
159- // and move the data up and mark it as dirty
171+ // Create the coin in the parent cache, move the data up
172+ // and mark it as dirty.
160173 CCoinsCacheEntry& entry = cacheCoins[it->first ];
161174 entry.coin = std::move (it->second .coin );
162175 cachedCoinsUsage += entry.coin .DynamicMemoryUsage ();
@@ -169,19 +182,18 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
169182 }
170183 }
171184 } else {
172- // Assert that the child cache entry was not marked FRESH if the
173- // parent cache entry has unspent outputs. If this ever happens,
174- // it means the FRESH flag was misapplied and there is a logic
175- // error in the calling code.
185+ // Found the entry in the parent cache
176186 if ((it->second .flags & CCoinsCacheEntry::FRESH) && !itUs->second .coin .IsSpent ()) {
177- throw std::logic_error (" FRESH flag misapplied to cache entry for base transaction with spendable outputs" );
187+ // The coin was marked FRESH in the child cache, but the coin
188+ // exists in the parent cache. If this ever happens, it means
189+ // the FRESH flag was misapplied and there is a logic error in
190+ // the calling code.
191+ throw std::logic_error (" FRESH flag misapplied to coin that exists in parent cache" );
178192 }
179193
180- // Found the entry in the parent cache
181194 if ((itUs->second .flags & CCoinsCacheEntry::FRESH) && it->second .coin .IsSpent ()) {
182- // The grandparent does not have an entry, and the child is
183- // modified and being pruned. This means we can just delete
184- // it from the parent.
195+ // The grandparent cache does not have an entry, and the coin
196+ // has been spent. We can just delete it from the parent cache.
185197 cachedCoinsUsage -= itUs->second .coin .DynamicMemoryUsage ();
186198 cacheCoins.erase (itUs);
187199 } else {
@@ -190,11 +202,10 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
190202 itUs->second .coin = std::move (it->second .coin );
191203 cachedCoinsUsage += itUs->second .coin .DynamicMemoryUsage ();
192204 itUs->second .flags |= CCoinsCacheEntry::DIRTY;
193- // NOTE: It is possible the child has a FRESH flag here in
194- // the event the entry we found in the parent is pruned. But
195- // we must not copy that FRESH flag to the parent as that
196- // pruned state likely still needs to be communicated to the
197- // grandparent.
205+ // NOTE: It isn't safe to mark the coin as FRESH in the parent
206+ // cache. If it already existed and was spent in the parent
207+ // cache then marking it FRESH would prevent that spentness
208+ // from being flushed to the grandparent.
198209 }
199210 }
200211 }
0 commit comments