Skip to content

Commit 7825b8b

Browse files
andrewtothl0rinc
andcommitted
coins: pass linked list of flagged entries to BatchWrite
BatchWrite now iterates through the linked list of flagged entries instead of the entire coinsCache map. Co-Authored-By: l0rinc <[email protected]>
1 parent a14edad commit 7825b8b

File tree

7 files changed

+59
-36
lines changed

7 files changed

+59
-36
lines changed

src/coins.cpp

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
1313
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
1414
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
15-
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) { return false; }
15+
bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; }
1616
std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; }
1717

1818
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
@@ -27,7 +27,7 @@ bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->
2727
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
2828
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
2929
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
30-
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) { return base->BatchWrite(mapCoins, hashBlock, erase); }
30+
bool CCoinsViewBacked::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return base->BatchWrite(cursor, hashBlock); }
3131
std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); }
3232
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
3333

@@ -183,10 +183,8 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
183183
hashBlock = hashBlockIn;
184184
}
185185

186-
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn, bool erase) {
187-
for (CCoinsMap::iterator it = mapCoins.begin();
188-
it != mapCoins.end();
189-
it = erase ? mapCoins.erase(it) : std::next(it)) {
186+
bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlockIn) {
187+
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
190188
// Ignore non-dirty entries (optimization).
191189
if (!it->second.IsDirty()) {
192190
continue;
@@ -200,10 +198,9 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
200198
// and mark it as dirty.
201199
itUs = cacheCoins.try_emplace(it->first).first;
202200
CCoinsCacheEntry& entry{itUs->second};
203-
if (erase) {
204-
// The `move` call here is purely an optimization; we rely on the
205-
// `mapCoins.erase` call in the `for` expression to actually remove
206-
// the entry from the child map.
201+
if (cursor.WillErase(*it)) {
202+
// Since this entry will be erased,
203+
// we can move the coin into us instead of copying it
207204
entry.coin = std::move(it->second.coin);
208205
} else {
209206
entry.coin = it->second.coin;
@@ -235,10 +232,9 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
235232
} else {
236233
// A normal modification.
237234
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
238-
if (erase) {
239-
// The `move` call here is purely an optimization; we rely on the
240-
// `mapCoins.erase` call in the `for` expression to actually remove
241-
// the entry from the child map.
235+
if (cursor.WillErase(*it)) {
236+
// Since this entry will be erased,
237+
// we can move the coin into us instead of copying it
242238
itUs->second.coin = std::move(it->second.coin);
243239
} else {
244240
itUs->second.coin = it->second.coin;
@@ -257,12 +253,10 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
257253
}
258254

259255
bool CCoinsViewCache::Flush() {
260-
bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/true);
256+
auto cursor{CoinsViewCacheCursor(cachedCoinsUsage, m_sentinel, cacheCoins, /*will_erase=*/true)};
257+
bool fOk = base->BatchWrite(cursor, hashBlock);
261258
if (fOk) {
262-
if (!cacheCoins.empty()) {
263-
/* BatchWrite must erase all cacheCoins elements when erase=true. */
264-
throw std::logic_error("Not all cached coins were erased");
265-
}
259+
cacheCoins.clear();
266260
ReallocateCache();
267261
}
268262
cachedCoinsUsage = 0;
@@ -271,7 +265,8 @@ bool CCoinsViewCache::Flush() {
271265

272266
bool CCoinsViewCache::Sync()
273267
{
274-
bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/false);
268+
auto cursor{CoinsViewCacheCursor(cachedCoinsUsage, m_sentinel, cacheCoins, /*will_erase=*/false)};
269+
bool fOk = base->BatchWrite(cursor, hashBlock);
275270
// Instead of clearing `cacheCoins` as we would in Flush(), just clear the
276271
// FRESH/DIRTY flags of any coin that isn't spent.
277272
for (auto it = cacheCoins.begin(); it != cacheCoins.end(); ) {

src/coins.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,30 @@ class CCoinsViewCursor
243243
uint256 hashBlock;
244244
};
245245

246+
/**
247+
* Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.
248+
*/
249+
struct CoinsViewCacheCursor
250+
{
251+
CoinsViewCacheCursor(size_t& usage LIFETIMEBOUND,
252+
CoinsCachePair& sentinel LIFETIMEBOUND,
253+
CCoinsMap& map LIFETIMEBOUND,
254+
bool will_erase) noexcept
255+
: m_usage(usage), m_sentinel(sentinel), m_map(map), m_will_erase(will_erase) {}
256+
257+
inline CoinsCachePair* Begin() const noexcept { return m_sentinel.second.Next(); }
258+
inline CoinsCachePair* End() const noexcept { return &m_sentinel; }
259+
260+
inline CoinsCachePair* NextAndMaybeErase(CoinsCachePair& current) noexcept { return current.second.Next(); }
261+
262+
inline bool WillErase(CoinsCachePair& current) const noexcept { return m_will_erase; }
263+
private:
264+
size_t& m_usage;
265+
CoinsCachePair& m_sentinel;
266+
CCoinsMap& m_map;
267+
bool m_will_erase;
268+
};
269+
246270
/** Abstract view on the open txout dataset. */
247271
class CCoinsView
248272
{
@@ -266,8 +290,8 @@ class CCoinsView
266290
virtual std::vector<uint256> GetHeadBlocks() const;
267291

268292
//! Do a bulk modification (multiple Coin changes + BestBlock change).
269-
//! The passed mapCoins can be modified.
270-
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true);
293+
//! The passed cursor is used to iterate through the coins.
294+
virtual bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock);
271295

272296
//! Get a cursor to iterate over the whole state
273297
virtual std::unique_ptr<CCoinsViewCursor> Cursor() const;
@@ -293,7 +317,7 @@ class CCoinsViewBacked : public CCoinsView
293317
uint256 GetBestBlock() const override;
294318
std::vector<uint256> GetHeadBlocks() const override;
295319
void SetBackend(CCoinsView &viewIn);
296-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
320+
bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) override;
297321
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
298322
size_t EstimateSize() const override;
299323
};
@@ -332,7 +356,7 @@ class CCoinsViewCache : public CCoinsViewBacked
332356
bool HaveCoin(const COutPoint &outpoint) const override;
333357
uint256 GetBestBlock() const override;
334358
void SetBestBlock(const uint256 &hashBlock);
335-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
359+
bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) override;
336360
std::unique_ptr<CCoinsViewCursor> Cursor() const override {
337361
throw std::logic_error("CCoinsViewCache cursor iteration not supported.");
338362
}

src/test/coins_tests.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ class CCoinsViewTest : public CCoinsView
5555

5656
uint256 GetBestBlock() const override { return hashBestBlock_; }
5757

58-
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, bool erase = true) override
58+
bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) override
5959
{
60-
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); it = erase ? mapCoins.erase(it) : std::next(it)) {
60+
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)){
6161
if (it->second.IsDirty()) {
6262
// Same optimization used in CCoinsViewDB is to only write dirty entries.
6363
map_[it->first] = it->second.coin;
@@ -618,8 +618,9 @@ void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags)
618618
sentinel.second.SelfRef(sentinel);
619619
CCoinsMapMemoryResource resource;
620620
CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
621-
InsertCoinsMapEntry(map, sentinel, value, flags);
622-
BOOST_CHECK(view.BatchWrite(map, {}));
621+
auto usage{InsertCoinsMapEntry(map, sentinel, value, flags)};
622+
auto cursor{CoinsViewCacheCursor(usage, sentinel, map, /*will_erase=*/true)};
623+
BOOST_CHECK(view.BatchWrite(cursor, {}));
623624
}
624625

