Implement a hard fork for extended/infinite claim expiration times #112

Closed
lbrynaut wants to merge 247 commits from claim-expiration into master
7 changed files with 771 additions and 70 deletions
Showing only changes of commit f2a7a5c401 - Show all commits

View file

@ -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<unsigned int, unsigned int> 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<CTxInUndo>& txinUndos = blockundo.vtxundo.back().vprevout;
for (std::map<unsigned int, unsigned int>::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 "

View file

@ -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__);
}
}

View file

@ -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<CNodeValue>::iterator position = std::find(values.begin(), values.end(), val);
//std::vector<CNodeValue>::iterator position = std::find(values.begin(), values.end(), val);
std::vector<CNodeValue>::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<CValueQueueEntry> queueRow;
std::pair<valueQueueType::iterator, bool> ret;
ret = valueQueue.insert(std::pair<int, std::vector<CValueQueueEntry> >(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<int> vChangedQueueRows;
std::vector<int> 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<std::string>& 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<std::string>& deletedNames, const uint256& hashBlockIn, std::vector<int> vChangedQueueRows, std::vector<int> vDeletedQueueRows, int nNewHeight)
{
CLevelDBBatch batch;
for (nodeCacheType::iterator itcache = changedNodes.begin(); itcache != changedNodes.end(); ++itcache)
BatchWriteNode(batch, itcache->first, itcache->second);
for (std::vector<std::string>::iterator itname = deletedNames.begin(); itname != deletedNames.end(); ++itname)
BatchEraseNode(batch, *itname);
for (std::vector<int>::iterator itRowNum = vChangedQueueRows.begin(); itRowNum != vChangedQueueRows.end(); ++itRowNum)
BatchWriteQueueRow(batch, *itRowNum);
for (std::vector<int>::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<CValueQueueEntry> 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<valueQueueType::iterator, bool> ret;
ret = valueQueueCache.insert(std::pair<int, std::vector<CValueQueueEntry> >(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<CValueQueueEntry>::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<CValueQueueEntry>::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();

View file

@ -15,6 +15,8 @@
#include <vector>
#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<CNodeValue> 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 <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(name);
READWRITE(val);
}
};
typedef std::map<int, std::vector<CValueQueueEntry> > valueQueueType;
typedef std::vector<CValueQueueEntry> CNCCTrieQueueUndo;
typedef std::map<std::string, CNCCTrieNode*, nodenamecompare> nodeCacheType;
typedef std::map<std::string, uint256> 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<std::string>& deletedNames, CNCCTrieNode** pNodeRet);
bool updateHash(const std::string& name, uint256& hash, CNCCTrieNode** pNodeRet);
bool recursiveNullify(CNCCTrieNode* node, std::string& name, std::vector<std::string>& deletedNames);
bool recursiveCheckConsistency(CNCCTrieNode* node);
bool BatchWrite(nodeCacheType& changedNodes, std::vector<std::string>& deletedNames, const uint256& hashBlock);
bool BatchWrite(nodeCacheType& changedNodes, std::vector<std::string>& deletedNames, const uint256& hashBlock, std::vector<int> vChangedQueueRows, std::vector<int> 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<std::string> 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;
};

View file

@ -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;

View file

@ -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<CNCCTrieQueueUndo>& block_undos)
{
CNCCTrieQueueUndo undo;
cache.incrementBlock(undo);
block_undos.push_back(undo);
}
void DetachBlock(CNCCTrieCache& cache, std::vector<CNCCTrieQueueUndo>& 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<CNCCTrieQueueUndo> 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<CNCCTrieQueueUndo> 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;

View file

@ -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<typename Stream>
@ -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<typename Stream>
@ -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<CTxUndo> vtxundo; // for all but the coinbase
CNCCTrieQueueUndo queueUndo; // any claims that went from the queue to the trie
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(vtxundo);
READWRITE(queueUndo);
}
};