Skip to content

Commit 74fb193

Browse files
committed
Merge bitcoin/bitcoin#30849: refactor: migrate bool GetCoin to return optional<Coin>
4feaa28 refactor: Rely on returned value of GetCoin instead of parameter (Lőrinc) 46dfbf1 refactor: Return optional of Coin in GetCoin (Lőrinc) e31bfb2 refactor: Remove unrealistic simulation state (Lőrinc) Pull request description: While reviewing [the removal of the unreachable combinations from the Coin cache logic](bitcoin/bitcoin#30673 (comment)), we've noticed that the related tests often [reflect impossible states](https://github.com/bitcoin/bitcoin/pull/30673/files#r1740154464). Browsing the Coin cache refactoring history revealed that migrating `bool GetCoin` to `optional<Coin> GetCoin` was [already proposed a few times before](bitcoin/bitcoin#18746 (comment)). This refactor makes certain invalid states impossible, reducing the possibility of errors and making the code easier to understand. This will let us remove test code that exercises the impossible states as well. The PR is done in multiple small steps, first swapping the new `optional` return value, slowly strangling out the usages of the return parameter, followed by the removal of the parameter. Most of the invalid test states were still kept, except for https://github.com/bitcoin/bitcoin/pull/30673/files#r1748087322, where the new design prohibits invalid usage and https://github.com/bitcoin/bitcoin/pull/30673/files#r1749350258 was just marked with a TODO, will be removed in a follow-up PR. ACKs for top commit: andrewtoth: re-ACK 4feaa28 achow101: ACK 4feaa28 laanwj: Code review ACK 4feaa28 theStack: Code-review ACK 4feaa28 Tree-SHA512: 818d60b2e97f58c489a61120fe761fb67a08dffbefe7a3fce712d362fc9eb8c2cced23074f1bec55fe71c616a3561b5a8737919ad6ffb2635467ec4711683df7
2 parents c16e909 + 4feaa28 commit 74fb193

16 files changed

+99
-129
lines changed

src/coins.cpp

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,19 @@
99
#include <random.h>
1010
#include <util/trace.h>
1111

12-
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
12+
std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
1313
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
1414
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
1515
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
1919
{
20-
Coin coin;
21-
return GetCoin(outpoint, coin);
20+
return GetCoin(outpoint).has_value();
2221
}
2322

2423
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
25-
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
24+
std::optional<Coin> CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); }
2625
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
2726
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
2827
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
@@ -45,26 +44,25 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const {
4544
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
4645
const auto [ret, inserted] = cacheCoins.try_emplace(outpoint);
4746
if (inserted) {
48-
if (!base->GetCoin(outpoint, ret->second.coin)) {
47+
if (auto coin{base->GetCoin(outpoint)}) {
48+
ret->second.coin = std::move(*coin);
49+
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
50+
if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins
51+
// The parent only has an empty entry for this outpoint; we can consider our version as fresh.
52+
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel);
53+
}
54+
} else {
4955
cacheCoins.erase(ret);
5056
return cacheCoins.end();
5157
}
52-
if (ret->second.coin.IsSpent()) {
53-
// The parent only has an empty entry for this outpoint; we can consider our version as fresh.
54-
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel);
55-
}
56-
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
5758
}
5859
return ret;
5960
}
6061

61-
bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
62-
CCoinsMap::const_iterator it = FetchCoin(outpoint);
63-
if (it != cacheCoins.end()) {
64-
coin = it->second.coin;
65-
return !coin.IsSpent();
66-
}
67-
return false;
62+
std::optional<Coin> CCoinsViewCache::GetCoin(const COutPoint& outpoint) const
63+
{
64+
if (auto it{FetchCoin(outpoint)}; it != cacheCoins.end() && !it->second.coin.IsSpent()) return it->second.coin;
65+
return std::nullopt;
6866
}
6967

7068
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
@@ -363,8 +361,8 @@ const Coin& AccessByTxid(const CCoinsViewCache& view, const Txid& txid)
363361
return coinEmpty;
364362
}
365363