625626
class SingleEntryCacheTest

src/test/fuzz/coins_view.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
122122
[&] {
123123
CoinsCachePair sentinel{};
124124
sentinel.second.SelfRef(sentinel);
125+
size_t usage{0};
125126
CCoinsMapMemoryResource resource;
126127
CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
127128
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
@@ -140,10 +141,12 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
140141
}
141142
auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
142143
it->second.AddFlags(flags, *it, sentinel);
144+
usage += it->second.coin.DynamicMemoryUsage();
143145
}
144146
bool expected_code_path = false;
145147
try {
146-
coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
148+
auto cursor{CoinsViewCacheCursor(usage, sentinel, coins_map, /*will_erase=*/true)};
149+
coins_view_cache.BatchWrite(cursor, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
147150
expected_code_path = true;
148151
} catch (const std::logic_error& e) {
149152
if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) {

src/test/fuzz/coinscache_sim.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,13 @@ class CoinsViewBottom final : public CCoinsView
172172
std::unique_ptr<CCoinsViewCursor> Cursor() const final { return {}; }
173173
size_t EstimateSize() const final { return m_data.size(); }
174174

175-
bool BatchWrite(CCoinsMap& data, const uint256&, bool erase) final
175+
bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
176176
{
177-
for (auto it = data.begin(); it != data.end(); it = erase ? data.erase(it) : std::next(it)) {
177+
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
178178
if (it->second.IsDirty()) {
179179
if (it->second.coin.IsSpent() && (it->first.n % 5) != 4) {
180180
m_data.erase(it->first);
181-
} else if (erase) {
181+
} else if (cursor.WillErase(*it)) {
182182
m_data[it->first] = std::move(it->second.coin);
183183
} else {
184184
m_data[it->first] = it->second.coin;

src/txdb.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
8888
return vhashHeadBlocks;
8989
}
9090

91-
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) {
91+
bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) {
9292
CDBBatch batch(*m_db);
9393
size_t count = 0;
9494
size_t changed = 0;
@@ -114,7 +114,7 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, boo
114114
batch.Erase(DB_BEST_BLOCK);
115115
batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
116116

117-
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
117+
for (auto it{cursor.Begin()}; it != cursor.End();) {
118118
if (it->second.IsDirty()) {
119119
CoinEntry entry(&it->first);
120120
if (it->second.coin.IsSpent())
@@ -124,7 +124,7 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, boo
124124
changed++;
125125
}
126126
count++;
127-
it = erase ? mapCoins.erase(it) : std::next(it);
127+
it = cursor.NextAndMaybeErase(*it);
128128
if (batch.SizeEstimate() > m_options.batch_write_bytes) {
129129
LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
130130
m_db->WriteBatch(batch);

src/txdb.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class CCoinsViewDB final : public CCoinsView
6363
bool HaveCoin(const COutPoint &outpoint) const override;
6464
uint256 GetBestBlock() const override;
6565
std::vector<uint256> GetHeadBlocks() const override;
66-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
66+
bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) override;
6767
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
6868

6969
//! Whether an unsupported database format is used.

0 commit comments

Comments
 (0)