Skip to content

Commit 05cf4e1

Browse files
andrewtothsipa
andcommitted
coins: move Sync logic to CoinsViewCacheCursor
Erase spent cache entries and clear flags of unspent entries inside the BatchWrite loop, instead of an additional loop after BatchWrite. Co-Authored-By: Pieter Wuille <[email protected]>
1 parent 7825b8b commit 05cf4e1

File tree

2 files changed

+38
-11
lines changed

2 files changed

+38
-11
lines changed

src/coins.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,15 +267,10 @@ bool CCoinsViewCache::Sync()
267267
{
268268
auto cursor{CoinsViewCacheCursor(cachedCoinsUsage, m_sentinel, cacheCoins, /*will_erase=*/false)};
269269
bool fOk = base->BatchWrite(cursor, hashBlock);
270-
// Instead of clearing `cacheCoins` as we would in Flush(), just clear the
271-
// FRESH/DIRTY flags of any coin that isn't spent.
272-
for (auto it = cacheCoins.begin(); it != cacheCoins.end(); ) {
273-
if (it->second.coin.IsSpent()) {
274-
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
275-
it = cacheCoins.erase(it);
276-
} else {
277-
it->second.ClearFlags();
278-
++it;
270+
if (fOk) {
271+
if (m_sentinel.second.Next() != &m_sentinel) {
272+
/* BatchWrite must clear flags of all entries */
273+
throw std::logic_error("Not all unspent flagged entries were cleared");
279274
}
280275
}
281276
return fOk;

src/coins.h

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
249259
struct 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(); }
263295
private:
264296
size_t& m_usage;
265297
CoinsCachePair& m_sentinel;

0 commit comments

Comments
 (0)