diff --git a/src/claimtrie.cpp b/src/claimtrie.cpp index 7e454b2e1..b439d8d3f 100644 --- a/src/claimtrie.cpp +++ b/src/claimtrie.cpp @@ -181,13 +181,16 @@ bool CClaimTrieCacheBase::deleteNodeIfPossible(const std::string& name, std::str if (name.empty()) return false; // to remove a node it must have one or less children and no claims vector_builder claimsBuilder; - db << "SELECT name FROM claims WHERE name = ? AND validHeight < ? AND expirationHeight >= ? " + db << "SELECT name FROM claims WHERE nodeName = ? AND validHeight < ? AND expirationHeight >= ? " << name << nNextHeight << nNextHeight >> claimsBuilder; claims = std::move(claimsBuilder); if (!claims.empty()) return false; // still has claims // we now know it has no claims, but we need to check its children int64_t count; std::string childName; + // this line assumes that we've set the parent on child nodes already, + // which means we are len(name) desc in our parent method + // alternately: SELECT COUNT(DISTINCT nodeName) FROM claims WHERE SUBSTR(nodeName, 1, len(?)) == ? AND LENGTH(nodeName) > len(?) db << "SELECT COUNT(*),MAX(name) FROM nodes WHERE parent = ?" << name >> std::tie(count, childName); if (count > 1) return false; // still has multiple children // okay. it's going away @@ -208,21 +211,23 @@ bool CClaimTrieCacheBase::deleteNodeIfPossible(const std::string& name, std::str void CClaimTrieCacheBase::ensureTreeStructureIsUpToDate() { if (!transacting) return; - // your children are your nodes that match your key, go at least one longer, - // and have nothing in common with the other nodes in that set -- a hard query w/o parent + // your children are your nodes that match your key but go at least one longer, + // and have no trailing prefix in common with the other nodes in that set -- a hard query w/o parent field + + // when we get into this method, we have some claims that have been added, removed, and updated + // those each have a corresponding node in the list with a null hash + // some of our nodes will go away, some new ones will be added, some will be reparented + // the plan: update all the claim hashes first vector_builder names; db << "SELECT name FROM nodes WHERE hash IS NULL ORDER BY LENGTH(name) DESC, name DESC" >> names; - - if (names.empty()) return; // could track this with a dirty flag to avoid the above query (but it would be some maintenance) + if (names.empty()) return; // nothing to do // there's an assumption that all nodes with claims are here; we do that as claims are inserted - // should we do the same to remove nodes? no; we need their last takeover height if they come back - //float time = 0; // assume parents are not set correctly here: - auto parentQuery = db << "SELECT name FROM nodes WHERE parent IS NOT NULL AND " + auto parentQuery = db << "SELECT name FROM nodes WHERE " "name IN (WITH RECURSIVE prefix(p) AS (VALUES(?) UNION ALL " "SELECT SUBSTR(p, 1, LENGTH(p)-1) FROM prefix WHERE p != '') SELECT p FROM prefix) " "ORDER BY LENGTH(name) DESC LIMIT 1"; @@ -235,25 +240,17 @@ void CClaimTrieCacheBase::ensureTreeStructureIsUpToDate() { deleteNodeIfPossible(parent, grandparent, claims); continue; } - if (claims.empty()) - continue; + if (name.empty() || claims.empty()) + continue; // if you have no claims but we couldn't delete you, you must have legitimate children - // pretend that we hash them all together: - std::string nameOrParent; - parentQuery << name; + parentQuery << name.substr(0, name.size() - 1); auto queryIt = parentQuery.begin(); if (queryIt != parentQuery.end()) - *queryIt >> nameOrParent; + *queryIt >> parent; + else + parent.clear(); parentQuery++; // reusing knocks off about 10% of the query time - // if it already exists: - if (nameOrParent == name) { // TODO: we could actually set the hash here - db << "UPDATE nodes SET hash = NULL WHERE name = ?" << name; - continue; - } - - parent = std::move(nameOrParent); - // we know now that we need to insert it, // but we may need to insert a parent node for it first (also called a split) vector_builder siblings; @@ -951,24 +948,26 @@ bool CClaimTrieCacheBase::incrementBlock(insertUndoType& insertUndo, claimUndoTy } // if somebody activates on this block and they are the new best, then everybody activates on this block - CClaimValue value; - auto hasCurrent = getInfoForName(nameWithTakeover, value, 1); + CClaimValue candidateValue; + auto hasCandidate = getInfoForName(nameWithTakeover, candidateValue, 1); // now that they're all in get the winner: int existingHeight; std::unique_ptr existingID; db << "SELECT IFNULL(takeoverHeight, 0), takeoverID FROM nodes WHERE name = ?" << nameWithTakeover >> std::tie(existingHeight, existingID); - auto newOwner = needsActivate || existingID == nullptr || !hasCurrent || *existingID != value.claimId; - if (newOwner && activateAllFor(insertUndo, insertSupportUndo, nameWithTakeover)) - getInfoForName(nameWithTakeover, value, 1); + auto hasBeenSetBefore = existingID != nullptr && !existingID->IsNull(); + auto takeoverHappening = needsActivate || !hasCandidate || (hasBeenSetBefore && *existingID != candidateValue.claimId); + if (takeoverHappening && activateAllFor(insertUndo, insertSupportUndo, nameWithTakeover)) + hasCandidate = getInfoForName(nameWithTakeover, candidateValue, 1); - if (existingID != nullptr) - takeoverUndo.emplace_back(nameWithTakeover, std::make_pair(existingHeight, *existingID)); - - if (newOwner) { - db << "UPDATE nodes SET takeoverHeight = ?, takeoverID = ? WHERE name = ?" - << nNextHeight << value.claimId << nameWithTakeover; + if (takeoverHappening || !hasBeenSetBefore) { + takeoverUndo.emplace_back(nameWithTakeover, std::make_pair(existingHeight, hasBeenSetBefore ? *existingID : uint160())); + if (hasCandidate) + db << "UPDATE nodes SET takeoverHeight = ?, takeoverID = ? WHERE name = ?" + << nNextHeight << candidateValue.claimId << nameWithTakeover; + else + db << "UPDATE nodes SET takeoverHeight = NULL, takeoverID = NULL WHERE name = ?" << nameWithTakeover; assert(db.rows_modified()); } } @@ -1057,10 +1056,13 @@ bool CClaimTrieCacheBase::finalizeDecrement(takeoverUndoType& takeoverUndo) db << "UPDATE nodes SET hash = NULL WHERE name IN " "(SELECT nodeName FROM supports WHERE validHeight = ?)" << nNextHeight; - for (auto it = takeoverUndo.crbegin(); it != takeoverUndo.crend(); ++it) - db << "UPDATE nodes SET takeoverHeight = ?, takeoverID = ?, hash = NULL WHERE name = ?" - << it->second.first << it->second.second << it->first; - + for (auto it = takeoverUndo.crbegin(); it != takeoverUndo.crend(); ++it) { + if (it->second.second.IsNull()) + db << "UPDATE nodes SET takeoverHeight = NULL, takeoverID = NULL, hash = NULL WHERE name = ?" << it->first; + else + db << "UPDATE nodes SET takeoverHeight = ?, takeoverID = ?, hash = NULL WHERE name = ?" + << it->second.first << it->second.second << it->first; + } return true; } diff --git a/src/claimtrie.h b/src/claimtrie.h index d156e44ac..5803b7c80 100644 --- a/src/claimtrie.h +++ b/src/claimtrie.h @@ -465,7 +465,8 @@ protected: int getDelayForName(const std::string& name, const uint160& claimId) const override; private: - bool normalizeAllNamesInTrieIfNecessary(bool forward); + bool normalizeAllNamesInTrieIfNecessary(takeoverUndoType& takeovers); + bool unnormalizeAllNamesInTrieIfNecessary(); }; class CClaimTrieCacheHashFork : public CClaimTrieCacheNormalizationFork diff --git a/src/claimtrieforks.cpp b/src/claimtrieforks.cpp index 18bd5fb59..e4bf17361 100644 --- a/src/claimtrieforks.cpp +++ b/src/claimtrieforks.cpp @@ -135,44 +135,76 @@ std::string CClaimTrieCacheNormalizationFork::normalizeClaimName(const std::stri return normalized; } -bool CClaimTrieCacheNormalizationFork::normalizeAllNamesInTrieIfNecessary(bool forward) +bool CClaimTrieCacheNormalizationFork::normalizeAllNamesInTrieIfNecessary(takeoverUndoType& takeoverUndo) { - if (nNextHeight != Params().GetConsensus().nNormalizedNameForkHeight) - return false; - if (!transacting) { transacting = true; db << "begin"; } // 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 - db.define("NORMALIZED", [this](const std::string& str) { return normalizeClaimName(str, true); }); - auto query = db << "SELECT NORMALIZED(name) as nn, name, claimID FROM claims WHERE nodeName != nn"; - for(auto&& row: query) { - std::string newName, oldName; - uint160 claimID; - row >> newName >> oldName >> claimID; - if (!forward) std::swap(newName, oldName); - db << "UPDATE claims SET nodeName = ? WHERE claimID = ?" << newName << claimID; - db << "DELETE FROM nodes WHERE name = ?" << oldName; - db << "INSERT INTO nodes(name) VALUES(?) ON CONFLICT(name) DO UPDATE SET hash = NULL" << newName; + // make the new nodes + db << "INSERT INTO nodes(name) SELECT NORMALIZED(name) AS nn FROM claims WHERE nn != nodeName " + "ON CONFLICT(name) DO UPDATE SET hash = NULL"; + db << "UPDATE nodes SET hash = NULL WHERE name IN " + "(SELECT NORMALIZED(name) AS nn FROM supports WHERE nn != nodeName)"; + + // update the claims and supports + db << "UPDATE claims SET nodeName = NORMALIZED(name)"; + db << "UPDATE supports SET nodeName = NORMALIZED(name)"; + + // remove the old nodes + auto query = db << "SELECT name, IFNULL(takeoverHeight, 0), takeoverID FROM nodes " + "WHERE name NOT IN (SELECT nodeName FROM claims)"; + for (auto&& row: query) { + std::string name; + int takeoverHeight; + std::unique_ptr takeoverID; + row >> name >> takeoverHeight >> takeoverID; + if (name.empty()) continue; // preserve our root node + takeoverUndo.emplace_back(name, std::make_pair(takeoverHeight, takeoverID ? *takeoverID : uint160())); + // we need to let the tree structure method do the actual node delete: + db << "UPDATE nodes SET hash = NULL WHERE name = ?" << name; } return true; } +bool CClaimTrieCacheNormalizationFork::unnormalizeAllNamesInTrieIfNecessary() +{ + if (!transacting) { transacting = true; db << "begin"; } + + // run the one-time upgrade of all names that need to change + db.define("NORMALIZED", [this](const std::string& str) { return normalizeClaimName(str, true); }); + + db << "INSERT INTO nodes(name) SELECT name FROM claims WHERE name != nodeName " + "ON CONFLICT(name) DO UPDATE SET hash = NULL"; + db << "UPDATE nodes SET hash = NULL WHERE name IN " + "(SELECT name FROM supports WHERE name != nodeName UNION " + "SELECT nodeName FROM supports WHERE name != nodeName UNION " + "SELECT nodeName FROM claims WHERE name != nodeName)"; + + db << "UPDATE claims SET nodeName = name"; + db << "UPDATE supports SET nodeName = name"; + // we need to let the tree structure method do the actual node delete + db << "UPDATE nodes SET hash = NULL WHERE name NOT IN " + "(SELECT name FROM claims)"; + + return true; +} + bool CClaimTrieCacheNormalizationFork::incrementBlock(insertUndoType& insertUndo, claimUndoType& expireUndo, insertUndoType& insertSupportUndo, supportUndoType& expireSupportUndo, takeoverUndoType& takeoverUndo) { - normalizeAllNamesInTrieIfNecessary(true); + if (nNextHeight == Params().GetConsensus().nNormalizedNameForkHeight) + normalizeAllNamesInTrieIfNecessary(takeoverUndo); return CClaimTrieCacheExpirationFork::incrementBlock(insertUndo, expireUndo, insertSupportUndo, expireSupportUndo, takeoverUndo); } bool CClaimTrieCacheNormalizationFork::decrementBlock(insertUndoType& insertUndo, claimUndoType& expireUndo, insertUndoType& insertSupportUndo, supportUndoType& expireSupportUndo) { auto ret = CClaimTrieCacheExpirationFork::decrementBlock(insertUndo, expireUndo, insertSupportUndo, expireSupportUndo); - if (ret) - normalizeAllNamesInTrieIfNecessary(false); + if (ret && nNextHeight == Params().GetConsensus().nNormalizedNameForkHeight) + unnormalizeAllNamesInTrieIfNecessary(); return ret; } diff --git a/src/test/claimtrienormalization_tests.cpp b/src/test/claimtrienormalization_tests.cpp index 00828ab6c..bfbc089cf 100644 --- a/src/test/claimtrienormalization_tests.cpp +++ b/src/test/claimtrienormalization_tests.cpp @@ -213,16 +213,15 @@ BOOST_AUTO_TEST_CASE(claimtriecache_normalization) CBlockIndex* pindex = chainActive.Tip(); CBlock block; int amelieValidHeight; + std::string removed; BOOST_CHECK(trieCache.shouldNormalize()); BOOST_CHECK(ReadBlockFromDisk(block, pindex, Params().GetConsensus())); BOOST_CHECK(g_chainstate.DisconnectBlock(block, pindex, coins, trieCache) == DisconnectResult::DISCONNECT_OK); BOOST_CHECK(!trieCache.shouldNormalize()); - BOOST_CHECK(!trieCache.removeClaim(ClaimIdHash(tx2.GetHash(), 0), COutPoint(tx2.GetHash(), 0), name_normd, amelieValidHeight)); - BOOST_CHECK(trieCache.removeClaim(ClaimIdHash(tx2.GetHash(), 0), COutPoint(tx2.GetHash(), 0), name_upper, amelieValidHeight)); + BOOST_CHECK(trieCache.removeClaim(ClaimIdHash(tx2.GetHash(), 0), COutPoint(tx2.GetHash(), 0), removed, amelieValidHeight)); 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)); + BOOST_CHECK_EQUAL(nval1.claimId, ClaimIdHash(tx1.GetHash(), 0)); insertUndoType insertUndo; claimUndoType expireUndo; insertUndoType insertSupportUndo; @@ -356,7 +355,7 @@ BOOST_AUTO_TEST_CASE(normalization_does_not_kill_supports) fixture.DecrementBlocks(1); BOOST_CHECK(fixture.best_claim_effective_amount_equals("A", 2)); fixture.IncrementBlocks(5); - BOOST_CHECK(fixture.best_claim_effective_amount_equals("a", 3)); + BOOST_CHECK(fixture.best_claim_effective_amount_equals("a", 2)); } BOOST_AUTO_TEST_CASE(normalization_does_not_fail_on_spend)