366-
template <typename Func>
367-
static bool ExecuteBackedWrapper(Func func, const std::vector<std::function<void()>>& err_callbacks)
364+
template <typename ReturnType, typename Func>
365+
static ReturnType ExecuteBackedWrapper(Func func, const std::vector<std::function<void()>>& err_callbacks)
368366
{
369367
try {
370368
return func();
@@ -381,10 +379,12 @@ static bool ExecuteBackedWrapper(Func func, const std::vector<std::function<void
381379
}
382380
}
383381

384-
bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) const {
385-
return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::GetCoin(outpoint, coin); }, m_err_callbacks);
382+
std::optional<Coin> CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint) const
383+
{
384+
return ExecuteBackedWrapper<std::optional<Coin>>([&]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks);
386385
}
387386

388-
bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint &outpoint) const {
389-
return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
387+
bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const
388+
{
389+
return ExecuteBackedWrapper<bool>([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
390390
}

src/coins.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,8 @@ struct CoinsViewCacheCursor
303303
class CCoinsView
304304
{
305305
public:
306-
/** Retrieve the Coin (unspent transaction output) for a given outpoint.
307-
* Returns true only when an unspent coin was found, which is returned in coin.
308-
* When false is returned, coin's value is unspecified.
309-
*/
310-
virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
306+
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
307+
virtual std::optional<Coin> GetCoin(const COutPoint& outpoint) const;
311308

312309
//! Just check whether a given outpoint is unspent.
313310
virtual bool HaveCoin(const COutPoint &outpoint) const;
@@ -344,7 +341,7 @@ class CCoinsViewBacked : public CCoinsView
344341

345342
public:
346343
CCoinsViewBacked(CCoinsView *viewIn);
347-
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
344+
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
348345
bool HaveCoin(const COutPoint &outpoint) const override;
349346
uint256 GetBestBlock() const override;
350347
std::vector<uint256> GetHeadBlocks() const override;
@@ -384,7 +381,7 @@ class CCoinsViewCache : public CCoinsViewBacked
384381
CCoinsViewCache(const CCoinsViewCache &) = delete;
385382

386383
// Standard CCoinsView methods
387-
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
384+
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
388385
bool HaveCoin(const COutPoint &outpoint) const override;
389386
uint256 GetBestBlock() const override;
390387
void SetBestBlock(const uint256 &hashBlock);
@@ -514,7 +511,7 @@ class CCoinsViewErrorCatcher final : public CCoinsViewBacked
514511
m_err_callbacks.emplace_back(std::move(f));
515512
}
516513

517-
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
514+
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
518515
bool HaveCoin(const COutPoint &outpoint) const override;
519516

520517
private:

src/node/coin.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins)
1616
LOCK2(cs_main, node.mempool->cs);
1717
CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip();
1818
CCoinsViewMemPool mempool_view(&chain_view, *node.mempool);
19-
for (auto& coin : coins) {
20-
if (!mempool_view.GetCoin(coin.first, coin.second)) {
21-
// Either the coin is not in the CCoinsViewCache or is spent. Clear it.
22-
coin.second.Clear();
19+
for (auto& [outpoint, coin] : coins) {
20+
if (auto c{mempool_view.GetCoin(outpoint)}) {
21+
coin = std::move(*c);
22+
} else {
23+
coin.Clear(); // Either the coin is not in the CCoinsViewCache or is spent
2324
}
2425
}
2526
}

src/node/interfaces.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,7 @@ class NodeImpl : public Node
358358
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
359359
{
360360
LOCK(::cs_main);
361-
Coin coin;
362-
if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin;
363-
return {};
361+
return chainman().ActiveChainstate().CoinsTip().GetCoin(output);
364362
}
365363
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
366364
{

src/rest.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -870,10 +870,9 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
870870
{
871871
auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) {
872872
for (const COutPoint& vOutPoint : vOutPoints) {
873-
Coin coin;
874-
bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin);
875-
hits.push_back(hit);
876-
if (hit) outs.emplace_back(std::move(coin));
873+
auto coin = !mempool || !mempool->isSpent(vOutPoint) ? view.GetCoin(vOutPoint) : std::nullopt;
874+
hits.push_back(coin.has_value());
875+
if (coin) outs.emplace_back(std::move(*coin));
877876
}
878877
active_height = chainman.ActiveHeight();
879878
active_hash = chainman.ActiveTip()->GetBlockHash();

src/rpc/blockchain.cpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,35 +1133,32 @@ static RPCHelpMan gettxout()
11331133
if (!request.params[2].isNull())
11341134
fMempool = request.params[2].get_bool();
11351135

1136-
Coin coin;
11371136
Chainstate& active_chainstate = chainman.ActiveChainstate();
11381137
CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
11391138

1139+
std::optional<Coin> coin;
11401140
if (fMempool) {
11411141
const CTxMemPool& mempool = EnsureMemPool(node);
11421142
LOCK(mempool.cs);
11431143
CCoinsViewMemPool view(coins_view, mempool);
1144-
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
1145-
return UniValue::VNULL;
1146-
}
1144+
if (!mempool.isSpent(out)) coin = view.GetCoin(out);
11471145
} else {
1148-
if (!coins_view->GetCoin(out, coin)) {
1149-
return UniValue::VNULL;
1150-
}
1146+
coin = coins_view->GetCoin(out);
11511147
}
1148+
if (!coin) return UniValue::VNULL;
11521149

11531150
const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
11541151
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
1155-
if (coin.nHeight == MEMPOOL_HEIGHT) {
1152+
if (coin->nHeight == MEMPOOL_HEIGHT) {
11561153
ret.pushKV("confirmations", 0);
11571154
} else {
1158-
ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1));
1155+
ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin->nHeight + 1));
11591156
}
1160-
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
1157+
ret.pushKV("value", ValueFromAmount(coin->out.nValue));
11611158
UniValue o(UniValue::VOBJ);
1162-
ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
1159+
ScriptToUniv(coin->out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
11631160
ret.pushKV("scriptPubKey", std::move(o));
1164-
ret.pushKV("coinbase", (bool)coin.fCoinBase);
1161+
ret.pushKV("coinbase", (bool)coin->fCoinBase);
11651162

11661163
return ret;
11671164
},

