Merge in proofs, and fix two delay related bugs

Bug #1: If a claim was inserted for name which had no previous
claims, it would be treated as if it had been under
continuous ownership since block 0

Bug #2: When disconnecting and connecting blocks, the official
trie was checked to see if changes of ownership took place,
which could result in the wrong answer if a single cache was
used to disconnect/connect multiple blocks without flushing
to the trie

Merge branch 'master' of git.jimmykiselak.com:/usr/git/ncc into real

Conflicts:
	src/ncctrie.cpp
	src/ncctrie.h
	src/rpcserver.cpp
	src/test/ncctrie_tests.cpp
This commit is contained in:
Jimmy Kiselak 2016-02-10 19:44:39 -05:00
commit 7b866aa51e
8 changed files with 712 additions and 62 deletions

View file

@ -21,7 +21,7 @@ std::vector<unsigned char> heightToVch(int n)
return vchHeight;
}
uint256 CClaimValue::GetHash() const
uint256 getValueHash(COutPoint outPoint, int nHeightOfLastTakeover)
{
CHash256 txHasher;
txHasher.Write(outPoint.hash.begin(), outPoint.hash.size());
@ -36,14 +36,21 @@ uint256 CClaimValue::GetHash() const
std::vector<unsigned char> vchnOutHash(nOutHasher.OUTPUT_SIZE);
nOutHasher.Finalize(&(vchnOutHash[0]));
CHash256 takeoverHasher;
std::vector<unsigned char> vchTakeoverHeightToHash = heightToVch(nHeightOfLastTakeover);
takeoverHasher.Write(vchTakeoverHeightToHash.data(), vchTakeoverHeightToHash.size());
std::vector<unsigned char> vchTakeoverHash(takeoverHasher.OUTPUT_SIZE);
takeoverHasher.Finalize(&(vchTakeoverHash[0]));
CHash256 hasher;
hasher.Write(vchtxHash.data(), vchtxHash.size());
hasher.Write(vchnOutHash.data(), vchnOutHash.size());
hasher.Write(vchTakeoverHash.data(), vchTakeoverHash.size());
std::vector<unsigned char> vchHash(hasher.OUTPUT_SIZE);
hasher.Finalize(&(vchHash[0]));
uint256 claimHash(vchHash);
return claimHash;
uint256 valueHash(vchHash);
return valueHash;
}
bool CClaimTrieNode::insertClaim(CClaimValue claim)
@ -86,7 +93,9 @@ bool CClaimTrieNode::removeClaim(const COutPoint& outPoint, CClaimValue& claim)
bool CClaimTrieNode::getBestClaim(CClaimValue& claim) const
{
if (claims.empty())
{
return false;
}
else
{
claim = claims.front();
@ -432,14 +441,16 @@ bool CClaimTrie::getInfoForName(const std::string& name, CClaimValue& claim) con
{
const CClaimTrieNode* current = getNodeForName(name);
if (current)
{
return current->getBestClaim(claim);
}
return false;
}
bool CClaimTrie::getLastTakeoverForName(const std::string& name, int& lastTakeoverHeight) const
{
const CClaimTrieNode* current = getNodeForName(name);
if (current)
if (current && !current->claims.empty())
{
lastTakeoverHeight = current->nHeightOfLastTakeover;
return true;
@ -474,10 +485,8 @@ bool CClaimTrie::recursiveCheckConsistency(const CClaimTrieNode* node) const
if (hasClaim)
{
uint256 claimHash = claim.GetHash();
vchToHash.insert(vchToHash.end(), claimHash.begin(), claimHash.end());
std::vector<unsigned char> heightToHash = heightToVch(node->nHeightOfLastTakeover);
vchToHash.insert(vchToHash.end(), heightToHash.begin(), heightToHash.end());
uint256 valueHash = getValueHash(claim.outPoint, node->nHeightOfLastTakeover);
vchToHash.insert(vchToHash.end(), valueHash.begin(), valueHash.end());
}
CHash256 hasher;
@ -1064,12 +1073,10 @@ bool CClaimTrieCache::recursiveComputeMerkleHash(CClaimTrieNode* tnCurrent, std:
if (hasClaim)
{
uint256 claimHash = claim.GetHash();
vchToHash.insert(vchToHash.end(), claimHash.begin(), claimHash.end());
int nHeightOfLastTakeover;
assert(getLastTakeoverForName(sPos, nHeightOfLastTakeover));
std::vector<unsigned char> heightToHash = heightToVch(nHeightOfLastTakeover);
vchToHash.insert(vchToHash.end(), heightToHash.begin(), heightToHash.end());
uint256 valueHash = getValueHash(claim.outPoint, nHeightOfLastTakeover);
vchToHash.insert(vchToHash.end(), valueHash.begin(), valueHash.end());
}
CHash256 hasher;
@ -1110,6 +1117,31 @@ bool CClaimTrieCache::empty() const
return base->empty() && cache.empty();
}
CClaimTrieNode* CClaimTrieCache::addNodeToCache(const std::string& position, CClaimTrieNode* original) const
{
if (!original)
original = new CClaimTrieNode();
CClaimTrieNode* cacheCopy = new CClaimTrieNode(*original);
cache[position] = cacheCopy;
nodeCacheType::const_iterator itOriginals = block_originals.find(position);
if (block_originals.end() == itOriginals)
{
CClaimTrieNode* originalCopy = new CClaimTrieNode(*original);
block_originals[position] = originalCopy;
}
return cacheCopy;
}
bool CClaimTrieCache::getOriginalInfoForName(const std::string& name, CClaimValue& claim) const
{
nodeCacheType::const_iterator itOriginalCache = block_originals.find(name);
if (itOriginalCache == block_originals.end())
{
return base->getInfoForName(name, claim);
}
return itOriginalCache->second->getBestClaim(claim);
}
bool CClaimTrieCache::insertClaimIntoTrie(const std::string& name, CClaimValue claim, bool fCheckTakeover) const
{
assert(base);
@ -1118,11 +1150,6 @@ bool CClaimTrieCache::insertClaimIntoTrie(const std::string& name, CClaimValue c
cachedNode = cache.find("");
if (cachedNode != cache.end())
currentNode = cachedNode->second;
if (currentNode == NULL)
{
currentNode = new CClaimTrieNode();
cache[""] = currentNode;
}
for (std::string::const_iterator itCur = name.begin(); itCur != name.end(); ++itCur)
{
std::string sCurrentSubstring(name.begin(), itCur);
@ -1157,12 +1184,10 @@ bool CClaimTrieCache::insertClaimIntoTrie(const std::string& name, CClaimValue c
}
else
{
currentNode = new CClaimTrieNode(*currentNode);
cache[sCurrentSubstring] = currentNode;
currentNode = addNodeToCache(sCurrentSubstring, currentNode);
}
CClaimTrieNode* newNode = new CClaimTrieNode();
CClaimTrieNode* newNode = addNodeToCache(sNextSubstring, NULL);
currentNode->children[*itCur] = newNode;
cache[sNextSubstring] = newNode;
currentNode = newNode;
}
@ -1173,8 +1198,7 @@ bool CClaimTrieCache::insertClaimIntoTrie(const std::string& name, CClaimValue c
}
else
{
currentNode = new CClaimTrieNode(*currentNode);
cache[name] = currentNode;
currentNode = addNodeToCache(name, currentNode);
}
bool fChanged = false;
if (currentNode->claims.empty())
@ -1241,8 +1265,7 @@ bool CClaimTrieCache::removeClaimFromTrie(const std::string& name, const COutPoi
assert(cachedNode->second == currentNode);
else
{
currentNode = new CClaimTrieNode(*currentNode);
cache[name] = currentNode;
currentNode = addNodeToCache(name, currentNode);
}
bool fChanged = false;
assert(currentNode != NULL);
@ -1335,8 +1358,7 @@ bool CClaimTrieCache::recursivePruneName(CClaimTrieNode* tnCurrent, unsigned int
{
// it isn't, so make a copy, stick it in the cache,
// and make it the new current node
tnCurrent = new CClaimTrieNode(*tnCurrent);
cache[sCurrentSubstring] = tnCurrent;
tnCurrent = addNodeToCache(sCurrentSubstring, tnCurrent);
}
// erase the character from the current node, which is
// now guaranteed to be in the cache
@ -1412,7 +1434,7 @@ bool CClaimTrieCache::addClaim(const std::string& name, const COutPoint& outPoin
assert(nHeight == nCurrentHeight);
CClaimValue currentClaim;
int delayForClaim;
if (base->getInfoForName(name, currentClaim) && currentClaim.claimId == claimId)
if (getOriginalInfoForName(name, currentClaim) && currentClaim.claimId == claimId)
{
LogPrintf("%s: This is an update to a best claim.\n", __func__);
delayForClaim = 0;
@ -1810,7 +1832,7 @@ bool CClaimTrieCache::addSupport(const std::string& name, const COutPoint& outPo
assert(nHeight == nCurrentHeight);
CClaimValue claim;
int delayForSupport;
if (base->getInfoForName(name, claim) && claim.claimId == supportedClaimId)
if (getOriginalInfoForName(name, claim) && claim.claimId == supportedClaimId)
{
LogPrintf("%s: This is a support to a best claim.\n", __func__);
delayForSupport = 0;
@ -2037,7 +2059,7 @@ bool CClaimTrieCache::incrementBlock(insertUndoType& insertUndo, claimQueueRowTy
// claims in the queue for that name and the takeover height should be the current height
// if the node is not in the cache, or getbestclaim fails, that means all of its claims were
// deleted
// if base->getInfoForName returns false, that means it's new and shouldn't go into the undo
// if getOriginalInfoForName returns false, that means it's new and shouldn't go into the undo
// if both exist, and the current best claim is not the same as or the parent to the new best
// claim, then ownership has changed and the current height of last takeover should go into
// the queue
@ -2053,7 +2075,7 @@ bool CClaimTrieCache::incrementBlock(insertUndoType& insertUndo, claimQueueRowTy
{
haveClaimInCache = itCachedNode->second->getBestClaim(claimInCache);
}
haveClaimInTrie = base->getInfoForName(*itNamesToCheck, claimInTrie);
haveClaimInTrie = getOriginalInfoForName(*itNamesToCheck, claimInTrie);
bool takeoverHappened = false;
if (!haveClaimInTrie)
{
@ -2169,6 +2191,15 @@ bool CClaimTrieCache::incrementBlock(insertUndoType& insertUndo, claimQueueRowTy
}
}
}
for (nodeCacheType::const_iterator itOriginals = block_originals.begin(); itOriginals != block_originals.end(); ++itOriginals)
{
delete itOriginals->second;
}
block_originals.clear();
for (nodeCacheType::const_iterator itCache = cache.begin(); itCache != cache.end(); ++itCache)
{
block_originals[itCache->first] = new CClaimTrieNode(*(itCache->second));
}
namesToCheckForTakeover.clear();
nCurrentHeight++;
return true;
@ -2226,43 +2257,65 @@ bool CClaimTrieCache::decrementBlock(insertUndoType& insertUndo, claimQueueRowTy
return true;
}
bool CClaimTrieCache::getLastTakeoverForName(const std::string& name, int& lastTakeoverHeight) const
bool CClaimTrieCache::finalizeDecrement() const
{
for (nodeCacheType::iterator itOriginals = block_originals.begin(); itOriginals != block_originals.end(); ++itOriginals)
{
delete itOriginals->second;
}
block_originals.clear();
for (nodeCacheType::const_iterator itCache = cache.begin(); itCache != cache.end(); ++itCache)
{
block_originals[itCache->first] = new CClaimTrieNode(*(itCache->second));
}
return true;
}
bool CClaimTrieCache::getLastTakeoverForName(const std::string& name, int& nLastTakeoverForName) const
{
if (!fRequireTakeoverHeights)
{
nLastTakeoverForName = 0;
return true;
}
std::map<std::string, int>::iterator itHeights = cacheTakeoverHeights.find(name);
if (itHeights == cacheTakeoverHeights.end())
{
if (base->getLastTakeoverForName(name, lastTakeoverHeight))
{
return true;
}
else
{
if (fRequireTakeoverHeights)
{
return false;
}
else
{
lastTakeoverHeight = 0;
return true;
}
}
return base->getLastTakeoverForName(name, nLastTakeoverForName);
}
lastTakeoverHeight = itHeights->second;
nLastTakeoverForName = itHeights->second;
return true;
}
int CClaimTrieCache::getNumBlocksOfContinuousOwnership(const std::string& name) const
{
const CClaimTrieNode* node = NULL;
nodeCacheType::const_iterator itCache = cache.find(name);
if (itCache != cache.end())
{
node = itCache->second;
}
if (!node)
{
node = base->getNodeForName(name);
}
if (!node || node->claims.empty())
{
return 0;
}
int nLastTakeoverHeight;
assert(getLastTakeoverForName(name, nLastTakeoverHeight));
return nCurrentHeight - nLastTakeoverHeight;
}
int CClaimTrieCache::getDelayForName(const std::string& name) const
{
int nHeightOfLastTakeover;
if (getLastTakeoverForName(name, nHeightOfLastTakeover))
{
return std::min((nCurrentHeight - nHeightOfLastTakeover) / base->nProportionalDelayFactor, 4032);
}
else
if (!fRequireTakeoverHeights)
{
return 0;
}
int nBlocksOfContinuousOwnership = getNumBlocksOfContinuousOwnership(name);
return std::min(nBlocksOfContinuousOwnership / base->nProportionalDelayFactor, 4032);
}
uint256 CClaimTrieCache::getBestBlock()
@ -2285,6 +2338,11 @@ bool CClaimTrieCache::clear() const
delete itcache->second;
}
cache.clear();
for (nodeCacheType::iterator itOriginals = block_originals.begin(); itOriginals != block_originals.end(); ++itOriginals)
{
delete itOriginals->second;
}
block_originals.clear();
dirtyHashes.clear();
cacheHashes.clear();
claimQueueCache.clear();
@ -2309,3 +2367,77 @@ bool CClaimTrieCache::flush()
}
return success;
}
uint256 CClaimTrieCache::getLeafHashForProof(const std::string& currentPosition, unsigned char nodeChar, const CClaimTrieNode* currentNode) const
{
std::stringstream leafPosition;
leafPosition << currentPosition << nodeChar;
hashMapType::iterator cachedHash = cacheHashes.find(leafPosition.str());
if (cachedHash != cacheHashes.end())
{
return cachedHash->second;
}
else
{
return currentNode->hash;
}
}
CClaimTrieProof CClaimTrieCache::getProofForName(const std::string& name) const
{
if (dirty())
getMerkleHash();
std::vector<CClaimTrieProofNode> nodes;
CClaimTrieNode* current = &(base->root);
nodeCacheType::const_iterator cachedNode;
bool fNameHasValue = false;
COutPoint outPoint;
int nHeightOfLastTakeover = 0;
for (std::string::const_iterator itName = name.begin(); current; ++itName)
{
std::string currentPosition(name.begin(), itName);
cachedNode = cache.find(currentPosition);
if (cachedNode != cache.end())
current = cachedNode->second;
CClaimValue claim;
bool fNodeHasValue = current->getBestClaim(claim);
uint256 valueHash;
if (fNodeHasValue)
{
int nHeightOfLastTakeover;
assert(getLastTakeoverForName(currentPosition, nHeightOfLastTakeover));
valueHash = getValueHash(claim.outPoint, nHeightOfLastTakeover);
}
std::vector<std::pair<unsigned char, uint256> > children;
CClaimTrieNode* nextCurrent = NULL;
for (nodeMapType::const_iterator itChildren = current->children.begin(); itChildren != current->children.end(); ++itChildren)
{
if (itName == name.end() || itChildren->first != *itName) // Leaf node
{
uint256 childHash = getLeafHashForProof(currentPosition, itChildren->first, itChildren->second);
children.push_back(std::make_pair(itChildren->first, childHash));
}
else // Full node
{
nextCurrent = itChildren->second;
uint256 childHash;
children.push_back(std::make_pair(itChildren->first, childHash));
}
}
if (currentPosition == name)
{
fNameHasValue = fNodeHasValue;
if (fNameHasValue)
{
outPoint = claim.outPoint;
assert(getLastTakeoverForName(name, nHeightOfLastTakeover));
}
valueHash.SetNull();
}
CClaimTrieProofNode node(children, fNodeHasValue, valueHash);
nodes.push_back(node);
current = nextCurrent;
}
return CClaimTrieProof(nodes, fNameHasValue, outPoint,
nHeightOfLastTakeover);
}

View file

@ -23,6 +23,8 @@
#define SUPPORT_QUEUE_NAME_ROW 'p'
#define SUPPORT_EXP_QUEUE_ROW 'x'
uint256 getValueHash(COutPoint outPoint, int nHeightOfLastTakeover);
class CClaimValue
{
public:
@ -41,8 +43,6 @@ public:
, nAmount(nAmount), nEffectiveAmount(nAmount)
, nHeight(nHeight), nValidAtHeight(nValidAtHeight)
{}
uint256 GetHash() const;
ADD_SERIALIZE_METHODS;
@ -394,6 +394,30 @@ private:
supportMapType dirtySupportNodes;
};
class CClaimTrieProofNode
{
public:
CClaimTrieProofNode() {};
CClaimTrieProofNode(std::vector<std::pair<unsigned char, uint256> > children,
bool hasValue, uint256 valHash)
: children(children), hasValue(hasValue), valHash(valHash)
{};
std::vector<std::pair<unsigned char, uint256> > children;
bool hasValue;
uint256 valHash;
};
class CClaimTrieProof
{
public:
CClaimTrieProof() {};
CClaimTrieProof(std::vector<CClaimTrieProofNode> nodes, bool hasValue, COutPoint outPoint, int nHeightOfLastTakeover) : nodes(nodes), hasValue(hasValue), outPoint(outPoint), nHeightOfLastTakeover(nHeightOfLastTakeover) {}
std::vector<CClaimTrieProofNode> nodes;
bool hasValue;
COutPoint outPoint;
int nHeightOfLastTakeover;
};
class CClaimTrieCache
{
public:
@ -453,12 +477,16 @@ public:
bool removeClaimFromTrie(const std::string& name, const COutPoint& outPoint,
CClaimValue& claim,
bool fCheckTakeover = false) const;
CClaimTrieProof getProofForName(const std::string& name) const;
bool finalizeDecrement() const;
private:
CClaimTrie* base;
bool fRequireTakeoverHeights;
mutable nodeCacheType cache;
mutable nodeCacheType block_originals;
mutable std::set<std::string> dirtyHashes;
mutable hashMapType cacheHashes;
mutable claimQueueType claimQueueCache;
@ -537,6 +565,15 @@ private:
bool getLastTakeoverForName(const std::string& name, int& lastTakeoverHeight) const;
int getDelayForName(const std::string& name) const;
uint256 getLeafHashForProof(const std::string& currentPosition, unsigned char nodeChar,
const CClaimTrieNode* currentNode) const;
CClaimTrieNode* addNodeToCache(const std::string& position, CClaimTrieNode* original) const;
bool getOriginalInfoForName(const std::string& name, CClaimValue& claim) const;
int getNumBlocksOfContinuousOwnership(const std::string& name) const;
};
#endif // BITCOIN_CLAIMTRIE_H

View file

@ -1694,6 +1694,7 @@ bool DisconnectBlock(const CBlock& block, CValidationState& state, const CBlockI
// move best block pointer to prevout block
view.SetBestBlock(pindex->pprev->GetBlockHash());
assert(trieCache.finalizeDecrement());
trieCache.setBestBlock(pindex->pprev->GetBlockHash());
assert(trieCache.getMerkleHash() == pindex->pprev->hashClaimTrie);
@ -3547,6 +3548,42 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth
return true;
}
bool GetProofForName(const CBlockIndex* pindexProof, const std::string& name, CClaimTrieProof& proof)
{
AssertLockHeld(cs_main);
if (!chainActive.Contains(pindexProof))
{
return false;
}
CCoinsViewCache coins(pcoinsTip);
CClaimTrieCache trieCache(pclaimTrie);
CBlockIndex* pindexState = chainActive.Tip();
CValidationState state;
for (CBlockIndex *pindex = chainActive.Tip(); pindex && pindex->pprev && pindexState != pindexProof; pindex=pindex->pprev)
{
boost::this_thread::interruption_point();
CBlock block;
if (!ReadBlockFromDisk(block, pindex))
{
return false;
}
if (pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage)
{
bool fClean = true;
if (!DisconnectBlock(block, state, pindex, coins, trieCache, &fClean))
{
return false;
}
pindexState = pindex->pprev;
}
if (ShutdownRequested())
return false;
}
assert(pindexState == pindexProof);
proof = trieCache.getProofForName(name);
return true;
}
void UnloadBlockIndex()
{
LOCK(cs_main);

View file

@ -160,6 +160,8 @@ FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly = false);
FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
/** Translation to a filesystem path */
boost::filesystem::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix);
/** Get a cryptographic proof that a name maps to a value **/
bool GetProofForName(const CBlockIndex* pindexProof, const std::string& name, CClaimTrieProof& proof);
/** Import blocks from an external file */
bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp = NULL);
/** Initialize a new block tree database + block data on disk */

View file

@ -319,3 +319,126 @@ UniValue getclaimsfortx(const UniValue& params, bool fHelp)
}
return ret;
}
UniValue proofToJSON(const CClaimTrieProof& proof)
{
UniValue result(UniValue::VOBJ);
UniValue nodes(UniValue::VARR);
for (std::vector<CClaimTrieProofNode>::const_iterator itNode = proof.nodes.begin(); itNode != proof.nodes.end(); ++itNode)
{
UniValue node(UniValue::VOBJ);
UniValue children(UniValue::VARR);
for (std::vector<std::pair<unsigned char, uint256> >::const_iterator itChildren = itNode->children.begin(); itChildren != itNode->children.end(); ++itChildren)
{
UniValue child(UniValue::VOBJ);
child.push_back(Pair("character", itChildren->first));
if (!itChildren->second.IsNull())
{
child.push_back(Pair("nodeHash", itChildren->second.GetHex()));
}
children.push_back(child);
}
node.push_back(Pair("children", children));
if (itNode->hasValue && !itNode->valHash.IsNull())
{
node.push_back(Pair("valueHash", itNode->valHash.GetHex()));
}
nodes.push_back(node);
}
result.push_back(Pair("nodes", nodes));
if (proof.hasValue)
{
result.push_back(Pair("txhash", proof.outPoint.hash.GetHex()));
result.push_back(Pair("nOut", (int)proof.outPoint.n));
result.push_back(Pair("last takeover height", (int)proof.nHeightOfLastTakeover));
}
return result;
}
UniValue getnameproof(const UniValue& params, bool fHelp)
{
if (fHelp || (params.size() != 1 && params.size() != 2))
throw std::runtime_error(
"getnameproof\n"
"Return the cryptographic proof that a name maps to a value\n"
"or doesn't.\n"
"Arguments:\n"
"1. \"name\" (string) the name to get a proof for\n"
"2. \"blockhash\" (string, optional) the hash of the block\n"
" which is the basis\n"
" of the proof. If\n"
" none is given, \n"
" the latest block\n"
" will be used.\n"
"Result: \n"
"{\n"
" \"nodes\" : [ (array of object) full nodes (i.e.\n"
" those which lead to\n"
" the requested name)\n"
" \"children\" : [ (array of object) the children of\n"
" this node\n"
" \"child\" : { (object) a child node, either leaf or\n"
" reference to a full node\n"
" \"character\" : \"char\" (string) the character which\n"
" leads from the parent\n"
" to this child node\n"
" \"nodeHash\" : \"hash\" (string, if exists) the hash of\n"
" the node if\n"
" this is a \n"
" leaf node\n"
" }\n"
" ]\n"
" \"valueHash\" (string, if exists) the hash of this\n"
" node's value, if\n"
" it has one. If \n"
" this is the\n"
" requested name\n"
" this will not\n"
" exist whether\n"
" the node has a\n"
" value or not\n"
" ]\n"
" \"txhash\" : \"hash\" (string, if exists) the txid of the\n"
" claim which controls\n"
" this name, if there\n"
" is one.\n"
" \"nOut\" : n, (numeric) the nOut of the claim which\n"
" controls this name, if there\n"
" is one.\n"
" \"last takeover height\" (numeric) the most recent height at\n"
" which the value of a name\n"
" changed other than through\n"
" an update to the winning\n"
" bid\n"
" }\n"
"}\n");
LOCK(cs_main);
std::string strName = params[0].get_str();
uint256 blockHash;
if (params.size() == 2)
{
std::string strBlockHash = params[1].get_str();
blockHash = uint256S(strBlockHash);
}
else
{
blockHash = chainActive.Tip()->GetBlockHash();
}
if (mapBlockIndex.count(blockHash) == 0)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
CBlockIndex* pblockIndex = mapBlockIndex[blockHash];
if (!chainActive.Contains(pblockIndex))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not in main chain");
if (chainActive.Tip()->nHeight > (pblockIndex->nHeight + 20))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block too deep to generate proof");
CClaimTrieProof proof;
if (!GetProofForName(pblockIndex, strName, proof))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed to generate proof");
return proofToJSON(proof);
}

View file

@ -385,6 +385,7 @@ static const CRPCCommand vRPCCommands[] =
{ "claimtrie", "gettotalclaims", &gettotalclaims, true },
{ "claimtrie", "gettotalvalueofclaims", &gettotalvalueofclaims, true },
{ "claimtrie", "getclaimsfortx", &getclaimsfortx, true },
{ "claimtrie", "getnameproof", &getnameproof, true },
};
CRPCTable::CRPCTable()

View file

@ -274,6 +274,7 @@ extern UniValue gettotalclaimednames(const UniValue& params, bool fHelp);
extern UniValue gettotalclaims(const UniValue& params, bool fHelp);
extern UniValue gettotalvalueofclaims(const UniValue& params, bool fHelp);
extern UniValue getclaimsfortx(const UniValue& params, bool fHelp);
extern UniValue getnameproof(const UniValue& params, bool fHelp);
bool StartRPC();

View file

@ -165,16 +165,16 @@ BOOST_AUTO_TEST_CASE(claimtrie_merkle_hash)
COutPoint tx6OutPoint(tx6.GetHash(), 0);
uint256 hash1;
hash1.SetHex("4bbf61ec5669c721bf007c71c59f85e6658f1de7b4562078e22f69f8f7ebcafd");
hash1.SetHex("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa");
uint256 hash2;
hash2.SetHex("33d00d8f4707eb0abef7297a7ff4975e7354fe1ed81455f28301dbceb939494d");
hash2.SetHex("c4fc0e2ad56562a636a0a237a96a5f250ef53495c2cb5edd531f087a8de83722");
uint256 hash3;
hash3.SetHex("eb19bbaeecfd6dee77a8cb69286ac01226caee3c3883f55b86d0ca5a2f4252d7");
hash3.SetHex("baf52472bd7da19fe1e35116cfb3bd180d8770ffbe3ae9243df1fb58a14b0975");
uint256 hash4;
hash4.SetHex("a889778ba28603294c1e5c7cec469a9019332ec93838b2f1331ebba547c5fd22");
hash4.SetHex("c73232a755bf015f22eaa611b283ff38100f2a23fb6222e86eca363452ba0c51");
BOOST_CHECK(pclaimTrie->empty());
@ -274,17 +274,21 @@ BOOST_AUTO_TEST_CASE(claimtrie_insert_update_claim)
std::string sName1("atest");
std::string sName2("btest");
std::string sName3("atest123");
std::string sValue1("testa");
std::string sValue2("testb");
std::string sValue3("123testa");
std::vector<unsigned char> vchName1(sName1.begin(), sName1.end());
std::vector<unsigned char> vchName2(sName2.begin(), sName2.end());
std::vector<unsigned char> vchName3(sName3.begin(), sName3.end());
std::vector<unsigned char> vchValue1(sValue1.begin(), sValue1.end());
std::vector<unsigned char> vchValue2(sValue2.begin(), sValue2.end());
std::vector<unsigned char> vchValue3(sValue3.begin(), sValue3.end());
std::vector<CTransaction> coinbases;
BOOST_CHECK(CreateCoinbases(5, coinbases));
BOOST_CHECK(CreateCoinbases(6, coinbases));
uint256 hash0(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
BOOST_CHECK(pclaimTrie->getMerkleHash() == hash0);
@ -345,6 +349,10 @@ BOOST_AUTO_TEST_CASE(claimtrie_insert_update_claim)
CMutableTransaction tx12 = BuildTransaction(tx10);
tx12.vout[0].scriptPubKey = CScript() << OP_TRUE;
COutPoint tx12OutPoint(tx12.GetHash(), 0);
CMutableTransaction tx13 = BuildTransaction(coinbases[5]);
tx13.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName3 << vchValue3 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx13OutPoint(tx13.GetHash(), 0);
CClaimValue val;
@ -396,6 +404,34 @@ BOOST_AUTO_TEST_CASE(claimtrie_insert_update_claim)
BOOST_CHECK(pclaimTrie->getInfoForName(sName1, val));
BOOST_CHECK(val.outPoint == tx3OutPoint);
BOOST_CHECK(pclaimTrie->haveClaimInQueue(sName1, tx9OutPoint, nThrowaway));
// Disconnect all blocks until the first block, and then reattach them, in memory only
//FlushStateToDisk();
CCoinsViewCache coins(pcoinsTip);
CClaimTrieCache trieCache(pclaimTrie);
CBlockIndex* pindexState = chainActive.Tip();
CValidationState state;
CBlockIndex* pindex;
for (pindex = chainActive.Tip(); pindex && pindex->pprev; pindex=pindex->pprev)
{
CBlock block;
BOOST_CHECK(ReadBlockFromDisk(block, pindex));
if (pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage)
{
bool fClean = true;
BOOST_CHECK(DisconnectBlock(block, state, pindex, coins, trieCache, &fClean));
pindexState = pindex->pprev;
}
}
while (pindex != chainActive.Tip())
{
pindex = chainActive.Next(pindex);
CBlock block;
BOOST_CHECK(ReadBlockFromDisk(block, pindex));
BOOST_CHECK(ConnectBlock(block, state, pindex, coins, trieCache));
}
// Roll back the last block, make sure tx1 and tx7 are put back in the trie
@ -902,6 +938,30 @@ BOOST_AUTO_TEST_CASE(claimtrie_insert_update_claim)
BOOST_CHECK(RemoveBlock(blocks_to_invalidate.back()));
blocks_to_invalidate.pop_back();
mempool.clear();
// make sure all claim for names which exist in the trie but have no
// values get inserted immediately
blocks_to_invalidate.push_back(chainActive.Tip()->GetBlockHash());
AddToMempool(tx13);
BOOST_CHECK(CreateBlocks(1, 2));
BOOST_CHECK(!pclaimTrie->empty());
BOOST_CHECK(pclaimTrie->queueEmpty());
AddToMempool(tx1);
BOOST_CHECK(CreateBlocks(1, 2));
BOOST_CHECK(!pclaimTrie->empty());
BOOST_CHECK(pclaimTrie->queueEmpty());
// roll back
BOOST_CHECK(RemoveBlock(blocks_to_invalidate.back()));
blocks_to_invalidate.pop_back();
mempool.clear();
}
BOOST_AUTO_TEST_CASE(claimtrie_claim_expiration)
@ -2323,4 +2383,261 @@ BOOST_AUTO_TEST_CASE(claimtrienode_serialize_unserialize)
BOOST_CHECK(n1 == n2);
}
bool verify_proof(const CClaimTrieProof proof, uint256 rootHash, const std::string& name)
{
uint256 previousComputedHash;
std::string computedReverseName;
bool verifiedValue = false;
for (std::vector<CClaimTrieProofNode>::const_reverse_iterator itNodes = proof.nodes.rbegin(); itNodes != proof.nodes.rend(); ++itNodes)
{
bool foundChildInChain = false;
std::vector<unsigned char> vchToHash;
for (std::vector<std::pair<unsigned char, uint256> >::const_iterator itChildren = itNodes->children.begin(); itChildren != itNodes->children.end(); ++itChildren)
{
vchToHash.push_back(itChildren->first);
uint256 childHash;
if (itChildren->second.IsNull())
{
if (previousComputedHash.IsNull())
{
return false;
}
if (foundChildInChain)
{
return false;
}
foundChildInChain = true;
computedReverseName += itChildren->first;
childHash = previousComputedHash;
}
else
{
childHash = itChildren->second;
}
vchToHash.insert(vchToHash.end(), childHash.begin(), childHash.end());
}
if (itNodes != proof.nodes.rbegin() && !foundChildInChain)
{
return false;
}
if (itNodes->hasValue)
{
uint256 valHash;
if (itNodes->valHash.IsNull())
{
if (itNodes != proof.nodes.rbegin())
{
return false;
}
if (!proof.hasValue)
{
return false;
}
valHash = getValueHash(proof.outPoint,
proof.nHeightOfLastTakeover);
verifiedValue = true;
}
else
{
valHash = itNodes->valHash;
}
vchToHash.insert(vchToHash.end(), valHash.begin(), valHash.end());
}
else if (proof.hasValue && itNodes == proof.nodes.rbegin())
{
return false;
}
CHash256 hasher;
std::vector<unsigned char> vchHash(hasher.OUTPUT_SIZE);
hasher.Write(vchToHash.data(), vchToHash.size());
hasher.Finalize(&(vchHash[0]));
uint256 calculatedHash(vchHash);
previousComputedHash = calculatedHash;
}
if (previousComputedHash != rootHash)
{
return false;
}
if (proof.hasValue && !verifiedValue)
{
return false;
}
std::string::reverse_iterator itComputedName = computedReverseName.rbegin();
std::string::const_iterator itName = name.begin();
for (; itName != name.end() && itComputedName != computedReverseName.rend(); ++itName, ++itComputedName)
{
if (*itName != *itComputedName)
{
return false;
}
}
return (!proof.hasValue || itName == name.end());
}
BOOST_AUTO_TEST_CASE(claimtrievalue_proof)
{
BOOST_CHECK(pclaimTrie->nCurrentHeight == chainActive.Height() + 1);
LOCK(cs_main);
std::string sName1("a");
std::string sValue1("testa");
std::string sName2("abc");
std::string sValue2("testabc");
std::string sName3("abd");
std::string sValue3("testabd");
std::string sName4("zyx");
std::string sValue4("testzyx");
std::string sName5("zyxa");
std::string sName6("omg");
std::string sName7("");
std::vector<unsigned char> vchName1(sName1.begin(), sName1.end());
std::vector<unsigned char> vchValue1(sValue1.begin(), sValue1.end());
std::vector<unsigned char> vchName2(sName2.begin(), sName2.end());
std::vector<unsigned char> vchValue2(sValue2.begin(), sValue2.end());
std::vector<unsigned char> vchName3(sName3.begin(), sName3.end());
std::vector<unsigned char> vchValue3(sValue3.begin(), sValue3.end());
std::vector<unsigned char> vchName4(sName4.begin(), sName4.end());
std::vector<unsigned char> vchValue4(sValue4.begin(), sValue4.end());
std::vector<unsigned char> vchName7(sName7.begin(), sName7.end());
std::vector<CTransaction> coinbases;
BOOST_CHECK(CreateCoinbases(5, coinbases));
CMutableTransaction tx1 = BuildTransaction(coinbases[0]);
tx1.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName1 << vchValue1 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx1OutPoint(tx1.GetHash(), 0);
CMutableTransaction tx2 = BuildTransaction(coinbases[1]);
tx2.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName2 << vchValue2 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx2OutPoint(tx2.GetHash(), 0);
CMutableTransaction tx3 = BuildTransaction(coinbases[2]);
tx3.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName3 << vchValue3 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx3OutPoint(tx3.GetHash(), 0);
CMutableTransaction tx4 = BuildTransaction(coinbases[3]);
tx4.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName4 << vchValue4 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx4OutPoint(tx4.GetHash(), 0);
CMutableTransaction tx5 = BuildTransaction(coinbases[4]);
tx5.vout[0].scriptPubKey = CScript() << OP_CLAIM_NAME << vchName7 << vchValue4 << OP_2DROP << OP_DROP << OP_TRUE;
COutPoint tx5OutPoint(tx5.GetHash(), 0);
CClaimValue val;
std::vector<uint256> blocks_to_invalidate;
// create a claim. verify the expiration event has been scheduled.
AddToMempool(tx1);
AddToMempool(tx2);
AddToMempool(tx3);
AddToMempool(tx4);
BOOST_CHECK(CreateBlocks(1, 5));
blocks_to_invalidate.push_back(chainActive.Tip()->GetBlockHash());
BOOST_CHECK(!pclaimTrie->empty());
BOOST_CHECK(pclaimTrie->queueEmpty());
BOOST_CHECK(pclaimTrie->getInfoForName(sName1, val));
BOOST_CHECK(val.outPoint == tx1OutPoint);
BOOST_CHECK(pclaimTrie->getInfoForName(sName2, val));
BOOST_CHECK(val.outPoint == tx2OutPoint);
BOOST_CHECK(pclaimTrie->getInfoForName(sName3, val));
BOOST_CHECK(val.outPoint == tx3OutPoint);
BOOST_CHECK(pclaimTrie->getInfoForName(sName4, val));
BOOST_CHECK(val.outPoint == tx4OutPoint);
CClaimTrieCache cache(pclaimTrie);
CClaimTrieProof proof;
proof = cache.getProofForName(sName1);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName1));
BOOST_CHECK(proof.outPoint == tx1OutPoint);
proof = cache.getProofForName(sName2);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName2));
BOOST_CHECK(proof.outPoint == tx2OutPoint);
proof = cache.getProofForName(sName3);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName3));
BOOST_CHECK(proof.outPoint == tx3OutPoint);
proof = cache.getProofForName(sName4);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName4));
BOOST_CHECK(proof.outPoint == tx4OutPoint);
proof = cache.getProofForName(sName5);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName5));
BOOST_CHECK(proof.hasValue == false);
proof = cache.getProofForName(sName6);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName6));
BOOST_CHECK(proof.hasValue == false);
proof = cache.getProofForName(sName7);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName7));
BOOST_CHECK(proof.hasValue == false);
AddToMempool(tx5);
BOOST_CHECK(CreateBlocks(1, 2));
BOOST_CHECK(pclaimTrie->getInfoForName(sName7, val));
BOOST_CHECK(val.outPoint == tx5OutPoint);
cache = CClaimTrieCache(pclaimTrie);
proof = cache.getProofForName(sName1);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName1));
BOOST_CHECK(proof.outPoint == tx1OutPoint);
proof = cache.getProofForName(sName2);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName2));
BOOST_CHECK(proof.outPoint == tx2OutPoint);
proof = cache.getProofForName(sName3);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName3));
BOOST_CHECK(proof.outPoint == tx3OutPoint);
proof = cache.getProofForName(sName4);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName4));
BOOST_CHECK(proof.outPoint == tx4OutPoint);
proof = cache.getProofForName(sName5);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName5));
BOOST_CHECK(proof.hasValue == false);
proof = cache.getProofForName(sName6);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName6));
BOOST_CHECK(proof.hasValue == false);
proof = cache.getProofForName(sName7);
BOOST_CHECK(verify_proof(proof, chainActive.Tip()->hashClaimTrie, sName7));
BOOST_CHECK(proof.outPoint == tx5OutPoint);
BOOST_CHECK(RemoveBlock(blocks_to_invalidate.back()));
blocks_to_invalidate.pop_back();
BOOST_CHECK(pclaimTrie->empty());
BOOST_CHECK(pclaimTrie->queueEmpty());
BOOST_CHECK(blocks_to_invalidate.empty());
}
BOOST_AUTO_TEST_SUITE_END()