From 1dbeef7319f0e721889676f6410fde9c22ef6489 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sun, 7 Feb 2016 14:02:37 -0800 Subject: [PATCH] Add withdraw-spent to CCoinsView/utxodb Allows CCoinsCacheEntry/CCoinsMap entries to either be a CCoins or, by setting a flag in the map key, a withdraw output from a chain from which withdraw proofs are allowed to move to this chain from. --- src/Makefile.am | 2 + src/coins.cpp | 81 ++++++++++++++++++++++++++++++++------ src/coins.h | 18 ++++++++- src/pow.cpp | 4 +- src/pow.h | 2 +- src/script/interpreter.cpp | 12 +++--- src/test/coins_tests.cpp | 4 +- src/txdb.cpp | 20 ++++++++-- src/txdb.h | 1 + 9 files changed, 115 insertions(+), 29 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index a00e854f33..7f04d64f9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -264,6 +264,8 @@ libbitcoin_consensus_a_SOURCES = \ amount.h \ arith_uint256.cpp \ arith_uint256.h \ + bloom.cpp \ + bloom.h \ consensus/merkle.cpp \ consensus/merkle.h \ consensus/params.h \ diff --git a/src/coins.cpp b/src/coins.cpp index 4d0e4bc0ad..9d35d5c5ff 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -43,6 +43,7 @@ bool CCoins::Spend(uint32_t nPos) bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } +bool CCoinsView::IsWithdrawSpent(const std::pair &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; } @@ -51,6 +52,7 @@ 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::IsWithdrawSpent(const std::pair &outpoint) const { return base->IsWithdrawSpent(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); } @@ -69,14 +71,18 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const { return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; } +static inline CCoinsMapKey make_txentry(const uint256 &txid) { + return std::make_pair(txid, COutPoint()); +} + CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const { - CCoinsMap::iterator it = cacheCoins.find(txid); + CCoinsMap::iterator it = cacheCoins.find(make_txentry(txid)); if (it != cacheCoins.end()) return it; CCoins tmp; if (!base->GetCoins(txid, tmp)) return cacheCoins.end(); - CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first; + CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(make_txentry(txid), CCoinsCacheEntry())).first; tmp.swap(ret->second.coins); if (ret->second.coins.IsPruned()) { // The parent only has an empty entry for this txid; we can consider our @@ -98,7 +104,7 @@ bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const { CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { assert(!hasModifier); - std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); + std::pair ret = cacheCoins.insert(std::make_pair(make_txentry(txid), CCoinsCacheEntry())); size_t cachedCoinUsage = 0; if (ret.second) { if (!base->GetCoins(txid, ret.first->second.coins)) { @@ -133,7 +139,7 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { */ CCoinsModifier CCoinsViewCache::ModifyNewCoins(const uint256 &txid, bool coinbase) { assert(!hasModifier); - std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); + std::pair ret = cacheCoins.insert(std::make_pair(make_txentry(txid), CCoinsCacheEntry())); if (!coinbase) { // New coins must not already exist. if (!ret.first->second.coins.IsPruned()) @@ -171,10 +177,50 @@ bool CCoinsViewCache::HaveCoins(const uint256 &txid) const { } bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const { - CCoinsMap::const_iterator it = cacheCoins.find(txid); + CCoinsMap::const_iterator it = cacheCoins.find(make_txentry(txid)); return it != cacheCoins.end(); } +bool CCoinsViewCache::IsWithdrawSpent(const std::pair &outpoint) const { + assert(!outpoint.second.hash.IsNull()); + assert(!outpoint.first.IsNull()); + + CCoinsMap::iterator it = cacheCoins.find(outpoint); + if (it == cacheCoins.end()) { + it = cacheCoins.insert(std::make_pair(outpoint, CCoinsCacheEntry())).first; + it->second.withdrawSpent = base->IsWithdrawSpent(outpoint); + it->second.flags |= CCoinsCacheEntry::WITHDRAW; + if (!it->second.withdrawSpent) + it->second.flags |= CCoinsCacheEntry::FRESH; + } + return it->second.withdrawSpent; +} + +void CCoinsViewCache::SetWithdrawSpent(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->IsWithdrawSpent(outpoint); + else + hadSpent = it->second.withdrawSpent; + + // If we aren't changing spentness, dont do anything at all + if (hadSpent == fSpent) + return; + + if (it == cacheCoins.end()) { + it = cacheCoins.insert(std::make_pair(outpoint, CCoinsCacheEntry())).first; + if (!hadSpent) + it->second.flags = CCoinsCacheEntry::FRESH; + } + it->second.withdrawSpent = fSpent; + it->second.flags |= CCoinsCacheEntry::WITHDRAW | CCoinsCacheEntry::DIRTY; +} + uint256 CCoinsViewCache::GetBestBlock() const { if (hashBlock.IsNull()) hashBlock = base->GetBestBlock(); @@ -189,17 +235,24 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn assert(!hasModifier); for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization). + bool fIsWithdraw = it->second.flags & CCoinsCacheEntry::WITHDRAW; 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.coins.IsPruned())) { + // We can ignore it if it's both FRESH and {pruned, spent withdraw} in the child + if (!((it->second.flags & CCoinsCacheEntry::FRESH) && + (( fIsWithdraw && !it->second.withdrawSpent) || + (!fIsWithdraw && it->second.coins.IsPruned())))) { // 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); - cachedCoinsUsage += entry.coins.DynamicMemoryUsage(); entry.flags = CCoinsCacheEntry::DIRTY; + if (fIsWithdraw) { + entry.withdrawSpent = it->second.withdrawSpent; + entry.flags |= CCoinsCacheEntry::WITHDRAW; + } else + entry.coins.swap(it->second.coins); + cachedCoinsUsage += entry.coins.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 @@ -215,7 +268,8 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn throw std::logic_error("FRESH flag misapplied to cache entry for base transaction with spendable outputs"); // Found the entry in the parent cache - if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { + if ((itUs->second.flags & CCoinsCacheEntry::FRESH) && + ((fIsWithdraw && !it->second.withdrawSpent) || (!fIsWithdraw && it->second.coins.IsPruned()))) { // 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. @@ -224,7 +278,10 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn } else { // A normal modification. cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage(); - itUs->second.coins.swap(it->second.coins); + if (fIsWithdraw) + itUs->second.withdrawSpent = it->second.withdrawSpent; + else + itUs->second.coins.swap(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 @@ -251,7 +308,7 @@ bool CCoinsViewCache::Flush() { void CCoinsViewCache::Uncache(const uint256& hash) { - CCoinsMap::iterator it = cacheCoins.find(hash); + CCoinsMap::iterator it = cacheCoins.find(make_txentry(hash)); if (it != cacheCoins.end() && it->second.flags == 0) { cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage(); cacheCoins.erase(it); diff --git a/src/coins.h b/src/coins.h index 902cb57f69..5358eb6f27 100644 --- a/src/coins.h +++ b/src/coins.h @@ -242,6 +242,9 @@ public: } }; +// For ~WITHDRAW entries, the first element is the txhash, the second is IsNull() +// For WITHDRAW entries, the first is the genesis hash, the second is the txo (on the other chain) spent +typedef std::pair CCoinsMapKey; class SaltedTxidHasher { private: @@ -256,6 +259,9 @@ public: * unordered_map will behave unpredictably if the custom hasher returns a * uint64_t, resulting in failures when syncing the chain (#4634). */ + size_t operator()(const CCoinsMapKey& key) const { + return SipHashUint256(k0, k1, key.first) ^ SipHashUint256(k0, k1, key.second.hash); + } size_t operator()(const uint256& txid) const { return SipHashUint256(k0, k1, txid); } @@ -264,11 +270,13 @@ public: struct CCoinsCacheEntry { CCoins coins; // The actual cached data. + bool withdrawSpent; unsigned char flags; enum Flags { DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view. FRESH = (1 << 1), // The parent view does not have this entry (or it is pruned). + WITHDRAW = (1 << 2), // represents a withdraw (coins is actually empty/useless, look at withdrawSpent instead) /* 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 @@ -276,10 +284,10 @@ struct CCoinsCacheEntry */ }; - CCoinsCacheEntry() : coins(), flags(0) {} + CCoinsCacheEntry() : coins(), withdrawSpent(false), flags(0) {} }; -typedef boost::unordered_map CCoinsMap; +typedef boost::unordered_map CCoinsMap; /** Cursor for iterating over CoinsView state */ class CCoinsViewCursor @@ -313,6 +321,9 @@ public: //! This may (but cannot always) return true for fully spent transactions virtual bool HaveCoins(const uint256 &txid) const; + //! Check if a given withdraw has been spent + virtual bool IsWithdrawSpent(const std::pair &outpoint) const; + //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; @@ -338,6 +349,7 @@ public: CCoinsViewBacked(CCoinsView *viewIn); bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; + bool IsWithdrawSpent(const std::pair &outpoint) const; uint256 GetBestBlock() const; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); @@ -392,6 +404,8 @@ public: // Standard CCoinsView methods bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; + bool IsWithdrawSpent(const std::pair &outpoint) const; + void SetWithdrawSpent(const std::pair &outpoint, bool fSpent); uint256 GetBestBlock() const; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); diff --git a/src/pow.cpp b/src/pow.cpp index 2300d06cb6..73d5699431 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -37,7 +37,7 @@ void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Con block.proof.challenge = indexLast.proof.challenge; } -bool CheckBitcoinProof(uint256 hash, unsigned int nBits, const Consensus::Params& params) +bool CheckBitcoinProof(uint256 hash, unsigned int nBits) { bool fNegative; bool fOverflow; @@ -46,7 +46,7 @@ bool CheckBitcoinProof(uint256 hash, unsigned int nBits, const Consensus::Params bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range - if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit)) + if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(Params().GetConsensus().powLimit)) return false; // Check proof of work matches claimed amount diff --git a/src/pow.h b/src/pow.h index 91e81b7a03..769616d10d 100644 --- a/src/pow.h +++ b/src/pow.h @@ -20,7 +20,7 @@ class uint256; /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ -bool CheckBitcoinProof(const CBlockHeader& block); +bool CheckBitcoinProof(uint256 hash, unsigned int nBits); bool CheckProof(const CBlockHeader& block, const Consensus::Params&); /** Scans nonces looking for a hash with at least some zero bits */ bool MaybeGenerateProof(CBlockHeader* pblock, CWallet* pwallet); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 8013957d58..63fc6ce94e 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1123,7 +1123,7 @@ bool EvalScript(vector >& stack, const CScript& script, un CMerkleBlock merkleBlock; CDataStream merkleBlockStream(vmerkleBlock, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX); merkleBlockStream >> merkleBlock; - if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkleBlock.header)) + if (!merkleBlockStream.empty() || !CheckBitcoinProof(merkleBlock.header.GetHash(), merkleBlock.header.bitcoinproof.challenge)) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK); vector txHashes; @@ -1137,17 +1137,17 @@ bool EvalScript(vector >& stack, const CScript& script, un if (merkleBlock.header.GetHash() == genesishash) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK); - CTransaction locktx; + CTransactionRef locktx; CDataStream locktxStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_BITCOIN_BLOCK_OR_TX); locktxStream >> locktx; if (!locktxStream.empty()) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); int nlocktxOut = CScriptNum(vlockTxOutIndex, fRequireMinimal).getint(); - if (nlocktxOut < 0 || (unsigned int)nlocktxOut >= locktx.vout.size()) + if (nlocktxOut < 0 || (unsigned int)nlocktxOut >= locktx->vout.size()) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); - if (locktx.GetHash() != txHashes[0]) + if (locktx->GetHash() != txHashes[0]) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX); if (vcontract.size() != 40) @@ -1179,7 +1179,7 @@ bool EvalScript(vector >& stack, const CScript& script, un } CScriptID expectedP2SH(scriptDestination); - if (locktx.vout[nlocktxOut].scriptPubKey != GetScriptForDestination(expectedP2SH)) + if (locktx->vout[nlocktxOut].scriptPubKey != GetScriptForDestination(expectedP2SH)) return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT); vcontract.erase(vcontract.begin() + 4, vcontract.begin() + 20); // Remove the nonce from the contract before further processing @@ -1188,7 +1188,7 @@ bool EvalScript(vector >& stack, const CScript& script, un // We check values by doing the following: // * Tx must relock at least - // * Tx must send at least the withdraw value to its P2SH withdraw, but may send more - CAmount withdrawVal = locktx.vout[nlocktxOut].nValue; + CAmount withdrawVal = locktx->vout[nlocktxOut].nValue; CAmount lockValueRequired = checker.GetValueIn() - withdrawVal; if (lockValueRequired > 0) { const CTxOut newLockOutput = checker.GetOutputOffsetFromCurrent(1); diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index b25c7ccc51..f072692b57 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -55,10 +55,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.coins; + map_[it->first.first] = it->second.coins; if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { // Randomly delete empty entries on write. - map_.erase(it->first); + map_.erase(it->first.first); } } mapCoins.erase(it++); diff --git a/src/txdb.cpp b/src/txdb.cpp index 43143914de..2d9c6f4156 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -18,6 +18,7 @@ static const char DB_COINS = 'c'; static const char DB_BLOCK_FILES = 'f'; static const char DB_TXINDEX = 't'; static const char DB_BLOCK_INDEX = 'b'; +static const char DB_WITHDRAW_FLAG = 'w'; static const char DB_BEST_BLOCK = 'B'; static const char DB_FLAG = 'F'; @@ -37,6 +38,10 @@ bool CCoinsViewDB::HaveCoins(const uint256 &txid) const { return db.Exists(std::make_pair(DB_COINS, txid)); } +bool CCoinsViewDB::IsWithdrawSpent(const std::pair &outpoint) const { + return db.Exists(std::make_pair(DB_WITHDRAW_FLAG, outpoint)); +} + uint256 CCoinsViewDB::GetBestBlock() const { uint256 hashBestChain; if (!db.Read(DB_BEST_BLOCK, hashBestChain)) @@ -50,10 +55,17 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { size_t changed = 0; for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { - if (it->second.coins.IsPruned()) - batch.Erase(std::make_pair(DB_COINS, it->first)); - else - batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins); + if (it->second.flags & CCoinsCacheEntry::WITHDRAW) { + if (!it->second.withdrawSpent) + batch.Erase(std::make_pair(DB_WITHDRAW_FLAG, it->first)); + else + batch.Write(std::make_pair(DB_WITHDRAW_FLAG, it->first), '1'); + } else { + if (it->second.coins.IsPruned()) + batch.Erase(std::make_pair(DB_COINS, it->first.first)); + else + batch.Write(std::make_pair(DB_COINS, it->first.first), it->second.coins); + } changed++; } count++; diff --git a/src/txdb.h b/src/txdb.h index d9214ba618..f4b5bed731 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -77,6 +77,7 @@ public: bool GetCoins(const uint256 &txid, CCoins &coins) const; bool HaveCoins(const uint256 &txid) const; + bool IsWithdrawSpent(const std::pair &outpoint) const; uint256 GetBestBlock() const; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); CCoinsViewCursor *Cursor() const;