src/test/coins_tests.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,14 @@ class CCoinsViewTest : public CCoinsView
4444
public:
4545
CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
4646

47-
[[nodiscard]] bool GetCoin(const COutPoint& outpoint, Coin& coin) const override
47+
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override
4848
{
49-
std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
50-
if (it == map_.end()) {
51-
return false;
52-
}
53-
coin = it->second;
54-
if (coin.IsSpent() && m_rng.randbool() == 0) {
55-
// Randomly return false in case of an empty entry.
56-
return false;
49+
if (auto it{map_.find(outpoint)}; it != map_.end()) {
50+
if (!it->second.IsSpent() || m_rng.randbool()) {
51+
return it->second; // TODO spent coins shouldn't be returned
52+
}
5753
}
58-
return true;
54+
return std::nullopt;
5955
}
6056

6157
uint256 GetBestBlock() const override { return hashBestBlock_; }

src/test/fuzz/coins_view.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -162,22 +162,20 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
162162
const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
163163
const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
164164
const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
165-
Coin coin_using_get_coin;
166-
const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
167-
if (exists_using_get_coin) {
168-
assert(coin_using_get_coin == coin_using_access_coin);
165+
if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
166+
assert(*coin == coin_using_access_coin);
167+
assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
168+
} else {
169+
assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
169170
}
170-
assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) ||
171-
(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin));
172171
// If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
173172
const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
174173
if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
175174
assert(exists_using_have_coin);
176175
}
177-
Coin coin_using_backend_get_coin;
178-
if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) {
176+
if (auto coin{backend_coins_view.GetCoin(random_out_point)}) {
179177
assert(exists_using_have_coin_in_backend);
180-
// Note we can't assert that `coin_using_get_coin == coin_using_backend_get_coin` because the coin in
178+
// Note we can't assert that `coin_using_get_coin == *coin` because the coin in
181179
// the cache may have been modified but not yet flushed.
182180
} else {
183181
assert(!exists_using_have_coin_in_backend);

src/test/fuzz/coinscache_sim.cpp

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -137,29 +137,20 @@ struct CacheLevel
137137

138138
/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).
139139
*
140-
* The initial state consists of the empty UTXO set, though coins whose output index
141-
* is 3 (mod 5) always have GetCoin() succeed (but returning an IsSpent() coin unless a UTXO
142-
* exists). Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent.
140+
* The initial state consists of the empty UTXO set.
141+
* Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent.
143142
* This exercises code paths with spent, non-DIRTY cache entries.
144143
*/
145144
class CoinsViewBottom final : public CCoinsView
146145
{
147146
std::map<COutPoint, Coin> m_data;
148147

149148
public:
150-
bool GetCoin(const COutPoint& outpoint, Coin& coin) const final
149+
std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
151150
{
152-
auto it = m_data.find(outpoint);
153-
if (it == m_data.end()) {
154-
if ((outpoint.n % 5) == 3) {
155-
coin.Clear();
156-
return true;
157-
}
158-
return false;
159-
} else {
160-
coin = it->second;
161-
return true;
162-
}
151+
// TODO GetCoin shouldn't return spent coins
152+
if (auto it = m_data.find(outpoint); it != m_data.end()) return it->second;
153+
return std::nullopt;
163154
}
164155

165156
bool HaveCoin(const COutPoint& outpoint) const final
@@ -270,17 +261,16 @@ FUZZ_TARGET(coinscache_sim)
270261
// Look up in simulation data.
271262
auto sim = lookup(outpointidx);
272263
// Look up in real caches.
273-
Coin realcoin;
274-
auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin);
264+
auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]);
275265
// Compare results.
276266
if (!sim.has_value()) {
277-
assert(!real || realcoin.IsSpent());
267+
assert(!realcoin || realcoin->IsSpent());
278268
} else {
279-
assert(real && !realcoin.IsSpent());
269+
assert(realcoin && !realcoin->IsSpent());
280270
const auto& simcoin = data.coins[sim->first];
281-
assert(realcoin.out == simcoin.out);
282-
assert(realcoin.fCoinBase == simcoin.fCoinBase);
283-
assert(realcoin.nHeight == sim->second);
271+
assert(realcoin->out == simcoin.out);
272+
assert(realcoin->fCoinBase == simcoin.fCoinBase);
273+
assert(realcoin->nHeight == sim->second);
284274
}
285275
},
286276

