mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Merge bitcoin/bitcoin#30849: refactor: migrate bool GetCoin
to return optional<Coin>
4feaa28728
refactor: Rely on returned value of GetCoin instead of parameter (Lőrinc)46dfbf169b
refactor: Return optional of Coin in GetCoin (Lőrinc)e31bfb26c2
refactor: Remove unrealistic simulation state (Lőrinc) Pull request description: While reviewing [the removal of the unreachable combinations from the Coin cache logic](https://github.com/bitcoin/bitcoin/pull/30673#discussion_r1721727681), 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](https://github.com/bitcoin/bitcoin/pull/18746#issuecomment-842393167). 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-ACK4feaa28728
achow101: ACK4feaa28728
laanwj: Code review ACK4feaa28728
theStack: Code-review ACK4feaa28728
Tree-SHA512: 818d60b2e97f58c489a61120fe761fb67a08dffbefe7a3fce712d362fc9eb8c2cced23074f1bec55fe71c616a3561b5a8737919ad6ffb2635467ec4711683df7
This commit is contained in:
commit
74fb19317a
16 changed files with 99 additions and 129 deletions
|
@ -9,7 +9,7 @@
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
#include <util/trace.h>
|
#include <util/trace.h>
|
||||||
|
|
||||||
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
|
std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
|
||||||
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
|
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
|
||||||
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
|
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
|
||||||
bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; }
|
bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; }
|
||||||
|
@ -17,12 +17,11 @@ std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; }
|
||||||
|
|
||||||
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
|
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
|
||||||
{
|
{
|
||||||
Coin coin;
|
return GetCoin(outpoint).has_value();
|
||||||
return GetCoin(outpoint, coin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
|
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
|
||||||
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
|
std::optional<Coin> CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); }
|
||||||
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
|
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
|
||||||
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
|
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
|
||||||
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
|
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
|
||||||
|
@ -45,26 +44,25 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const {
|
||||||
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
|
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
|
||||||
const auto [ret, inserted] = cacheCoins.try_emplace(outpoint);
|
const auto [ret, inserted] = cacheCoins.try_emplace(outpoint);
|
||||||
if (inserted) {
|
if (inserted) {
|
||||||
if (!base->GetCoin(outpoint, ret->second.coin)) {
|
if (auto coin{base->GetCoin(outpoint)}) {
|
||||||
|
ret->second.coin = std::move(*coin);
|
||||||
|
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
|
||||||
|
if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins
|
||||||
|
// The parent only has an empty entry for this outpoint; we can consider our version as fresh.
|
||||||
|
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
cacheCoins.erase(ret);
|
cacheCoins.erase(ret);
|
||||||
return cacheCoins.end();
|
return cacheCoins.end();
|
||||||
}
|
}
|
||||||
if (ret->second.coin.IsSpent()) {
|
|
||||||
// The parent only has an empty entry for this outpoint; we can consider our version as fresh.
|
|
||||||
ret->second.AddFlags(CCoinsCacheEntry::FRESH, *ret, m_sentinel);
|
|
||||||
}
|
|
||||||
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
|
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
std::optional<Coin> CCoinsViewCache::GetCoin(const COutPoint& outpoint) const
|
||||||
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
{
|
||||||
if (it != cacheCoins.end()) {
|
if (auto it{FetchCoin(outpoint)}; it != cacheCoins.end() && !it->second.coin.IsSpent()) return it->second.coin;
|
||||||
coin = it->second.coin;
|
return std::nullopt;
|
||||||
return !coin.IsSpent();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
|
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
|
||||||
|
@ -363,8 +361,8 @@ const Coin& AccessByTxid(const CCoinsViewCache& view, const Txid& txid)
|
||||||
return coinEmpty;
|
return coinEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Func>
|
template <typename ReturnType, typename Func>
|
||||||
static bool ExecuteBackedWrapper(Func func, const std::vector<std::function<void()>>& err_callbacks)
|
static ReturnType ExecuteBackedWrapper(Func func, const std::vector<std::function<void()>>& err_callbacks)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return func();
|
return func();
|
||||||
|
@ -381,10 +379,12 @@ static bool ExecuteBackedWrapper(Func func, const std::vector<std::function<void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
std::optional<Coin> CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint) const
|
||||||
return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::GetCoin(outpoint, coin); }, m_err_callbacks);
|
{
|
||||||
|
return ExecuteBackedWrapper<std::optional<Coin>>([&]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint &outpoint) const {
|
bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const
|
||||||
return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
|
{
|
||||||
|
return ExecuteBackedWrapper<bool>([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
|
||||||
}
|
}
|
||||||
|
|
13
src/coins.h
13
src/coins.h
|
@ -303,11 +303,8 @@ private:
|
||||||
class CCoinsView
|
class CCoinsView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/** Retrieve the Coin (unspent transaction output) for a given outpoint.
|
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
|
||||||
* Returns true only when an unspent coin was found, which is returned in coin.
|
virtual std::optional<Coin> GetCoin(const COutPoint& outpoint) const;
|
||||||
* When false is returned, coin's value is unspecified.
|
|
||||||
*/
|
|
||||||
virtual bool GetCoin(const COutPoint &outpoint, Coin &coin) const;
|
|
||||||
|
|
||||||
//! Just check whether a given outpoint is unspent.
|
//! Just check whether a given outpoint is unspent.
|
||||||
virtual bool HaveCoin(const COutPoint &outpoint) const;
|
virtual bool HaveCoin(const COutPoint &outpoint) const;
|
||||||
|
@ -344,7 +341,7 @@ protected:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CCoinsViewBacked(CCoinsView *viewIn);
|
CCoinsViewBacked(CCoinsView *viewIn);
|
||||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||||
uint256 GetBestBlock() const override;
|
uint256 GetBestBlock() const override;
|
||||||
std::vector<uint256> GetHeadBlocks() const override;
|
std::vector<uint256> GetHeadBlocks() const override;
|
||||||
|
@ -384,7 +381,7 @@ public:
|
||||||
CCoinsViewCache(const CCoinsViewCache &) = delete;
|
CCoinsViewCache(const CCoinsViewCache &) = delete;
|
||||||
|
|
||||||
// Standard CCoinsView methods
|
// Standard CCoinsView methods
|
||||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||||
uint256 GetBestBlock() const override;
|
uint256 GetBestBlock() const override;
|
||||||
void SetBestBlock(const uint256 &hashBlock);
|
void SetBestBlock(const uint256 &hashBlock);
|
||||||
|
@ -514,7 +511,7 @@ public:
|
||||||
m_err_callbacks.emplace_back(std::move(f));
|
m_err_callbacks.emplace_back(std::move(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -16,10 +16,11 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins)
|
||||||
LOCK2(cs_main, node.mempool->cs);
|
LOCK2(cs_main, node.mempool->cs);
|
||||||
CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip();
|
CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip();
|
||||||
CCoinsViewMemPool mempool_view(&chain_view, *node.mempool);
|
CCoinsViewMemPool mempool_view(&chain_view, *node.mempool);
|
||||||
for (auto& coin : coins) {
|
for (auto& [outpoint, coin] : coins) {
|
||||||
if (!mempool_view.GetCoin(coin.first, coin.second)) {
|
if (auto c{mempool_view.GetCoin(outpoint)}) {
|
||||||
// Either the coin is not in the CCoinsViewCache or is spent. Clear it.
|
coin = std::move(*c);
|
||||||
coin.second.Clear();
|
} else {
|
||||||
|
coin.Clear(); // Either the coin is not in the CCoinsViewCache or is spent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,9 +358,7 @@ public:
|
||||||
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
|
std::optional<Coin> getUnspentOutput(const COutPoint& output) override
|
||||||
{
|
{
|
||||||
LOCK(::cs_main);
|
LOCK(::cs_main);
|
||||||
Coin coin;
|
return chainman().ActiveChainstate().CoinsTip().GetCoin(output);
|
||||||
if (chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin)) return coin;
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
|
TransactionError broadcastTransaction(CTransactionRef tx, CAmount max_tx_fee, std::string& err_string) override
|
||||||
{
|
{
|
||||||
|
|
|
@ -870,10 +870,9 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
|
||||||
{
|
{
|
||||||
auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) {
|
auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) {
|
||||||
for (const COutPoint& vOutPoint : vOutPoints) {
|
for (const COutPoint& vOutPoint : vOutPoints) {
|
||||||
Coin coin;
|
auto coin = !mempool || !mempool->isSpent(vOutPoint) ? view.GetCoin(vOutPoint) : std::nullopt;
|
||||||
bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin);
|
hits.push_back(coin.has_value());
|
||||||
hits.push_back(hit);
|
if (coin) outs.emplace_back(std::move(*coin));
|
||||||
if (hit) outs.emplace_back(std::move(coin));
|
|
||||||
}
|
}
|
||||||
active_height = chainman.ActiveHeight();
|
active_height = chainman.ActiveHeight();
|
||||||
active_hash = chainman.ActiveTip()->GetBlockHash();
|
active_hash = chainman.ActiveTip()->GetBlockHash();
|
||||||
|
|
|
@ -1133,35 +1133,32 @@ static RPCHelpMan gettxout()
|
||||||
if (!request.params[2].isNull())
|
if (!request.params[2].isNull())
|
||||||
fMempool = request.params[2].get_bool();
|
fMempool = request.params[2].get_bool();
|
||||||
|
|
||||||
Coin coin;
|
|
||||||
Chainstate& active_chainstate = chainman.ActiveChainstate();
|
Chainstate& active_chainstate = chainman.ActiveChainstate();
|
||||||
CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
|
CCoinsViewCache* coins_view = &active_chainstate.CoinsTip();
|
||||||
|
|
||||||
|
std::optional<Coin> coin;
|
||||||
if (fMempool) {
|
if (fMempool) {
|
||||||
const CTxMemPool& mempool = EnsureMemPool(node);
|
const CTxMemPool& mempool = EnsureMemPool(node);
|
||||||
LOCK(mempool.cs);
|
LOCK(mempool.cs);
|
||||||
CCoinsViewMemPool view(coins_view, mempool);
|
CCoinsViewMemPool view(coins_view, mempool);
|
||||||
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
|
if (!mempool.isSpent(out)) coin = view.GetCoin(out);
|
||||||
return UniValue::VNULL;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!coins_view->GetCoin(out, coin)) {
|
coin = coins_view->GetCoin(out);
|
||||||
return UniValue::VNULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (!coin) return UniValue::VNULL;
|
||||||
|
|
||||||
const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
|
const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(coins_view->GetBestBlock());
|
||||||
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
|
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
|
||||||
if (coin.nHeight == MEMPOOL_HEIGHT) {
|
if (coin->nHeight == MEMPOOL_HEIGHT) {
|
||||||
ret.pushKV("confirmations", 0);
|
ret.pushKV("confirmations", 0);
|
||||||
} else {
|
} else {
|
||||||
ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1));
|
ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin->nHeight + 1));
|
||||||
}
|
}
|
||||||
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
|
ret.pushKV("value", ValueFromAmount(coin->out.nValue));
|
||||||
UniValue o(UniValue::VOBJ);
|
UniValue o(UniValue::VOBJ);
|
||||||
ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
|
ScriptToUniv(coin->out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
|
||||||
ret.pushKV("scriptPubKey", std::move(o));
|
ret.pushKV("scriptPubKey", std::move(o));
|
||||||
ret.pushKV("coinbase", (bool)coin.fCoinBase);
|
ret.pushKV("coinbase", (bool)coin->fCoinBase);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,18 +44,14 @@ class CCoinsViewTest : public CCoinsView
|
||||||
public:
|
public:
|
||||||
CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
|
CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
|
||||||
|
|
||||||
[[nodiscard]] bool GetCoin(const COutPoint& outpoint, Coin& coin) const override
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override
|
||||||
{
|
{
|
||||||
std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
|
if (auto it{map_.find(outpoint)}; it != map_.end()) {
|
||||||
if (it == map_.end()) {
|
if (!it->second.IsSpent() || m_rng.randbool()) {
|
||||||
return false;
|
return it->second; // TODO spent coins shouldn't be returned
|
||||||
|
}
|
||||||
}
|
}
|
||||||
coin = it->second;
|
return std::nullopt;
|
||||||
if (coin.IsSpent() && m_rng.randbool() == 0) {
|
|
||||||
// Randomly return false in case of an empty entry.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 GetBestBlock() const override { return hashBestBlock_; }
|
uint256 GetBestBlock() const override { return hashBestBlock_; }
|
||||||
|
|
|
@ -162,22 +162,20 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
|
||||||
const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
|
const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
|
||||||
const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
|
const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
|
||||||
const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
|
const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
|
||||||
Coin coin_using_get_coin;
|
if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
|
||||||
const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
|
assert(*coin == coin_using_access_coin);
|
||||||
if (exists_using_get_coin) {
|
assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
|
||||||
assert(coin_using_get_coin == coin_using_access_coin);
|
} else {
|
||||||
|
assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
|
||||||
}
|
}
|
||||||
assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) ||
|
|
||||||
(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin));
|
|
||||||
// If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
|
// If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
|
||||||
const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
|
const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
|
||||||
if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
|
if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
|
||||||
assert(exists_using_have_coin);
|
assert(exists_using_have_coin);
|
||||||
}
|
}
|
||||||
Coin coin_using_backend_get_coin;
|
if (auto coin{backend_coins_view.GetCoin(random_out_point)}) {
|
||||||
if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) {
|
|
||||||
assert(exists_using_have_coin_in_backend);
|
assert(exists_using_have_coin_in_backend);
|
||||||
// Note we can't assert that `coin_using_get_coin == coin_using_backend_get_coin` because the coin in
|
// Note we can't assert that `coin_using_get_coin == *coin` because the coin in
|
||||||
// the cache may have been modified but not yet flushed.
|
// the cache may have been modified but not yet flushed.
|
||||||
} else {
|
} else {
|
||||||
assert(!exists_using_have_coin_in_backend);
|
assert(!exists_using_have_coin_in_backend);
|
||||||
|
|
|
@ -137,9 +137,8 @@ struct CacheLevel
|
||||||
|
|
||||||
/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).
|
/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).
|
||||||
*
|
*
|
||||||
* The initial state consists of the empty UTXO set, though coins whose output index
|
* The initial state consists of the empty UTXO set.
|
||||||
* is 3 (mod 5) always have GetCoin() succeed (but returning an IsSpent() coin unless a UTXO
|
* Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent.
|
||||||
* exists). Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent.
|
|
||||||
* This exercises code paths with spent, non-DIRTY cache entries.
|
* This exercises code paths with spent, non-DIRTY cache entries.
|
||||||
*/
|
*/
|
||||||
class CoinsViewBottom final : public CCoinsView
|
class CoinsViewBottom final : public CCoinsView
|
||||||
|
@ -147,19 +146,11 @@ class CoinsViewBottom final : public CCoinsView
|
||||||
std::map<COutPoint, Coin> m_data;
|
std::map<COutPoint, Coin> m_data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool GetCoin(const COutPoint& outpoint, Coin& coin) const final
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
|
||||||
{
|
{
|
||||||
auto it = m_data.find(outpoint);
|
// TODO GetCoin shouldn't return spent coins
|
||||||
if (it == m_data.end()) {
|
if (auto it = m_data.find(outpoint); it != m_data.end()) return it->second;
|
||||||
if ((outpoint.n % 5) == 3) {
|
return std::nullopt;
|
||||||
coin.Clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
coin = it->second;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HaveCoin(const COutPoint& outpoint) const final
|
bool HaveCoin(const COutPoint& outpoint) const final
|
||||||
|
@ -270,17 +261,16 @@ FUZZ_TARGET(coinscache_sim)
|
||||||
// Look up in simulation data.
|
// Look up in simulation data.
|
||||||
auto sim = lookup(outpointidx);
|
auto sim = lookup(outpointidx);
|
||||||
// Look up in real caches.
|
// Look up in real caches.
|
||||||
Coin realcoin;
|
auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]);
|
||||||
auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin);
|
|
||||||
// Compare results.
|
// Compare results.
|
||||||
if (!sim.has_value()) {
|
if (!sim.has_value()) {
|
||||||
assert(!real || realcoin.IsSpent());
|
assert(!realcoin || realcoin->IsSpent());
|
||||||
} else {
|
} else {
|
||||||
assert(real && !realcoin.IsSpent());
|
assert(realcoin && !realcoin->IsSpent());
|
||||||
const auto& simcoin = data.coins[sim->first];
|
const auto& simcoin = data.coins[sim->first];
|
||||||
assert(realcoin.out == simcoin.out);
|
assert(realcoin->out == simcoin.out);
|
||||||
assert(realcoin.fCoinBase == simcoin.fCoinBase);
|
assert(realcoin->fCoinBase == simcoin.fCoinBase);
|
||||||
assert(realcoin.nHeight == sim->second);
|
assert(realcoin->nHeight == sim->second);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -465,16 +455,15 @@ FUZZ_TARGET(coinscache_sim)
|
||||||
|
|
||||||
// Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
|
// Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
|
||||||
for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
|
for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
|
||||||
Coin realcoin;
|
auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
|
||||||
bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin);
|
|
||||||
auto sim = lookup(outpointidx, 0);
|
auto sim = lookup(outpointidx, 0);
|
||||||
if (!sim.has_value()) {
|
if (!sim.has_value()) {
|
||||||
assert(!real || realcoin.IsSpent());
|
assert(!realcoin || realcoin->IsSpent());
|
||||||
} else {
|
} else {
|
||||||
assert(real && !realcoin.IsSpent());
|
assert(realcoin && !realcoin->IsSpent());
|
||||||
assert(realcoin.out == data.coins[sim->first].out);
|
assert(realcoin->out == data.coins[sim->first].out);
|
||||||
assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase);
|
assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
|
||||||
assert(realcoin.nHeight == sim->second);
|
assert(realcoin->nHeight == sim->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,9 +214,8 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
|
||||||
// Helper to query an amount
|
// Helper to query an amount
|
||||||
const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
|
const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool};
|
||||||
const auto GetAmount = [&](const COutPoint& outpoint) {
|
const auto GetAmount = [&](const COutPoint& outpoint) {
|
||||||
Coin c;
|
auto coin{amount_view.GetCoin(outpoint).value()};
|
||||||
Assert(amount_view.GetCoin(outpoint, c));
|
return coin.out.nValue;
|
||||||
return c.out.nValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
|
||||||
|
|
|
@ -433,9 +433,8 @@ std::pair<CMutableTransaction, CAmount> TestChain100Setup::CreateValidTransactio
|
||||||
std::map<COutPoint, Coin> input_coins;
|
std::map<COutPoint, Coin> input_coins;
|
||||||
CAmount inputs_amount{0};
|
CAmount inputs_amount{0};
|
||||||
for (const auto& outpoint_to_spend : inputs) {
|
for (const auto& outpoint_to_spend : inputs) {
|
||||||
// - Use GetCoin to properly populate utxo_to_spend,
|
// Use GetCoin to properly populate utxo_to_spend
|
||||||
Coin utxo_to_spend;
|
auto utxo_to_spend{coins_cache.GetCoin(outpoint_to_spend).value()};
|
||||||
assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend));
|
|
||||||
input_coins.insert({outpoint_to_spend, utxo_to_spend});
|
input_coins.insert({outpoint_to_spend, utxo_to_spend});
|
||||||
inputs_amount += utxo_to_spend.out.nValue;
|
inputs_amount += utxo_to_spend.out.nValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,10 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
std::optional<Coin> CCoinsViewDB::GetCoin(const COutPoint& outpoint) const
|
||||||
return m_db->Read(CoinEntry(&outpoint), coin);
|
{
|
||||||
|
if (Coin coin; m_db->Read(CoinEntry(&outpoint), coin)) return coin;
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
|
bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
|
||||||
|
|
|
@ -57,7 +57,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options);
|
explicit CCoinsViewDB(DBParams db_params, CoinsViewOptions options);
|
||||||
|
|
||||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||||
bool HaveCoin(const COutPoint &outpoint) const override;
|
bool HaveCoin(const COutPoint &outpoint) const override;
|
||||||
uint256 GetBestBlock() const override;
|
uint256 GetBestBlock() const override;
|
||||||
std::vector<uint256> GetHeadBlocks() const override;
|
std::vector<uint256> GetHeadBlocks() const override;
|
||||||
|
|
|
@ -988,12 +988,12 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
|
||||||
|
|
||||||
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
|
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
|
||||||
|
|
||||||
bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
std::optional<Coin> CCoinsViewMemPool::GetCoin(const COutPoint& outpoint) const
|
||||||
|
{
|
||||||
// Check to see if the inputs are made available by another tx in the package.
|
// Check to see if the inputs are made available by another tx in the package.
|
||||||
// These Coins would not be available in the underlying CoinsView.
|
// These Coins would not be available in the underlying CoinsView.
|
||||||
if (auto it = m_temp_added.find(outpoint); it != m_temp_added.end()) {
|
if (auto it = m_temp_added.find(outpoint); it != m_temp_added.end()) {
|
||||||
coin = it->second;
|
return it->second;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an entry in the mempool exists, always return that one, as it's guaranteed to never
|
// If an entry in the mempool exists, always return that one, as it's guaranteed to never
|
||||||
|
@ -1002,14 +1002,13 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
||||||
CTransactionRef ptx = mempool.get(outpoint.hash);
|
CTransactionRef ptx = mempool.get(outpoint.hash);
|
||||||
if (ptx) {
|
if (ptx) {
|
||||||
if (outpoint.n < ptx->vout.size()) {
|
if (outpoint.n < ptx->vout.size()) {
|
||||||
coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
|
Coin coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
|
||||||
m_non_base_coins.emplace(outpoint);
|
m_non_base_coins.emplace(outpoint);
|
||||||
return true;
|
return coin;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return base->GetCoin(outpoint, coin);
|
return base->GetCoin(outpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx)
|
void CCoinsViewMemPool::PackageAddTransaction(const CTransactionRef& tx)
|
||||||
|
|
|
@ -851,7 +851,7 @@ public:
|
||||||
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
|
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
|
||||||
/** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the
|
/** GetCoin, returning whether it exists and is not spent. Also updates m_non_base_coins if the
|
||||||
* coin is not fetched from base. */
|
* coin is not fetched from base. */
|
||||||
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
|
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
|
||||||
/** Add the coins created by this transaction. These coins are only temporarily stored in
|
/** Add the coins created by this transaction. These coins are only temporarily stored in
|
||||||
* m_temp_added and cannot be flushed to the back end. Only used for package validation. */
|
* m_temp_added and cannot be flushed to the back end. Only used for package validation. */
|
||||||
void PackageAddTransaction(const CTransactionRef& tx);
|
void PackageAddTransaction(const CTransactionRef& tx);
|
||||||
|
|
|
@ -176,18 +176,14 @@ std::optional<std::vector<int>> CalculatePrevHeights(
|
||||||
std::vector<int> prev_heights;
|
std::vector<int> prev_heights;
|
||||||
prev_heights.resize(tx.vin.size());
|
prev_heights.resize(tx.vin.size());
|
||||||
for (size_t i = 0; i < tx.vin.size(); ++i) {
|
for (size_t i = 0; i < tx.vin.size(); ++i) {
|
||||||
const CTxIn& txin = tx.vin[i];
|
if (auto coin{coins.GetCoin(tx.vin[i].prevout)}) {
|
||||||
Coin coin;
|
prev_heights[i] = coin->nHeight == MEMPOOL_HEIGHT
|
||||||
if (!coins.GetCoin(txin.prevout, coin)) {
|
? tip.nHeight + 1 // Assume all mempool transaction confirm in the next block.
|
||||||
|
: coin->nHeight;
|
||||||
|
} else {
|
||||||
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex());
|
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex());
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
if (coin.nHeight == MEMPOOL_HEIGHT) {
|
|
||||||
// Assume all mempool transaction confirm in the next block.
|
|
||||||
prev_heights[i] = tip.nHeight + 1;
|
|
||||||
} else {
|
|
||||||
prev_heights[i] = coin.nHeight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return prev_heights;
|
return prev_heights;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue