From 50830796889ecaa458871f1db878c255dd2554cb Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 25 Apr 2017 11:29:39 -0700 Subject: Switch CCoinsView and chainstate db from per-txid to per-txout This patch makes several related changes: * Changes the CCoinsView virtual methods (GetCoins, HaveCoins, ...) to be COutPoint/Coin-based rather than txid/CCoins-based. * Changes the chainstate db to a new incompatible format that is also COutPoint/Coin based. * Implements reconstruction code for hash_serialized_2. * Adapts the coins_tests unit tests (thanks to Russell Yanofsky). A side effect of the new CCoinsView model is that we can no longer use the (unreliable) test for transaction outputs in the UTXO set to determine whether we already have a particular transaction. --- src/coins.cpp | 112 +++++++++++++++++++++------------------------------------- 1 file changed, 40 insertions(+), 72 deletions(-) (limited to 'src/coins.cpp') diff --git a/src/coins.cpp b/src/coins.cpp index 8b2f148379..38b8df1713 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -42,41 +42,40 @@ bool CCoins::Spend(uint32_t nPos) return true; } -bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } -bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } +bool CCoinsView::GetCoins(const COutPoint &outpoint, Coin &coin) const { return false; } +bool CCoinsView::HaveCoins(const COutPoint &outpoint) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } CCoinsViewCursor *CCoinsView::Cursor() const { return 0; } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } -bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); } -bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } +bool CCoinsViewBacked::GetCoins(const COutPoint &outpoint, Coin &coin) const { return base->GetCoins(outpoint, coin); } +bool CCoinsViewBacked::HaveCoins(const COutPoint &outpoint) const { return base->HaveCoins(outpoint); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} +SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} -CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) { } +CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {} size_t CCoinsViewCache::DynamicMemoryUsage() const { return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; } -CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { - CCoinsMap::iterator it = cacheCoins.find(txid); +CCoinsMap::iterator CCoinsViewCache::FetchCoins(const COutPoint &outpoint) const { + CCoinsMap::iterator it = cacheCoins.find(outpoint); if (it != cacheCoins.end()) return it; - CCoins tmp; - if (!base->GetCoins(txid, tmp)) + Coin tmp; + if (!base->GetCoins(outpoint, tmp)) return cacheCoins.end(); - CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; - tmp.swap(ret->second.coins); + CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first; if (ret->second.coins.IsPruned()) { - // The parent only has an empty entry for this txid; we can consider our + // The parent only has an empty entry for this outpoint; we can consider our // version as fresh. ret->second.flags = CCoinsCacheEntry::FRESH; } @@ -84,10 +83,10 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { return ret; } -bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { - CCoinsMap::const_iterator it = FetchCoins(txid); +bool CCoinsViewCache::GetCoins(const COutPoint &outpoint, Coin &coin) const { + CCoinsMap::const_iterator it = FetchCoins(outpoint); if (it != cacheCoins.end()) { - coins = it->second.coins; + coin = it->second.coins; return true; } return false; @@ -98,23 +97,18 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi if (coin.out.scriptPubKey.IsUnspendable()) return; CCoinsMap::iterator it; bool inserted; - std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint.hash), std::tuple<>()); + std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>()); bool fresh = false; if (!inserted) { cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); } if (!possible_overwrite) { - if (it->second.coins.IsAvailable(outpoint.n)) { + if (!it->second.coins.IsPruned()) { throw std::logic_error("Adding new coin that replaces non-pruned entry"); } - fresh = it->second.coins.IsPruned() && !(it->second.flags & CCoinsCacheEntry::DIRTY); + fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY); } - if (it->second.coins.vout.size() <= outpoint.n) { - it->second.coins.vout.resize(outpoint.n + 1); - } - it->second.coins.vout[outpoint.n] = std::move(coin.out); - it->second.coins.nHeight = coin.nHeight; - it->second.coins.fCoinBase = coin.fCoinBase; + it->second.coins = std::move(coin); it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0); cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); } @@ -130,58 +124,38 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) { } void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) { - CCoinsMap::iterator it = FetchCoins(outpoint.hash); + CCoinsMap::iterator it = FetchCoins(outpoint); if (it == cacheCoins.end()) return; cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); - if (moveout && it->second.coins.IsAvailable(outpoint.n)) { - *moveout = Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); + if (moveout) { + *moveout = std::move(it->second.coins); } - it->second.coins.Spend(outpoint.n); // Ignore return value: SpendCoin has no effect if no UTXO found. - if (it->second.coins.IsPruned() && it->second.flags & CCoinsCacheEntry::FRESH) { + if (it->second.flags & CCoinsCacheEntry::FRESH) { cacheCoins.erase(it); } else { - cachedCoinsUsage += it->second.coins.DynamicMemoryUsage(); it->second.flags |= CCoinsCacheEntry::DIRTY; - } -} - -const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { - CCoinsMap::const_iterator it = FetchCoins(txid); - if (it == cacheCoins.end()) { - return NULL; - } else { - return &it->second.coins; + it->second.coins.Clear(); } } static const Coin coinEmpty; -const Coin CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { - CCoinsMap::const_iterator it = FetchCoins(outpoint.hash); - if (it == cacheCoins.end() || !it->second.coins.IsAvailable(outpoint.n)) { +const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const { + CCoinsMap::const_iterator it = FetchCoins(outpoint); + if (it == cacheCoins.end()) { return coinEmpty; } else { - return Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase); + return it->second.coins; } } - -bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { - CCoinsMap::const_iterator it = FetchCoins(txid); - // We're using vtx.empty() instead of IsPruned here for performance reasons, - // as we only care about the case where a transaction was replaced entirely - // in a reorganization (which wipes vout entirely, as opposed to spending - // which just cleans individual outputs). - return (it != cacheCoins.end() && !it->second.coins.vout.empty()); -} - bool CCoinsViewCache::HaveCoins(const COutPoint &outpoint) const { - CCoinsMap::const_iterator it = FetchCoins(outpoint.hash); - return (it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n)); + CCoinsMap::const_iterator it = FetchCoins(outpoint); + return (it != cacheCoins.end() && !it->second.coins.IsPruned()); } -bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const { - CCoinsMap::const_iterator it = cacheCoins.find(txid); +bool CCoinsViewCache::HaveCoinsInCache(const COutPoint &outpoint) const { + CCoinsMap::const_iterator it = cacheCoins.find(outpoint); return it != cacheCoins.end(); } @@ -206,7 +180,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn // Otherwise we will need to create it in the parent // and move the data up and mark it as dirty CCoinsCacheEntry& entry = cacheCoins[it->first]; - entry.coins.swap(it->second.coins); + entry.coins = std::move(it->second.coins); cachedCoinsUsage += entry.coins.DynamicMemoryUsage(); entry.flags = CCoinsCacheEntry::DIRTY; // We can mark it FRESH in the parent if it was FRESH in the child @@ -233,7 +207,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn } else { // A normal modification. cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); - itUs->second.coins.swap(it->second.coins); + itUs->second.coins = std::move(it->second.coins); cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage(); itUs->second.flags |= CCoinsCacheEntry::DIRTY; // NOTE: It is possible the child has a FRESH flag here in @@ -258,7 +232,7 @@ bool CCoinsViewCache::Flush() { return fOk; } -void CCoinsViewCache::Uncache(const uint256& hash) +void CCoinsViewCache::Uncache(const COutPoint& hash) { CCoinsMap::iterator it = cacheCoins.find(hash); if (it != cacheCoins.end() && it->second.flags == 0) { @@ -273,9 +247,9 @@ unsigned int CCoinsViewCache::GetCacheSize() const { const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const { - const CCoins* coins = AccessCoins(input.prevout.hash); - assert(coins && coins->IsAvailable(input.prevout.n)); - return coins->vout[input.prevout.n]; + const Coin& coin = AccessCoin(input.prevout); + assert(!coin.IsPruned()); + return coin.out; } CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const @@ -294,9 +268,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { if (!tx.IsCoinBase()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { - const COutPoint &prevout = tx.vin[i].prevout; - const CCoins* coins = AccessCoins(prevout.hash); - if (!coins || !coins->IsAvailable(prevout.n)) { + if (!HaveCoins(tx.vin[i].prevout)) { return false; } } @@ -304,13 +276,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const return true; } -CCoinsViewCursor::~CCoinsViewCursor() -{ -} - static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); // TODO: merge with similar definition in undo.h. -const Coin AccessByTxid(const CCoinsViewCache& view, const uint256& txid) +const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid) { COutPoint iter(txid, 0); while (iter.n < MAX_OUTPUTS_PER_BLOCK) { -- cgit v1.2.3