diff --git a/src/main.cpp b/src/main.cpp index 47a2689af..1d2acee7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1630,7 +1630,9 @@ static bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, CNCCTrie assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); LogPrintf("%s: Restoring %s to the NCC trie due to a block being disconnected\n", __func__, name.c_str()); - if (!trieCache.insertName(name, out.hash, out.n, undo.txout.nValue, undo.nHeight)) + int nValidHeight = undo.nNCCValidHeight; + assert(nValidHeight > 0 && nValidHeight >= coins->nHeight); + if (!trieCache.undoSpendClaim(name, out.hash, out.n, undo.txout.nValue, coins->nHeight, nValidHeight)) LogPrintf("%s: Something went wrong inserting the name\n", __func__); } @@ -1659,6 +1661,8 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) return error("DisconnectBlock(): block and undo data inconsistent"); + assert(trieCache.decrementBlock(blockUndo.queueUndo)); + // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = block.vtx[i]; @@ -1691,7 +1695,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); LogPrintf("%s: Removing %s from the ncc trie due to its block being disconnected\n", __func__, name.c_str()); - if (!trieCache.removeName(name, hash, i)) + if (!trieCache.undoAddClaim(name, hash, i, pindex->nHeight)) LogPrintf("%s: Something went wrong removing the name %s in hash %s\n", __func__, name.c_str(), hash.GetHex()); } } @@ -1849,6 +1853,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); + std::map mNCCUndoHeights; + if (!tx.IsCoinBase()) { if (!view.HaveInputs(tx)) @@ -1884,8 +1890,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin { assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); + int nValidAtHeight; LogPrintf("%s: Removing %s from the ncc trie. Tx: %s, nOut: %d\n", __func__, name, txin.prevout.hash.GetHex(), txin.prevout.n); - if (!trieCache.removeName(name, txin.prevout.hash, txin.prevout.n)) + if (!trieCache.spendClaim(name, txin.prevout.hash, txin.prevout.n, coins->nHeight, nValidAtHeight)) LogPrintf("%s: Something went wrong removing the name\n", __func__); } } @@ -1901,28 +1908,38 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); LogPrintf("%s: Inserting %s into the ncc trie. Tx: %s, nOut: %d\n", __func__, name, tx.GetHash().GetHex(), i); - if (!trieCache.insertName(name, tx.GetHash(), i, txout.nValue, pindex->nHeight)) + if (!trieCache.addClaim(name, tx.GetHash(), i, txout.nValue, pindex->nHeight)) LogPrintf("%s: Something went wrong inserting the name\n", __func__); } } } CTxUndo undoDummy; - if (i > 0) { + if (i > 0) + { blockundo.vtxundo.push_back(CTxUndo()); } UpdateCoins(tx, state, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); - - // The CUndo vector contains all of the - // necessary information for putting NCC claims - // removed by this block back into the trie. - // (As long as the coinbase can't remove any - // NCC claims from the trie.) + if (i > 0 && !mNCCUndoHeights.empty()) + { + std::vector& txinUndos = blockundo.vtxundo.back().vprevout; + for (std::map::iterator itHeight = mNCCUndoHeights.begin(); itHeight != mNCCUndoHeights.end(); ++itHeight) + { + txinUndos[itHeight->first].nNCCValidHeight = itHeight->second; + } + } + // The CTxUndo vector contains the heights at which NCC claims should be put into the trie. + // This is necessary because some NCC claims are inserted immediately into the trie, and + // others are inserted after a delay, depending on the state of the NCC trie at the time + // that the claim was originally inserted into the blockchain. That state will not be + // available when and if this block is disconnected. vPos.push_back(std::make_pair(tx.GetHash(), pos)); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); } + assert(trieCache.incrementBlock(blockundo.queueUndo)); + if (trieCache.getMerkleHash() != block.hashNCCTrie) return state.DoS(100, error("ConnectBlock() : the merkle root of the NCC trie does not match " diff --git a/src/miner.cpp b/src/miner.cpp index ba3a777db..f3edc4840 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -300,7 +300,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) { assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); - if (!trieCache.removeName(name, txin.prevout.hash, txin.prevout.n)) + int throwaway; + if (!trieCache.spendClaim(name, txin.prevout.hash, txin.prevout.n, coins->nHeight, throwaway)) LogPrintf("%s: Something went wrong removing the name\n", __func__); } } @@ -317,7 +318,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) { assert(vvchParams.size() == 2); std::string name(vvchParams[0].begin(), vvchParams[0].end()); - if (!trieCache.insertName(name, tx.GetHash(), i, txout.nValue, nHeight)) + if (!trieCache.addClaim(name, tx.GetHash(), i, txout.nValue, nHeight)) LogPrintf("%s: Something went wrong inserting the name\n", __func__); } } diff --git a/src/ncctrie.cpp b/src/ncctrie.cpp index 11689fc61..e193dd69d 100644 --- a/src/ncctrie.cpp +++ b/src/ncctrie.cpp @@ -33,14 +33,23 @@ bool CNCCTrieNode::insertValue(CNodeValue val, bool * pfChanged) return true; } -bool CNCCTrieNode::removeValue(CNodeValue val, bool * pfChanged) +bool CNCCTrieNode::removeValue(uint256& txhash, uint32_t nOut, CNodeValue& val, bool * pfChanged) { LogPrintf("%s: Removing %s from the ncc trie\n", __func__, val.ToString()); bool fChanged = false; CNodeValue currentTop = values.front(); - std::vector::iterator position = std::find(values.begin(), values.end(), val); + //std::vector::iterator position = std::find(values.begin(), values.end(), val); + std::vector::iterator position; + for (position = values.begin(); position != values.end(); ++position) + { + if (position->txhash == txhash && position->nOut == nOut) + { + std::swap(val, *position); + break; + } + } if (position != values.end()) values.erase(position); else @@ -67,7 +76,7 @@ bool CNCCTrieNode::removeValue(CNodeValue val, bool * pfChanged) return true; } -bool CNCCTrieNode::getValue(CNodeValue& value) const +bool CNCCTrieNode::getBestValue(CNodeValue& value) const { if (values.empty()) return false; @@ -88,6 +97,11 @@ bool CNCCTrie::empty() const return root.empty(); } +bool CNCCTrie::queueEmpty() const +{ + return valueQueue.empty(); +} + bool CNCCTrie::recursiveDumpToJSON(const std::string& name, const CNCCTrieNode* current, json_spirit::Array& ret) const { using namespace json_spirit; @@ -95,7 +109,7 @@ bool CNCCTrie::recursiveDumpToJSON(const std::string& name, const CNCCTrieNode* objNode.push_back(Pair("name", name)); objNode.push_back(Pair("hash", current->hash.GetHex())); CNodeValue val; - if (current->getValue(val)) + if (current->getBestValue(val)) { objNode.push_back(Pair("txid", val.txhash.GetHex())); objNode.push_back(Pair("n", (int)val.nOut)); @@ -131,7 +145,7 @@ bool CNCCTrie::getInfoForName(const std::string& name, CNodeValue& val) const return false; current = itchildren->second; } - return current->getValue(val); + return current->getBestValue(val); } bool CNCCTrie::checkConsistency() @@ -157,9 +171,9 @@ bool CNCCTrie::recursiveCheckConsistency(CNCCTrieNode* node) else return false; } - + CNodeValue val; - bool hasValue = node->getValue(val); + bool hasValue = node->getBestValue(val); if (hasValue) { @@ -179,7 +193,30 @@ bool CNCCTrie::recursiveCheckConsistency(CNCCTrieNode* node) return calculatedHash == node->hash; } -bool CNCCTrie::update(nodeCacheType& cache, hashMapType& hashes, const uint256& hashBlockIn) +valueQueueType::iterator CNCCTrie::getQueueRow(int nHeight) +{ + valueQueueType::iterator itQueueRow = valueQueue.find(nHeight); + if (itQueueRow == valueQueue.end()) + { + std::vector queueRow; + std::pair ret; + ret = valueQueue.insert(std::pair >(nHeight, queueRow)); + assert(ret.second); + itQueueRow = ret.first; + } + return itQueueRow; +} + +void CNCCTrie::deleteQueueRow(int nHeight) +{ + valueQueueType::iterator itQueueRow = valueQueue.find(nHeight); + if (itQueueRow != valueQueue.end()) + { + valueQueue.erase(itQueueRow); + } +} + +bool CNCCTrie::update(nodeCacheType& cache, hashMapType& hashes, const uint256& hashBlockIn, valueQueueType& queueCache, int nNewHeight) { // General strategy: the cache is ordered by length, ensuring child // nodes are always inserted after their parents. Insert each node @@ -219,8 +256,25 @@ bool CNCCTrie::update(nodeCacheType& cache, hashMapType& hashes, const uint256& return false; changedNodes[ithash->first] = pNode; } - BatchWrite(changedNodes, deletedNames, hashBlockIn); + std::vector vChangedQueueRows; + std::vector vDeletedQueueRows; + for (valueQueueType::iterator itQueueCacheRow = queueCache.begin(); itQueueCacheRow != queueCache.end(); ++itQueueCacheRow) + { + if (itQueueCacheRow->second.empty()) + { + vDeletedQueueRows.push_back(itQueueCacheRow->first); + deleteQueueRow(itQueueCacheRow->first); + } + else + { + vChangedQueueRows.push_back(itQueueCacheRow->first); + valueQueueType::iterator itQueueRow = getQueueRow(itQueueCacheRow->first); + itQueueRow->second.swap(itQueueCacheRow->second); + } + } + BatchWrite(changedNodes, deletedNames, hashBlockIn, vChangedQueueRows, vDeletedQueueRows, nNewHeight); hashBlock = hashBlockIn; + nCurrentHeight = nNewHeight; return true; } @@ -302,25 +356,41 @@ bool CNCCTrie::updateHash(const std::string& name, uint256& hash, CNCCTrieNode** return true; } -void BatchWriteNode(CLevelDBBatch& batch, const std::string& name, const CNCCTrieNode* pNode) +void CNCCTrie::BatchWriteNode(CLevelDBBatch& batch, const std::string& name, const CNCCTrieNode* pNode) const { LogPrintf("%s: Writing %s to disk with %d values\n", __func__, name, pNode->values.size()); batch.Write(std::make_pair('n', name), *pNode); } -void BatchEraseNode(CLevelDBBatch& batch, const std::string& name) +void CNCCTrie::BatchEraseNode(CLevelDBBatch& batch, const std::string& name) const { batch.Erase(std::make_pair('n', name)); } -bool CNCCTrie::BatchWrite(nodeCacheType& changedNodes, std::vector& deletedNames, const uint256& hashBlockIn) +void CNCCTrie::BatchWriteQueueRow(CLevelDBBatch& batch, int nRowNum) +{ + valueQueueType::iterator itQueueRow = getQueueRow(nRowNum); + batch.Write(std::make_pair('r', nRowNum), itQueueRow->second); +} + +void CNCCTrie::BatchEraseQueueRow(CLevelDBBatch& batch, int nRowNum) +{ + batch.Erase(std::make_pair('r', nRowNum)); +} + +bool CNCCTrie::BatchWrite(nodeCacheType& changedNodes, std::vector& deletedNames, const uint256& hashBlockIn, std::vector vChangedQueueRows, std::vector vDeletedQueueRows, int nNewHeight) { CLevelDBBatch batch; for (nodeCacheType::iterator itcache = changedNodes.begin(); itcache != changedNodes.end(); ++itcache) BatchWriteNode(batch, itcache->first, itcache->second); for (std::vector::iterator itname = deletedNames.begin(); itname != deletedNames.end(); ++itname) BatchEraseNode(batch, *itname); + for (std::vector::iterator itRowNum = vChangedQueueRows.begin(); itRowNum != vChangedQueueRows.end(); ++itRowNum) + BatchWriteQueueRow(batch, *itRowNum); + for (std::vector::iterator itRowNum = vDeletedQueueRows.begin(); itRowNum != vDeletedQueueRows.end(); ++itRowNum) + BatchEraseQueueRow(batch, *itRowNum); batch.Write('h', hashBlockIn); + batch.Write('t', nNewHeight); return db.WriteBatch(batch); } @@ -370,6 +440,15 @@ bool CNCCTrie::ReadFromDisk(bool check) if (!InsertFromDisk(name, node)) return false; } + else if (chType == 'r') + { + leveldb::Slice slValue = pcursor->value(); + int nHeight; + ssKey >> nHeight; + CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); + valueQueueType::iterator itQueueRow = getQueueRow(nHeight); + ssValue >> itQueueRow->second; + } pcursor->Next(); } catch (const std::exception& e) @@ -381,7 +460,7 @@ bool CNCCTrie::ReadFromDisk(bool check) if (check) { LogPrintf("Checking NCC trie consistency..."); - if( checkConsistency()) + if (checkConsistency()) { LogPrintf("consistent\n"); return true; @@ -427,7 +506,7 @@ bool CNCCTrieCache::recursiveComputeMerkleHash(CNCCTrieNode* tnCurrent, std::str } CNodeValue val; - bool hasValue = tnCurrent->getValue(val); + bool hasValue = tnCurrent->getBestValue(val); if (hasValue) { @@ -477,7 +556,7 @@ bool CNCCTrieCache::empty() const return base->empty() && cache.empty(); } -bool CNCCTrieCache::insertName(const std::string name, uint256 txhash, int nOut, CAmount nAmount, int nHeight) const +bool CNCCTrieCache::insertClaimIntoTrie(const std::string name, CNodeValue val) const { assert(base); CNCCTrieNode* currentNode = &(base->root); @@ -544,7 +623,7 @@ bool CNCCTrieCache::insertName(const std::string name, uint256 txhash, int nOut, cache[name] = currentNode; } bool fChanged = false; - currentNode->insertValue(CNodeValue(txhash, nOut, nAmount, nHeight), &fChanged); + currentNode->insertValue(val, &fChanged); if (fChanged) { for (std::string::const_iterator itCur = name.begin(); itCur != name.end(); ++itCur) @@ -557,7 +636,7 @@ bool CNCCTrieCache::insertName(const std::string name, uint256 txhash, int nOut, return true; } -bool CNCCTrieCache::removeName(const std::string name, uint256 txhash, int nOut) const +bool CNCCTrieCache::removeClaimFromTrie(const std::string name, uint256 txhash, uint32_t nOut, int& nValidAtHeight) const { assert(base); CNCCTrieNode* currentNode = &(base->root); @@ -583,7 +662,6 @@ bool CNCCTrieCache::removeName(const std::string name, uint256 txhash, int nOut) currentNode = childNode->second; continue; } - LogPrintf("%s: The name %s does not exist in the trie\n", __func__, name.c_str()); return false; } @@ -598,11 +676,16 @@ bool CNCCTrieCache::removeName(const std::string name, uint256 txhash, int nOut) } bool fChanged = false; assert(currentNode != NULL); - bool success = currentNode->removeValue(CNodeValue(txhash, nOut), &fChanged); + CNodeValue val; + bool success = currentNode->removeValue(txhash, nOut, val, &fChanged); if (!success) { LogPrintf("%s: Removing a value was unsuccessful. name = %s, txhash = %s, nOut = %d", __func__, name.c_str(), txhash.GetHex(), nOut); } + else + { + nValidAtHeight = val.nValidAtHeight; + } assert(success); if (fChanged) { @@ -692,6 +775,167 @@ bool CNCCTrieCache::recursivePruneName(CNCCTrieNode* tnCurrent, unsigned int nPo return true; } +valueQueueType::iterator CNCCTrieCache::getQueueCacheRow(int nHeight, bool createIfNotExists) const +{ + valueQueueType::iterator itQueueRow = valueQueueCache.find(nHeight); + if (itQueueRow == valueQueueCache.end()) + { + // Have to make a new row it put in the cache, if createIfNotExists is true + std::vector queueRow; + // If the row exists in the base, copy its values into the new row. + valueQueueType::iterator itBaseQueueRow = base->valueQueue.find(nHeight); + if (itBaseQueueRow == base->valueQueue.end()) + { + if (!createIfNotExists) + return itQueueRow; + } + else + queueRow = itBaseQueueRow->second; + // Stick the new row in the cache + std::pair ret; + ret = valueQueueCache.insert(std::pair >(nHeight, queueRow)); + assert(ret.second); + itQueueRow = ret.first; + } + return itQueueRow; +} + +bool CNCCTrieCache::getInfoForName(const std::string name, CNodeValue& val) const +{ + nodeCacheType::iterator itcache = cache.find(name); + if (itcache != cache.end()) + { + return itcache->second->getBestValue(val); + } + else + { + return base->getInfoForName(name, val); + } +} + +bool CNCCTrieCache::addClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight) const +{ + assert(nHeight == nCurrentHeight); + return addClaimToQueue(name, txhash, nOut, nAmount, nHeight, nHeight + DEFAULT_DELAY); +} + +bool CNCCTrieCache::addClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, uint256 prevTxhash, uint32_t nPrevOut) const +{ + assert(nHeight == nCurrentHeight); + CNodeValue val; + if (getInfoForName(name, val)) + { + if (val.txhash == prevTxhash && val.nOut == nPrevOut) + return addClaimToQueue(name, txhash, nOut, nAmount, nHeight, nHeight); + } + return addClaim(name, txhash, nOut, nAmount, nHeight); +} + +bool CNCCTrieCache::undoSpendClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, int nValidAtHeight) const +{ + if (nValidAtHeight < nCurrentHeight) + { + CNodeValue val(txhash, nOut, nAmount, nHeight, nValidAtHeight); + CValueQueueEntry entry(name, val); + insertClaimIntoTrie(name, CNodeValue(txhash, nOut, nAmount, nHeight, nValidAtHeight)); + } + else + { + addClaimToQueue(name, txhash, nOut, nAmount, nHeight, nValidAtHeight); + } + return true; +} + +bool CNCCTrieCache::addClaimToQueue(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, int nValidAtHeight) const +{ + CNodeValue val(txhash, nOut, nAmount, nHeight, nValidAtHeight); + CValueQueueEntry entry(name, val); + valueQueueType::iterator itQueueRow = getQueueCacheRow(nValidAtHeight, true); + itQueueRow->second.push_back(entry); + return true; +} + +bool CNCCTrieCache::removeClaimFromQueue(const std::string name, uint256 txhash, uint32_t nOut, int nHeightToCheck, int& nValidAtHeight) const +{ + valueQueueType::iterator itQueueRow = getQueueCacheRow(nHeightToCheck, false); + if (itQueueRow == valueQueueCache.end()) + { + return false; + } + std::vector::iterator itQueue; + for (itQueue = itQueueRow->second.begin(); itQueue != itQueueRow->second.end(); ++itQueue) + { + CNodeValue& val = itQueue->val; + if (name == itQueue->name && val.txhash == txhash && val.nOut == nOut) + { + nValidAtHeight = val.nValidAtHeight; + break; + } + } + if (itQueue != itQueueRow->second.end()) + { + itQueueRow->second.erase(itQueue); + return true; + } + return false; +} + +bool CNCCTrieCache::undoAddClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight) const +{ + int throwaway; + return removeClaim(name, txhash, nOut, nHeight, throwaway); +} + +bool CNCCTrieCache::spendClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight, int& nValidAtHeight) const +{ + return removeClaim(name, txhash, nOut, nHeight, nValidAtHeight); +} + +bool CNCCTrieCache::removeClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight, int& nValidAtHeight) const +{ + if (nHeight + DEFAULT_DELAY >= nCurrentHeight) + { + if (removeClaimFromQueue(name, txhash, nOut, nHeight + DEFAULT_DELAY, nValidAtHeight)) + return true; + if (removeClaimFromQueue(name, txhash, nOut, nHeight, nValidAtHeight)) + return true; + } + if (removeClaimFromQueue(name, txhash, nOut, nHeight, nCurrentHeight)) + return true; + return removeClaimFromTrie(name, txhash, nOut, nValidAtHeight); +} + +bool CNCCTrieCache::incrementBlock(CNCCTrieQueueUndo& undo) const +{ + valueQueueType::iterator itQueueRow = getQueueCacheRow(nCurrentHeight, false); + nCurrentHeight++; + if (itQueueRow == valueQueueCache.end()) + { + return true; + } + for (std::vector::iterator itEntry = itQueueRow->second.begin(); itEntry != itQueueRow->second.end(); ++itEntry) + { + insertClaimIntoTrie(itEntry->name, itEntry->val); + undo.push_back(*itEntry); + } + itQueueRow->second.clear(); + return true; +} + +bool CNCCTrieCache::decrementBlock(CNCCTrieQueueUndo& undo) const +{ + nCurrentHeight--; + valueQueueType::iterator itQueueRow = getQueueCacheRow(nCurrentHeight, true); + for (CNCCTrieQueueUndo::iterator itUndo = undo.begin(); itUndo != undo.end(); ++itUndo) + { + int nValidHeightInTrie; + assert(removeClaimFromTrie(itUndo->name, itUndo->val.txhash, itUndo->val.nOut, nValidHeightInTrie)); + assert(nValidHeightInTrie == itUndo->val.nValidAtHeight); + itQueueRow->second.push_back(*itUndo); + } + return true; +} + uint256 CNCCTrieCache::getBestBlock() { if (hashBlock.IsNull()) @@ -714,6 +958,7 @@ bool CNCCTrieCache::clear() const cache.clear(); dirtyHashes.clear(); cacheHashes.clear(); + valueQueueCache.clear(); return true; } @@ -721,7 +966,7 @@ bool CNCCTrieCache::flush() { if (dirty()) getMerkleHash(); - bool success = base->update(cache, cacheHashes, hashBlock); + bool success = base->update(cache, cacheHashes, hashBlock, valueQueueCache, nCurrentHeight); if (success) { success = clear(); diff --git a/src/ncctrie.h b/src/ncctrie.h index faa7b55c7..7a4294237 100644 --- a/src/ncctrie.h +++ b/src/ncctrie.h @@ -15,6 +15,8 @@ #include #include "json/json_spirit_value.h" +#define DEFAULT_DELAY 100 + class CNodeValue { public: @@ -22,9 +24,10 @@ public: uint32_t nOut; CAmount nAmount; int nHeight; + int nValidAtHeight; CNodeValue() {}; - CNodeValue(uint256 txhash, uint32_t nOut) : txhash(txhash), nOut(nOut), nAmount(0), nHeight(0) {} - CNodeValue(uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight) : txhash(txhash), nOut(nOut), nAmount(nAmount), nHeight(nHeight) {} + //CNodeValue(uint256 txhash, uint32_t nOut) : txhash(txhash), nOut(nOut), nAmount(0), nHeight(0), nValidAtHeight(0) {} + CNodeValue(uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, int nValidAtHeight) : txhash(txhash), nOut(nOut), nAmount(nAmount), nHeight(nHeight), nValidAtHeight(nValidAtHeight) {} std::string ToString(); ADD_SERIALIZE_METHODS; @@ -35,6 +38,7 @@ public: READWRITE(nOut); READWRITE(nAmount); READWRITE(nHeight); + READWRITE(nValidAtHeight); } bool operator<(const CNodeValue& other) const @@ -58,7 +62,7 @@ public: } bool operator==(const CNodeValue& other) const { - return txhash == other.txhash && nOut == other.nOut; + return txhash == other.txhash && nOut == other.nOut && nAmount == other.nAmount && nHeight == other.nHeight && nValidAtHeight == other.nValidAtHeight; } bool operator!=(const CNodeValue& other) const { @@ -81,8 +85,8 @@ public: nodeMapType children; std::vector values; bool insertValue(CNodeValue val, bool * fChanged = NULL); - bool removeValue(CNodeValue val, bool * fChanged = NULL); - bool getValue(CNodeValue& val) const; + bool removeValue(uint256& txhash, uint32_t nOut, CNodeValue& val, bool * fChanged = NULL); + bool getBestValue(CNodeValue& val) const; bool empty() const {return children.empty() && values.empty();} ADD_SERIALIZE_METHODS; @@ -102,7 +106,8 @@ public: { return !(*this == other); } - +private: + bool getValue(uint256& txhash, uint32_t nOut, CNodeValue& val) const; }; struct nodenamecompare @@ -115,9 +120,30 @@ struct nodenamecompare } }; +class CValueQueueEntry +{ + public: + CValueQueueEntry() {} + CValueQueueEntry(std::string name, CNodeValue val) : name(name), val(val) {} + std::string name; + CNodeValue val; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(name); + READWRITE(val); + } +}; + +typedef std::map > valueQueueType; +typedef std::vector CNCCTrieQueueUndo; + typedef std::map nodeCacheType; typedef std::map hashMapType; + class CNCCTrieCache; class CNCCTrie @@ -131,42 +157,65 @@ public: bool ReadFromDisk(bool check = false); json_spirit::Array dumpToJSON() const; bool getInfoForName(const std::string& name, CNodeValue& val) const; + int nCurrentHeight; + bool queueEmpty() const; friend class CNCCTrieCache; private: - bool update(nodeCacheType& cache, hashMapType& hashes, const uint256& hashBlock); + bool update(nodeCacheType& cache, hashMapType& hashes, const uint256& hashBlock, valueQueueType& queueCache, int nNewHeight); bool updateName(const std::string& name, CNCCTrieNode* updatedNode, std::vector& deletedNames, CNCCTrieNode** pNodeRet); bool updateHash(const std::string& name, uint256& hash, CNCCTrieNode** pNodeRet); bool recursiveNullify(CNCCTrieNode* node, std::string& name, std::vector& deletedNames); bool recursiveCheckConsistency(CNCCTrieNode* node); - bool BatchWrite(nodeCacheType& changedNodes, std::vector& deletedNames, const uint256& hashBlock); + bool BatchWrite(nodeCacheType& changedNodes, std::vector& deletedNames, const uint256& hashBlock, std::vector vChangedQueueRows, std::vector vDeletedQueueRows, int nNewHeight); bool InsertFromDisk(const std::string& name, CNCCTrieNode* node); bool recursiveDumpToJSON(const std::string& name, const CNCCTrieNode* current, json_spirit::Array& ret) const; CNCCTrieNode root; uint256 hashBlock; + valueQueueType valueQueue; + valueQueueType::iterator getQueueRow(int nHeight); + void deleteQueueRow(int nHeight); + void BatchWriteNode(CLevelDBBatch& batch, const std::string& name, const CNCCTrieNode* pNode) const; + void BatchEraseNode(CLevelDBBatch& batch, const std::string& nome) const; + void BatchWriteQueueRow(CLevelDBBatch& batch, int nRowNum); + void BatchEraseQueueRow(CLevelDBBatch& batch, int nRowNum); }; class CNCCTrieCache { public: - CNCCTrieCache(CNCCTrie* base): base(base) {assert(base);} + CNCCTrieCache(CNCCTrie* base): base(base), nCurrentHeight(base->nCurrentHeight) {} uint256 getMerkleHash() const; bool empty() const; bool flush(); bool dirty() const { return !dirtyHashes.empty(); } - bool insertName (const std::string name, uint256 txhash, int nOut, CAmount nAmount, int nDepth) const; - bool removeName (const std::string name, uint256 txhash, int nOut) const; + bool addClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight) const; + bool addClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, uint256 prevTxhash, uint32_t nPrevOut) const; + bool undoAddClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight) const; + bool spendClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight, int& nValidAtHeight) const; + bool undoSpendClaim(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, int nValidAtHeight) const; uint256 getBestBlock(); void setBestBlock(const uint256& hashBlock); + bool incrementBlock(CNCCTrieQueueUndo& undo) const; + bool decrementBlock(CNCCTrieQueueUndo& undo) const; ~CNCCTrieCache() { clear(); } + bool insertClaimIntoTrie(const std::string name, CNodeValue val) const; + bool removeClaimFromTrie(const std::string name, uint256 txhash, uint32_t nOut, int& nValidAtHeight) const; private: CNCCTrie* base; + bool getInfoForName(const std::string name, CNodeValue& val) const; mutable nodeCacheType cache; mutable std::set dirtyHashes; mutable hashMapType cacheHashes; + mutable valueQueueType valueQueueCache; + mutable int nCurrentHeight; uint256 computeHash() const; bool recursiveComputeMerkleHash(CNCCTrieNode* tnCurrent, std::string sPos) const; bool recursivePruneName(CNCCTrieNode* tnCurrent, unsigned int nPos, std::string sName, bool* pfNullified = NULL) const; bool clear() const; + bool removeClaim(const std::string name, uint256 txhash, uint32_t nOut, int nHeight, int& nValidAtHeight) const; + bool addClaimToQueue(const std::string name, uint256 txhash, uint32_t nOut, CAmount nAmount, int nHeight, int nValidAtHeight) const; + bool removeClaimFromQueue(const std::string name, uint256 txhash, uint32_t nOut, int nHeightToCheck, int& nValidAtHeight) const; + valueQueueType::iterator getQueueCacheRow(int nHeight, bool createIfNotExists) const; uint256 hashBlock; }; diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index dd8099f0b..8f590b95a 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -246,8 +246,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) delete pblocktemplate; mempool.clear(); -/* This has been removed because we don't have that many blocks in the active chain yet. - It should be returned when we do. +/* TODO: fix this // subsidy changing int nHeight = chainActive.Height(); chainActive.Tip()->nHeight = 209999; diff --git a/src/test/ncctrie_tests.cpp b/src/test/ncctrie_tests.cpp index 1c394756a..3fa34ee59 100644 --- a/src/test/ncctrie_tests.cpp +++ b/src/test/ncctrie_tests.cpp @@ -31,8 +31,23 @@ CMutableTransaction BuildTransaction(const uint256& prevhash) return tx; } -BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) +void AttachBlock(CNCCTrieCache& cache, std::vector& block_undos) { + CNCCTrieQueueUndo undo; + cache.incrementBlock(undo); + block_undos.push_back(undo); +} + +void DetachBlock(CNCCTrieCache& cache, std::vector& block_undos) +{ + CNCCTrieQueueUndo& undo = block_undos.back(); + cache.decrementBlock(undo); + block_undos.pop_back(); +} + +BOOST_AUTO_TEST_CASE(ncctrie_merkle_hash) +{ + int unused; uint256 hash0(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); CMutableTransaction tx1 = BuildTransaction(hash0); CMutableTransaction tx2 = BuildTransaction(tx1.GetHash()); @@ -56,19 +71,19 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->empty()); CNCCTrieCache ntState(pnccTrie); - ntState.insertName(std::string("test"), tx1.GetHash(), 0, 50, 100); - ntState.insertName(std::string("test2"), tx2.GetHash(), 0, 50, 100); + ntState.insertClaimIntoTrie(std::string("test"), CNodeValue(tx1.GetHash(), 0, 50, 100, 200)); + ntState.insertClaimIntoTrie(std::string("test2"), CNodeValue(tx2.GetHash(), 0, 50, 100, 200)); BOOST_CHECK(pnccTrie->empty()); BOOST_CHECK(!ntState.empty()); BOOST_CHECK(ntState.getMerkleHash() == hash1); - ntState.insertName(std::string("test"), tx3.GetHash(), 0, 50, 101); + ntState.insertClaimIntoTrie(std::string("test"), CNodeValue(tx3.GetHash(), 0, 50, 101, 201)); BOOST_CHECK(ntState.getMerkleHash() == hash1); - ntState.insertName(std::string("tes"), tx4.GetHash(), 0, 50, 100); + ntState.insertClaimIntoTrie(std::string("tes"), CNodeValue(tx4.GetHash(), 0, 50, 100, 200)); BOOST_CHECK(ntState.getMerkleHash() == hash2); - ntState.insertName(std::string("testtesttesttest"), tx5.GetHash(), 0, 50, 100); - ntState.removeName(std::string("testtesttesttest"), tx5.GetHash(), 0); + ntState.insertClaimIntoTrie(std::string("testtesttesttest"), CNodeValue(tx5.GetHash(), 0, 50, 100, 200)); + ntState.removeClaimFromTrie(std::string("testtesttesttest"), tx5.GetHash(), 0, unused); BOOST_CHECK(ntState.getMerkleHash() == hash2); ntState.flush(); @@ -77,16 +92,16 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->checkConsistency()); CNCCTrieCache ntState1(pnccTrie); - ntState1.removeName(std::string("test"), tx1.GetHash(), 0); - ntState1.removeName(std::string("test2"), tx2.GetHash(), 0); - ntState1.removeName(std::string("test"), tx3.GetHash(), 0); - ntState1.removeName(std::string("tes"), tx4.GetHash(), 0); + ntState1.removeClaimFromTrie(std::string("test"), tx1.GetHash(), 0, unused); + ntState1.removeClaimFromTrie(std::string("test2"), tx2.GetHash(), 0, unused); + ntState1.removeClaimFromTrie(std::string("test"), tx3.GetHash(), 0, unused); + ntState1.removeClaimFromTrie(std::string("tes"), tx4.GetHash(), 0, unused); BOOST_CHECK(ntState1.getMerkleHash() == hash0); CNCCTrieCache ntState2(pnccTrie); - ntState2.insertName(std::string("abab"), tx6.GetHash(), 0, 50, 100); - ntState2.removeName(std::string("test"), tx1.GetHash(), 0); + ntState2.insertClaimIntoTrie(std::string("abab"), CNodeValue(tx6.GetHash(), 0, 50, 100, 200)); + ntState2.removeClaimFromTrie(std::string("test"), tx1.GetHash(), 0, unused); BOOST_CHECK(ntState2.getMerkleHash() == hash3); @@ -97,7 +112,7 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->checkConsistency()); CNCCTrieCache ntState3(pnccTrie); - ntState3.insertName(std::string("test"), tx1.GetHash(), 0, 50, 100); + ntState3.insertClaimIntoTrie(std::string("test"), CNodeValue(tx1.GetHash(), 0, 50, 100, 200)); BOOST_CHECK(ntState3.getMerkleHash() == hash4); ntState3.flush(); BOOST_CHECK(!pnccTrie->empty()); @@ -105,7 +120,7 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->checkConsistency()); CNCCTrieCache ntState4(pnccTrie); - ntState4.removeName(std::string("abab"), tx6.GetHash(), 0); + ntState4.removeClaimFromTrie(std::string("abab"), tx6.GetHash(), 0, unused); BOOST_CHECK(ntState4.getMerkleHash() == hash2); ntState4.flush(); BOOST_CHECK(!pnccTrie->empty()); @@ -113,7 +128,7 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->checkConsistency()); CNCCTrieCache ntState5(pnccTrie); - ntState5.removeName(std::string("test"), tx3.GetHash(), 0); + ntState5.removeClaimFromTrie(std::string("test"), tx3.GetHash(), 0, unused); BOOST_CHECK(ntState5.getMerkleHash() == hash2); ntState5.flush(); @@ -122,13 +137,380 @@ BOOST_AUTO_TEST_CASE(ncctrie_create_insert_remove) BOOST_CHECK(pnccTrie->checkConsistency()); CNCCTrieCache ntState6(pnccTrie); - ntState6.insertName(std::string("test"), tx3.GetHash(), 0, 50, 101); + ntState6.insertClaimIntoTrie(std::string("test"), CNodeValue(tx3.GetHash(), 0, 50, 101, 201)); BOOST_CHECK(ntState6.getMerkleHash() == hash2); ntState6.flush(); BOOST_CHECK(!pnccTrie->empty()); BOOST_CHECK(pnccTrie->getMerkleHash() == hash2); BOOST_CHECK(pnccTrie->checkConsistency()); + + CNCCTrieCache ntState7(pnccTrie); + ntState7.removeClaimFromTrie(std::string("test"), tx3.GetHash(), 0, unused); + ntState7.removeClaimFromTrie(std::string("test"), tx1.GetHash(), 0, unused); + ntState7.removeClaimFromTrie(std::string("tes"), tx4.GetHash(), 0, unused); + ntState7.removeClaimFromTrie(std::string("test2"), tx2.GetHash(), 0, unused); + + BOOST_CHECK(ntState7.getMerkleHash() == hash0); + ntState7.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->getMerkleHash() == hash0); + BOOST_CHECK(pnccTrie->checkConsistency()); +} + +BOOST_AUTO_TEST_CASE(ncctrie_insert_update_claim) +{ + std::vector block_undos; + int start_block = pnccTrie->nCurrentHeight; + int current_block = start_block; + + uint256 hash0(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + CMutableTransaction tx1 = BuildTransaction(hash0); + CMutableTransaction tx2 = BuildTransaction(tx1.GetHash()); + CMutableTransaction tx3 = BuildTransaction(tx2.GetHash()); + CMutableTransaction tx4 = BuildTransaction(tx3.GetHash()); + int tx1_height, tx2_height, tx3_height, tx4_height, tx1_undo_height, tx3_undo_height; + + BOOST_CHECK(pnccTrie->getMerkleHash() == hash0); + + CNCCTrieCache ntState(pnccTrie); + + tx1_height = current_block; + ntState.addClaim(std::string("atest"), tx1.GetHash(), 0, 50, tx1_height); + tx3_height = current_block; + ntState.addClaim(std::string("btest"), tx3.GetHash(), 0, 50, tx3_height); + AttachBlock(ntState, block_undos); + current_block++; + ntState.flush(); + for (; current_block < start_block + 100; ++current_block) + { + CNCCTrieCache s(pnccTrie); + AttachBlock(s, block_undos); + s.flush(); + } + CNodeValue v; + BOOST_CHECK(!pnccTrie->getInfoForName(std::string("atest"), v)); + BOOST_CHECK(!pnccTrie->getInfoForName(std::string("btest"), v)); + + CNCCTrieCache ntState1(pnccTrie); + AttachBlock(ntState1, block_undos); + ntState1.flush(); + current_block++; + + CNCCTrieCache ntState2(pnccTrie); + BOOST_CHECK(pnccTrie->getInfoForName(std::string("atest"), v)); + BOOST_CHECK(pnccTrie->getInfoForName(std::string("btest"), v)); + + BOOST_CHECK(ntState2.spendClaim(std::string("atest"), tx1.GetHash(), 0, tx1_height, tx1_undo_height)); + BOOST_CHECK(ntState2.spendClaim(std::string("btest"), tx3.GetHash(), 0, tx3_height, tx3_undo_height)); + + tx2_height = current_block; + ntState2.addClaim(std::string("atest"), tx2.GetHash(), 0, 50, tx2_height, tx1.GetHash(), 0); + tx4_height = current_block; + ntState2.addClaim(std::string("btest"), tx4.GetHash(), 0, 50, tx4_height); + + AttachBlock(ntState2, block_undos); + current_block++; + ntState2.flush(); + + BOOST_CHECK(pnccTrie->getInfoForName(std::string("atest"), v)); + BOOST_CHECK(!pnccTrie->getInfoForName(std::string("btest"), v)); + BOOST_CHECK(v.txhash == tx2.GetHash()); + + CNCCTrieCache ntState3(pnccTrie); + DetachBlock(ntState3, block_undos); + current_block--; + BOOST_CHECK(ntState3.undoAddClaim(std::string("atest"), tx2.GetHash(), 0, tx2_height)); + BOOST_CHECK(ntState3.undoAddClaim(std::string("btest"), tx4.GetHash(), 0, tx4_height)); + ntState3.undoSpendClaim(std::string("atest"), tx1.GetHash(), 0, 50, tx1_height, tx1_undo_height); + ntState3.undoSpendClaim(std::string("btest"), tx3.GetHash(), 0, 50, tx3_height, tx3_undo_height); + ntState3.flush(); + + for (; current_block > start_block; --current_block) + { + CNCCTrieCache s(pnccTrie); + DetachBlock(s, block_undos); + s.flush(); + } + CNCCTrieCache ntState4(pnccTrie); + BOOST_CHECK(ntState4.undoAddClaim(std::string("atest"), tx1.GetHash(), 0, tx1_height)); + BOOST_CHECK(ntState4.undoAddClaim(std::string("btest"), tx3.GetHash(), 0, tx3_height)); + ntState4.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->getMerkleHash() == hash0); + BOOST_CHECK(pnccTrie->queueEmpty()); +} + +BOOST_AUTO_TEST_CASE(ncctrie_undo) +{ + std::vector block_undos; + int start_block = pnccTrie->nCurrentHeight; + int current_block = start_block; + CNodeValue val; + + uint256 hash0(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + CMutableTransaction tx1 = BuildTransaction(hash0); + CMutableTransaction tx2 = BuildTransaction(tx1.GetHash()); + int tx1_height, tx2_height; + int tx1_undo_height, tx2_undo_height; + + BOOST_CHECK(pnccTrie->getMerkleHash() == hash0); + BOOST_CHECK(pnccTrie->queueEmpty()); + + CNCCTrieCache ntState(pnccTrie); + + std::string name("a"); + + /* Test undoing a claim before the claim gets into the trie */ + + // make claim at start_block + tx1_height = current_block; + ntState.addClaim(name, tx1.GetHash(), 0, 50, tx1_height); + AttachBlock(ntState, block_undos); + current_block++; + ntState.flush(); + BOOST_CHECK(!pnccTrie->queueEmpty()); + + // undo block and remove claim + DetachBlock(ntState, block_undos); + current_block--; + ntState.undoAddClaim(name, tx1.GetHash(), 0, tx1_height); + ntState.flush(); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing a claim that has gotten into the trie */ + + // make claim at start_block + tx1_height = current_block; + ntState.addClaim(name, tx1.GetHash(), 0, 50, tx1_height); + AttachBlock(ntState, block_undos); + current_block++; + ntState.flush(); + BOOST_CHECK(!pnccTrie->queueEmpty()); + + // move to block start_block + 101 + for (; current_block < start_block + 101; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // undo block and remove claim + for (; current_block > start_block; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + ntState.undoAddClaim(name, tx1.GetHash(), 0, tx1_height); + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing a spend which involves a claim just inserted into the queue */ + + // make and spend claim at start_block + tx1_height = current_block; + ntState.addClaim(name, tx1.GetHash(), 0, 50, tx1_height); + ntState.spendClaim(name, tx1.GetHash(), 0, tx1_height, tx1_undo_height); + AttachBlock(ntState, block_undos); + current_block++; + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // roll back the block + DetachBlock(ntState, block_undos); + current_block--; + ntState.undoSpendClaim(name, tx1.GetHash(), 0, 50, tx1_height, tx1_undo_height); + ntState.undoAddClaim(name, tx1.GetHash(), 0, tx1_height); + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing a spend which involves a claim inserted into the queue by a previous block*/ + + // make claim at block start_block + tx1_height = current_block; + ntState.addClaim(name, tx1.GetHash(), 0, 50, tx1_height); + AttachBlock(ntState, block_undos); + current_block++; + ntState.flush(); + + // spend the claim in block start_block + 50 + for (; current_block < start_block + 50; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + ntState.spendClaim(name, tx1.GetHash(), 0, tx1_height, tx1_undo_height); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // attach block start_block + 100 and make sure nothing is inserted + for (; current_block < start_block + 101; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // roll back to block start_block + 50 and undo the spend + for (; current_block > start_block + 50; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + ntState.undoSpendClaim(name, tx1.GetHash(), 0, 50, tx1_height, tx1_undo_height); + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + + // make sure it still gets inserted at block start_block + 100 + for (; current_block < start_block + 100; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing a spend which involves a claim in the trie */ + + // spend the claim at block start_block + 150 + for (; current_block < start_block + 150; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + ntState.spendClaim(name, tx1.GetHash(), 0, tx1_height, tx1_undo_height); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // roll back the block + DetachBlock(ntState, block_undos); + --current_block; + ntState.undoSpendClaim(name, tx1.GetHash(), 0, 50, tx1_height, tx1_undo_height); + ntState.flush(); + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing an spent update which updated the best claim to a name*/ + + // update the claim at block start_block + 150 + tx2_height = current_block; + ntState.spendClaim(name, tx1.GetHash(), 0, tx1_height, tx1_undo_height); + ntState.addClaim(name, tx2.GetHash(), 0, 75, tx2_height, tx1.GetHash(), 0); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // move forward a bit + for (; current_block < start_block + 200; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + + // spend the update + ntState.spendClaim(name, tx2.GetHash(), 0, tx2_height, tx2_undo_height); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // undo the spend + DetachBlock(ntState, block_undos); + --current_block; + ntState.undoSpendClaim(name, tx2.GetHash(), 0, 75, tx2_height, tx2_undo_height); + ntState.flush(); + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + /* Test undoing a spent update which updated a claim still in the queue */ + + // roll everything back to block start_block + 50 + for (; current_block > start_block + 151; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + DetachBlock(ntState, block_undos); + --current_block; + ntState.undoAddClaim(name, tx2.GetHash(), 0, tx2_height); + ntState.undoSpendClaim(name, tx1.GetHash(), 0, 50, tx1_height, tx1_undo_height); + ntState.flush(); + for (; current_block > start_block + 50; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + + // update the claim at block start_block + 50 + tx2_height = current_block; + ntState.spendClaim(name, tx1.GetHash(), 0, tx1_height, tx1_undo_height); + ntState.addClaim(name, tx2.GetHash(), 0, 75, tx2_height, tx1.GetHash(), 0); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + + // check that it gets inserted at block start_block + 150 + for (; current_block < start_block + 150; ++current_block) + { + AttachBlock(ntState, block_undos); + ntState.flush(); + } + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(!pnccTrie->queueEmpty()); + AttachBlock(ntState, block_undos); + ++current_block; + ntState.flush(); + BOOST_CHECK(!pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + + // roll back to start_block + for (; current_block > start_block + 50; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + ntState.undoAddClaim(name, tx2.GetHash(), 0, tx2_height); + ntState.undoSpendClaim(name, tx1.GetHash(), 0, 75, tx1_height, tx1_undo_height); + ntState.flush(); + for (; current_block > start_block; --current_block) + { + DetachBlock(ntState, block_undos); + ntState.flush(); + } + ntState.undoAddClaim(name, tx1.GetHash(), 0, tx1_height); + ntState.flush(); + BOOST_CHECK(pnccTrie->empty()); + BOOST_CHECK(pnccTrie->queueEmpty()); + BOOST_CHECK(pnccTrie->nCurrentHeight == start_block); } BOOST_AUTO_TEST_CASE(ncctrienode_serialize_unserialize) @@ -137,6 +519,7 @@ BOOST_AUTO_TEST_CASE(ncctrienode_serialize_unserialize) CNCCTrieNode n1; CNCCTrieNode n2; + CNodeValue throwaway; ss << n1; ss >> n2; @@ -154,8 +537,8 @@ BOOST_AUTO_TEST_CASE(ncctrienode_serialize_unserialize) ss >> n2; BOOST_CHECK(n1 == n2); - CNodeValue v1(uint256S("0000000000000000000000000000000000000000000000000000000000000001"), 0, 50, 0); - CNodeValue v2(uint256S("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), 1, 100, 1); + CNodeValue v1(uint256S("0000000000000000000000000000000000000000000000000000000000000001"), 0, 50, 0, 100); + CNodeValue v2(uint256S("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"), 1, 100, 1, 101); n1.insertValue(v1); BOOST_CHECK(n1 != n2); @@ -169,13 +552,13 @@ BOOST_AUTO_TEST_CASE(ncctrienode_serialize_unserialize) ss >> n2; BOOST_CHECK(n1 == n2); - n1.removeValue(v1); + n1.removeValue(v1.txhash, v1.nOut, throwaway); BOOST_CHECK(n1 != n2); ss << n1; ss >> n2; BOOST_CHECK(n1 == n2); - n1.removeValue(v2); + n1.removeValue(v2.txhash, v2.nOut, throwaway); BOOST_CHECK(n1 != n2); ss << n1; ss >> n2; diff --git a/src/undo.h b/src/undo.h index 1c4ed95bf..d67e6bc39 100644 --- a/src/undo.h +++ b/src/undo.h @@ -7,6 +7,7 @@ #define BITCOIN_UNDO_H #include "compressor.h" +#include "ncctrie.h" #include "primitives/transaction.h" #include "serialize.h" @@ -23,14 +24,16 @@ public: bool fCoinBase; // if the outpoint was the last unspent: whether it belonged to a coinbase unsigned int nHeight; // if the outpoint was the last unspent: its height int nVersion; // if the outpoint was the last unspent: its version + unsigned int nNCCValidHeight; // If the outpoint was an NCC claim, the height at which the claim should be inserted into the trie - CTxInUndo() : txout(), fCoinBase(false), nHeight(0), nVersion(0) {} - CTxInUndo(const CTxOut &txoutIn, bool fCoinBaseIn = false, unsigned int nHeightIn = 0, int nVersionIn = 0) : txout(txoutIn), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), nVersion(nVersionIn) { } + CTxInUndo() : txout(), fCoinBase(false), nHeight(0), nVersion(0), nNCCValidHeight(0) {} + CTxInUndo(const CTxOut &txoutIn, bool fCoinBaseIn = false, unsigned int nHeightIn = 0, int nVersionIn = 0, unsigned int nNCCValidHeight = 0) : txout(txoutIn), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), nVersion(nVersionIn), nNCCValidHeight(nNCCValidHeight) { } unsigned int GetSerializeSize(int nType, int nVersion) const { return ::GetSerializeSize(VARINT(nHeight*2+(fCoinBase ? 1 : 0)), nType, nVersion) + (nHeight > 0 ? ::GetSerializeSize(VARINT(this->nVersion), nType, nVersion) : 0) + - ::GetSerializeSize(CTxOutCompressor(REF(txout)), nType, nVersion); + ::GetSerializeSize(CTxOutCompressor(REF(txout)), nType, nVersion) + + ::GetSerializeSize(VARINT(nNCCValidHeight), nType, nVersion); } template @@ -39,6 +42,7 @@ public: if (nHeight > 0) ::Serialize(s, VARINT(this->nVersion), nType, nVersion); ::Serialize(s, CTxOutCompressor(REF(txout)), nType, nVersion); + ::Serialize(s, VARINT(nNCCValidHeight), nType, nVersion); } template @@ -50,6 +54,7 @@ public: if (nHeight > 0) ::Unserialize(s, VARINT(this->nVersion), nType, nVersion); ::Unserialize(s, REF(CTxOutCompressor(REF(txout))), nType, nVersion); + ::Unserialize(s, VARINT(nNCCValidHeight), nType, nVersion); } }; @@ -73,12 +78,14 @@ class CBlockUndo { public: std::vector vtxundo; // for all but the coinbase + CNCCTrieQueueUndo queueUndo; // any claims that went from the queue to the trie ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { READWRITE(vtxundo); + READWRITE(queueUndo); } };