@@ -465,16 +455,15 @@ FUZZ_TARGET(coinscache_sim)
465455

466456
// Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
467457
for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
468-
Coin realcoin;
469-
bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin);
458+
auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
470459
auto sim = lookup(outpointidx, 0);
471460
if (!sim.has_value()) {
472-
assert(!real || realcoin.IsSpent());
461+
assert(!realcoin || realcoin->IsSpent());
473462
} else {
474-
assert(real && !realcoin.IsSpent());
475-
assert(realcoin.out == data.coins[sim->first].out);
476-
assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase);
477-
assert(realcoin.nHeight == sim->second);
463+
assert(realcoin && !realcoin->IsSpent());
464+
assert(realcoin->out == data.coins[sim->first].out);
465+
assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
466+
assert(realcoin->nHeight == sim->second);
478467
}
479468
}
480469
}

src/test/fuzz/tx_pool.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,8 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
214214
// Helper to query an amount
215215
const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
216216
const auto GetAmount = [&](const COutPoint& outpoint) {
217-
Coin c;
218-
Assert(amount_view.GetCoin(outpoint, c));
219-
return c.out.nValue;
217+
auto coin{amount_view.GetCoin(outpoint).value()};
218+
return coin.out.nValue;
220219
};
221220

222221
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)

0 commit comments

Comments
 (0)