diff --git a/src/claimtrie.cpp b/src/claimtrie.cpp index e080d7147..561f15b5f 100644 --- a/src/claimtrie.cpp +++ b/src/claimtrie.cpp @@ -21,7 +21,7 @@ std::vector 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 vchnOutHash(nOutHasher.OUTPUT_SIZE); nOutHasher.Finalize(&(vchnOutHash[0])); + CHash256 takeoverHasher; + std::vector vchTakeoverHeightToHash = heightToVch(nHeightOfLastTakeover); + takeoverHasher.Write(vchTakeoverHeightToHash.data(), vchTakeoverHeightToHash.size()); + std::vector 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 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 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 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::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 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 > 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); +} diff --git a/src/claimtrie.h b/src/claimtrie.h index f95a92c5e..b7275538a 100644 --- a/src/claimtrie.h +++ b/src/claimtrie.h @@ -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 > children, + bool hasValue, uint256 valHash) + : children(children), hasValue(hasValue), valHash(valHash) + {}; + std::vector > children; + bool hasValue; + uint256 valHash; +}; + +class CClaimTrieProof +{ +public: + CClaimTrieProof() {}; + CClaimTrieProof(std::vector nodes, bool hasValue, COutPoint outPoint, int nHeightOfLastTakeover) : nodes(nodes), hasValue(hasValue), outPoint(outPoint), nHeightOfLastTakeover(nHeightOfLastTakeover) {} + std::vector 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 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 diff --git a/src/main.cpp b/src/main.cpp index 71fbf24e0..6e4c15b1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); diff --git a/src/main.h b/src/main.h index 239880727..8b21535b9 100644 --- a/src/main.h +++ b/src/main.h @@ -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 */ diff --git a/src/rpcclaimtrie.cpp b/src/rpcclaimtrie.cpp index 6f706a695..834f1a3de 100644 --- a/src/rpcclaimtrie.cpp +++ b/src/rpcclaimtrie.cpp @@ -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::const_iterator itNode = proof.nodes.begin(); itNode != proof.nodes.end(); ++itNode) + { + UniValue node(UniValue::VOBJ); + UniValue children(UniValue::VARR); + for (std::vector >::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); +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 3fbb71a28..d5c431525 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -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() diff --git a/src/rpcserver.h b/src/rpcserver.h index add8bca85..ef9d28768 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -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(); diff --git a/src/test/claimtrie_tests.cpp b/src/test/claimtrie_tests.cpp index 60f720e04..fe01df273 100644 --- a/src/test/claimtrie_tests.cpp +++ b/src/test/claimtrie_tests.cpp @@ -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 vchName1(sName1.begin(), sName1.end()); std::vector vchName2(sName2.begin(), sName2.end()); + std::vector vchName3(sName3.begin(), sName3.end()); std::vector vchValue1(sValue1.begin(), sValue1.end()); std::vector vchValue2(sValue2.begin(), sValue2.end()); + std::vector vchValue3(sValue3.begin(), sValue3.end()); std::vector 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::const_reverse_iterator itNodes = proof.nodes.rbegin(); itNodes != proof.nodes.rend(); ++itNodes) + { + bool foundChildInChain = false; + std::vector vchToHash; + for (std::vector >::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 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 vchName1(sName1.begin(), sName1.end()); + std::vector vchValue1(sValue1.begin(), sValue1.end()); + + std::vector vchName2(sName2.begin(), sName2.end()); + std::vector vchValue2(sValue2.begin(), sValue2.end()); + + std::vector vchName3(sName3.begin(), sName3.end()); + std::vector vchValue3(sValue3.begin(), sValue3.end()); + + std::vector vchName4(sName4.begin(), sName4.end()); + std::vector vchValue4(sValue4.begin(), sValue4.end()); + + std::vector vchName7(sName7.begin(), sName7.end()); + + std::vector 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 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()