@@ -245,9 +245,26 @@ class CCoinsViewCursor
245245
246246/* *
247247 * Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.
248+ *
249+ * This is a helper struct to encapsulate the diverging logic between a non-erasing
250+ * CCoinsViewCache::Sync and an erasing CCoinsViewCache::Flush. This allows the receiver
251+ * of CCoinsView::BatchWrite to iterate through the flagged entries without knowing
252+ * the caller's intent.
253+ *
254+ * However, the receiver can still call CoinsViewCacheCursor::WillErase to see if the
255+ * caller will erase the entry after BatchWrite returns. If so, the receiver can
256+ * perform optimizations such as moving the coin out of the CCoinsCachEntry instead
257+ * of copying it.
248258 */
249259struct CoinsViewCacheCursor
250260{
261+ // ! If will_erase is not set, iterating through the cursor will erase spent coins from the map,
262+ // ! and other coins will be unflagged (removing them from the linked list).
263+ // ! If will_erase is set, the underlying map and linked list will not be modified,
264+ // ! as the caller is expected to wipe the entire map anyway.
265+ // ! This is an optimization compared to erasing all entries as the cursor iterates them when will_erase is set.
266+ // ! Calling CCoinsMap::clear() afterwards is faster because a CoinsCachePair cannot be coerced back into a
267+ // ! CCoinsMap::iterator to be erased, and must therefore be looked up again by key in the CCoinsMap before being erased.
251268 CoinsViewCacheCursor (size_t & usage LIFETIMEBOUND,
252269 CoinsCachePair& sentinel LIFETIMEBOUND,
253270 CCoinsMap& map LIFETIMEBOUND,
@@ -257,9 +274,24 @@ struct CoinsViewCacheCursor
257274 inline CoinsCachePair* Begin () const noexcept { return m_sentinel.second .Next (); }
258275 inline CoinsCachePair* End () const noexcept { return &m_sentinel; }
259276
260- inline CoinsCachePair* NextAndMaybeErase (CoinsCachePair& current) noexcept { return current.second .Next (); }
277+ // ! Return the next entry after current, possibly erasing current
278+ inline CoinsCachePair* NextAndMaybeErase (CoinsCachePair& current) noexcept
279+ {
280+ const auto next_entry{current.second .Next ()};
281+ // If we are not going to erase the cache, we must still erase spent entries.
282+ // Otherwise clear the flags on the entry.
283+ if (!m_will_erase) {
284+ if (current.second .coin .IsSpent ()) {
285+ m_usage -= current.second .coin .DynamicMemoryUsage ();
286+ m_map.erase (current.first );
287+ } else {
288+ current.second .ClearFlags ();
289+ }
290+ }
291+ return next_entry;
292+ }
261293
262- inline bool WillErase (CoinsCachePair& current) const noexcept { return m_will_erase; }
294+ inline bool WillErase (CoinsCachePair& current) const noexcept { return m_will_erase || current. second . coin . IsSpent () ; }
263295private:
264296 size_t & m_usage;
265297 CoinsCachePair& m_sentinel;
0 commit comments