Skip to content

Commit 17372d7

Browse files
committed
Merge bitcoin/bitcoin#30906: refactor: prohibit direct flags access in CCoinsCacheEntry and remove invalid tests
50cce20 test, refactor: Compact ccoins_access and ccoins_spend (Lőrinc) 0a159f0 test, refactor: Remove remaining unbounded flags from coins_tests (Lőrinc) c0b4b2c test: Validate error messages on fail (Lőrinc) d5f8d60 test: Group values and states in tests into CoinEntry wrappers (Lőrinc) ca74aa7 test, refactor: Migrate GetCoinsMapEntry to return MaybeCoin (Lőrinc) 15aaa81 coins, refactor: Remove direct GetFlags access (Lőrinc) 6b73369 coins, refactor: Assume state after SetClean in AddFlags to prevent dangling pointers (Lőrinc) fc8c282 coins, refactor: Make AddFlags, SetDirty, SetFresh static (Lőrinc) cd0498e coins, refactor: Split up AddFlags to remove invalid states (Lőrinc) Pull request description: Similarly to bitcoin/bitcoin#30849, this cleanup is intended to de-risk bitcoin/bitcoin#30673 (comment) by simplifying the coin cache public interface. `CCoinsCacheEntry` provided general access to its internal flags state, even though, in reality, it could only be `clean`, `fresh`, `dirty`, or `fresh|dirty` (in the follow-up, we will remove `fresh` without `dirty`). Once it was marked as `dirty`, we couldn’t set the state back to clean with `AddFlags(0)`—tests explicitly checked against that. This PR refines the public interface to make this distinction clearer and to make invalid behavior impossible, rather than just checked by tests. We don't need extensive access to the internals of `CCoinsCacheEntry`, as many tests were simply validating invalid combinations in this way. The last few commits contain significant test refactorings to make `coins_tests` easier to change in follow-ups. ACKs for top commit: andrewtoth: Code Review ACK 50cce20 laanwj: Code review ACK 50cce20 ryanofsky: Code review ACK 50cce20. Looks good! Thanks for the followups. Tree-SHA512: c0d65f1c7680b4bb9cd368422b218f2473c2ec75a32c7350a6e11e8a1601c81d3c0ae651b9f1dae08400fb4e5d43431d9e4ccca305a718183f9a936fe47c1a6c
2 parents 11f68cc + 50cce20 commit 17372d7

File tree

5 files changed

+300
-366
lines changed

5 files changed

+300
-366
lines changed

src/coins.cpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const
5353
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
5454
if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins
5555
// The parent only has an empty entry for this outpoint; we can consider our version as fresh.
56-
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel);
56+
CCoinsCacheEntry::SetFresh(*ret, m_sentinel);
5757
}
5858
} else {
5959
cacheCoins.erase(ret);
@@ -99,7 +99,8 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
9999
fresh = !it->second.IsDirty();
100100
}
101101
it->second.coin = std::move(coin);
102-
it->second.AddFlags(CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0), *it, m_sentinel);
102+
CCoinsCacheEntry::SetDirty(*it, m_sentinel);
103+
if (fresh) CCoinsCacheEntry::SetFresh(*it, m_sentinel);
103104
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
104105
TRACEPOINT(utxocache, add,
105106
outpoint.hash.data(),
@@ -111,13 +112,8 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
111112

112113
void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) {
113114
cachedCoinsUsage += coin.DynamicMemoryUsage();
114-
auto [it, inserted] = cacheCoins.emplace(
115-
std::piecewise_construct,
116-
std::forward_as_tuple(std::move(outpoint)),
117-
std::forward_as_tuple(std::move(coin)));
118-
if (inserted) {
119-
it->second.AddFlags(CCoinsCacheEntry::DIRTY, *it, m_sentinel);
120-
}
115+
auto [it, inserted] = cacheCoins.try_emplace(std::move(outpoint), std::move(coin));
116+
if (inserted) CCoinsCacheEntry::SetDirty(*it, m_sentinel);
121117
}
122118

123119
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
@@ -147,7 +143,7 @@ bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
147143
if (it->second.IsFresh()) {
148144
cacheCoins.erase(it);
149145
} else {
150-
it->second.AddFlags(CCoinsCacheEntry::DIRTY, *it, m_sentinel);
146+
CCoinsCacheEntry::SetDirty(*it, m_sentinel);
151147
it->second.coin.Clear();
152148
}
153149
return true;
@@ -207,13 +203,11 @@ bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &ha
207203
entry.coin = it->second.coin;
208204
}
209205
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
210-
entry.AddFlags(CCoinsCacheEntry::DIRTY, *itUs, m_sentinel);
206+
CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);
211207
// We can mark it FRESH in the parent if it was FRESH in the child
212208
// Otherwise it might have just been flushed from the parent's cache
213209
// and already exist in the grandparent
214-
if (it->second.IsFresh()) {
215-
entry.AddFlags(CCoinsCacheEntry::FRESH, *itUs, m_sentinel);
216-
}
210+
if (it->second.IsFresh()) CCoinsCacheEntry::SetFresh(*itUs, m_sentinel);
217211
}
218212
} else {
219213
// Found the entry in the parent cache
@@ -241,7 +235,7 @@ bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &ha
241235
itUs->second.coin = it->second.coin;
242236
}
243237
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
244-
itUs->second.AddFlags(CCoinsCacheEntry::DIRTY, *itUs, m_sentinel);
238+
CCoinsCacheEntry::SetDirty(*itUs, m_sentinel);
245239
// NOTE: It isn't safe to mark the coin as FRESH in the parent
246240
// cache. If it already existed and was spent in the parent
247241
// cache then marking it FRESH would prevent that spentness

