From f4adb464e8194f41ebd8649f837295cb0ba4eb57 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 11 Dec 2018 18:26:34 +0100 Subject: [PATCH] Add spentness flag to pegin coins --- src/coins.cpp | 82 +++++++++++++++++++++++++++++++++++++--- src/coins.h | 13 +++++++ src/test/coins_tests.cpp | 16 ++++---- src/txdb.cpp | 31 ++++++++++++--- src/txdb.h | 2 + src/txmempool.cpp | 5 +++ src/txmempool.h | 4 ++ 7 files changed, 134 insertions(+), 19 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 1d26495788..f263837f60 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -12,6 +12,8 @@ uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } CCoinsViewCursor *CCoinsView::Cursor() const { return nullptr; } +// ELEMENTS: +bool CCoinsView::IsPeginSpent(const std::pair &outpoint) const { return false; } bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { @@ -28,6 +30,8 @@ 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(); } +// ELEMENTS: +bool CCoinsViewBacked::IsPeginSpent(const std::pair &outpoint) const { return base->IsPeginSpent(outpoint); } SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} @@ -141,6 +145,56 @@ bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const { return (it != cacheCoins.end() && !it->second.coin.IsSpent()); } +// +// ELEMENTS: + +bool CCoinsViewCache::IsPeginSpent(const std::pair &outpoint) const { + assert(!outpoint.second.hash.IsNull()); + assert(!outpoint.first.IsNull()); + + CCoinsMap::iterator it = cacheCoins.find(outpoint); + if (it == cacheCoins.end()) { + bool inserted; + std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, + std::forward_as_tuple(outpoint), std::tuple<>()); + it->second.peginSpent = base->IsPeginSpent(outpoint); + it->second.flags |= CCoinsCacheEntry::PEGIN; + if (!it->second.peginSpent) + it->second.flags |= CCoinsCacheEntry::FRESH; + } + return it->second.peginSpent; +} + +void CCoinsViewCache::SetPeginSpent(const std::pair &outpoint, bool fSpent) { + assert(!outpoint.second.hash.IsNull()); + assert(!outpoint.first.IsNull()); + + CCoinsMap::iterator it = cacheCoins.find(outpoint); + + bool hadSpent; + if (it == cacheCoins.end()) + hadSpent = base->IsPeginSpent(outpoint); + else + hadSpent = it->second.peginSpent; + + // If we aren't changing spentness, dont do anything at all + if (hadSpent == fSpent) + return; + + if (it == cacheCoins.end()) { + bool inserted; + std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, + std::forward_as_tuple(outpoint), std::tuple<>()); + if (!hadSpent) + it->second.flags = CCoinsCacheEntry::FRESH; + } + it->second.peginSpent = fSpent; + it->second.flags |= CCoinsCacheEntry::PEGIN | CCoinsCacheEntry::DIRTY; +} + +// END ELEMENTS +// + uint256 CCoinsViewCache::GetBestBlock() const { if (hashBlock.IsNull()) hashBlock = base->GetBestBlock(); @@ -157,17 +211,28 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn if (!(it->second.flags & CCoinsCacheEntry::DIRTY)) { continue; } + + // ELEMENTS: + bool fIsPegin = it->second.flags & CCoinsCacheEntry::PEGIN; + CCoinsMap::iterator itUs = cacheCoins.find(it->first); if (itUs == cacheCoins.end()) { // The parent cache does not have an entry, while the child does - // We can ignore it if it's both FRESH and pruned in the child - if (!(it->second.flags & CCoinsCacheEntry::FRESH && it->second.coin.IsSpent())) { + // We can ignore it if it's both FRESH and {pruned, spent pegin} in the child + if (!((it->second.flags & CCoinsCacheEntry::FRESH) && + (( fIsPegin && !it->second.peginSpent) || + (!fIsPegin && it->second.coin.IsSpent())))) { // 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.coin = std::move(it->second.coin); - cachedCoinsUsage += entry.coin.DynamicMemoryUsage(); entry.flags = CCoinsCacheEntry::DIRTY; + if (fIsPegin) { + entry.peginSpent = it->second.peginSpent; + entry.flags |= CCoinsCacheEntry::PEGIN; + } else { + entry.coin = it->second.coin; + } + cachedCoinsUsage += entry.coin.DynamicMemoryUsage(); // We can mark it FRESH in the parent if it was FRESH in the child // Otherwise it might have just been flushed from the parent's cache // and already exist in the grandparent @@ -185,7 +250,8 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn } // Found the entry in the parent cache - if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coin.IsSpent()) { + if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && + ((fIsPegin && !it->second.peginSpent) || (!fIsPegin && it->second.coin.IsSpent()))) { // The grandparent does not have an entry, and the child is // modified and being pruned. This means we can just delete // it from the parent. @@ -194,7 +260,11 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn } else { // A normal modification. cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage(); - itUs->second.coin = std::move(it->second.coin); + if (fIsPegin) { + itUs->second.peginSpent = it->second.peginSpent; + } else { + itUs->second.coin = it->second.coin; + } cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage(); itUs->second.flags |= CCoinsCacheEntry::DIRTY; // NOTE: It is possible the child has a FRESH flag here in diff --git a/src/coins.h b/src/coins.h index be44a6fad2..41997fb1fa 100644 --- a/src/coins.h +++ b/src/coins.h @@ -85,6 +85,8 @@ struct CCoinsCacheEntry { Coin coin; // The actual cached data. unsigned char flags; + // ELEMENTS: + bool peginSpent; enum Flags { DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view. @@ -94,6 +96,8 @@ struct CCoinsCacheEntry * flush the changes to the parent cache. It is always safe to * not mark FRESH if that condition is not guaranteed. */ + // ELEMENTS: + PEGIN = (1 << 2), // represents a pegin (coins is actually empty/useless, look at peginSpent instead) }; CCoinsCacheEntry() : flags(0) {} @@ -159,6 +163,10 @@ public: //! Just check whether a given outpoint is unspent. virtual bool HaveCoin(const COutPoint &outpoint) const; + // ELEMENTS: + //! Check if a given pegin has been spent + virtual bool IsPeginSpent(const std::pair &outpoint) const; + //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; @@ -199,6 +207,8 @@ public: bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; CCoinsViewCursor *Cursor() const override; size_t EstimateSize() const override; + // ELEMENTS: + bool IsPeginSpent(const std::pair &outpoint) const override; }; @@ -233,6 +243,9 @@ public: CCoinsViewCursor* Cursor() const override { throw std::logic_error("CCoinsViewCache cursor iteration not supported."); } + // ELEMENTS: + bool IsPeginSpent(const std::pair &outpoint) const override; + void SetPeginSpent(const std::pair &outpoint, bool fSpent); /** * Check if we have the given utxo already loaded in this cache. diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 63b91b74c0..ef54286b64 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -16,7 +16,7 @@ #include -int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out); +int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, const CTxIn& txin, const CScriptWitness& pegin_witness); void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight); namespace @@ -57,10 +57,10 @@ public: for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Same optimization used in CCoinsViewDB is to only write dirty entries. - map_[it->first] = it->second.coin; + map_[it->first.second] = it->second.coin; if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) { // Randomly delete empty entries on write. - map_.erase(it->first); + map_.erase(it->first.second); } } mapCoins.erase(it++); @@ -407,7 +407,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) if (!tx.IsCoinBase()) { const COutPoint &out = tx.vin[0].prevout; Coin coin = undo.vprevout[0]; - ApplyTxInUndo(std::move(coin), *(stack.back()), out); + ApplyTxInUndo(std::move(coin), *(stack.back()), out, tx.vin[0], tx.vin[0].m_pegin_witness); } // Store as a candidate for reconnection disconnected_coins.insert(utxod->first); @@ -525,7 +525,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization) } } -const static COutPoint OUTPOINT; +const static std::pair OUTPOINT; const static CAmount PRUNED = -1; const static CAmount ABSENT = -2; const static CAmount FAIL = -3; @@ -608,7 +608,7 @@ public: static void CheckAccessCoin(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); - test.cache.AccessCoin(OUTPOINT); + test.cache.AccessCoin(OUTPOINT.second); test.cache.SelfTest(); CAmount result_value; @@ -659,7 +659,7 @@ BOOST_AUTO_TEST_CASE(ccoins_access) static void CheckSpendCoins(CAmount base_value, CAmount cache_value, CAmount expected_value, char cache_flags, char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); - test.cache.SpendCoin(OUTPOINT); + test.cache.SpendCoin(OUTPOINT.second); test.cache.SelfTest(); CAmount result_value; @@ -716,7 +716,7 @@ static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount mo try { CTxOut output; output.nValue = modify_value; - test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); + test.cache.AddCoin(OUTPOINT.second, Coin(std::move(output), 1, coinbase), coinbase); test.cache.SelfTest(); GetCoinsMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error& e) { diff --git a/src/txdb.cpp b/src/txdb.cpp index d4c9fa0b10..98f5cde9b7 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -32,6 +32,9 @@ static const char DB_FLAG = 'F'; static const char DB_REINDEX_FLAG = 'R'; static const char DB_LAST_BLOCK = 'l'; +// ELEMENTS: +static const char DB_PEGIN_FLAG = 'w'; + namespace { struct CoinEntry { @@ -68,6 +71,11 @@ bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const { return db.Exists(CoinEntry(&outpoint)); } +// ELEMENTS: +bool CCoinsViewDB::IsPeginSpent(const std::pair &outpoint) const { + return db.Exists(std::make_pair(DB_PEGIN_FLAG, outpoint)); +} + uint256 CCoinsViewDB::GetBestBlock() const { uint256 hashBestChain; if (!db.Read(DB_BEST_BLOCK, hashBestChain)) @@ -110,11 +118,24 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { - CoinEntry entry(&it->first); - if (it->second.coin.IsSpent()) - batch.Erase(entry); - else - batch.Write(entry, it->second.coin); + // ELEMENTS: + if (it->second.flags & CCoinsCacheEntry::PEGIN) { + if (!it->second.peginSpent) { + batch.Erase(std::make_pair(DB_PEGIN_FLAG, it->first)); + } else { + // Once spent, we don't care about the entry data, so we store + // a static byte to indicate spentness. + batch.Write(std::make_pair(DB_PEGIN_FLAG, it->first), '1'); + } + } else { + // Non-pegin entries are stored the same way as in Core. + CoinEntry entry(&it->first.second); + if (it->second.coin.IsSpent()) { + batch.Erase(entry); + } else { + batch.Write(entry, it->second.coin); + } + } changed++; } count++; diff --git a/src/txdb.h b/src/txdb.h index d5a4bfaeb1..f5c231c528 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -54,6 +54,8 @@ public: std::vector GetHeadBlocks() const override; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; CCoinsViewCursor *Cursor() const override; + // ELEMENTS: + bool IsPeginSpent(const std::pair &outpoint) const override; //! Attempt to update from an older database format. Returns whether an error occurred. bool Upgrade(); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 8bada4dce8..d0e48fba62 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -898,6 +898,11 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); } +// ELEMENTS: +bool CCoinsViewMemPool::IsPeginSpent(const std::pair &outpoint) const { + return mempool.mapPeginsSpentToTxid.count(outpoint) || base->IsPeginSpent(outpoint); +} + size_t CTxMemPool::DynamicMemoryUsage() const { LOCK(cs); // Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. diff --git a/src/txmempool.h b/src/txmempool.h index 587d931d95..aee58cb615 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -499,6 +499,8 @@ public: const setEntries & GetMemPoolParents(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); const setEntries & GetMemPoolChildren(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); + // ELEMENTS: + std::map, uint256> mapPeginsSpentToTxid; private: typedef std::map cacheMap; @@ -711,6 +713,8 @@ protected: public: CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + // ELEMENTS: + bool IsPeginSpent(const std::pair &outpoint) const override; }; /**