diff --git a/src/coins.cpp b/src/coins.cpp index a48c66f3f..de826380d 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -41,6 +41,7 @@ bool CCoins::Spend(uint32_t nPos) return true; } bool CCoinsView::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const { return false; } +bool CCoinsView::GetSerial(const uint256 &serial) const { return false; } bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } @@ -48,13 +49,15 @@ uint256 CCoinsView::GetBestAnchor() const { return uint256(); }; bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors) { return false; } + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials) { return false; } bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } bool CCoinsViewBacked::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const { return base->GetAnchorAt(rt, tree); } +bool CCoinsViewBacked::GetSerial(const uint256 &serial) const { return base->GetSerial(serial); } 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); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } @@ -63,7 +66,8 @@ void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors) { return base->BatchWrite(mapCoins, hashBlock, hashAnchor, mapAnchors); } + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials) { return base->BatchWrite(mapCoins, hashBlock, hashAnchor, mapAnchors, mapSerials); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); } CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} @@ -121,6 +125,21 @@ bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMer return true; } +bool CCoinsViewCache::GetSerial(const uint256 &serial) const { + CSerialsMap::iterator it = cacheSerials.find(serial); + if (it != cacheSerials.end()) + return it->second.entered; + + CSerialsCacheEntry entry; + bool tmp = base->GetSerial(serial); + entry.entered = tmp; + + cacheSerials.insert(std::make_pair(serial, entry)); + + // TODO: cache usage + + return tmp; +} void CCoinsViewCache::PushAnchor(const libzerocash::IncrementalMerkleTree &tree) { std::vector newrt_v(32); @@ -161,6 +180,12 @@ void CCoinsViewCache::PopAnchor(const uint256 &newrt) { } } +void CCoinsViewCache::SetSerial(const uint256 &serial, bool spent) { + std::pair ret = cacheSerials.insert(std::make_pair(serial, CSerialsCacheEntry())); + ret.first->second.entered = spent; + ret.first->second.flags |= CSerialsCacheEntry::DIRTY; +} + bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { CCoinsMap::const_iterator it = FetchCoins(txid); if (it != cacheCoins.end()) { @@ -229,7 +254,8 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn, const uint256 &hashAnchorIn, - CAnchorsMap &mapAnchors) { + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials) { assert(!hasModifier); for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). @@ -294,15 +320,44 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, CAnchorsMap::iterator itOld = child_it++; mapAnchors.erase(itOld); } + + for (CSerialsMap::iterator child_it = mapSerials.begin(); child_it != mapSerials.end();) + { + if (child_it->second.flags & CSerialsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). + CSerialsMap::iterator parent_it = cacheSerials.find(child_it->first); + + if (parent_it == cacheSerials.end()) { + if (child_it->second.entered) { + // Parent doesn't have an entry, but child has a SPENT serial. + // Move the spent serial up. + + CSerialsCacheEntry& entry = cacheSerials[child_it->first]; + entry.entered = true; + entry.flags = CSerialsCacheEntry::DIRTY; + + // TODO: cache usage + } + } else { + if (parent_it->second.entered != child_it->second.entered) { + parent_it->second.entered = child_it->second.entered; + parent_it->second.flags |= CSerialsCacheEntry::DIRTY; + } + } + } + CSerialsMap::iterator itOld = child_it++; + mapSerials.erase(itOld); + } + hashAnchor = hashAnchorIn; hashBlock = hashBlockIn; return true; } bool CCoinsViewCache::Flush() { - bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashAnchor, cacheAnchors); + bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashAnchor, cacheAnchors, cacheSerials); cacheCoins.clear(); cacheAnchors.clear(); + cacheSerials.clear(); cachedCoinsUsage = 0; return fOk; } diff --git a/src/coins.h b/src/coins.h index 2c761aca6..ad942a2d6 100644 --- a/src/coins.h +++ b/src/coins.h @@ -311,8 +311,21 @@ struct CAnchorsCacheEntry CAnchorsCacheEntry() : entered(false), flags(0), tree(INCREMENTAL_MERKLE_TREE_DEPTH) {} }; +struct CSerialsCacheEntry +{ + bool entered; // If the serial is spent or not + unsigned char flags; + + enum Flags { + DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view. + }; + + CSerialsCacheEntry() : entered(false), flags(0) {} +}; + typedef boost::unordered_map CCoinsMap; typedef boost::unordered_map CAnchorsMap; +typedef boost::unordered_map CSerialsMap; struct CCoinsStats { @@ -335,6 +348,9 @@ public: //! Retrieve the tree at a particular anchored root in the chain virtual bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const; + //! Determine whether a serial is spent or not + virtual bool GetSerial(const uint256 &serial) const; + //! Retrieve the CCoins (unspent transaction outputs) for a given txid virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; @@ -353,7 +369,8 @@ public: virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors); + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials); //! Calculate statistics about the unspent transaction output set virtual bool GetStats(CCoinsStats &stats) const; @@ -372,6 +389,7 @@ protected: public: CCoinsViewBacked(CCoinsView *viewIn); bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const; + bool GetSerial(const uint256 &serial) const; bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; @@ -380,7 +398,8 @@ public: bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors); + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials); bool GetStats(CCoinsStats &stats) const; }; @@ -423,6 +442,7 @@ protected: mutable CCoinsMap cacheCoins; mutable uint256 hashAnchor; mutable CAnchorsMap cacheAnchors; + mutable CSerialsMap cacheSerials; /* Cached dynamic memory usage for the inner CCoins objects. */ mutable size_t cachedCoinsUsage; @@ -433,6 +453,7 @@ public: // Standard CCoinsView methods bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const; + bool GetSerial(const uint256 &serial) const; bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; @@ -441,7 +462,8 @@ public: bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors); + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials); // Adds the tree to mapAnchors and sets the current commitment @@ -452,6 +474,9 @@ public: // the new current root. void PopAnchor(const uint256 &rt); + // Marks a serial as spent or not. + void SetSerial(const uint256 &serial, bool spent); + /** * Return a pointer to CCoins in the cache, or NULL if not found. This is * more efficient than GetCoins. Modifications to other cache entries are diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 4ec89b7df..8f39971ee 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -21,6 +21,7 @@ class CCoinsViewTest : public CCoinsView uint256 hashBestAnchor_; std::map map_; std::map mapAnchors_; + std::map mapSerials_; public: bool GetAnchorAt(const uint256& rt, libzerocash::IncrementalMerkleTree &tree) const { @@ -39,6 +40,19 @@ public: } } + bool GetSerial(const uint256 &serial) const + { + std::map::const_iterator it = mapSerials_.find(serial); + + if (it == mapSerials_.end()) { + return false; + } else { + // The map shouldn't contain any false entries. + assert(it->second); + return true; + } + } + uint256 GetBestAnchor() const { return hashBestAnchor_; } bool GetCoins(const uint256& txid, CCoins& coins) const @@ -66,7 +80,8 @@ public: bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock, const uint256& hashAnchor, - CAnchorsMap& mapAnchors) + CAnchorsMap& mapAnchors, + CSerialsMap& mapSerials) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) { map_[it->first] = it->second.coins; @@ -84,8 +99,17 @@ public: } mapAnchors.erase(it++); } + for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end(); ) { + if (it->second.entered) { + mapSerials_[it->first] = true; + } else { + mapSerials_.erase(it->first); + } + mapSerials.erase(it++); + } mapCoins.clear(); mapAnchors.clear(); + mapSerials.clear(); hashBestBlock_ = hashBlock; hashBestAnchor_ = hashAnchor; return true; @@ -113,6 +137,30 @@ public: } +BOOST_AUTO_TEST_CASE(serials_test) +{ + CCoinsViewTest base; + CCoinsViewCacheTest cache(&base); + + uint256 myserial = GetRandHash(); + + BOOST_CHECK(!cache.GetSerial(myserial)); + cache.SetSerial(myserial, true); + BOOST_CHECK(cache.GetSerial(myserial)); + cache.Flush(); + + CCoinsViewCacheTest cache2(&base); + + BOOST_CHECK(cache2.GetSerial(myserial)); + cache2.SetSerial(myserial, false); + BOOST_CHECK(!cache2.GetSerial(myserial)); + cache2.Flush(); + + CCoinsViewCacheTest cache3(&base); + + BOOST_CHECK(!cache3.GetSerial(myserial)); +} + void appendRandomCommitment(IncrementalMerkleTree &tree) { Address addr = Address::CreateNewRandomAddress(); diff --git a/src/txdb.cpp b/src/txdb.cpp index aa4070aa1..130ae41b0 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -18,6 +18,7 @@ using namespace std; static const char DB_ANCHOR = 'A'; +static const char DB_SERIAL = 's'; static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; static const char DB_TXINDEX = 't'; @@ -42,6 +43,13 @@ void static BatchWriteAnchor(CLevelDBBatch &batch, } } +void static BatchWriteSerial(CLevelDBBatch &batch, const uint256 &serial, const bool &entered) { + if (!entered) + batch.Erase(make_pair(DB_SERIAL, serial)); + else + batch.Write(make_pair(DB_SERIAL, serial), true); +} + void static BatchWriteCoins(CLevelDBBatch &batch, const uint256 &hash, const CCoins &coins) { if (coins.IsPruned()) batch.Erase(make_pair(DB_COINS, hash)); @@ -81,6 +89,16 @@ bool CCoinsViewDB::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkle return true; } +bool CCoinsViewDB::GetSerial(const uint256 &serial) const { + bool spent = false; + bool read = db.Read(make_pair(DB_SERIAL, serial), spent); + + // We should never read false from the database. + assert(spent != read); + + return spent; +} + bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const { return db.Read(make_pair(DB_COINS, txid), coins); } @@ -106,7 +124,8 @@ uint256 CCoinsViewDB::GetBestAnchor() const { bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors) { + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials) { CLevelDBBatch batch; size_t count = 0; size_t changed = 0; @@ -129,6 +148,15 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, mapAnchors.erase(itOld); } + for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end();) { + if (it->second.flags & CSerialsCacheEntry::DIRTY) { + BatchWriteSerial(batch, it->first, it->second.entered); + // TODO: changed++? + } + CSerialsMap::iterator itOld = it++; + mapSerials.erase(itOld); + } + if (!hashBlock.IsNull()) BatchWriteHashBestChain(batch, hashBlock); if (!hashAnchor.IsNull()) diff --git a/src/txdb.h b/src/txdb.h index 9980d6eb4..2992a03e2 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -37,6 +37,7 @@ public: CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const; + bool GetSerial(const uint256 &serial) const; bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; @@ -44,7 +45,8 @@ public: bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, const uint256 &hashAnchor, - CAnchorsMap &mapAnchors); + CAnchorsMap &mapAnchors, + CSerialsMap &mapSerials); bool GetStats(CCoinsStats &stats) const; };