src/coins.h

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct CCoinsCacheEntry
110110
private:
111111
/**
112112
* These are used to create a doubly linked list of flagged entries.
113-
* They are set in AddFlags and unset in ClearFlags.
113+
* They are set in SetDirty, SetFresh, and unset in SetClean.
114114
* A flagged entry is any entry that is either DIRTY, FRESH, or both.
115115
*
116116
* DIRTY entries are tracked so that only modified entries can be passed to
@@ -128,6 +128,21 @@ struct CCoinsCacheEntry
128128
CoinsCachePair* m_next{nullptr};
129129
uint8_t m_flags{0};
130130

131+
//! Adding a flag requires a reference to the sentinel of the flagged pair linked list.
132+
static void AddFlags(uint8_t flags, CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept
133+
{
134+
Assume(flags & (DIRTY | FRESH));
135+
if (!pair.second.m_flags) {
136+
Assume(!pair.second.m_prev && !pair.second.m_next);
137+
pair.second.m_prev = sentinel.second.m_prev;
138+
pair.second.m_next = &sentinel;
139+
sentinel.second.m_prev = &pair;
140+
pair.second.m_prev->second.m_next = &pair;
141+
}
142+
Assume(pair.second.m_prev && pair.second.m_next);
143+
pair.second.m_flags |= flags;
144+
}
145+
131146
public:
132147
Coin coin; // The actual cached data.
133148

@@ -156,52 +171,43 @@ struct CCoinsCacheEntry
156171
explicit CCoinsCacheEntry(Coin&& coin_) noexcept : coin(std::move(coin_)) {}
157172
~CCoinsCacheEntry()
158173
{
159-
ClearFlags();
174+
SetClean();
160175
}
161176

162-
//! Adding a flag also requires a self reference to the pair that contains
163-
//! this entry in the CCoinsCache map and a reference to the sentinel of the
164-
//! flagged pair linked list.
165-
inline void AddFlags(uint8_t flags, CoinsCachePair& self, CoinsCachePair& sentinel) noexcept
166-
{
167-
Assume(&self.second == this);
168-
if (!m_flags && flags) {
169-
m_prev = sentinel.second.m_prev;
170-
m_next = &sentinel;
171-
sentinel.second.m_prev = &self;
172-
m_prev->second.m_next = &self;
173-
}
174-
m_flags |= flags;
175-
}
176-
inline void ClearFlags() noexcept
177+
static void SetDirty(CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept { AddFlags(DIRTY, pair, sentinel); }
178+
static void SetFresh(CoinsCachePair& pair, CoinsCachePair& sentinel) noexcept { AddFlags(FRESH, pair, sentinel); }
179+
180+
void SetClean() noexcept
177181
{
178182
if (!m_flags) return;
179183
m_next->second.m_prev = m_prev;
180184
m_prev->second.m_next = m_next;
181185
m_flags = 0;
186+
m_prev = m_next = nullptr;
182187
}
183-
inline uint8_t GetFlags() const noexcept { return m_flags; }
184-
inline bool IsDirty() const noexcept { return m_flags & DIRTY; }
185-
inline bool IsFresh() const noexcept { return m_flags & FRESH; }
188+
bool IsDirty() const noexcept { return m_flags & DIRTY; }
189+
bool IsFresh() const noexcept { return m_flags & FRESH; }
186190

187191
//! Only call Next when this entry is DIRTY, FRESH, or both
188-
inline CoinsCachePair* Next() const noexcept {
192+
CoinsCachePair* Next() const noexcept
193+
{
189194
Assume(m_flags);
190195
return m_next;
191196
}
192197

193198
//! Only call Prev when this entry is DIRTY, FRESH, or both
194-
inline CoinsCachePair* Prev() const noexcept {
199+
CoinsCachePair* Prev() const noexcept
200+
{
195201
Assume(m_flags);
196202
return m_prev;
197203
}
198204

199205
//! Only use this for initializing the linked list sentinel
200-
inline void SelfRef(CoinsCachePair& self) noexcept
206+
void SelfRef(CoinsCachePair& pair) noexcept
201207
{
202-
Assume(&self.second == this);
203-
m_prev = &self;
204-
m_next = &self;
208+
Assume(&pair.second == this);
209+
m_prev = &pair;
210+
m_next = &pair;
205211
// Set sentinel to DIRTY so we can call Next on it
206212
m_flags = DIRTY;
207213
}
@@ -279,13 +285,13 @@ struct CoinsViewCacheCursor
279285
{
280286
const auto next_entry{current.second.Next()};
281287
// If we are not going to erase the cache, we must still erase spent entries.
282-
// Otherwise clear the flags on the entry.
288+
// Otherwise, clear the state of the entry.
283289
if (!m_will_erase) {
284290
if (current.second.coin.IsSpent()) {
285291
m_usage -= current.second.coin.DynamicMemoryUsage();
286292
m_map.erase(current.first);
287293
} else {
288-
current.second.ClearFlags();
294+
current.second.SetClean();
289295
}
290296
}
291297
return next_entry;

0 commit comments

Comments
 (0)