@@ -77,8 +77,21 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
77
77
}
78
78
if (!possible_overwrite) {
79
79
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) " );
81
81
}
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.
82
95
fresh = !(it->second .flags & CCoinsCacheEntry::DIRTY);
83
96
}
84
97
it->second .coin = std::move (coin);
@@ -152,11 +165,11 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
152
165
}
153
166
CCoinsMap::iterator itUs = cacheCoins.find (it->first );
154
167
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
157
170
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.
160
173
CCoinsCacheEntry& entry = cacheCoins[it->first ];
161
174
entry.coin = std::move (it->second .coin );
162
175
cachedCoinsUsage += entry.coin .DynamicMemoryUsage ();
@@ -169,19 +182,18 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
169
182
}
170
183
}
171
184
} 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
176
186
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" );
178
192
}
179
193
180
- // Found the entry in the parent cache
181
194
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.
185
197
cachedCoinsUsage -= itUs->second .coin .DynamicMemoryUsage ();
186
198
cacheCoins.erase (itUs);
187
199
} else {
@@ -190,11 +202,10 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
190
202
itUs->second .coin = std::move (it->second .coin );
191
203
cachedCoinsUsage += itUs->second .coin .DynamicMemoryUsage ();
192
204
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.
198
209
}
199
210
}
200
211
}
0 commit comments