From 7afebb02f413e8d358333af0eca8502b3f45a9a7 Mon Sep 17 00:00:00 2001 From: Brannon King Date: Mon, 30 Sep 2019 12:13:05 -0600 Subject: [PATCH] Use memory mapped file for claim data allocations Signed-off-by: Anthony Fieroni match previous serialization tweaks added check for RC's data --- src/claimtrie.cpp | 516 +++++++++------------- src/claimtrie.h | 98 ++-- src/claimtrieforks.cpp | 114 ++--- src/init.cpp | 6 +- src/lbry.cpp | 2 + src/lbry.h | 1 + src/miner.cpp | 2 +- src/prefixtrie.cpp | 148 +++++-- src/prefixtrie.h | 22 +- src/rpc/claimtrie.cpp | 4 +- src/test/claimtriebranching_tests.cpp | 13 +- src/test/claimtriecache_tests.cpp | 35 +- src/test/claimtrienormalization_tests.cpp | 11 +- src/test/test_bitcoin.cpp | 10 +- src/validation.cpp | 12 +- 15 files changed, 473 insertions(+), 521 deletions(-) diff --git a/src/claimtrie.cpp b/src/claimtrie.cpp index 5dd1a18c0..9b149e1ff 100644 --- a/src/claimtrie.cpp +++ b/src/claimtrie.cpp @@ -208,15 +208,15 @@ COptional>> CClaimTrieCacheBase::getQueueCac } template <> -queueNameRowType* CClaimTrieCacheBase::getQueueCacheNameRow(const std::string& name, bool createIfNotExists) +queueNameRowType* CClaimTrieCacheBase::getQueueCacheNameRow(const std::string& name, bool createIfNoExists) { - return getQueue(*(base->db), CLAIM_QUEUE_NAME_ROW, name, claimQueueNameCache, createIfNotExists); + return getQueue(*(base->db), CLAIM_QUEUE_NAME_ROW, name, claimQueueNameCache, createIfNoExists); } template <> -queueNameRowType* CClaimTrieCacheBase::getQueueCacheNameRow(const std::string& name, bool createIfNotExists) +queueNameRowType* CClaimTrieCacheBase::getQueueCacheNameRow(const std::string& name, bool createIfNoExists) { - return getQueue(*(base->db), SUPPORT_QUEUE_NAME_ROW, name, supportQueueNameCache, createIfNotExists); + return getQueue(*(base->db), SUPPORT_QUEUE_NAME_ROW, name, supportQueueNameCache, createIfNoExists); } template @@ -246,15 +246,15 @@ COptional CClaimTrieCacheBase::getQueueCacheNameRow(cons } template <> -expirationQueueRowType* CClaimTrieCacheBase::getExpirationQueueCacheRow(int nHeight, bool createIfNotExists) +expirationQueueRowType* CClaimTrieCacheBase::getExpirationQueueCacheRow(int nHeight, bool createIfNoExists) { - return getQueue(*(base->db), CLAIM_EXP_QUEUE_ROW, nHeight, expirationQueueCache, createIfNotExists); + return getQueue(*(base->db), CLAIM_EXP_QUEUE_ROW, nHeight, expirationQueueCache, createIfNoExists); } template <> -expirationQueueRowType* CClaimTrieCacheBase::getExpirationQueueCacheRow(int nHeight, bool createIfNotExists) +expirationQueueRowType* CClaimTrieCacheBase::getExpirationQueueCacheRow(int nHeight, bool createIfNoExists) { - return getQueue(*(base->db), SUPPORT_EXP_QUEUE_ROW, nHeight, supportExpirationQueueCache, createIfNotExists); + return getQueue(*(base->db), SUPPORT_EXP_QUEUE_ROW, nHeight, supportExpirationQueueCache, createIfNoExists); } template @@ -266,13 +266,8 @@ expirationQueueRowType* CClaimTrieCacheBase::getExpirationQueueCacheRow(int, boo bool CClaimTrieCacheBase::haveClaim(const std::string& name, const COutPoint& outPoint) const { - auto it = nodesToAddOrUpdate.find(name); - if (it && it->haveClaim(outPoint)) - return true; - if (it || nodesToDelete.count(name)) - return false; - CClaimTrieData data; - return base->find(name, data) && data.haveClaim(outPoint); + auto it = find(name); + return it && it->haveClaim(outPoint); } bool CClaimTrieCacheBase::haveSupport(const std::string& name, const COutPoint& outPoint) const @@ -325,70 +320,39 @@ bool CClaimTrieCacheBase::haveSupportInQueue(const std::string& name, const COut return haveInQueue(name, outPoint, nValidAtHeight); } -void CClaimTrie::recurseNodes(const std::string& name, const CClaimTrieDataNode& current, std::function function) const { - CClaimTrieData data; - find(name, data); - - data.hash = current.hash; - data.flags |= current.children.empty() ? 0 : CClaimTrieDataFlags::POTENTIAL_CHILDREN; - function(name, data, current.children); - - for (auto& child: current.children) { - CClaimTrieDataNode node; - auto childName = name + child; - if (find(childName, node)) - recurseNodes(childName, node, function); - } -} - std::size_t CClaimTrie::getTotalNamesInTrie() const { std::size_t count = 0; - CClaimTrieDataNode node; - if (find({}, node)) - recurseNodes({}, node, [&count](const std::string &name, const CClaimTrieData &data, const std::vector& children) { - count += !data.empty(); - }); + for (auto it = begin(); it != end(); ++it) + if (!it->empty()) ++count; return count; } std::size_t CClaimTrie::getTotalClaimsInTrie() const { std::size_t count = 0; - CClaimTrieDataNode node; - if (find({}, node)) - recurseNodes({}, node, [&count] - (const std::string &name, const CClaimTrieData &data, const std::vector& children) { - count += data.claims.size(); - }); + for (auto it = begin(); it != end(); ++it) + count += it->claims.size(); return count; } CAmount CClaimTrie::getTotalValueOfClaimsInTrie(bool fControllingOnly) const { CAmount value_in_subtrie = 0; - CClaimTrieDataNode node; - if (find({}, node)) - recurseNodes({}, node, [&value_in_subtrie, fControllingOnly] - (const std::string &name, const CClaimTrieData &data, const std::vector& children) { - for (const auto &claim : data.claims) { - value_in_subtrie += claim.nAmount; - if (fControllingOnly) - break; - } - }); + for (auto it = begin(); it != end(); ++it) { + for (auto& claim : it->claims) { + value_in_subtrie += claim.nAmount; + if (fControllingOnly) + break; + } + } return value_in_subtrie; } bool CClaimTrieCacheBase::getInfoForName(const std::string& name, CClaimValue& claim) const { - auto it = nodesToAddOrUpdate.find(name); - if (it && it->getBestClaim(claim)) - return true; - if (it || nodesToDelete.count(name)) - return false; - CClaimTrieData claims; - return base->find(name, claims) && claims.getBestClaim(claim); + auto it = find(name); + return it && it->getBestClaim(claim); } template @@ -410,15 +374,9 @@ CClaimSupportToName CClaimTrieCacheBase::getClaimsForName(const std::string& nam auto supports = getSupportsForName(name); insertRowsFromQueue(supports, name); - if (auto it = nodesToAddOrUpdate.find(name)) { + if (auto it = find(name)) { claims = it->claims; nLastTakeoverHeight = it->nHeightOfLastTakeover; - } else if (!nodesToDelete.count(name)) { - CClaimTrieData data; - if (base->find(name, data)) { - claims = data.claims; - nLastTakeoverHeight = data.nHeightOfLastTakeover; - } } insertRowsFromQueue(claims, name); @@ -453,94 +411,79 @@ void completeHash(uint256& partialHash, const std::string& key, std::size_t to) .Finalize(partialHash.begin()); } -bool CClaimTrie::checkConsistency(const uint256& rootHash) const +template +using iCbType = std::function; + +template +uint256 recursiveMerkleHash(TIterator& it, const iCbType& process) { - CClaimTrieDataNode node; - if (!find({}, node) || node.hash != rootHash) { - if (rootHash == one) - return true; - - return error("Mismatched root claim trie hashes. This may happen when there is not a clean process shutdown. Please run with -reindex."); + std::vector vchToHash; + const auto pos = it.key().size(); + for (auto& child : it.children()) { + process(child); + auto& key = child.key(); + auto hash = child->hash; + completeHash(hash, key, pos); + vchToHash.push_back(key[pos]); + vchToHash.insert(vchToHash.end(), hash.begin(), hash.end()); } - bool success = true; - recurseNodes({}, node, [&success, this](const std::string &name, const CClaimTrieData &data, const std::vector& children) { - if (!success) return; - - std::vector vchToHash; - const auto pos = name.size(); - for (auto &child : children) { - auto key = name + child; - CClaimTrieDataNode node; - success &= find(key, node); - auto hash = node.hash; - completeHash(hash, key, pos); - vchToHash.push_back(key[pos]); - vchToHash.insert(vchToHash.end(), hash.begin(), hash.end()); - } - - CClaimValue claim; - if (data.getBestClaim(claim)) { - uint256 valueHash = getValueHash(claim.outPoint, data.nHeightOfLastTakeover); - vchToHash.insert(vchToHash.end(), valueHash.begin(), valueHash.end()); - } else { - success &= !children.empty(); // we disallow leaf nodes without claims - } - success &= data.hash == Hash(vchToHash.begin(), vchToHash.end()); - }); - - return success; -} - -std::vector> CClaimTrie::nodes(const std::string &key) const { - std::vector> ret; - CClaimTrieDataNode node; - - if (!find({}, node)) - return ret; - - ret.emplace_back(std::string{}, node); - - std::string partialKey = key; - - while (!node.children.empty()) { - // auto it = node.children.lower_bound(partialKey); // for using a std::map - auto it = std::lower_bound(node.children.begin(), node.children.end(), partialKey); - if (it != node.children.end() && *it == partialKey) { - // we're completely done - if (find(key, node)) - ret.emplace_back(key, node); - break; - } - if (it != node.children.begin()) --it; - const auto count = match(partialKey, *it); - - if (count != it->size()) break; - if (count == partialKey.size()) break; - partialKey = partialKey.substr(count); - auto frontKey = key.substr(0, key.size() - partialKey.size()); - if (find(frontKey, node)) - ret.emplace_back(frontKey, node); - else break; + CClaimValue claim; + if (it->getBestClaim(claim)) { + uint256 valueHash = getValueHash(claim.outPoint, it->nHeightOfLastTakeover); + vchToHash.insert(vchToHash.end(), valueHash.begin(), valueHash.end()); + } else if (!it.hasChildren()) { + return {}; } - return ret; + return Hash(vchToHash.begin(), vchToHash.end()); } -bool CClaimTrie::contains(const std::string &key) const { - return db->Exists(std::make_pair(TRIE_NODE_CHILDREN, key)); +bool CClaimTrieCacheBase::recursiveCheckConsistency(CClaimTrie::const_iterator& it, std::string& failed) const +{ + struct CRecursiveBreak {}; + using iterator = CClaimTrie::const_iterator; + iCbType process = [&failed, &process](iterator& it) { + if (it->hash.IsNull() || it->hash != recursiveMerkleHash(it, process)) { + failed = it.key(); + throw CRecursiveBreak(); + } + }; + + try { + process(it); + } catch (const CRecursiveBreak&) { + return false; + } + return true; } -bool CClaimTrie::empty() const { - return !contains({}); -} +bool CClaimTrieCacheBase::checkConsistency() const +{ + if (base->empty()) + return true; -bool CClaimTrie::find(const std::string& key, CClaimTrieDataNode &node) const { - return db->Read(std::make_pair(TRIE_NODE_CHILDREN, key), node); -} - -bool CClaimTrie::find(const std::string& key, CClaimTrieData &data) const { - return db->Read(std::make_pair(TRIE_NODE_CLAIMS, key), data); + auto it = base->cbegin(); + std::string failed; + auto consistent = recursiveCheckConsistency(it, failed); + if (!consistent) { + LogPrintf("\nPrinting base tree from its parent:\n"); + auto basePath = base->nodes(failed); + if (basePath.size() > 1) basePath.pop_back(); + dumpToLog(basePath.back(), false); + auto cachePath = nodesToAddOrUpdate.nodes(failed); + if (!cachePath.empty()) { + LogPrintf("\nPrinting %s's parent from cache:\n", failed); + if (cachePath.size() > 1) cachePath.pop_back(); + dumpToLog(cachePath.back(), false); + } + if (!nodesToDelete.empty()) { + std::string joined; + for (const auto &piece : nodesToDelete) joined += ", " + piece; + LogPrintf("Nodes to be deleted: %s\n", joined.substr(2)); + } + } + return consistent; } template @@ -579,24 +522,22 @@ bool CClaimTrieCacheBase::flush() getMerkleHash(); - for (auto it = nodesToAddOrUpdate.begin(); it != nodesToAddOrUpdate.end(); ++it) { - bool removed = forDeleteFromBase.erase(it.key()); - if (it->flags & CClaimTrieDataFlags::HASH_DIRTY) { - CClaimTrieDataNode node; - node.hash = it->hash; - for (auto &child: it.children()) // ordering here is important - node.children.push_back(child.key().substr(it.key().size())); - - batch.Write(std::make_pair(TRIE_NODE_CHILDREN, it.key()), node); - - if (removed || (it->flags & CClaimTrieDataFlags::CLAIMS_DIRTY)) - batch.Write(std::make_pair(TRIE_NODE_CLAIMS, it.key()), it.data()); - } + for (const auto& nodeName : nodesToDelete) { + if (nodesToAddOrUpdate.contains(nodeName)) + continue; + auto nodes = base->nodes(nodeName); + base->erase(nodeName); + for (auto& node : nodes) + if (!node) + batch.Erase(std::make_pair(TRIE_NODE, node.key())); } - for (auto& name: forDeleteFromBase) { - batch.Erase(std::make_pair(TRIE_NODE_CHILDREN, name)); - batch.Erase(std::make_pair(TRIE_NODE_CLAIMS, name)); + for (auto it = nodesToAddOrUpdate.begin(); it != nodesToAddOrUpdate.end(); ++it) { + auto old = base->find(it.key()); + if (!old || old.data() != it.data()) { + base->copy(it); + batch.Write(std::make_pair(TRIE_NODE, it.key()), it.data()); + } } BatchWriteQueue(batch, SUPPORT, supportCache); @@ -620,32 +561,70 @@ bool CClaimTrieCacheBase::flush() return ret; } -bool CClaimTrieCacheBase::validateTrieConsistency(const CBlockIndex* tip) +bool CClaimTrieCacheBase::ReadFromDisk(const CBlockIndex* tip) { - if (!tip || tip->nHeight < 1) - return true; + LogPrintf("Loading the claim trie from disk...\n"); + + base->nNextHeight = nNextHeight = tip ? tip->nHeight + 1 : 0; + + if (tip && base->db->Exists(std::make_pair(TRIE_NODE_CHILDREN, std::string()))) { + LogPrintf("The claim trie database contains deprecated data and will need to be rebuilt.\n"); + return false; + } + + clear(); + base->clear(); + boost::scoped_ptr pcursor(base->db->NewIterator()); + + for (pcursor->SeekToFirst(); pcursor->Valid(); pcursor->Next()) { + std::pair key; + if (!pcursor->GetKey(key) || key.first != TRIE_NODE) + continue; + + CClaimTrieData data; + if (pcursor->GetValue(data)) { + if (data.empty()) { + // we have a situation where our old trie had many empty nodes + // we don't want to automatically throw those all into our prefix trie + // we'll run a second pass to clean them up + continue; + } + + // nEffectiveAmount isn't serialized but it needs to be initialized (as done in reorderClaims): + auto supports = getSupportsForName(key.second); + data.reorderClaims(supports); + base->insert(key.second, std::move(data)); + } else { + return error("%s(): error reading claim trie from disk", __func__); + } + } + + for (pcursor->SeekToFirst(); pcursor->Valid(); pcursor->Next()) { + std::pair key; + if (!pcursor->GetKey(key) || key.first != TRIE_NODE) + continue; + auto hit = base->find(key.second); + if (hit) { + CClaimTrieData data; + if (pcursor->GetValue(data)) + hit->hash = data.hash; + } + else { + base->db->Erase(key); // this uses a lot of memory and it's 1-time upgrade from 12.4 so we aren't going to batch it + } + } LogPrintf("Checking claim trie consistency... "); - if (base->checkConsistency(tip->hashClaimTrie)) { + if (checkConsistency()) { LogPrintf("consistent\n"); + if (tip && tip->hashClaimTrie != getMerkleHash()) + return error("%s(): hashes don't match when reading claimtrie from disk", __func__); return true; } LogPrintf("inconsistent!\n"); return false; } -bool CClaimTrieCacheBase::ReadFromDisk(const CBlockIndex* tip) -{ - base->nNextHeight = nNextHeight = tip ? tip->nHeight + 1 : 0; - clear(); - - if (tip && base->db->Exists(std::make_pair(TRIE_NODE, std::string()))) { - LogPrintf("The claim trie database contains deprecated data and will need to be rebuilt\n"); - return false; - } - return validateTrieConsistency(tip); -} - CClaimTrieCacheBase::CClaimTrieCacheBase(CClaimTrie* base) : base(base) { assert(base); @@ -657,92 +636,65 @@ int CClaimTrieCacheBase::expirationTime() const return Params().GetConsensus().nOriginalClaimExpirationTime; } -uint256 CClaimTrieCacheBase::recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it) +uint256 CClaimTrieCacheBase::recursiveComputeMerkleHash(CClaimTrie::iterator& it) { - if (!it->hash.IsNull()) - return it->hash; - - std::vector vchToHash; - const auto pos = it.key().size(); - for (auto& child : it.children()) { - auto hash = recursiveComputeMerkleHash(child); - auto& key = child.key(); - completeHash(hash, key, pos); - vchToHash.push_back(key[pos]); - vchToHash.insert(vchToHash.end(), hash.begin(), hash.end()); - } - - CClaimValue claim; - if (it->getBestClaim(claim)) { - uint256 valueHash = getValueHash(claim.outPoint, it->nHeightOfLastTakeover); - vchToHash.insert(vchToHash.end(), valueHash.begin(), valueHash.end()); - } - - return it->hash = Hash(vchToHash.begin(), vchToHash.end()); + using iterator = CClaimTrie::iterator; + iCbType process = [&process](iterator& it) { + if (it->hash.IsNull()) + it->hash = recursiveMerkleHash(it, process); + assert(!it->hash.IsNull()); + }; + process(it); + return it->hash; } uint256 CClaimTrieCacheBase::getMerkleHash() { - if (auto it = nodesToAddOrUpdate.begin()) - return recursiveComputeMerkleHash(it); - if (nodesToDelete.empty() && nodesAlreadyCached.empty()) { - CClaimTrieDataNode node; - if (base->find({}, node)) - return node.hash; // it may be valuable to have base cache its current root hash - } - return one; // we have no data or we deleted everything + auto it = nodesToAddOrUpdate.begin(); + if (!it && nodesToDelete.empty()) + it = base->begin(); + return !it ? one : recursiveComputeMerkleHash(it); } -CClaimPrefixTrie::const_iterator CClaimTrieCacheBase::begin() const +CClaimTrie::const_iterator CClaimTrieCacheBase::find(const std::string& name) const { - return nodesToAddOrUpdate.begin(); -} - -CClaimPrefixTrie::const_iterator CClaimTrieCacheBase::end() const -{ - return nodesToAddOrUpdate.end(); + auto it = nodesToAddOrUpdate.find(name); + if (it || nodesToDelete.count(name)) + return it; + return base->find(name); } bool CClaimTrieCacheBase::empty() const { - return nodesToAddOrUpdate.empty(); + return nodesToAddOrUpdate.empty(); // only used with the dump method, and we don't want to dump base } -CClaimPrefixTrie::iterator CClaimTrieCacheBase::cacheData(const std::string& name, bool create) +CClaimTrie::iterator CClaimTrieCacheBase::cacheData(const std::string& name, bool create) { + // get data from the cache. if no data, create empty one + const auto insert = [this](CClaimTrie::iterator& it) { + auto& key = it.key(); + // we only ever cache nodes once per cache instance + if (!nodesAlreadyCached.count(key)) { + // do not insert nodes that are already present + nodesAlreadyCached.insert(key); + nodesToAddOrUpdate.insert(key, it.data()); + } + }; + // we need all parent nodes and their one level deep children // to calculate merkle hash auto nodes = base->nodes(name); for (auto& node: nodes) { - if (nodesAlreadyCached.insert(node.first).second) { - // do not insert nodes that are already present - CClaimTrieData data; - base->find(node.first, data); - data.hash = node.second.hash; - data.flags = node.second.children.empty() ? 0 : CClaimTrieDataFlags::POTENTIAL_CHILDREN; - nodesToAddOrUpdate.insert(node.first, data); - } - for (auto& child : node.second.children) { - auto childKey = node.first + child; - if (nodesAlreadyCached.insert(childKey).second) { - CClaimTrieData childData; - if (!base->find(childKey, childData)) - childData = {}; - CClaimTrieDataNode childNode; - if (base->find(childKey, childNode)) { - childData.hash = childNode.hash; - childData.flags = childNode.children.empty() ? 0 : CClaimTrieDataFlags::POTENTIAL_CHILDREN; - } - nodesToAddOrUpdate.insert(childKey, childData); - } - } + for (auto& child : node.children()) + if (!nodesAlreadyCached.count(child.key())) + nodesToAddOrUpdate.copy(child); + insert(node); } auto it = nodesToAddOrUpdate.find(name); if (!it && create) { it = nodesToAddOrUpdate.insert(name, CClaimTrieData{}); - // if (it.hasChildren()) any children should be in the trie (not base alone) - // it->flags |= CClaimTrieDataFlags::POTENTIAL_CHILDREN; confirmTakeoverWorkaroundNeeded(name); } @@ -763,11 +715,10 @@ bool CClaimTrieCacheBase::getLastTakeoverForName(const std::string& name, uint16 std::tie(claimId, takeoverHeight) = cit->second; return true; } - CClaimTrieData data; - if (base->find(name, data)) { - takeoverHeight = data.nHeightOfLastTakeover; + if (auto it = base->find(name)) { + takeoverHeight = it->nHeightOfLastTakeover; CClaimValue claim; - if (data.getBestClaim(claim)) { + if (it->getBestClaim(claim)) { claimId = claim.claimId; return true; } @@ -777,12 +728,8 @@ bool CClaimTrieCacheBase::getLastTakeoverForName(const std::string& name, uint16 void CClaimTrieCacheBase::markAsDirty(const std::string& name, bool fCheckTakeover) { - for (auto& node : nodesToAddOrUpdate.nodes(name)) { - node->flags |= CClaimTrieDataFlags::HASH_DIRTY; + for (auto& node : nodesToAddOrUpdate.nodes(name)) node->hash.SetNull(); - if (node.key() == name) - node->flags |= CClaimTrieDataFlags::CLAIMS_DIRTY; - } if (fCheckTakeover) namesToCheckForTakeover.insert(name); @@ -816,9 +763,6 @@ bool CClaimTrieCacheBase::removeClaimFromTrie(const std::string& name, const COu for (auto& child: it.children()) cacheData(child.key(), false); - for (auto& node : nodesToAddOrUpdate.nodes(name)) - forDeleteFromBase.emplace(node.key()); - nodesToAddOrUpdate.erase(name); nodesToDelete.insert(name); @@ -1055,13 +999,11 @@ bool CClaimTrieCacheBase::removeSupportFromMap(const std::string& name, const CO return false; } -void CClaimTrieCacheBase::dumpToLog(CClaimPrefixTrie::const_iterator it, bool diffFromBase) const +void CClaimTrieCacheBase::dumpToLog(CClaimTrie::const_iterator it, bool diffFromBase) const { - if (!it) return; - if (diffFromBase) { - CClaimTrieDataNode node; - if (base->find(it.key(), node) && node.hash == it->hash) + auto hit = base->find(it.key()); + if (hit && hit->hash == it->hash) return; } @@ -1375,12 +1317,8 @@ int CClaimTrieCacheBase::getNumBlocksOfContinuousOwnership(const std::string& na that->removalWorkaround.erase(hit); return 0; } - if (auto it = nodesToAddOrUpdate.find(name)) - return it->empty() ? 0 : nNextHeight - it->nHeightOfLastTakeover; - CClaimTrieData data; - if (base->find(name, data) && !data.empty()) - return nNextHeight - data.nHeightOfLastTakeover; - return 0; + auto it = nodesToAddOrUpdate.find(name); + return (it || (it = base->find(name))) && !it->empty() ? nNextHeight - it->nHeightOfLastTakeover : 0; } int CClaimTrieCacheBase::getDelayForName(const std::string& name) const @@ -1407,23 +1345,22 @@ std::string CClaimTrieCacheBase::adjustNameForValidHeight(const std::string& nam bool CClaimTrieCacheBase::clear() { - forDeleteFromBase.clear(); - nodesToAddOrUpdate.clear(); - claimsToAddToByIdIndex.clear(); supportCache.clear(); nodesToDelete.clear(); - claimsToDeleteFromByIdIndex.clear(); takeoverCache.clear(); claimQueueCache.clear(); supportQueueCache.clear(); + removalWorkaround.clear(); + nodesToAddOrUpdate.clear(); nodesAlreadyCached.clear(); takeoverWorkaround.clear(); - removalWorkaround.clear(); claimQueueNameCache.clear(); expirationQueueCache.clear(); supportQueueNameCache.clear(); + claimsToAddToByIdIndex.clear(); namesToCheckForTakeover.clear(); supportExpirationQueueCache.clear(); + claimsToDeleteFromByIdIndex.clear(); return true; } @@ -1433,7 +1370,7 @@ bool CClaimTrieCacheBase::getProofForName(const std::string& name, CClaimTriePro cacheData(name, false); getMerkleHash(); proof = CClaimTrieProof(); - for (auto& it : static_cast(nodesToAddOrUpdate).nodes(name)) { + for (auto& it : static_cast(nodesToAddOrUpdate).nodes(name)) { CClaimValue claim; const auto& key = it.key(); bool fNodeHasValue = it->getBestClaim(claim); @@ -1449,7 +1386,7 @@ bool CClaimTrieCacheBase::getProofForName(const std::string& name, CClaimTriePro for (auto i = pos; i + 1 < childKey.size(); ++i) { children.emplace_back(childKey[i], uint256{}); proof.nodes.emplace_back(children, fNodeHasValue, valueHash); - children.clear(); // move promises to leave it in a valid state only + children.clear(); valueHash.SetNull(); fNodeHasValue = false; } @@ -1473,33 +1410,22 @@ bool CClaimTrieCacheBase::getProofForName(const std::string& name, CClaimTriePro return true; } -void CClaimTrieCacheBase::recurseNodes(const std::string &name, - std::function function) const { - - std::function baseFunction = [this, &function] - (const std::string& name, const CClaimTrieData& data, const std::vector&) { - if (nodesToDelete.find(name) == nodesToDelete.end()) - function(name, data); - }; - - if (empty()) { - CClaimTrieDataNode node; - if (base->find(name, node)) - base->recurseNodes(name, node, baseFunction); +void CClaimTrieCacheBase::iterate(std::function callback) const +{ + if (nodesToAddOrUpdate.empty()) { + for (auto it = base->cbegin(); it != base->cend(); ++it) + if (!nodesToDelete.count(it.key())) + callback(it.key(), it.data()); + return; } - else { - for (auto it = begin(); it != end(); ++it) { - function(it.key(), it.data()); - if ((it->flags & CClaimTrieDataFlags::POTENTIAL_CHILDREN) && !it.hasChildren()) { - CClaimTrieDataNode node; - if (base->find(it.key(), node)) - for (auto& partialKey: node.children) { - auto childKey = it.key() + partialKey; - CClaimTrieDataNode childNode; - if (base->find(childKey, childNode)) - base->recurseNodes(childKey, childNode, baseFunction); - } - } - } + for (auto it = nodesToAddOrUpdate.begin(); it != nodesToAddOrUpdate.end(); ++it) { + callback(it.key(), it.data()); + if (it.hasChildren() || nodesToDelete.count(it.key())) + continue; + auto children = base->find(it.key()).children(); + for (auto& child : children) + for (; child; ++child) + if (!nodesToDelete.count(child.key())) + callback(child.key(), child.data()); } } diff --git a/src/claimtrie.h b/src/claimtrie.h index e76a78cf1..91edb893f 100644 --- a/src/claimtrie.h +++ b/src/claimtrie.h @@ -18,9 +18,8 @@ #include // leveldb keys -#define TRIE_NODE 'n' // deprecated +#define TRIE_NODE 'n' #define TRIE_NODE_CHILDREN 'b' -#define TRIE_NODE_CLAIMS 'c' #define CLAIM_BY_ID 'i' #define CLAIM_QUEUE_ROW 'r' #define CLAIM_QUEUE_NAME_ROW 'm' @@ -63,7 +62,6 @@ struct CClaimValue READWRITE(nAmount); READWRITE(nHeight); READWRITE(nValidAtHeight); - READWRITE(nEffectiveAmount); } bool operator<(const CClaimValue& other) const @@ -136,21 +134,12 @@ struct CSupportValue typedef std::vector claimEntryType; typedef std::vector supportEntryType; -enum CClaimTrieDataFlags: uint32_t { - HASH_DIRTY = 1U, - CLAIMS_DIRTY = 2U, - POTENTIAL_CHILDREN = 4U, // existing on disk -}; - struct CClaimTrieData { + uint256 hash; claimEntryType claims; int nHeightOfLastTakeover = 0; - // non-serialized data: - uint32_t flags = 0; - uint256 hash; - CClaimTrieData() = default; CClaimTrieData(CClaimTrieData&&) = default; CClaimTrieData(const CClaimTrieData&) = default; @@ -168,13 +157,25 @@ struct CClaimTrieData template inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(hash); + + if (ser_action.ForRead()) { + if (s.eof()) { + claims.clear(); + nHeightOfLastTakeover = 0; + return; + } + } + else if (claims.empty()) + return; + READWRITE(claims); READWRITE(nHeightOfLastTakeover); } bool operator==(const CClaimTrieData& other) const { - return nHeightOfLastTakeover == other.nHeightOfLastTakeover && claims == other.claims; + return hash == other.hash && nHeightOfLastTakeover == other.nHeightOfLastTakeover && claims == other.claims; } bool operator!=(const CClaimTrieData& other) const @@ -188,28 +189,6 @@ struct CClaimTrieData } }; -struct CClaimTrieDataNode { - uint256 hash; - // we're using a vector to avoid RAM thrashing and for faster serialization ops. - // We're assuming its data is inserted in order and never modified. - std::vector children; - - CClaimTrieDataNode() = default; - CClaimTrieDataNode(CClaimTrieDataNode&&) = default; - CClaimTrieDataNode(const CClaimTrieDataNode&) = default; - CClaimTrieDataNode& operator=(CClaimTrieDataNode&&) = default; - CClaimTrieDataNode& operator=(const CClaimTrieDataNode& d) = default; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(hash); - READWRITE(children); - } -}; - struct COutPointHeightType { COutPoint outPoint; @@ -364,7 +343,7 @@ struct CClaimSupportToName const std::vector unmatchedSupports; }; -class CClaimTrie +class CClaimTrie : public CPrefixTrie { public: CClaimTrie() = default; @@ -387,23 +366,12 @@ public: std::size_t getTotalNamesInTrie() const; std::size_t getTotalClaimsInTrie() const; - virtual bool checkConsistency(const uint256& rootHash) const; CAmount getTotalValueOfClaimsInTrie(bool fControllingOnly) const; - bool contains(const std::string& key) const; - bool empty() const; - bool find(const std::string& key, CClaimTrieDataNode& node) const; - bool find(const std::string& key, CClaimTrieData& claims) const; - - std::vector> nodes(const std::string& key) const; - protected: int nNextHeight = 0; int nProportionalDelayFactor = 0; std::unique_ptr db; - - using recurseNodesCB = void(const std::string&, const CClaimTrieData&, const std::vector&); - void recurseNodes(const std::string& name, const CClaimTrieDataNode& current, std::function function) const; }; struct CClaimTrieProofNode @@ -508,8 +476,6 @@ typedef std::map expirationQueueType; typedef std::set claimIndexClaimListType; typedef std::vector claimIndexElementListType; -typedef CPrefixTrie CClaimPrefixTrie; - class CClaimTrieCacheBase { public: @@ -520,6 +486,7 @@ public: bool flush(); bool empty() const; + bool checkConsistency() const; bool ReadFromDisk(const CBlockIndex* tip); bool haveClaim(const std::string& name, const COutPoint& outPoint) const; @@ -560,21 +527,20 @@ public: virtual CClaimSupportToName getClaimsForName(const std::string& name) const; - CClaimPrefixTrie::const_iterator begin() const; - CClaimPrefixTrie::const_iterator end() const; + CClaimTrie::const_iterator find(const std::string& name) const; + void iterate(std::function callback) const; - void dumpToLog(CClaimPrefixTrie::const_iterator it, bool diffFromBase = true) const; + void dumpToLog(CClaimTrie::const_iterator it, bool diffFromBase = true) const; virtual std::string adjustNameForValidHeight(const std::string& name, int validHeight) const; - void recurseNodes(const std::string& name, std::function function) const; - - protected: +protected: CClaimTrie* base; - CClaimPrefixTrie nodesToAddOrUpdate; // nodes pulled in from base (and possibly modified thereafter), written to base on flush + CClaimTrie nodesToAddOrUpdate; // nodes pulled in from base (and possibly modified thereafter), written to base on flush std::unordered_set nodesAlreadyCached; // set of nodes already pulled into cache from base std::unordered_set namesToCheckForTakeover; // takeover numbers are updated on increment - virtual uint256 recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it); + virtual uint256 recursiveComputeMerkleHash(CClaimTrie::iterator& it); + virtual bool recursiveCheckConsistency(CClaimTrie::const_iterator& it, std::string& failed) const; virtual bool insertClaimIntoTrie(const std::string& name, const CClaimValue& claim, bool fCheckTakeover); virtual bool removeClaimFromTrie(const std::string& name, const COutPoint& outPoint, CClaimValue& claim, bool fCheckTakeover); @@ -587,7 +553,7 @@ public: int getDelayForName(const std::string& name) const; virtual int getDelayForName(const std::string& name, const uint160& claimId) const; - CClaimPrefixTrie::iterator cacheData(const std::string& name, bool create = true); + CClaimTrie::iterator cacheData(const std::string& name, bool create = true); bool getLastTakeoverForName(const std::string& name, uint160& claimId, int& takeoverHeight) const; @@ -618,7 +584,6 @@ private: std::unordered_set nodesToDelete; // to be removed from base (and disk) on flush std::unordered_map takeoverWorkaround; std::unordered_set removalWorkaround; - std::unordered_set forDeleteFromBase; bool shouldUseTakeoverWorkaround(const std::string& key) const; void addTakeoverWorkaroundPotential(const std::string& key); @@ -630,8 +595,6 @@ private: bool removeSupport(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight, bool fCheckTakeover); bool removeClaim(const std::string& name, const COutPoint& outPoint, int nHeight, int& nValidAtHeight, bool fCheckTakeover); - bool validateTrieConsistency(const CBlockIndex* tip); - template void insertRowsFromQueue(std::vector& result, const std::string& name) const; @@ -781,20 +744,13 @@ public: bool allowSupportMetadata() const; protected: - uint256 recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it) override; + uint256 recursiveComputeMerkleHash(CClaimTrie::iterator& it) override; + bool recursiveCheckConsistency(CClaimTrie::const_iterator& it, std::string& failed) const override; private: void copyAllBaseToCache(); }; -class CClaimTrieHashFork : public CClaimTrie -{ -public: - using CClaimTrie::CClaimTrie; -protected: - bool checkConsistency(const uint256& rootHash) const override; -}; - typedef CClaimTrieCacheHashFork CClaimTrieCache; #endif // BITCOIN_CLAIMTRIE_H diff --git a/src/claimtrieforks.cpp b/src/claimtrieforks.cpp index 591d2a75a..939da1543 100644 --- a/src/claimtrieforks.cpp +++ b/src/claimtrieforks.cpp @@ -4,8 +4,6 @@ #include #include -#include -#include #include #include #include @@ -174,17 +172,12 @@ bool CClaimTrieCacheNormalizationFork::normalizeAllNamesInTrieIfNecessary(insert // run the one-time upgrade of all names that need to change // it modifies the (cache) trie as it goes, so we need to grab everything to be modified first - boost::scoped_ptr pcursor(base->db->NewIterator()); - for (pcursor->SeekToFirst(); pcursor->Valid(); pcursor->Next()) { - std::pair key; - if (!pcursor->GetKey(key) || key.first != TRIE_NODE_CHILDREN) - continue; - - const auto& name = key.second; - const std::string normalized = normalizeClaimName(name, true); - if (normalized == key.second) + for (auto it = base->cbegin(); it != base->cend(); ++it) { + const std::string normalized = normalizeClaimName(it.key(), true); + if (normalized == it.key()) continue; + auto& name = it.key(); auto supports = getSupportsForName(name); for (auto support : supports) { // if it's already going to expire just skip it @@ -279,23 +272,17 @@ std::vector getClaimHashes(const CClaimTrieData& data) template using iCbType = std::function; -template -using decay = typename std::decay::type; - -template -uint256 recursiveMerkleHash(const CClaimTrieData& data, Vector&& children, const iCbType& childHash) +template +uint256 recursiveBinaryTreeHash(TIterator& it, const iCbType& process) { - static_assert(std::is_same, std::vector>>::value, "Vector should be std vector"); - static_assert(std::is_same::value, "Vector element type should match callback type"); - std::vector childHashes; - for (auto& child : children) - childHashes.emplace_back(childHash(child)); + for (auto& child : it.children()) + childHashes.emplace_back(process(child)); std::vector claimHashes; - if (!data.empty()) - claimHashes = getClaimHashes(data); - else if (children.empty()) + if (!it->empty()) + claimHashes = getClaimHashes(it.data()); + else if (!it.hasChildren()) return {}; auto left = childHashes.empty() ? leafHash : ComputeMerkleRoot(childHashes); @@ -304,53 +291,44 @@ uint256 recursiveMerkleHash(const CClaimTrieData& data, Vector&& children, const return Hash(left.begin(), left.end(), right.begin(), right.end()); } -extern const uint256 one; - -bool CClaimTrieHashFork::checkConsistency(const uint256& rootHash) const -{ - if (nNextHeight < Params().GetConsensus().nAllClaimsInMerkleForkHeight) - return CClaimTrie::checkConsistency(rootHash); - - CClaimTrieDataNode node; - if (!find({}, node) || node.hash != rootHash) { - if (rootHash == one) - return true; - - return error("Mismatched root claim trie hashes. This may happen when there is not a clean process shutdown. Please run with -reindex."); - } - - bool success = true; - recurseNodes({}, node, [&success, this](const std::string& name, const CClaimTrieData& data, const std::vector& children) { - if (!success) return; - - iCbType callback = [&success, &name, this](const std::string& child) -> uint256 { - auto key = name + child; - CClaimTrieDataNode node; - success &= find(key, node); - return node.hash; - }; - - success &= !data.hash.IsNull(); - success &= data.hash == recursiveMerkleHash(data, children, callback); - }); - - return success; -} - -uint256 CClaimTrieCacheHashFork::recursiveComputeMerkleHash(CClaimPrefixTrie::iterator& it) +uint256 CClaimTrieCacheHashFork::recursiveComputeMerkleHash(CClaimTrie::iterator& it) { if (nNextHeight < Params().GetConsensus().nAllClaimsInMerkleForkHeight) return CClaimTrieCacheNormalizationFork::recursiveComputeMerkleHash(it); - using iterator = CClaimPrefixTrie::iterator; + using iterator = CClaimTrie::iterator; iCbType process = [&process](iterator& it) -> uint256 { if (it->hash.IsNull()) - it->hash = recursiveMerkleHash(it.data(), it.children(), process); + it->hash = recursiveBinaryTreeHash(it, process); + assert(!it->hash.IsNull()); return it->hash; }; return process(it); } +bool CClaimTrieCacheHashFork::recursiveCheckConsistency(CClaimTrie::const_iterator& it, std::string& failed) const +{ + if (nNextHeight < Params().GetConsensus().nAllClaimsInMerkleForkHeight) + return CClaimTrieCacheNormalizationFork::recursiveCheckConsistency(it, failed); + + struct CRecursiveBreak {}; + using iterator = CClaimTrie::const_iterator; + iCbType process = [&failed, &process](iterator& it) -> uint256 { + if (it->hash.IsNull() || it->hash != recursiveBinaryTreeHash(it, process)) { + failed = it.key(); + throw CRecursiveBreak(); + } + return it->hash; + }; + + try { + process(it); + } catch (const CRecursiveBreak&) { + return false; + } + return true; +} + std::vector ComputeMerklePath(const std::vector& hashes, uint32_t idx) { uint32_t count = 0; @@ -426,7 +404,7 @@ bool CClaimTrieCacheHashFork::getProofForName(const std::string& name, CClaimTri cacheData(name, false); getMerkleHash(); proof = CClaimTrieProof(); - for (auto& it : static_cast(nodesToAddOrUpdate).nodes(name)) { + for (auto& it : static_cast(nodesToAddOrUpdate).nodes(name)) { std::vector childHashes; uint32_t nextCurrentIdx = 0; for (auto& child : it.children()) { @@ -469,15 +447,12 @@ bool CClaimTrieCacheHashFork::getProofForName(const std::string& name, CClaimTri void CClaimTrieCacheHashFork::copyAllBaseToCache() { - recurseNodes({}, [this](const std::string& name, const CClaimTrieData& data) { - if (nodesAlreadyCached.insert(name).second) - nodesToAddOrUpdate.insert(name, data); - }); + for (auto it = base->cbegin(); it != base->cend(); ++it) + if (nodesAlreadyCached.insert(it.key()).second) + nodesToAddOrUpdate.insert(it.key(), it.data()); - for (auto it = nodesToAddOrUpdate.begin(); it != nodesToAddOrUpdate.end(); ++it) { + for (auto it = nodesToAddOrUpdate.begin(); it != nodesToAddOrUpdate.end(); ++it) it->hash.SetNull(); - it->flags |= CClaimTrieDataFlags::HASH_DIRTY; - } } void CClaimTrieCacheHashFork::initializeIncrement() @@ -500,6 +475,7 @@ bool CClaimTrieCacheHashFork::finalizeDecrement(std::vector= Params().GetConsensus().nAllClaimsInMerkleForkHeight; } diff --git a/src/init.cpp b/src/init.cpp index 4bcd4627a..b9faa1ecd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -398,6 +399,7 @@ void SetupServerArgs() hidden_args.emplace_back("-sysperms"); #endif gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-memfile=", "Use a memory mapped file for the claimtrie allocations (default: use RAM instead)", false, OptionsCategory::OPTIONS); gArgs.AddArg("-addnode=", "Add a node to connect to and attempt to keep the connection open (see the `addnode` RPC command help for more info). This option can be specified multiple times to add multiple nodes.", false, OptionsCategory::CONNECTION); gArgs.AddArg("-banscore=", strprintf("Threshold for disconnecting misbehaving peers (default: %u)", DEFAULT_BANSCORE_THRESHOLD), false, OptionsCategory::CONNECTION); @@ -1441,6 +1443,8 @@ bool AppInitMain() LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for in-memory UTXO set (plus up to %.1fMiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); + g_memfileSize = gArgs.GetArg("-memfile", 0u); + bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { bool fReset = fReindex; @@ -1465,7 +1469,7 @@ bool AppInitMain() int64_t trieCacheMB = gArgs.GetArg("-claimtriecache", nDefaultDbCache); trieCacheMB = std::min(trieCacheMB, nMaxDbCache); trieCacheMB = std::max(trieCacheMB, nMinDbCache); - pclaimTrie = new CClaimTrieHashFork(false, fReindex || fReindexChainState, 32, trieCacheMB); + pclaimTrie = new CClaimTrie(false, fReindex || fReindexChainState, 32, trieCacheMB); if (fReset) { pblocktree->WriteReindexing(true); diff --git a/src/lbry.cpp b/src/lbry.cpp index 6aa94d644..921db7ade 100644 --- a/src/lbry.cpp +++ b/src/lbry.cpp @@ -3,6 +3,8 @@ #include +uint32_t g_memfileSize = 0; + unsigned int CalculateLbryNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params) { if (params.fPowNoRetargeting) diff --git a/src/lbry.h b/src/lbry.h index 601ccc4a3..aa7fc331b 100644 --- a/src/lbry.h +++ b/src/lbry.h @@ -4,6 +4,7 @@ #include #include +extern uint32_t g_memfileSize; unsigned int CalculateLbryNextWorkRequired(const CBlockIndex* pindexLast, int64_t nLastRetargetTime, const Consensus::Params& params); #endif diff --git a/src/miner.cpp b/src/miner.cpp index 633f4618d..8ba1e8fd6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -208,7 +208,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc CValidationState state; if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) { if (!trieCache.empty()) - trieCache.dumpToLog(trieCache.begin()); + trieCache.dumpToLog(trieCache.find({})); throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, FormatStateMessage(state))); } int64_t nTime2 = GetTimeMicros(); diff --git a/src/prefixtrie.cpp b/src/prefixtrie.cpp index 09878db8e..36a88e4c6 100644 --- a/src/prefixtrie.cpp +++ b/src/prefixtrie.cpp @@ -1,6 +1,78 @@ -#include "prefixtrie.h" -#include "claimtrie.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace bip = boost::interprocess; + +typedef bip::basic_managed_mapped_file < + char, + bip::rbtree_best_fit>, + bip::null_index +> managed_mapped_file; + +template +using node_allocator = bip::private_node_allocator; + +static managed_mapped_file::segment_manager* segmentManager() +{ + struct CSharedMemoryFile + { + CSharedMemoryFile() : file(GetDataDir() / "shared.mem") + { + std::remove(file.c_str()); + auto size = (uint64_t)g_memfileSize * 1024ULL * 1024ULL * 1024ULL; + menaged_file.reset(new managed_mapped_file(bip::create_only, file.c_str(), size)); + } + ~CSharedMemoryFile() + { + menaged_file.reset(); + std::remove(file.c_str()); + } + managed_mapped_file::segment_manager* segmentManager() + { + return menaged_file->get_segment_manager(); + } + const fs::path file; + std::unique_ptr menaged_file; + }; + static CSharedMemoryFile shem; + return shem.segmentManager(); +} + +template +static node_allocator& nodeAllocator() +{ + static node_allocator allocator(segmentManager()); + return allocator; +} + +template +static std::shared_ptr nodeAllocate(Args&&... args) +{ + return std::allocate_shared(nodeAllocator(), std::forward(args)...); +} + +template +static std::shared_ptr allocateShared(Args&&... args) +{ + static auto allocate = g_memfileSize ? nodeAllocate : std::make_shared; + try { + return allocate(std::forward(args)...); + } + catch (const bip::bad_alloc&) { + allocate = std::make_shared; // in case we fill up the memfile + LogPrint(BCLog::BENCH, "WARNING: The memfile is full; reverting to the RAM allocator for %s.\n", typeid(T).name()); + return allocate(std::forward(args)...); + } +} template template @@ -18,7 +90,7 @@ typename CPrefixTrie::template Iterator& CPrefixTrie::template Iterator& CPrefixTriechildren.empty()) { auto& children = shared->children; auto it = children.begin(); - stack.emplace_back(Bookmark{name, shared, it, children.end()}); + stack.emplace_back(Bookmark{name, it, children.end()}); auto& postfix = it->first; name.insert(name.end(), postfix.begin(), postfix.end()); node = it->second; @@ -106,18 +178,30 @@ bool CPrefixTrie::Iterator::operator!=(const Iterator& o) template template -typename CPrefixTrie::template Iterator::reference CPrefixTrie::Iterator::operator*() const +typename CPrefixTrie::template Iterator::reference CPrefixTrie::Iterator::operator*() { - assert(!node.expired()); - return TPair(name, node.lock()->data); + return reference{name, data()}; } template template -typename CPrefixTrie::template Iterator::pointer CPrefixTrie::Iterator::operator->() const +typename CPrefixTrie::template Iterator::const_reference CPrefixTrie::Iterator::operator*() const { - assert(!node.expired()); - return &(node.lock()->data); + return const_reference{name, data()}; +} + +template +template +typename CPrefixTrie::template Iterator::pointer CPrefixTrie::Iterator::operator->() +{ + return &(data()); +} + +template +template +typename CPrefixTrie::template Iterator::const_pointer CPrefixTrie::Iterator::operator->() const +{ + return &(data()); } template @@ -129,18 +213,20 @@ const TKey& CPrefixTrie::Iterator::key() const template template -TData& CPrefixTrie::Iterator::data() +typename CPrefixTrie::template Iterator::data_reference CPrefixTrie::Iterator::data() { - assert(!node.expired()); - return node.lock()->data; + auto shared = node.lock(); + assert(shared); + return *(shared->data); } template template const TData& CPrefixTrie::Iterator::data() const { - assert(!node.expired()); - return node.lock()->data; + auto shared = node.lock(); + assert(shared); + return *(shared->data); } template @@ -240,19 +326,20 @@ std::shared_ptr::Node>& CPrefixTrie()).first; + it = children.emplace(key, allocateShared()).first; return it->second; } if (count < it->first.size()) { - const TKey prefix(key.begin(), key.begin() + count); - const TKey postfix(it->first.begin() + count, it->first.end()); + TKey prefix(key.begin(), key.begin() + count); + TKey postfix(it->first.begin() + count, it->first.end()); auto nodes = std::move(it->second); children.erase(it); ++size; - it = children.emplace(prefix, std::make_shared()).first; - it->second->children.emplace(postfix, std::move(nodes)); + it = children.emplace(std::move(prefix), allocateShared()).first; + it->second->children.emplace(std::move(postfix), std::move(nodes)); if (key.size() == count) return it->second; + it->second->data = allocateShared(); } return insert(TKey(key.begin() + count, key.end()), it->second); } @@ -269,12 +356,12 @@ void CPrefixTrie::erase(const TKey& key, std::shared_ptr& nod if (!find(key, node, cb)) return; - nodes.back().second->data = {}; + nodes.back().second->data = allocateShared(); for (; nodes.size() > 1; nodes.pop_back()) { // if we have only one child and no data ourselves, bring them up to our level auto& cNode = nodes.back().second; auto onlyOneChild = cNode->children.size() == 1; - auto noData = cNode->data.empty(); + auto noData = cNode->data->empty(); if (onlyOneChild && noData) { auto child = cNode->children.begin(); auto& prefix = nodes.back().first; @@ -282,7 +369,7 @@ void CPrefixTrie::erase(const TKey& key, std::shared_ptr& nod auto& postfix = child->first; newKey.insert(newKey.end(), postfix.begin(), postfix.end()); auto& pNode = nodes[nodes.size() - 2].second; - pNode->children.emplace(newKey, std::move(child->second)); + pNode->children.emplace(std::move(newKey), std::move(child->second)); pNode->children.erase(prefix); --size; continue; @@ -300,8 +387,9 @@ void CPrefixTrie::erase(const TKey& key, std::shared_ptr& nod } template -CPrefixTrie::CPrefixTrie() : size(0), root(std::make_shared()) +CPrefixTrie::CPrefixTrie() : size(0), root(allocateShared()) { + root->data = allocateShared(); } template @@ -309,7 +397,7 @@ template typename CPrefixTrie::iterator CPrefixTrie::insert(const TKey& key, TDataUni&& data) { auto& node = key.empty() ? root : insert(key, root); - node->data = std::forward(data); + node->data = allocateShared(std::forward(data)); return key.empty() ? begin() : iterator{key, node}; } @@ -333,9 +421,9 @@ typename CPrefixTrie::iterator CPrefixTrie::insert(CPr auto name = it.key(); name.insert(name.end(), key.begin(), key.end()); auto& node = insert(key, shared); - copy = iterator{name, node}; + copy = iterator{std::move(name), node}; } - copy.node.lock()->data = std::forward(data); + copy.node.lock()->data = allocateShared(std::forward(data)); return copy; } @@ -404,7 +492,7 @@ bool CPrefixTrie::erase(const TKey& key) { auto size_was = height(); if (key.empty()) { - root->data = {}; + root->data = allocateShared(); } else { erase(key, root); } @@ -415,7 +503,7 @@ template void CPrefixTrie::clear() { size = 0; - root->data = {}; + root->data = allocateShared(); root->children.clear(); } @@ -428,7 +516,7 @@ bool CPrefixTrie::empty() const template std::size_t CPrefixTrie::height() const { - return size + (root->data.empty() ? 0 : 1); + return size + (root->data->empty() ? 0 : 1); } template diff --git a/src/prefixtrie.h b/src/prefixtrie.h index 11d89060f..42682867a 100644 --- a/src/prefixtrie.h +++ b/src/prefixtrie.h @@ -27,9 +27,9 @@ class CPrefixTrie Node() = default; Node(const Node&) = delete; Node(Node&& o) noexcept = default; - Node& operator=(Node&& o) noexcept = default; + Node& operator=(Node&&) noexcept = default; Node& operator=(const Node&) = delete; - TData data; + std::shared_ptr data; }; using TChildren = decltype(Node::children); @@ -42,15 +42,15 @@ class CPrefixTrie friend class CPrefixTrie; using TKeyRef = std::reference_wrapper; - using TDataRef = std::reference_wrapper; + using TDataRef = std::reference_wrapper::type>; using TPair = std::pair; + using ConstTPair = std::pair; TKey name; std::weak_ptr node; struct Bookmark { TKey name; - std::weak_ptr parent; typename TChildren::iterator it; typename TChildren::iterator end; }; @@ -60,8 +60,11 @@ class CPrefixTrie public: // Iterator traits using value_type = TPair; + using const_pointer = const TData* const; + using const_reference = ConstTPair; + using data_reference = typename std::conditional::type; using pointer = typename std::conditional::type; - using reference = typename std::conditional::type; + using reference = typename std::conditional::type; using difference_type = std::ptrdiff_t; using iterator_category = std::forward_iterator_tag; @@ -89,12 +92,15 @@ class CPrefixTrie bool operator==(const Iterator& o) const; bool operator!=(const Iterator& o) const; - reference operator*() const; - pointer operator->() const; + reference operator*(); + const_reference operator*() const; + + pointer operator->(); + const_pointer operator->() const; const TKey& key() const; - TData& data(); + data_reference data(); const TData& data() const; std::size_t depth() const; diff --git a/src/rpc/claimtrie.cpp b/src/rpc/claimtrie.cpp index c8918ca89..87280e760 100644 --- a/src/rpc/claimtrie.cpp +++ b/src/rpc/claimtrie.cpp @@ -307,7 +307,7 @@ static UniValue getclaimsintrie(const JSONRPCRequest& request) } UniValue ret(UniValue::VARR); - trieCache.recurseNodes({}, [&ret, &trieCache, &coinsCache] (const std::string& name, const CClaimTrieData& data) { + trieCache.iterate([&ret, &trieCache, &coinsCache] (const std::string& name, const CClaimTrieData& data) { if (ShutdownRequested()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Shutdown requested"); @@ -348,7 +348,7 @@ static UniValue getnamesintrie(const JSONRPCRequest& request) } UniValue ret(UniValue::VARR); - trieCache.recurseNodes({}, [&ret](const std::string &name, const CClaimTrieData &data) { + trieCache.iterate([&ret](const std::string &name, const CClaimTrieData &data) { if (!data.empty()) ret.push_back(escapeNonUtf8(name)); if (ShutdownRequested()) diff --git a/src/test/claimtriebranching_tests.cpp b/src/test/claimtriebranching_tests.cpp index 1fb71ac76..9e5487a13 100644 --- a/src/test/claimtriebranching_tests.cpp +++ b/src/test/claimtriebranching_tests.cpp @@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(claim_replace_test) { fixture.Spend(tx1); CMutableTransaction tx2 = fixture.MakeClaim(fixture.GetCoinbase(), "bassfisher", "one", 1); fixture.IncrementBlocks(1); - BOOST_CHECK(pclaimTrie->checkConsistency(fixture.getMerkleHash())); + BOOST_CHECK(fixture.checkConsistency()); BOOST_CHECK(!fixture.is_best_claim("bass", tx1)); BOOST_CHECK(fixture.is_best_claim("bassfisher", tx2)); } @@ -1852,17 +1852,18 @@ BOOST_AUTO_TEST_CASE(update_on_support2_test) CMutableTransaction s2 = fixture.MakeSupport(fixture.GetCoinbase(), tx1, name, 1); fixture.IncrementBlocks(1); - CClaimTrieData node; - BOOST_CHECK(pclaimTrie->find(name, node)); - BOOST_CHECK_EQUAL(node.nHeightOfLastTakeover, height + 1); + auto it = pclaimTrie->find(name); + BOOST_CHECK(it); + BOOST_CHECK_EQUAL(it->nHeightOfLastTakeover, height + 1); fixture.Spend(s1); fixture.Spend(s2); CMutableTransaction u1 = fixture.MakeUpdate(tx1, name, value, ClaimIdHash(tx1.GetHash(), 0), 3); fixture.IncrementBlocks(1); - BOOST_CHECK(pclaimTrie->find(name, node)); - BOOST_CHECK_EQUAL(node.nHeightOfLastTakeover, height + 1); + it = pclaimTrie->find(name); + BOOST_CHECK(it); + BOOST_CHECK_EQUAL(it->nHeightOfLastTakeover, height + 1); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/claimtriecache_tests.cpp b/src/test/claimtriecache_tests.cpp index d92fac18b..3394c4971 100644 --- a/src/test/claimtriecache_tests.cpp +++ b/src/test/claimtriecache_tests.cpp @@ -37,7 +37,7 @@ public: return nodesToAddOrUpdate.height(); } - CClaimPrefixTrie::iterator getCache(const std::string& key) + CClaimTrie::iterator getCache(const std::string& key) { return nodesToAddOrUpdate.find(key); } @@ -108,7 +108,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState.getMerkleHash(), hash2); - BOOST_CHECK(pclaimTrie->checkConsistency(hash2)); + BOOST_CHECK(ntState.checkConsistency()); CClaimTrieCacheTest ntState1(pclaimTrie); ntState1.removeClaimFromTrie(std::string("test"), tx1OutPoint, unused, true); @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState2.getMerkleHash(), hash3); - BOOST_CHECK(pclaimTrie->checkConsistency(hash3)); + BOOST_CHECK(ntState2.checkConsistency()); CClaimTrieCacheTest ntState3(pclaimTrie); ntState3.insertClaimIntoTrie(std::string("test"), CClaimValue(tx1OutPoint, hash160, 50, 100, 200), true); @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) ntState3.flush(); BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState3.getMerkleHash(), hash4); - BOOST_CHECK(pclaimTrie->checkConsistency(hash4)); + BOOST_CHECK(ntState3.checkConsistency()); CClaimTrieCacheTest ntState4(pclaimTrie); ntState4.removeClaimFromTrie(std::string("abab"), tx6OutPoint, unused, true); @@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) ntState4.flush(); BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState4.getMerkleHash(), hash2); - BOOST_CHECK(pclaimTrie->checkConsistency(hash2)); + BOOST_CHECK(ntState4.checkConsistency()); CClaimTrieCacheTest ntState5(pclaimTrie); ntState5.removeClaimFromTrie(std::string("test"), tx3OutPoint, unused, true); @@ -153,7 +153,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) ntState5.flush(); BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState5.getMerkleHash(), hash2); - BOOST_CHECK(pclaimTrie->checkConsistency(hash2)); + BOOST_CHECK(ntState5.checkConsistency()); CClaimTrieCacheTest ntState6(pclaimTrie); ntState6.insertClaimIntoTrie(std::string("test"), CClaimValue(tx3OutPoint, hash160, 50, 101, 201), true); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) ntState6.flush(); BOOST_CHECK(!pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState6.getMerkleHash(), hash2); - BOOST_CHECK(pclaimTrie->checkConsistency(hash2)); + BOOST_CHECK(ntState6.checkConsistency()); CClaimTrieCacheTest ntState7(pclaimTrie); ntState7.removeClaimFromTrie(std::string("test"), tx3OutPoint, unused, true); @@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(merkle_hash_multiple_test) ntState7.flush(); BOOST_CHECK(pclaimTrie->empty()); BOOST_CHECK_EQUAL(ntState7.getMerkleHash(), hash0); - BOOST_CHECK(pclaimTrie->checkConsistency(hash0)); + BOOST_CHECK(ntState7.checkConsistency()); } BOOST_AUTO_TEST_CASE(basic_insertion_info_test) @@ -281,14 +281,12 @@ BOOST_AUTO_TEST_CASE(iteratetrie_test) ctc.insertClaimIntoTrie("test", claimVal, true); BOOST_CHECK(ctc.flush()); - CClaimTrieDataNode node; - BOOST_CHECK(pclaimTrie->find("", node)); - BOOST_CHECK_EQUAL(node.children.size(), 1U); - BOOST_CHECK(pclaimTrie->find("test", node)); - BOOST_CHECK_EQUAL(node.children.size(), 0U); - CClaimTrieData data; - BOOST_CHECK(pclaimTrie->find("test", data)); - BOOST_CHECK_EQUAL(data.claims.size(), 1); + auto hit = pclaimTrie->find(""); + BOOST_CHECK(hit); + BOOST_CHECK_EQUAL(hit.children().size(), 1U); + BOOST_CHECK(hit = pclaimTrie->find("test")); + BOOST_CHECK_EQUAL(hit.children().size(), 0U); + BOOST_CHECK_EQUAL(hit.data().claims.size(), 1); } BOOST_AUTO_TEST_CASE(trie_stays_consistent_test) @@ -305,13 +303,13 @@ BOOST_AUTO_TEST_CASE(trie_stays_consistent_test) BOOST_CHECK(cache.insertClaimIntoTrie(name, value, false)); cache.flush(); - BOOST_CHECK(trie.checkConsistency(cache.getMerkleHash())); + BOOST_CHECK(cache.checkConsistency()); for (auto& name: names) { CClaimValue temp; BOOST_CHECK(cache.removeClaimFromTrie(name, COutPoint(), temp, false)); cache.flush(); - BOOST_CHECK(trie.checkConsistency(cache.getMerkleHash())); + BOOST_CHECK(cache.checkConsistency()); } BOOST_CHECK(trie.empty()); } @@ -384,7 +382,6 @@ BOOST_AUTO_TEST_CASE(verify_basic_serialization) ssData >> cv2; BOOST_CHECK_EQUAL(cv, cv2); - BOOST_CHECK_EQUAL(cv.nEffectiveAmount, cv2.nEffectiveAmount); } BOOST_AUTO_TEST_CASE(claimtrienode_serialize_unserialize) diff --git a/src/test/claimtrienormalization_tests.cpp b/src/test/claimtrienormalization_tests.cpp index b36043ab9..0e6ea70fb 100644 --- a/src/test/claimtrienormalization_tests.cpp +++ b/src/test/claimtrienormalization_tests.cpp @@ -93,8 +93,7 @@ BOOST_AUTO_TEST_CASE(claimtriebranching_normalization) BOOST_CHECK(fixture.is_best_claim("normalizetest", tx1)); BOOST_CHECK(fixture.best_claim_effective_amount_equals("normalizetest", 3)); - CClaimTrieData data; - BOOST_CHECK(!pclaimTrie->find("normalizeTest", data)); + BOOST_CHECK(!pclaimTrie->find("normalizeTest")); // Check equivalence of normalized claim names BOOST_CHECK(fixture.is_best_claim("normalizetest", tx1)); // collapsed tx2 @@ -223,8 +222,7 @@ BOOST_AUTO_TEST_CASE(claimtriecache_normalization) BOOST_CHECK(!trieCache.spendClaim(name_normd, COutPoint(tx2.GetHash(), 0), currentHeight, amelieValidHeight)); BOOST_CHECK(trieCache.spendClaim(name_upper, COutPoint(tx2.GetHash(), 0), currentHeight, amelieValidHeight)); - CClaimTrieData data; - BOOST_CHECK(!pclaimTrie->find(name, data)); + BOOST_CHECK(!pclaimTrie->find(name)); BOOST_CHECK(trieCache.getInfoForName(name, nval1)); BOOST_CHECK(trieCache.addClaim(name, COutPoint(tx1.GetHash(), 0), ClaimIdHash(tx1.GetHash(), 0), CAmount(2), currentHeight + 1)); BOOST_CHECK(trieCache.getInfoForName(name, nval1)); @@ -236,8 +234,9 @@ BOOST_AUTO_TEST_CASE(claimtriecache_normalization) BOOST_CHECK(trieCache.incrementBlock(insertUndo, expireUndo, insertSupportUndo, expireSupportUndo, takeoverHeightUndo)); BOOST_CHECK(trieCache.shouldNormalize()); - CClaimTrieDataNode node; - BOOST_CHECK(!pclaimTrie->find(name, node)); + // we cannot use getXXXForName cause they will normalized name + for (auto it = pclaimTrie->cbegin(); it != pclaimTrie->cend(); ++it) + BOOST_CHECK(it.key() != name); } BOOST_AUTO_TEST_CASE(undo_normalization_does_not_kill_claim_order) diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index d7d52d680..309e09206 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -81,10 +81,10 @@ std::ostream& operator<<(std::ostream& os, const CClaimValue& claim) std::ostream& operator<<(std::ostream& os, const CSupportValue& support) { os << "support(" << support.outPoint.ToString() - << ", " << support.supportedClaimId.ToString() - << ", " << support.nAmount - << ", " << support.nHeight - << ", " << support.nValidAtHeight << ')'; + << ", " << support.supportedClaimId.ToString() + << ", " << support.nAmount + << ", " << support.nHeight + << ", " << support.nValidAtHeight << ')'; return os; } @@ -144,7 +144,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha pblocktree.reset(new CBlockTreeDB(1 << 20, true)); pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); - pclaimTrie = new CClaimTrieHashFork(true, false, 1); + pclaimTrie = new CClaimTrie(true, false, 1); if (!LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } diff --git a/src/validation.cpp b/src/validation.cpp index 687d0b938..95a8a82fb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1562,12 +1562,8 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI assert(trieCache.finalizeDecrement(blockUndo.takeoverHeightUndo)); auto merkleHash = trieCache.getMerkleHash(); if (merkleHash != pindex->pprev->hashClaimTrie) { - for (auto cit = trieCache.begin(); cit != trieCache.end(); ++cit) { - if (cit->claims.size() && cit->nHeightOfLastTakeover <= 0) - LogPrintf("Invalid takeover height discovered in cache for %s\n", cit.key()); - if (cit->hash.IsNull()) - LogPrintf("Invalid hash discovered in cache for %s\n", cit.key()); - } + if (!trieCache.empty()) + trieCache.dumpToLog(trieCache.find({})); LogPrintf("Hash comparison failure at block %d\n", pindex->nHeight); assert(merkleHash == pindex->pprev->hashClaimTrie); } @@ -2045,7 +2041,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (trieCache.getMerkleHash() != block.hashClaimTrie) { if (!trieCache.empty()) // we could run checkConsistency here, but it would take a while - trieCache.dumpToLog(trieCache.begin()); + trieCache.dumpToLog(trieCache.find({})); return state.DoS(100, error("ConnectBlock() : the merkle root of the claim trie does not match " "(actual=%s vs block=%s on height=%d)", trieCache.getMerkleHash().GetHex(), block.hashClaimTrie.GetHex(), pindex->nHeight), REJECT_INVALID, "bad-claim-merkle-hash"); @@ -2188,7 +2184,7 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & // overwrite one. Still, use a conservative safety factor of 2. if (!CheckDiskSpace(48 * 2 * 2 * pcoinsTip->GetCacheSize())) return state.Error("out of disk space"); - if (!pclaimTrie->SyncToDisk()) + if (mode == FlushStateMode::ALWAYS && !pclaimTrie->SyncToDisk()) return state.Error("Failed to write to claim trie database"); // Flush the chainstate (which may refer to block index entries). if (!pcoinsTip->Flush())