diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -169,6 +169,7 @@ util.h \ utilmoneystr.h \ utiltime.h \ + utxocommit.cpp \ validation.h \ validationinterface.h \ versionbits.h \ @@ -233,7 +234,6 @@ txdb.cpp \ txmempool.cpp \ ui_interface.cpp \ - utxocommit.cpp \ validation.cpp \ validationinterface.cpp \ versionbits.cpp \ diff --git a/src/coins.h b/src/coins.h --- a/src/coins.h +++ b/src/coins.h @@ -12,11 +12,14 @@ #include "memusage.h" #include "serialize.h" #include "uint256.h" +#include "utxocommit.h" #include #include #include +class CUtxoCommit; + /** * A UTXO entry. * @@ -54,10 +57,11 @@ } template void Serialize(Stream &s) const { - assert(!IsSpent()); ::Serialize(s, VARINT(nHeightAndIsCoinBase)); // only compress for disk format if (s.GetType() & SER_DISK) { + assert(!IsSpent()); + ::Serialize(s, CTxOutCompressor(REF(out))); } else { ::Serialize(s, REF(out)); @@ -108,12 +112,16 @@ // This cache entry is potentially different from the version in the // parent view. DIRTY = (1 << 0), - // The parent view does not have this entry (or it is pruned). + // Neither the commitDelta nor the parent view does not have this entry + // (or it is pruned) FRESH = (1 << 1), /* Note that FRESH is a performance optimization with which we can erase coins that are fully spent if we know we do not need to flush the changes to the parent cache. It is always safe to not mark FRESH if that condition is not guaranteed. */ + + // The commitDelta is not yet updated with this entry + UNCOMMITED = (1 << 2), }; CCoinsCacheEntry() : flags(0) {} @@ -176,6 +184,11 @@ //! Estimate database size (0 if not implemented) virtual size_t EstimateSize() const { return 0; } + + virtual CUtxoCommit GetCommitment(); + + //! Updates the commitment with pending UNCOMMITTED changes + virtual void UpdateCommitment(CCoinsMap &mapCoins); }; /** CCoinsView backed by another CCoinsView */ @@ -193,6 +206,7 @@ bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; CCoinsViewCursor *Cursor() const override; size_t EstimateSize() const override; + CUtxoCommit GetCommitment() override; }; /** @@ -256,6 +270,14 @@ */ bool Flush(); + /** + * Returns the UTXO commitment from the current full set + * This is the combination of the backed commitment and the commitment delta + */ + CUtxoCommit GetCommitment(); + + void UpdateCommitment(CCoinsMap &mapCoins); + /** * Removes the UTXO with the given outpoint from the cache, if it is not * modified. diff --git a/src/coins.cpp b/src/coins.cpp --- a/src/coins.cpp +++ b/src/coins.cpp @@ -7,6 +7,7 @@ #include "consensus/consensus.h" #include "memusage.h" #include "random.h" +#include "util.h" #include @@ -29,6 +30,12 @@ return nullptr; } +CUtxoCommit CCoinsView::GetCommitment() { + return CUtxoCommit(); +} + +void CCoinsView::UpdateCommitment(CCoinsMap &mapCoins) {} + CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) {} bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); @@ -56,6 +63,10 @@ return base->EstimateSize(); } +CUtxoCommit CCoinsViewBacked::GetCommitment() { + return base->GetCommitment(); +} + SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} @@ -123,8 +134,8 @@ fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY); } it->second.coin = std::move(coin); - it->second.flags |= - CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0); + it->second.flags |= CCoinsCacheEntry::UNCOMMITED | CCoinsCacheEntry::DIRTY | + (fresh ? CCoinsCacheEntry::FRESH : 0); cachedCoinsUsage += it->second.coin.DynamicMemoryUsage(); } @@ -154,7 +165,8 @@ if (it->second.flags & CCoinsCacheEntry::FRESH) { cacheCoins.erase(it); } else { - it->second.flags |= CCoinsCacheEntry::DIRTY; + it->second.flags |= + CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::UNCOMMITED; it->second.coin.Clear(); } return true; @@ -208,11 +220,15 @@ entry.coin = std::move(it->second.coin); cachedCoinsUsage += entry.coin.DynamicMemoryUsage(); entry.flags = CCoinsCacheEntry::DIRTY; + // 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 if (it->second.flags & CCoinsCacheEntry::FRESH) entry.flags |= CCoinsCacheEntry::FRESH; + + if (it->second.flags & CCoinsCacheEntry::UNCOMMITED) + entry.flags |= CCoinsCacheEntry::UNCOMMITED; } } else { // Assert that the child cache entry was not marked FRESH if the @@ -244,6 +260,8 @@ // we must not copy that FRESH flag to the parent as that // pruned state likely still needs to be communicated to the // grandparent. + if (it->second.flags & CCoinsCacheEntry::UNCOMMITED) + itUs->second.flags |= CCoinsCacheEntry::UNCOMMITED; } } } @@ -254,6 +272,10 @@ return true; } +void CCoinsViewCache::UpdateCommitment(CCoinsMap &mapCoins) { + base->UpdateCommitment(mapCoins); +} + bool CCoinsViewCache::Flush() { bool fOk = base->BatchWrite(cacheCoins, hashBlock); cacheCoins.clear(); @@ -261,6 +283,11 @@ return fOk; } +CUtxoCommit CCoinsViewCache::GetCommitment() { + base->UpdateCommitment(cacheCoins); + return base->GetCommitment(); +} + void CCoinsViewCache::Uncache(const COutPoint &outpoint) { CCoinsMap::iterator it = cacheCoins.find(outpoint); if (it != cacheCoins.end() && it->second.flags == 0) { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -610,6 +610,7 @@ void WriteCoinViewEntry(CCoinsView &view, const Amount value, char flags) { CCoinsMap map; InsertCoinMapEntry(map, value, flags); + view.BatchWrite(map, {}); } diff --git a/src/txdb.h b/src/txdb.h --- a/src/txdb.h +++ b/src/txdb.h @@ -66,6 +66,9 @@ protected: CDBWrapper db; + //! Pending changes to commitment not yet written to the database + CUtxoCommit utxoCommitDelta; + public: CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); @@ -76,6 +79,9 @@ bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; CCoinsViewCursor *Cursor() const override; + CUtxoCommit GetCommitment() override; + void UpdateCommitment(CCoinsMap &mapCoins) override; + //! Attempt to update from an older database format. //! Returns whether an error occurred. bool Upgrade(); diff --git a/src/txdb.cpp b/src/txdb.cpp --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -78,7 +78,42 @@ return vhashHeadBlocks; } +CUtxoCommit CCoinsViewDB::GetCommitment() { + + // TODO fetch from storage + // TODO perform initial upgrade using CUtxoCommit::AddCoinView + return CUtxoCommit(); +} + +void CCoinsViewDB::UpdateCommitment(CCoinsMap &mapCoins) { + + size_t updated = 0; + LogPrintf("Starting update commitment delta\n"); + // TODO parallelize + for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); + it++) { + // Ignore non-dirty entries (optimization). + if (it->second.flags & CCoinsCacheEntry::UNCOMMITED) { + + if (it->second.coin.IsSpent()) { + // TODO: This doesn't work; we'll need to change + // Coin::IsSpent to keep the TxOut in memory if spent + // and add a bool is spent + utxoCommitDelta.Remove(it->first, it->second.coin); + } else { + utxoCommitDelta.Add(it->first, it->second.coin); + } + updated++; + + it->second.flags &= + ~(CCoinsCacheEntry::UNCOMMITED | CCoinsCacheEntry::FRESH); + } + } + LogPrintf("Done update commitment delta; %d entries\n", updated); +} + bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { + CDBBatch batch(db); size_t count = 0; size_t changed = 0; @@ -104,6 +139,8 @@ batch.Erase(DB_BEST_BLOCK); batch.Write(DB_HEAD_BLOCKS, std::vector{hashBlock, old_tip}); + UpdateCommitment(mapCoins); + for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { CoinEntry entry(&it->first); diff --git a/src/utxocommit.h b/src/utxocommit.h --- a/src/utxocommit.h +++ b/src/utxocommit.h @@ -5,12 +5,12 @@ #ifndef BITCOIN_UTXOCOMMIT_H #define BITCOIN_UTXOCOMMIT_H -#include "coins.h" #include "hash.h" #include "secp256k1/include/secp256k1_multiset.h" #include "streams.h" #include +class COutPoint; class Coin; class CCoinsViewCursor; diff --git a/src/utxocommit.cpp b/src/utxocommit.cpp --- a/src/utxocommit.cpp +++ b/src/utxocommit.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "utxocommit.h" - +#include "coins.h" #include "util.h" namespace {