From e8949c876612cc874f57c9c3d4a6b5b6114199f1 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 28 Jun 2025 02:17:02 +0700 Subject: [PATCH 01/13] refactor: move CSimplifiedMNListDiffs::ToJson to evo/core_write --- src/evo/core_write.cpp | 95 +++++++++++++++++++++++++++++++++++++++ src/evo/simplifiedmns.cpp | 95 --------------------------------------- 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/src/evo/core_write.cpp b/src/evo/core_write.cpp index 28ba23342f0c1..9ef179dd0e194 100644 --- a/src/evo/core_write.cpp +++ b/src/evo/core_write.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -149,3 +150,97 @@ ret.pushKV("commitment", commitment.ToJson()); return ret; } + +[[nodiscard]] UniValue CSimplifiedMNListEntry::ToJson(bool extended) const +{ + UniValue obj(UniValue::VOBJ); + obj.pushKV("nVersion", nVersion); + obj.pushKV("nType", ToUnderlying(nType)); + obj.pushKV("proRegTxHash", proRegTxHash.ToString()); + obj.pushKV("confirmedHash", confirmedHash.ToString()); + if (IsServiceDeprecatedRPCEnabled()) { + obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); + } + obj.pushKV("addresses", netInfo->ToJson()); + obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); + obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); + obj.pushKV("isValid", isValid); + if (nType == MnType::Evo) { + obj.pushKV("platformHTTPPort", platformHTTPPort); + obj.pushKV("platformNodeID", platformNodeID.ToString()); + } + + if (extended) { + CTxDestination dest; + if (ExtractDestination(scriptPayout, dest)) { + obj.pushKV("payoutAddress", EncodeDestination(dest)); + } + if (ExtractDestination(scriptOperatorPayout, dest)) { + obj.pushKV("operatorPayoutAddress", EncodeDestination(dest)); + } + } + return obj; +} + +[[nodiscard]] UniValue CSimplifiedMNListDiff::ToJson(bool extended) const +{ + UniValue obj(UniValue::VOBJ); + + obj.pushKV("nVersion", nVersion); + obj.pushKV("baseBlockHash", baseBlockHash.ToString()); + obj.pushKV("blockHash", blockHash.ToString()); + + CDataStream ssCbTxMerkleTree(SER_NETWORK, PROTOCOL_VERSION); + ssCbTxMerkleTree << cbTxMerkleTree; + obj.pushKV("cbTxMerkleTree", HexStr(ssCbTxMerkleTree)); + + obj.pushKV("cbTx", EncodeHexTx(*cbTx)); + + UniValue deletedMNsArr(UniValue::VARR); + for (const auto& h : deletedMNs) { + deletedMNsArr.push_back(h.ToString()); + } + obj.pushKV("deletedMNs", deletedMNsArr); + + UniValue mnListArr(UniValue::VARR); + for (const auto& e : mnList) { + mnListArr.push_back(e.ToJson(extended)); + } + obj.pushKV("mnList", mnListArr); + + UniValue deletedQuorumsArr(UniValue::VARR); + for (const auto& e : deletedQuorums) { + UniValue eObj(UniValue::VOBJ); + eObj.pushKV("llmqType", e.first); + eObj.pushKV("quorumHash", e.second.ToString()); + deletedQuorumsArr.push_back(eObj); + } + obj.pushKV("deletedQuorums", deletedQuorumsArr); + + UniValue newQuorumsArr(UniValue::VARR); + for (const auto& e : newQuorums) { + newQuorumsArr.push_back(e.ToJson()); + } + obj.pushKV("newQuorums", newQuorumsArr); + + // Do not assert special tx type here since this can be called prior to DIP0003 activation + if (const auto opt_cbTxPayload = GetTxPayload(*cbTx, /*assert_type=*/false)) { + obj.pushKV("merkleRootMNList", opt_cbTxPayload->merkleRootMNList.ToString()); + if (opt_cbTxPayload->nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) { + obj.pushKV("merkleRootQuorums", opt_cbTxPayload->merkleRootQuorums.ToString()); + } + } + + UniValue quorumsCLSigsArr(UniValue::VARR); + for (const auto& [signature, quorumsIndexes] : quorumsCLSigs) { + UniValue j(UniValue::VOBJ); + UniValue idxArr(UniValue::VARR); + for (const auto& idx : quorumsIndexes) { + idxArr.push_back(idx); + } + j.pushKV(signature.ToString(), idxArr); + quorumsCLSigsArr.push_back(j); + } + obj.pushKV("quorumsCLSigs", quorumsCLSigsArr); + return obj; +} diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index d88d743800042..1056ce9923ad3 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include @@ -74,37 +73,6 @@ std::string CSimplifiedMNListEntry::ToString() const operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString(), netInfo->ToString()); } -UniValue CSimplifiedMNListEntry::ToJson(bool extended) const -{ - UniValue obj(UniValue::VOBJ); - obj.pushKV("nVersion", nVersion); - obj.pushKV("nType", ToUnderlying(nType)); - obj.pushKV("proRegTxHash", proRegTxHash.ToString()); - obj.pushKV("confirmedHash", confirmedHash.ToString()); - if (IsServiceDeprecatedRPCEnabled()) { - obj.pushKV("service", netInfo->GetPrimary().ToStringAddrPort()); - } - obj.pushKV("addresses", netInfo->ToJson()); - obj.pushKV("pubKeyOperator", pubKeyOperator.ToString()); - obj.pushKV("votingAddress", EncodeDestination(PKHash(keyIDVoting))); - obj.pushKV("isValid", isValid); - if (nType == MnType::Evo) { - obj.pushKV("platformHTTPPort", platformHTTPPort); - obj.pushKV("platformNodeID", platformNodeID.ToString()); - } - - if (extended) { - CTxDestination dest; - if (ExtractDestination(scriptPayout, dest)) { - obj.pushKV("payoutAddress", EncodeDestination(dest)); - } - if (ExtractDestination(scriptOperatorPayout, dest)) { - obj.pushKV("operatorPayoutAddress", EncodeDestination(dest)); - } - } - return obj; -} - CSimplifiedMNList::CSimplifiedMNList(const std::vector& smlEntries) { mnList.reserve(smlEntries.size()); @@ -243,69 +211,6 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& return true; } -UniValue CSimplifiedMNListDiff::ToJson(bool extended) const -{ - UniValue obj(UniValue::VOBJ); - - obj.pushKV("nVersion", nVersion); - obj.pushKV("baseBlockHash", baseBlockHash.ToString()); - obj.pushKV("blockHash", blockHash.ToString()); - - CDataStream ssCbTxMerkleTree(SER_NETWORK, PROTOCOL_VERSION); - ssCbTxMerkleTree << cbTxMerkleTree; - obj.pushKV("cbTxMerkleTree", HexStr(ssCbTxMerkleTree)); - - obj.pushKV("cbTx", EncodeHexTx(*cbTx)); - - UniValue deletedMNsArr(UniValue::VARR); - for (const auto& h : deletedMNs) { - deletedMNsArr.push_back(h.ToString()); - } - obj.pushKV("deletedMNs", deletedMNsArr); - - UniValue mnListArr(UniValue::VARR); - for (const auto& e : mnList) { - mnListArr.push_back(e.ToJson(extended)); - } - obj.pushKV("mnList", mnListArr); - - UniValue deletedQuorumsArr(UniValue::VARR); - for (const auto& e : deletedQuorums) { - UniValue eObj(UniValue::VOBJ); - eObj.pushKV("llmqType", e.first); - eObj.pushKV("quorumHash", e.second.ToString()); - deletedQuorumsArr.push_back(eObj); - } - obj.pushKV("deletedQuorums", deletedQuorumsArr); - - UniValue newQuorumsArr(UniValue::VARR); - for (const auto& e : newQuorums) { - newQuorumsArr.push_back(e.ToJson()); - } - obj.pushKV("newQuorums", newQuorumsArr); - - // Do not assert special tx type here since this can be called prior to DIP0003 activation - if (const auto opt_cbTxPayload = GetTxPayload(*cbTx, /*assert_type=*/false)) { - obj.pushKV("merkleRootMNList", opt_cbTxPayload->merkleRootMNList.ToString()); - if (opt_cbTxPayload->nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) { - obj.pushKV("merkleRootQuorums", opt_cbTxPayload->merkleRootQuorums.ToString()); - } - } - - UniValue quorumsCLSigsArr(UniValue::VARR); - for (const auto& [signature, quorumsIndexes] : quorumsCLSigs) { - UniValue j(UniValue::VOBJ); - UniValue idxArr(UniValue::VARR); - for (const auto& idx : quorumsIndexes) { - idxArr.push_back(idx); - } - j.pushKV(signature.ToString(),idxArr); - quorumsCLSigsArr.push_back(j); - } - obj.pushKV("quorumsCLSigs", quorumsCLSigsArr); - return obj; -} - CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, const CDeterministicMNList& to, bool extended) { CSimplifiedMNListDiff diffRet; From c2e053f5f86538c18a95fd39eaa0b132dc1eb77b Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 28 Jun 2025 01:57:45 +0700 Subject: [PATCH 02/13] refactor: move diff related code from evo/simplifiedmns to new module evo/smldiff This commit is to prevent multiple circular dependencies over llmq/ module because evo/simplifiedmns depends on llmq/blockprocessor, llmq/commitment, llmq/quorums, but they includes evo/deterministicmns. Once evo/deterministicmns knows about simplifiedmns it causes 10 more loops over llmq/* and evo/deterministicmns --- src/Makefile.am | 2 + src/evo/core_write.cpp | 1 + src/evo/simplifiedmns.cpp | 218 +---------------------- src/evo/simplifiedmns.h | 71 -------- src/evo/smldiff.cpp | 220 ++++++++++++++++++++++++ src/evo/smldiff.h | 98 +++++++++++ src/llmq/snapshot.cpp | 1 + src/llmq/snapshot.h | 2 +- src/net_processing.cpp | 2 +- src/rpc/evo.cpp | 2 +- test/lint/lint-circular-dependencies.py | 4 +- 11 files changed, 331 insertions(+), 290 deletions(-) create mode 100644 src/evo/smldiff.cpp create mode 100644 src/evo/smldiff.h diff --git a/src/Makefile.am b/src/Makefile.am index daa766aedf1d3..1b26de7005fcb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,6 +195,7 @@ BITCOIN_CORE_H = \ evo/netinfo.h \ evo/providertx.h \ evo/simplifiedmns.h \ + evo/smldiff.h \ evo/specialtx.h \ evo/specialtxman.h \ dsnotificationinterface.h \ @@ -472,6 +473,7 @@ libbitcoin_node_a_SOURCES = \ evo/mnhftx.cpp \ evo/providertx.cpp \ evo/simplifiedmns.cpp \ + evo/smldiff.cpp \ evo/specialtx.cpp \ evo/specialtxman.cpp \ flatfile.cpp \ diff --git a/src/evo/core_write.cpp b/src/evo/core_write.cpp index 9ef179dd0e194..828b7938881d7 100644 --- a/src/evo/core_write.cpp +++ b/src/evo/core_write.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 1056ce9923ad3..e235e6e672c43 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -4,30 +4,16 @@ #include -#include -#include -#include +#include #include #include -#include -#include -#include -#include -#include - +#include +#include #include #include - -#include -#include -#include #include -#include -#include #include -#include - -using node::ReadBlockFromDisk; +#include CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : proRegTxHash(dmn.proTxHash), @@ -118,202 +104,6 @@ bool CSimplifiedMNList::operator==(const CSimplifiedMNList& rhs) const ); } -CSimplifiedMNListDiff::CSimplifiedMNListDiff() = default; - -CSimplifiedMNListDiff::~CSimplifiedMNListDiff() = default; - -bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex, - const llmq::CQuorumBlockProcessor& quorum_block_processor) -{ - auto baseQuorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(baseBlockIndex); - auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(blockIndex); - - std::set> baseQuorumHashes; - std::set> quorumHashes; - for (const auto& [llmqType, vecBlockIndex] : baseQuorums) { - for (const auto& blockindex : vecBlockIndex) { - baseQuorumHashes.emplace(llmqType, blockindex->GetBlockHash()); - } - } - for (const auto& [llmqType, vecBlockIndex] : quorums) { - for (const auto& blockindex : vecBlockIndex) { - quorumHashes.emplace(llmqType, blockindex->GetBlockHash()); - } - } - - for (const auto& p : baseQuorumHashes) { - if (!quorumHashes.count(p)) { - deletedQuorums.emplace_back((uint8_t)p.first, p.second); - } - } - for (const auto& p : quorumHashes) { - const auto& [llmqType, hash] = p; - if (!baseQuorumHashes.count(p)) { - uint256 minedBlockHash; - llmq::CFinalCommitmentPtr qc = quorum_block_processor.GetMinedCommitment(llmqType, hash, minedBlockHash); - if (qc == nullptr) { - return false; - } - newQuorums.emplace_back(*qc); - } - } - - return true; -} - -bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex) -{ - // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL signature in CbTx. - // We want to avoid to load CbTx now, as more than one quorum will target the same block: hence we want to load CbTxs once per block (heavy operation). - std::multimap workBaseBlockIndexMap; - - for (const auto [idx, e] : enumerate(newQuorums)) { - // We assume that we have on hand, quorums that correspond to the hashes queried. - // If we cannot find them, something must have gone wrong and we should cease trying - // to build any further. - auto quorum = qman.GetQuorum(e.llmqType, e.quorumHash); - if (!quorum) { - LogPrintf("%s: ERROR! Unexpected missing quorum with llmqType=%d, quorumHash=%s\n", __func__, - ToUnderlying(e.llmqType), e.quorumHash.ToString()); - return false; - } - - // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 - // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 - const CBlockIndex* pWorkBaseBlockIndex = - blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - quorum->qc->quorumIndex - 8); - - workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); - } - - for (auto it = workBaseBlockIndexMap.begin(); it != workBaseBlockIndexMap.end();) { - // Process each key (CBlockIndex containing the expected CL signature in CbTx) of the std::multimap once - const CBlockIndex* pWorkBaseBlockIndex = it->first; - const auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex); - CBLSSignature sig; - if (cbcl.has_value()) { - sig = cbcl.value().first; - } - // Get the range of indexes (values) for the current key and merge them into a single std::set - const auto [it_begin, it_end] = workBaseBlockIndexMap.equal_range(it->first); - std::set idx_set; - std::transform(it_begin, it_end, std::inserter(idx_set, idx_set.end()), [](const auto& pair) { return pair.second; }); - // Advance the iterator to the next key - it = it_end; - - // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation) - // Hence, we need to merge the std::set if another std::set already exists for the same sig. - if (auto [it_sig, inserted] = quorumsCLSigs.insert({sig, idx_set}); !inserted) { - it_sig->second.insert(idx_set.begin(), idx_set.end()); - } - } - - return true; -} - -CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, const CDeterministicMNList& to, bool extended) -{ - CSimplifiedMNListDiff diffRet; - diffRet.baseBlockHash = from.GetBlockHash(); - diffRet.blockHash = to.GetBlockHash(); - - to.ForEachMN(false, [&](const auto& toPtr) { - auto fromPtr = from.GetMN(toPtr.proTxHash); - if (fromPtr == nullptr) { - CSimplifiedMNListEntry sme(toPtr); - diffRet.mnList.push_back(std::move(sme)); - } else { - CSimplifiedMNListEntry sme1(toPtr); - CSimplifiedMNListEntry sme2(*fromPtr); - if ((sme1 != sme2) || - (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { - diffRet.mnList.push_back(std::move(sme1)); - } - } - }); - - from.ForEachMN(false, [&](auto& fromPtr) { - auto toPtr = to.GetMN(fromPtr.proTxHash); - if (toPtr == nullptr) { - diffRet.deletedMNs.emplace_back(fromPtr.proTxHash); - } - }); - - return diffRet; -} - -bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, const llmq::CQuorumBlockProcessor& qblockman, - const llmq::CQuorumManager& qman, const uint256& baseBlockHash, const uint256& blockHash, - CSimplifiedMNListDiff& mnListDiffRet, std::string& errorRet, bool extended) -{ - AssertLockHeld(::cs_main); - mnListDiffRet = CSimplifiedMNListDiff(); - - const CBlockIndex* baseBlockIndex = chainman.ActiveChain().Genesis(); - if (!baseBlockHash.IsNull()) { - baseBlockIndex = chainman.m_blockman.LookupBlockIndex(baseBlockHash); - if (!baseBlockIndex) { - errorRet = strprintf("block %s not found", baseBlockHash.ToString()); - return false; - } - } - - const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(blockHash); - if (!blockIndex) { - errorRet = strprintf("block %s not found", blockHash.ToString()); - return false; - } - - if (!chainman.ActiveChain().Contains(baseBlockIndex) || !chainman.ActiveChain().Contains(blockIndex)) { - errorRet = strprintf("block %s and %s are not in the same chain", baseBlockHash.ToString(), blockHash.ToString()); - return false; - } - if (baseBlockIndex->nHeight > blockIndex->nHeight) { - errorRet = strprintf("base block %s is higher then block %s", baseBlockHash.ToString(), blockHash.ToString()); - return false; - } - - auto baseDmnList = dmnman.GetListForBlock(baseBlockIndex); - auto dmnList = dmnman.GetListForBlock(blockIndex); - mnListDiffRet = BuildSimplifiedDiff(baseDmnList, dmnList, extended); - - // We need to return the value that was provided by the other peer as it otherwise won't be able to recognize the - // response. This will usually be identical to the block found in baseBlockIndex. The only difference is when a - // null block hash was provided to get the diff from the genesis block. - mnListDiffRet.baseBlockHash = baseBlockHash; - - if (!mnListDiffRet.BuildQuorumsDiff(baseBlockIndex, blockIndex, qblockman)) { - errorRet = strprintf("failed to build quorums diff"); - return false; - } - - if (DeploymentActiveAfter(blockIndex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) { - if (!mnListDiffRet.BuildQuorumChainlockInfo(qman, blockIndex)) { - errorRet = strprintf("failed to build quorum chainlock info"); - return false; - } - } - - // TODO store coinbase TX in CBlockIndex - CBlock block; - if (!ReadBlockFromDisk(block, blockIndex, Params().GetConsensus())) { - errorRet = strprintf("failed to read block %s from disk", blockHash.ToString()); - return false; - } - - mnListDiffRet.cbTx = block.vtx[0]; - - std::vector vHashes; - std::vector vMatch(block.vtx.size(), false); - for (const auto& tx : block.vtx) { - vHashes.emplace_back(tx->GetHash()); - } - vMatch[0] = true; // only coinbase matches - mnListDiffRet.cbTxMerkleTree = CPartialMerkleTree(vHashes, vMatch); - - return true; -} - bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, BlockValidationState& state) { try { diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 14882d70878be..38e107b17b56c 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -12,19 +12,13 @@ #include #include #include -#include -#include #include class UniValue; -class CBlockIndex; class CDeterministicMN; class CDeterministicMNList; -class CDeterministicMNManager; class ChainstateManager; -extern RecursiveMutex cs_main; - namespace llmq { class CFinalCommitment; class CQuorumBlockProcessor; @@ -116,71 +110,6 @@ class CSimplifiedMNList bool operator==(const CSimplifiedMNList& rhs) const; }; -/// P2P messages - -class CGetSimplifiedMNListDiff -{ -public: - uint256 baseBlockHash; - uint256 blockHash; - - SERIALIZE_METHODS(CGetSimplifiedMNListDiff, obj) - { - READWRITE(obj.baseBlockHash, obj.blockHash); - } -}; - -class CSimplifiedMNListDiff -{ -public: - static constexpr uint16_t CURRENT_VERSION = 1; - - uint256 baseBlockHash; - uint256 blockHash; - CPartialMerkleTree cbTxMerkleTree; - CTransactionRef cbTx; - std::vector deletedMNs; - std::vector mnList; - uint16_t nVersion{CURRENT_VERSION}; - - std::vector> deletedQuorums; // p - std::vector newQuorums; - - // Map of Chainlock Signature used for shuffling per set of quorums - // The set of quorums is the set of indexes corresponding to entries in newQuorums - std::map> quorumsCLSigs; - - SERIALIZE_METHODS(CSimplifiedMNListDiff, obj) - { - if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_VERSION_ORDER) { - READWRITE(obj.nVersion); - } - READWRITE(obj.baseBlockHash, obj.blockHash, obj.cbTxMerkleTree, obj.cbTx); - if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= BLS_SCHEME_PROTO_VERSION && s.GetVersion() < MNLISTDIFF_VERSION_ORDER) { - READWRITE(obj.nVersion); - } - READWRITE(obj.deletedMNs, obj.mnList); - READWRITE(obj.deletedQuorums, obj.newQuorums); - if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_CHAINLOCKS_PROTO_VERSION) { - READWRITE(obj.quorumsCLSigs); - } - } - - CSimplifiedMNListDiff(); - ~CSimplifiedMNListDiff(); - - bool BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex, - const llmq::CQuorumBlockProcessor& quorum_block_processor); - bool BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex); - - [[nodiscard]] UniValue ToJson(bool extended = false) const; -}; - -bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, - const llmq::CQuorumBlockProcessor& qblockman, const llmq::CQuorumManager& qman, - const uint256& baseBlockHash, const uint256& blockHash, CSimplifiedMNListDiff& mnListDiffRet, - std::string& errorRet, bool extended = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, BlockValidationState& state); #endif // BITCOIN_EVO_SIMPLIFIEDMNS_H diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp new file mode 100644 index 0000000000000..e07fa0a874ba9 --- /dev/null +++ b/src/evo/smldiff.cpp @@ -0,0 +1,220 @@ +// Copyright (c) 2017-2023 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using node::ReadBlockFromDisk; + +CSimplifiedMNListDiff::CSimplifiedMNListDiff() = default; + +CSimplifiedMNListDiff::~CSimplifiedMNListDiff() = default; + +bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex, + const llmq::CQuorumBlockProcessor& quorum_block_processor) +{ + auto baseQuorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(baseBlockIndex); + auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(blockIndex); + + std::set> baseQuorumHashes; + std::set> quorumHashes; + for (const auto& [llmqType, vecBlockIndex] : baseQuorums) { + for (const auto& blockindex : vecBlockIndex) { + baseQuorumHashes.emplace(llmqType, blockindex->GetBlockHash()); + } + } + for (const auto& [llmqType, vecBlockIndex] : quorums) { + for (const auto& blockindex : vecBlockIndex) { + quorumHashes.emplace(llmqType, blockindex->GetBlockHash()); + } + } + + for (const auto& p : baseQuorumHashes) { + if (!quorumHashes.count(p)) { + deletedQuorums.emplace_back((uint8_t)p.first, p.second); + } + } + for (const auto& p : quorumHashes) { + const auto& [llmqType, hash] = p; + if (!baseQuorumHashes.count(p)) { + uint256 minedBlockHash; + llmq::CFinalCommitmentPtr qc = quorum_block_processor.GetMinedCommitment(llmqType, hash, minedBlockHash); + if (qc == nullptr) { + return false; + } + newQuorums.emplace_back(*qc); + } + } + + return true; +} + +bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex) +{ + // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL signature in CbTx. + // We want to avoid to load CbTx now, as more than one quorum will target the same block: hence we want to load CbTxs once per block (heavy operation). + std::multimap workBaseBlockIndexMap; + + for (const auto [idx, e] : enumerate(newQuorums)) { + // We assume that we have on hand, quorums that correspond to the hashes queried. + // If we cannot find them, something must have gone wrong and we should cease trying + // to build any further. + auto quorum = qman.GetQuorum(e.llmqType, e.quorumHash); + if (!quorum) { + LogPrintf("%s: ERROR! Unexpected missing quorum with llmqType=%d, quorumHash=%s\n", __func__, + ToUnderlying(e.llmqType), e.quorumHash.ToString()); + return false; + } + + // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 + // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 + const CBlockIndex* pWorkBaseBlockIndex = + blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - quorum->qc->quorumIndex - 8); + + workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); + } + + for (auto it = workBaseBlockIndexMap.begin(); it != workBaseBlockIndexMap.end();) { + // Process each key (CBlockIndex containing the expected CL signature in CbTx) of the std::multimap once + const CBlockIndex* pWorkBaseBlockIndex = it->first; + const auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex); + CBLSSignature sig; + if (cbcl.has_value()) { + sig = cbcl.value().first; + } + // Get the range of indexes (values) for the current key and merge them into a single std::set + const auto [it_begin, it_end] = workBaseBlockIndexMap.equal_range(it->first); + std::set idx_set; + std::transform(it_begin, it_end, std::inserter(idx_set, idx_set.end()), [](const auto& pair) { return pair.second; }); + // Advance the iterator to the next key + it = it_end; + + // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation) + // Hence, we need to merge the std::set if another std::set already exists for the same sig. + if (auto [it_sig, inserted] = quorumsCLSigs.insert({sig, idx_set}); !inserted) { + it_sig->second.insert(idx_set.begin(), idx_set.end()); + } + } + + return true; +} + +CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, const CDeterministicMNList& to, bool extended) +{ + CSimplifiedMNListDiff diffRet; + diffRet.baseBlockHash = from.GetBlockHash(); + diffRet.blockHash = to.GetBlockHash(); + + to.ForEachMN(false, [&](const auto& toPtr) { + auto fromPtr = from.GetMN(toPtr.proTxHash); + if (fromPtr == nullptr) { + CSimplifiedMNListEntry sme(toPtr); + diffRet.mnList.push_back(std::move(sme)); + } else { + CSimplifiedMNListEntry sme1(toPtr); + CSimplifiedMNListEntry sme2(*fromPtr); + if ((sme1 != sme2) || + (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { + diffRet.mnList.push_back(std::move(sme1)); + } + } + }); + + from.ForEachMN(false, [&](auto& fromPtr) { + auto toPtr = to.GetMN(fromPtr.proTxHash); + if (toPtr == nullptr) { + diffRet.deletedMNs.emplace_back(fromPtr.proTxHash); + } + }); + + return diffRet; +} + +bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, const llmq::CQuorumBlockProcessor& qblockman, + const llmq::CQuorumManager& qman, const uint256& baseBlockHash, const uint256& blockHash, + CSimplifiedMNListDiff& mnListDiffRet, std::string& errorRet, bool extended) +{ + AssertLockHeld(::cs_main); + mnListDiffRet = CSimplifiedMNListDiff(); + + const CBlockIndex* baseBlockIndex = chainman.ActiveChain().Genesis(); + if (!baseBlockHash.IsNull()) { + baseBlockIndex = chainman.m_blockman.LookupBlockIndex(baseBlockHash); + if (!baseBlockIndex) { + errorRet = strprintf("block %s not found", baseBlockHash.ToString()); + return false; + } + } + + const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(blockHash); + if (!blockIndex) { + errorRet = strprintf("block %s not found", blockHash.ToString()); + return false; + } + + if (!chainman.ActiveChain().Contains(baseBlockIndex) || !chainman.ActiveChain().Contains(blockIndex)) { + errorRet = strprintf("block %s and %s are not in the same chain", baseBlockHash.ToString(), blockHash.ToString()); + return false; + } + if (baseBlockIndex->nHeight > blockIndex->nHeight) { + errorRet = strprintf("base block %s is higher then block %s", baseBlockHash.ToString(), blockHash.ToString()); + return false; + } + + auto baseDmnList = dmnman.GetListForBlock(baseBlockIndex); + auto dmnList = dmnman.GetListForBlock(blockIndex); + mnListDiffRet = BuildSimplifiedDiff(baseDmnList, dmnList, extended); + + // We need to return the value that was provided by the other peer as it otherwise won't be able to recognize the + // response. This will usually be identical to the block found in baseBlockIndex. The only difference is when a + // null block hash was provided to get the diff from the genesis block. + mnListDiffRet.baseBlockHash = baseBlockHash; + + if (!mnListDiffRet.BuildQuorumsDiff(baseBlockIndex, blockIndex, qblockman)) { + errorRet = strprintf("failed to build quorums diff"); + return false; + } + + if (DeploymentActiveAfter(blockIndex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) { + if (!mnListDiffRet.BuildQuorumChainlockInfo(qman, blockIndex)) { + errorRet = strprintf("failed to build quorum chainlock info"); + return false; + } + } + + // TODO store coinbase TX in CBlockIndex + CBlock block; + if (!ReadBlockFromDisk(block, blockIndex, Params().GetConsensus())) { + errorRet = strprintf("failed to read block %s from disk", blockHash.ToString()); + return false; + } + + mnListDiffRet.cbTx = block.vtx[0]; + + std::vector vHashes; + std::vector vMatch(block.vtx.size(), false); + for (const auto& tx : block.vtx) { + vHashes.emplace_back(tx->GetHash()); + } + vMatch[0] = true; // only coinbase matches + mnListDiffRet.cbTxMerkleTree = CPartialMerkleTree(vHashes, vMatch); + + return true; +} diff --git a/src/evo/smldiff.h b/src/evo/smldiff.h new file mode 100644 index 0000000000000..3891184f7638a --- /dev/null +++ b/src/evo/smldiff.h @@ -0,0 +1,98 @@ +// Copyright (c) 2017-2023 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_EVO_SMLDIFF_H +#define BITCOIN_EVO_SMLDIFF_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CBlockIndex; +class CDeterministicMNManager; +class UniValue; +class ChainstateManager; + +namespace llmq { +class CFinalCommitment; +class CQuorumBlockProcessor; +class CQuorumManager; +} // namespace llmq + +extern RecursiveMutex cs_main; + +/// P2P messages + +class CGetSimplifiedMNListDiff +{ +public: + uint256 baseBlockHash; + uint256 blockHash; + + SERIALIZE_METHODS(CGetSimplifiedMNListDiff, obj) + { + READWRITE(obj.baseBlockHash, obj.blockHash); + } +}; + +class CSimplifiedMNListDiff +{ +public: + static constexpr uint16_t CURRENT_VERSION = 1; + + uint256 baseBlockHash; + uint256 blockHash; + CPartialMerkleTree cbTxMerkleTree; + CTransactionRef cbTx; + std::vector deletedMNs; + std::vector mnList; + uint16_t nVersion{CURRENT_VERSION}; + + std::vector> deletedQuorums; // p + std::vector newQuorums; + + // Map of Chainlock Signature used for shuffling per set of quorums + // The set of quorums is the set of indexes corresponding to entries in newQuorums + std::map> quorumsCLSigs; + + SERIALIZE_METHODS(CSimplifiedMNListDiff, obj) + { + if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_VERSION_ORDER) { + READWRITE(obj.nVersion); + } + READWRITE(obj.baseBlockHash, obj.blockHash, obj.cbTxMerkleTree, obj.cbTx); + if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= BLS_SCHEME_PROTO_VERSION && s.GetVersion() < MNLISTDIFF_VERSION_ORDER) { + READWRITE(obj.nVersion); + } + READWRITE(obj.deletedMNs, obj.mnList); + READWRITE(obj.deletedQuorums, obj.newQuorums); + if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= MNLISTDIFF_CHAINLOCKS_PROTO_VERSION) { + READWRITE(obj.quorumsCLSigs); + } + } + + CSimplifiedMNListDiff(); + ~CSimplifiedMNListDiff(); + + bool BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex, + const llmq::CQuorumBlockProcessor& quorum_block_processor); + bool BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex); + + [[nodiscard]] UniValue ToJson(bool extended = false) const; +}; + +bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, + const llmq::CQuorumBlockProcessor& qblockman, const llmq::CQuorumManager& qman, + const uint256& baseBlockHash, const uint256& blockHash, CSimplifiedMNListDiff& mnListDiffRet, + std::string& errorRet, bool extended = false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + +#endif // BITCOIN_EVO_SMLDIFF_H diff --git a/src/llmq/snapshot.cpp b/src/llmq/snapshot.cpp index 5ee8ab6bcca73..b0097798e409a 100644 --- a/src/llmq/snapshot.cpp +++ b/src/llmq/snapshot.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/src/llmq/snapshot.h b/src/llmq/snapshot.h index 071d3cbc69b34..a72992ed8d910 100644 --- a/src/llmq/snapshot.h +++ b/src/llmq/snapshot.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_LLMQ_SNAPSHOT_H #define BITCOIN_LLMQ_SNAPSHOT_H -#include +#include #include #include #include diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 37d2fabf3bb4c..1d634907c5bc9 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -61,7 +61,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index c20af3ec6429c..60cc1a62c02ce 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index e9c0efc438e5e..723aaf2395451 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -38,8 +38,8 @@ "evo/deterministicmns -> index/txindex -> validation -> evo/deterministicmns", "evo/deterministicmns -> index/txindex -> validation -> txmempool -> evo/deterministicmns", "evo/netinfo -> evo/providertx -> evo/netinfo", - "evo/simplifiedmns -> llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> evo/simplifiedmns", - "core_io -> evo/assetlocktx -> llmq/signing -> net_processing -> evo/simplifiedmns -> core_io", + "evo/smldiff -> llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> evo/smldiff", + "core_io -> evo/assetlocktx -> llmq/signing -> net_processing -> evo/smldiff -> core_io", "evo/specialtxman -> validation -> evo/specialtxman", "governance/governance -> governance/object -> governance/governance", "governance/governance -> masternode/sync -> governance/governance", From bbd8731b2bfcf63d5b42e3d15331b8c85d75fdcf Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 1 Jul 2025 12:24:31 +0700 Subject: [PATCH 03/13] fmt: apply clang-format suggestions --- src/evo/smldiff.cpp | 31 +++++++++++++++++-------------- src/evo/smldiff.h | 8 +++----- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp index e07fa0a874ba9..9ff7a32eb1e32 100644 --- a/src/evo/smldiff.cpp +++ b/src/evo/smldiff.cpp @@ -18,8 +18,8 @@ #include #include #include -#include #include +#include using node::ReadBlockFromDisk; @@ -68,9 +68,10 @@ bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex) { - // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL signature in CbTx. - // We want to avoid to load CbTx now, as more than one quorum will target the same block: hence we want to load CbTxs once per block (heavy operation). - std::multimap workBaseBlockIndexMap; + // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL + // signature in CbTx. We want to avoid to load CbTx now, as more than one quorum will target the same block: hence + // we want to load CbTxs once per block (heavy operation). + std::multimap workBaseBlockIndexMap; for (const auto [idx, e] : enumerate(newQuorums)) { // We assume that we have on hand, quorums that correspond to the hashes queried. @@ -83,10 +84,10 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& return false; } - // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the first DKG) - 8 - // In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 - const CBlockIndex* pWorkBaseBlockIndex = - blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - quorum->qc->quorumIndex - 8); + // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the + // first DKG) - 8 In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8 + const CBlockIndex* pWorkBaseBlockIndex = blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight - + quorum->qc->quorumIndex - 8); workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx)); } @@ -102,7 +103,8 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& // Get the range of indexes (values) for the current key and merge them into a single std::set const auto [it_begin, it_end] = workBaseBlockIndexMap.equal_range(it->first); std::set idx_set; - std::transform(it_begin, it_end, std::inserter(idx_set, idx_set.end()), [](const auto& pair) { return pair.second; }); + std::transform(it_begin, it_end, std::inserter(idx_set, idx_set.end()), + [](const auto& pair) { return pair.second; }); // Advance the iterator to the next key it = it_end; @@ -130,9 +132,9 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons } else { CSimplifiedMNListEntry sme1(toPtr); CSimplifiedMNListEntry sme2(*fromPtr); - if ((sme1 != sme2) || - (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { - diffRet.mnList.push_back(std::move(sme1)); + if ((sme1 != sme2) || (extended && (sme1.scriptPayout != sme2.scriptPayout || + sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { + diffRet.mnList.push_back(std::move(sme1)); } } }); @@ -147,8 +149,9 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons return diffRet; } -bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, const llmq::CQuorumBlockProcessor& qblockman, - const llmq::CQuorumManager& qman, const uint256& baseBlockHash, const uint256& blockHash, +bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman, + const llmq::CQuorumBlockProcessor& qblockman, const llmq::CQuorumManager& qman, + const uint256& baseBlockHash, const uint256& blockHash, CSimplifiedMNListDiff& mnListDiffRet, std::string& errorRet, bool extended) { AssertLockHeld(::cs_main); diff --git a/src/evo/smldiff.h b/src/evo/smldiff.h index 3891184f7638a..8b664d9ee5ef6 100644 --- a/src/evo/smldiff.h +++ b/src/evo/smldiff.h @@ -38,10 +38,7 @@ class CGetSimplifiedMNListDiff uint256 baseBlockHash; uint256 blockHash; - SERIALIZE_METHODS(CGetSimplifiedMNListDiff, obj) - { - READWRITE(obj.baseBlockHash, obj.blockHash); - } + SERIALIZE_METHODS(CGetSimplifiedMNListDiff, obj) { READWRITE(obj.baseBlockHash, obj.blockHash); } }; class CSimplifiedMNListDiff @@ -70,7 +67,8 @@ class CSimplifiedMNListDiff READWRITE(obj.nVersion); } READWRITE(obj.baseBlockHash, obj.blockHash, obj.cbTxMerkleTree, obj.cbTx); - if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= BLS_SCHEME_PROTO_VERSION && s.GetVersion() < MNLISTDIFF_VERSION_ORDER) { + if ((s.GetType() & SER_NETWORK) && s.GetVersion() >= BLS_SCHEME_PROTO_VERSION && + s.GetVersion() < MNLISTDIFF_VERSION_ORDER) { READWRITE(obj.nVersion); } READWRITE(obj.deletedMNs, obj.mnList); From 3b4a1a0d2a6cb8cb7bd49c5cbc63a62c8626c237 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 23 Jun 2025 14:29:16 +0700 Subject: [PATCH 04/13] feat: new GetSML method for CDeterministicMNList --- src/evo/deterministicmns.cpp | 14 ++++++++++++++ src/evo/deterministicmns.h | 14 ++++++++++++++ test/lint/lint-circular-dependencies.py | 1 + 3 files changed, 29 insertions(+) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index ad27cd69e121f..e11887d242cd9 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -258,6 +259,14 @@ std::vector CDeterministicMNList::GetProjectedMNPayees(gsl return result; } +gsl::not_null> CDeterministicMNList::GetSML() const +{ + if (!m_cached_sml) { + m_cached_sml = std::make_shared(*this); + } + return m_cached_sml; +} + int CDeterministicMNList::CalcMaxPoSePenalty() const { // Maximum PoSe penalty is dynamic and equals the number of registered MNs @@ -443,6 +452,7 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTota mnMap = mnMap.set(dmn->proTxHash, dmn); mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash); + m_cached_sml = nullptr; if (fBumpTotalCount) { // nTotalRegisteredCount acts more like a checkpoint, not as a limit, nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount); @@ -514,6 +524,9 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s dmn->pdmnState = pdmnState; mnMap = mnMap.set(oldDmn.proTxHash, dmn); + if (m_cached_sml && CSimplifiedMNListEntry{oldDmn} != CSimplifiedMNListEntry{*dmn}) { + m_cached_sml = nullptr; + } } void CDeterministicMNList::UpdateMN(const uint256& proTxHash, const std::shared_ptr& pdmnState) @@ -585,6 +598,7 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash) mnMap = mnMap.erase(proTxHash); mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId()); + m_cached_sml = nullptr; } bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 6987d8448b8d1..eda3832dec565 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -30,6 +30,7 @@ class CBlock; class CBlockIndex; class CCoinsViewCache; class CEvoDB; +class CSimplifiedMNList; class TxValidationState; extern RecursiveMutex cs_main; @@ -145,6 +146,13 @@ class CDeterministicMNList // we keep track of this as checking for duplicates would otherwise be painfully slow MnUniquePropertyMap mnUniquePropertyMap; + // This SML could be null + // This cache is used to improve performance and meant to be reused + // for multiple CDeterministicMNList until mnMap is actually changed. + // Calls of AddMN, RemoveMN and (in some cases) UpdateMN reset this cache; + // it happens also for indirect calls such as ApplyDiff + mutable std::shared_ptr m_cached_sml; + public: CDeterministicMNList() = default; explicit CDeterministicMNList(const uint256& _blockHash, int _height, uint32_t _totalRegisteredCount) : @@ -195,6 +203,7 @@ class CDeterministicMNList mnMap = MnMap(); mnUniquePropertyMap = MnUniquePropertyMap(); mnInternalIdMap = MnInternalIdMap(); + m_cached_sml = nullptr; } [[nodiscard]] size_t GetAllMNsCount() const @@ -314,6 +323,11 @@ class CDeterministicMNList */ [[nodiscard]] std::vector GetProjectedMNPayees(gsl::not_null pindexPrev, int nCount = std::numeric_limits::max()) const; + /** + * Calculates CSimplifiedMNList for current list and cache it + */ + gsl::not_null> GetSML() const; + /** * Calculates the maximum penalty which is allowed at the height of this MN list. It is dynamic and might change * for every block. diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 723aaf2395451..13e929f8b50e0 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -37,6 +37,7 @@ "evo/chainhelper -> evo/specialtxman -> validation -> evo/chainhelper", "evo/deterministicmns -> index/txindex -> validation -> evo/deterministicmns", "evo/deterministicmns -> index/txindex -> validation -> txmempool -> evo/deterministicmns", + "evo/deterministicmns -> evo/simplifiedmns -> evo/deterministicmns", "evo/netinfo -> evo/providertx -> evo/netinfo", "evo/smldiff -> llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> evo/smldiff", "core_io -> evo/assetlocktx -> llmq/signing -> net_processing -> evo/smldiff -> core_io", From 7bed08043ff2d6a69302714eb6f9527935ac09bc Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 23 Jun 2025 03:01:18 +0700 Subject: [PATCH 05/13] perf: cache shared_ptr to SML instead of SML list Shared_ptr to SML is preserved between multiple CDeterministicMNList and it helps to avoid building SML from a scratch; also it helps to avoid comparision of SML item-by-item. --- src/evo/cbtx.h | 2 ++ src/evo/deterministicmns.cpp | 8 +++++++- src/evo/simplifiedmns.cpp | 12 +++++++----- src/evo/simplifiedmns.h | 2 +- src/evo/specialtxman.cpp | 2 +- src/node/miner.cpp | 2 +- src/test/util/setup_common.cpp | 2 +- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/evo/cbtx.h b/src/evo/cbtx.h index 32913eb89780f..c9087f5866b06 100644 --- a/src/evo/cbtx.h +++ b/src/evo/cbtx.h @@ -14,7 +14,9 @@ class BlockValidationState; class CBlock; class CBlockIndex; +class CDeterministicMNList; class TxValidationState; + namespace llmq { class CQuorumBlockProcessor; }// namespace llmq diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index e11887d242cd9..191797d6d8a09 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -618,6 +618,8 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; try { + newList.GetSML(); // to fullfill cache of SML + LOCK(cs); oldList = GetListForBlockInternal(pindex->pprev); @@ -633,6 +635,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; mnListDiffsCache.emplace(pindex->GetBlockHash(), diff); + mnListsCache.emplace(newList.GetBlockHash(), newList); } catch (const std::exception& e) { LogPrintf("CDeterministicMNManager::%s -- internal error: %s\n", __func__, e.what()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-dmn-block"); @@ -766,7 +769,10 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlockInternal(gsl::not_n if (tipIndex) { // always keep a snapshot for the tip if (snapshot.GetBlockHash() == tipIndex->GetBlockHash()) { - mnListsCache.emplace(snapshot.GetBlockHash(), snapshot); + auto hash = snapshot.GetBlockHash(); + if (mnListsCache.find(hash) == mnListsCache.end()) { + mnListsCache.emplace(snapshot.GetBlockHash(), snapshot); + } } else { // keep snapshots for yet alive quorums if (ranges::any_of(Params().GetConsensus().llmqs, diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index e235e6e672c43..745108f7f452b 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -104,7 +104,7 @@ bool CSimplifiedMNList::operator==(const CSimplifiedMNList& rhs) const ); } -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, BlockValidationState& state) +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, const CDeterministicMNList& mn_list, BlockValidationState& state) { try { static std::atomic nTimeMerkle = 0; @@ -112,12 +112,14 @@ bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, B int64_t nTime1 = GetTimeMicros(); static Mutex cached_mutex; - static CSimplifiedMNList smlCached GUARDED_BY(cached_mutex); + static std::shared_ptr cached_sml GUARDED_BY(cached_mutex){ + std::make_shared()}; static uint256 merkleRootCached GUARDED_BY(cached_mutex); static bool mutatedCached GUARDED_BY(cached_mutex){false}; + std::shared_ptr sml{mn_list.GetSML()}; LOCK(cached_mutex); - if (sml == smlCached) { + if (sml == cached_sml || *sml == *cached_sml) { merkleRootRet = merkleRootCached; if (mutatedCached) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "mutated-cached-calc-cb-mnmerkleroot"); @@ -126,14 +128,14 @@ bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, B } bool mutated = false; - merkleRootRet = sml.CalcMerkleRoot(&mutated); + merkleRootRet = sml->CalcMerkleRoot(&mutated); int64_t nTime2 = GetTimeMicros(); nTimeMerkle += nTime2 - nTime1; LogPrint(BCLog::BENCHMARK, " - CalcMerkleRoot: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1), nTimeMerkle * 0.000001); - smlCached = std::move(sml); + cached_sml = sml; merkleRootCached = merkleRootRet; mutatedCached = mutated; diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 38e107b17b56c..5dbbbf9fe32c7 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -110,6 +110,6 @@ class CSimplifiedMNList bool operator==(const CSimplifiedMNList& rhs) const; }; -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, CSimplifiedMNList&& sml, BlockValidationState& state); +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, const CDeterministicMNList& sml, BlockValidationState& state); #endif // BITCOIN_EVO_SIMPLIFIEDMNS_H diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 9efa6b5b62e34..078d52fef157d 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -608,7 +608,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB nTimeDMN * 0.000001); uint256 calculatedMerkleRoot; - if (!CalcCbTxMerkleRootMNList(calculatedMerkleRoot, CSimplifiedMNList{std::move(mn_list)}, state)) { + if (!CalcCbTxMerkleRootMNList(calculatedMerkleRoot, mn_list, state)) { // pass the state returned by the function above return false; } diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 58c7693130e15..55ee6c61a1e0e 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -291,7 +291,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (!m_chain_helper.special_tx->BuildNewListFromBlock(*pblock, pindexPrev, m_chainstate.CoinsTip(), true, state, mn_list)) { throw std::runtime_error(strprintf("%s: BuildNewListFromBlock failed: %s", __func__, state.ToString())); } - if (!CalcCbTxMerkleRootMNList(cbTx.merkleRootMNList, CSimplifiedMNList(mn_list), state)) { + if (!CalcCbTxMerkleRootMNList(cbTx.merkleRootMNList, mn_list, state)) { throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootMNList failed: %s", __func__, state.ToString())); } if (fDIP0008Active_context) { diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 864c46260c8f7..d13fdd5482fb3 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -519,7 +519,7 @@ CBlock TestChainSetup::CreateBlock( if (!chainstate.ChainHelper().special_tx->BuildNewListFromBlock(block, chainstate.m_chain.Tip(), chainstate.CoinsTip(), true, state, mn_list)) { Assert(false); } - if (!CalcCbTxMerkleRootMNList(cbTx->merkleRootMNList, CSimplifiedMNList(mn_list), state)) { + if (!CalcCbTxMerkleRootMNList(cbTx->merkleRootMNList, mn_list, state)) { Assert(false); } if (!CalcCbTxMerkleRootQuorums(block, chainstate.m_chain.Tip(), *m_node.llmq_ctx->quorum_block_processor, cbTx->merkleRootQuorums, state)) { From 63031daddc3c32b54bd2c95863b906fd5d614b33 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 1 Jul 2025 02:10:11 +0700 Subject: [PATCH 06/13] refactor: drop dependency SML on DMN --- src/evo/deterministicmns.cpp | 23 ++++++++-- src/evo/deterministicmns.h | 4 +- src/evo/simplifiedmns.cpp | 58 +++++++++++-------------- src/evo/simplifiedmns.h | 24 +++++----- src/evo/smldiff.cpp | 6 +-- src/evo/specialtxman.cpp | 2 +- src/node/miner.cpp | 2 +- src/test/evo_simplifiedmns_tests.cpp | 8 ++-- src/test/util/setup_common.cpp | 2 +- test/lint/lint-circular-dependencies.py | 1 - 10 files changed, 68 insertions(+), 62 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 191797d6d8a09..8b8b58187512f 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -37,6 +37,14 @@ uint64_t CDeterministicMN::GetInternalId() const return internalId; } +CSimplifiedMNListEntry CDeterministicMN::to_sml_entry() const +{ + const CDeterministicMNState& state{*pdmnState}; + return CSimplifiedMNListEntry(proTxHash, state.confirmedHash, state.netInfo, state.pubKeyOperator, + state.keyIDVoting, !state.IsBanned(), state.platformHTTPPort, state.platformNodeID, + state.scriptPayout, state.scriptOperatorPayout, state.nVersion, nType); +} + std::string CDeterministicMN::ToString() const { return strprintf("CDeterministicMN(proTxHash=%s, collateralOutpoint=%s, nOperatorReward=%f, state=%s", proTxHash.ToString(), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, pdmnState->ToString()); @@ -259,11 +267,18 @@ std::vector CDeterministicMNList::GetProjectedMNPayees(gsl return result; } -gsl::not_null> CDeterministicMNList::GetSML() const +gsl::not_null> CDeterministicMNList::to_sml() const { if (!m_cached_sml) { - m_cached_sml = std::make_shared(*this); + std::vector> sml_entries; + sml_entries.reserve(mnMap.size()); + + ForEachMN(false, [&sml_entries](auto& dmn) { + sml_entries.emplace_back(std::make_unique(dmn.to_sml_entry())); + }); + m_cached_sml = std::make_shared(std::move(sml_entries)); } + return m_cached_sml; } @@ -524,7 +539,7 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s dmn->pdmnState = pdmnState; mnMap = mnMap.set(oldDmn.proTxHash, dmn); - if (m_cached_sml && CSimplifiedMNListEntry{oldDmn} != CSimplifiedMNListEntry{*dmn}) { + if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) { m_cached_sml = nullptr; } } @@ -618,7 +633,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; try { - newList.GetSML(); // to fullfill cache of SML + newList.to_sml(); // to fullfill cache of SML LOCK(cs); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index eda3832dec565..764963f5f6966 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -31,6 +31,7 @@ class CBlockIndex; class CCoinsViewCache; class CEvoDB; class CSimplifiedMNList; +class CSimplifiedMNListEntry; class TxValidationState; extern RecursiveMutex cs_main; @@ -80,6 +81,7 @@ class CDeterministicMN [[nodiscard]] uint64_t GetInternalId() const; + [[nodiscard]] CSimplifiedMNListEntry to_sml_entry() const; [[nodiscard]] std::string ToString() const; [[nodiscard]] UniValue ToJson() const; }; @@ -326,7 +328,7 @@ class CDeterministicMNList /** * Calculates CSimplifiedMNList for current list and cache it */ - gsl::not_null> GetSML() const; + gsl::not_null> to_sml() const; /** * Calculates the maximum penalty which is allowed at the height of this MN list. It is dynamic and might change diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index 745108f7f452b..d4ee9ae5b7c9c 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -4,30 +4,37 @@ #include +#include #include -#include #include #include #include #include #include +#include #include +#include #include #include -CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : - proRegTxHash(dmn.proTxHash), - confirmedHash(dmn.pdmnState->confirmedHash), - netInfo(dmn.pdmnState->netInfo), - pubKeyOperator(dmn.pdmnState->pubKeyOperator), - keyIDVoting(dmn.pdmnState->keyIDVoting), - isValid(!dmn.pdmnState->IsBanned()), - platformHTTPPort(dmn.pdmnState->platformHTTPPort), - platformNodeID(dmn.pdmnState->platformNodeID), - scriptPayout(dmn.pdmnState->scriptPayout), - scriptOperatorPayout(dmn.pdmnState->scriptOperatorPayout), - nVersion(dmn.pdmnState->nVersion), - nType(dmn.nType) +CSimplifiedMNListEntry::CSimplifiedMNListEntry(const uint256& proreg_tx_hash, const uint256& confirmed_hash, + const std::shared_ptr& net_info, + const CBLSLazyPublicKey& pubkey_operator, const CKeyID& keyid_voting, + bool is_valid, uint16_t platform_http_port, + const uint160& platform_node_id, const CScript& script_payout, + const CScript& script_operator_payout, uint16_t version, MnType type) : + proRegTxHash(proreg_tx_hash), + confirmedHash(confirmed_hash), + netInfo(net_info), + pubKeyOperator(pubkey_operator), + keyIDVoting(keyid_voting), + isValid(is_valid), + platformHTTPPort(platform_http_port), + platformNodeID(platform_node_id), + scriptPayout(script_payout), + scriptOperatorPayout(script_operator_payout), + nVersion(version), + nType(type) { } @@ -59,24 +66,9 @@ std::string CSimplifiedMNListEntry::ToString() const operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString(), netInfo->ToString()); } -CSimplifiedMNList::CSimplifiedMNList(const std::vector& smlEntries) +CSimplifiedMNList::CSimplifiedMNList(std::vector>&& smlEntries) { - mnList.reserve(smlEntries.size()); - for (const auto& entry : smlEntries) { - mnList.emplace_back(std::make_unique(entry)); - } - - std::sort(mnList.begin(), mnList.end(), [&](const std::unique_ptr& a, const std::unique_ptr& b) { - return a->proRegTxHash.Compare(b->proRegTxHash) < 0; - }); -} - -CSimplifiedMNList::CSimplifiedMNList(const CDeterministicMNList& dmnList) -{ - mnList.reserve(dmnList.GetAllMNsCount()); - dmnList.ForEachMN(false, [this](auto& dmn) { - mnList.emplace_back(std::make_unique(dmn)); - }); + mnList = std::move(smlEntries); std::sort(mnList.begin(), mnList.end(), [&](const std::unique_ptr& a, const std::unique_ptr& b) { return a->proRegTxHash.Compare(b->proRegTxHash) < 0; @@ -104,7 +96,8 @@ bool CSimplifiedMNList::operator==(const CSimplifiedMNList& rhs) const ); } -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, const CDeterministicMNList& mn_list, BlockValidationState& state) +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, std::shared_ptr sml, + BlockValidationState& state) { try { static std::atomic nTimeMerkle = 0; @@ -117,7 +110,6 @@ bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, const CDeterministicMNList static uint256 merkleRootCached GUARDED_BY(cached_mutex); static bool mutatedCached GUARDED_BY(cached_mutex){false}; - std::shared_ptr sml{mn_list.GetSML()}; LOCK(cached_mutex); if (sml == cached_sml || *sml == *cached_sml) { merkleRootRet = merkleRootCached; diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index 5dbbbf9fe32c7..ed434fd9594cb 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -14,16 +14,10 @@ #include #include -class UniValue; -class CDeterministicMN; -class CDeterministicMNList; -class ChainstateManager; +#include +#include -namespace llmq { -class CFinalCommitment; -class CQuorumBlockProcessor; -class CQuorumManager; -} // namespace llmq +class UniValue; class CSimplifiedMNListEntry { @@ -42,7 +36,11 @@ class CSimplifiedMNListEntry MnType nType{MnType::Regular}; CSimplifiedMNListEntry() = default; - explicit CSimplifiedMNListEntry(const CDeterministicMN& dmn); + CSimplifiedMNListEntry(const uint256& proreg_tx_hash, const uint256& confirmed_hash, + const std::shared_ptr& net_info, const CBLSLazyPublicKey& pubkey_operator, + const CKeyID& keyid_voting, bool is_valid, uint16_t platform_http_port, + const uint160& platform_node_id, const CScript& script_payout, + const CScript& script_operator_payout, uint16_t version, MnType type); bool operator==(const CSimplifiedMNListEntry& rhs) const { @@ -101,15 +99,15 @@ class CSimplifiedMNList std::vector> mnList; CSimplifiedMNList() = default; - explicit CSimplifiedMNList(const CDeterministicMNList& dmnList); // This constructor from std::vector is used in unit-tests - explicit CSimplifiedMNList(const std::vector& smlEntries); + explicit CSimplifiedMNList(std::vector>&& smlEntries); uint256 CalcMerkleRoot(bool* pmutated = nullptr) const; bool operator==(const CSimplifiedMNList& rhs) const; }; -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, const CDeterministicMNList& sml, BlockValidationState& state); +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, std::shared_ptr sml, + BlockValidationState& state); #endif // BITCOIN_EVO_SIMPLIFIEDMNS_H diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp index 9ff7a32eb1e32..e0e9bf6d5a175 100644 --- a/src/evo/smldiff.cpp +++ b/src/evo/smldiff.cpp @@ -127,11 +127,11 @@ CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, cons to.ForEachMN(false, [&](const auto& toPtr) { auto fromPtr = from.GetMN(toPtr.proTxHash); if (fromPtr == nullptr) { - CSimplifiedMNListEntry sme(toPtr); + CSimplifiedMNListEntry sme{toPtr.to_sml_entry()}; diffRet.mnList.push_back(std::move(sme)); } else { - CSimplifiedMNListEntry sme1(toPtr); - CSimplifiedMNListEntry sme2(*fromPtr); + CSimplifiedMNListEntry sme1{toPtr.to_sml_entry()}; + CSimplifiedMNListEntry sme2(fromPtr->to_sml_entry()); if ((sme1 != sme2) || (extended && (sme1.scriptPayout != sme2.scriptPayout || sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) { diffRet.mnList.push_back(std::move(sme1)); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 078d52fef157d..4b63e64c60650 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -608,7 +608,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB nTimeDMN * 0.000001); uint256 calculatedMerkleRoot; - if (!CalcCbTxMerkleRootMNList(calculatedMerkleRoot, mn_list, state)) { + if (!CalcCbTxMerkleRootMNList(calculatedMerkleRoot, mn_list.to_sml(), state)) { // pass the state returned by the function above return false; } diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 55ee6c61a1e0e..7409093904b06 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -291,7 +291,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (!m_chain_helper.special_tx->BuildNewListFromBlock(*pblock, pindexPrev, m_chainstate.CoinsTip(), true, state, mn_list)) { throw std::runtime_error(strprintf("%s: BuildNewListFromBlock failed: %s", __func__, state.ToString())); } - if (!CalcCbTxMerkleRootMNList(cbTx.merkleRootMNList, mn_list, state)) { + if (!CalcCbTxMerkleRootMNList(cbTx.merkleRootMNList, mn_list.to_sml(), state)) { throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootMNList failed: %s", __func__, state.ToString())); } if (fDIP0008Active_context) { diff --git a/src/test/evo_simplifiedmns_tests.cpp b/src/test/evo_simplifiedmns_tests.cpp index 12bb61fab1cbb..02fef713c0e2d 100644 --- a/src/test/evo_simplifiedmns_tests.cpp +++ b/src/test/evo_simplifiedmns_tests.cpp @@ -16,7 +16,7 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) { //TODO: Provide raw data for basic scheme as well bls::bls_legacy_scheme.store(true); - std::vector entries; + std::vector> entries; for (size_t i = 1; i < 16; i++) { CSimplifiedMNListEntry smle; smle.nVersion = ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false); @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) smle.keyIDVoting.SetHex(strprintf("%040x", i)); smle.isValid = true; - entries.emplace_back(smle); + entries.emplace_back(std::make_unique(smle)); } std::vector expectedHashes = { @@ -56,13 +56,13 @@ BOOST_AUTO_TEST_CASE(simplifiedmns_merkleroots) std::vector calculatedHashes; for (auto& smle : entries) { - calculatedHashes.emplace_back(smle.CalcHash().ToString()); + calculatedHashes.emplace_back(smle->CalcHash().ToString()); //printf("\"%s\",\n", calculatedHashes.back().c_str()); } BOOST_CHECK(expectedHashes == calculatedHashes); - CSimplifiedMNList sml(entries); + CSimplifiedMNList sml{std::move(entries)}; std::string expectedMerkleRoot = "0bae2176078cf42fa3e1fda761d4255d1c1c54777c6a793d0ab2b07c85ed4022"; std::string calculatedMerkleRoot = sml.CalcMerkleRoot(nullptr).ToString(); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index d13fdd5482fb3..457d52460cd94 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -519,7 +519,7 @@ CBlock TestChainSetup::CreateBlock( if (!chainstate.ChainHelper().special_tx->BuildNewListFromBlock(block, chainstate.m_chain.Tip(), chainstate.CoinsTip(), true, state, mn_list)) { Assert(false); } - if (!CalcCbTxMerkleRootMNList(cbTx->merkleRootMNList, mn_list, state)) { + if (!CalcCbTxMerkleRootMNList(cbTx->merkleRootMNList, mn_list.to_sml(), state)) { Assert(false); } if (!CalcCbTxMerkleRootQuorums(block, chainstate.m_chain.Tip(), *m_node.llmq_ctx->quorum_block_processor, cbTx->merkleRootQuorums, state)) { diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 13e929f8b50e0..723aaf2395451 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -37,7 +37,6 @@ "evo/chainhelper -> evo/specialtxman -> validation -> evo/chainhelper", "evo/deterministicmns -> index/txindex -> validation -> evo/deterministicmns", "evo/deterministicmns -> index/txindex -> validation -> txmempool -> evo/deterministicmns", - "evo/deterministicmns -> evo/simplifiedmns -> evo/deterministicmns", "evo/netinfo -> evo/providertx -> evo/netinfo", "evo/smldiff -> llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> evo/smldiff", "core_io -> evo/assetlocktx -> llmq/signing -> net_processing -> evo/smldiff -> core_io", From 83c667eb9ff6bfb3aff9c06bf2f2483d4978a8e3 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 1 Jul 2025 08:57:57 -0500 Subject: [PATCH 07/13] refactor: update comment for SML cache population --- src/evo/deterministicmns.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 8b8b58187512f..cef727bdea67a 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -633,7 +633,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; try { - newList.to_sml(); // to fullfill cache of SML + newList.to_sml(); // to populate the SML cache LOCK(cs); From 53190a5a78eae8c71109277336481b26b086676b Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 1 Jul 2025 11:25:47 -0500 Subject: [PATCH 08/13] refactor: enhance thread safety for SML caching with mutex protection --- src/evo/deterministicmns.cpp | 18 ++++++++++++---- src/evo/deterministicmns.h | 40 ++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index cef727bdea67a..a91163bd66377 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -269,6 +269,7 @@ std::vector CDeterministicMNList::GetProjectedMNPayees(gsl gsl::not_null> CDeterministicMNList::to_sml() const { + LOCK(m_cached_sml_mutex); if (!m_cached_sml) { std::vector> sml_entries; sml_entries.reserve(mnMap.size()); @@ -467,7 +468,10 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTota mnMap = mnMap.set(dmn->proTxHash, dmn); mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash); - m_cached_sml = nullptr; + { + LOCK(m_cached_sml_mutex); + m_cached_sml = nullptr; + } if (fBumpTotalCount) { // nTotalRegisteredCount acts more like a checkpoint, not as a limit, nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount); @@ -539,8 +543,11 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s dmn->pdmnState = pdmnState; mnMap = mnMap.set(oldDmn.proTxHash, dmn); - if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) { - m_cached_sml = nullptr; + { + LOCK(m_cached_sml_mutex); + if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) { + m_cached_sml = nullptr; + } } } @@ -613,7 +620,10 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash) mnMap = mnMap.erase(proTxHash); mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId()); - m_cached_sml = nullptr; + { + LOCK(m_cached_sml_mutex); + m_cached_sml = nullptr; + } } bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 764963f5f6966..16a5f9b696753 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -153,7 +153,9 @@ class CDeterministicMNList // for multiple CDeterministicMNList until mnMap is actually changed. // Calls of AddMN, RemoveMN and (in some cases) UpdateMN reset this cache; // it happens also for indirect calls such as ApplyDiff - mutable std::shared_ptr m_cached_sml; + // Thread safety: Protected by its own mutex for thread-safe access + mutable Mutex m_cached_sml_mutex; + mutable std::shared_ptr m_cached_sml GUARDED_BY(m_cached_sml_mutex); public: CDeterministicMNList() = default; @@ -165,6 +167,36 @@ class CDeterministicMNList assert(nHeight >= 0); } + // Copy constructor + CDeterministicMNList(const CDeterministicMNList& other) : + blockHash(other.blockHash), + nHeight(other.nHeight), + nTotalRegisteredCount(other.nTotalRegisteredCount), + mnMap(other.mnMap), + mnInternalIdMap(other.mnInternalIdMap), + mnUniquePropertyMap(other.mnUniquePropertyMap) + { + LOCK(other.m_cached_sml_mutex); + m_cached_sml = other.m_cached_sml; + } + + // Assignment operator + CDeterministicMNList& operator=(const CDeterministicMNList& other) + { + if (this != &other) { + blockHash = other.blockHash; + nHeight = other.nHeight; + nTotalRegisteredCount = other.nTotalRegisteredCount; + mnMap = other.mnMap; + mnInternalIdMap = other.mnInternalIdMap; + mnUniquePropertyMap = other.mnUniquePropertyMap; + + LOCK2(m_cached_sml_mutex, other.m_cached_sml_mutex); + m_cached_sml = other.m_cached_sml; + } + return *this; + } + template inline void SerializationOpBase(Stream& s, Operation ser_action) { @@ -205,7 +237,10 @@ class CDeterministicMNList mnMap = MnMap(); mnUniquePropertyMap = MnUniquePropertyMap(); mnInternalIdMap = MnInternalIdMap(); - m_cached_sml = nullptr; + { + LOCK(m_cached_sml_mutex); + m_cached_sml = nullptr; + } } [[nodiscard]] size_t GetAllMNsCount() const @@ -327,6 +362,7 @@ class CDeterministicMNList /** * Calculates CSimplifiedMNList for current list and cache it + * Thread safety: Uses internal mutex for thread-safe cache access */ gsl::not_null> to_sml() const; From d61f44e78dcff170f7b581949784b1a8ea1ff6c2 Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 6 Jul 2025 15:14:09 -0500 Subject: [PATCH 09/13] refactor: centralize SML cache invalidation logic Add private helper methods InvalidateSMLCache() and InvalidateSMLCacheIfChanged() to centralize cache invalidation logic and reduce code duplication. This improves maintainability by: - Centralizing cache invalidation logic in dedicated methods - Reducing code duplication across AddMN, RemoveMN, UpdateMN, and Unserialize - Making cache invalidation patterns consistent and easier to maintain - Providing both unconditional and conditional invalidation helpers The conditional invalidation method preserves the optimization in UpdateMN where cache is only invalidated if the SML entry actually changed. --- src/evo/deterministicmns.cpp | 18 +++++------------- src/evo/deterministicmns.h | 12 ++++++++---- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index a91163bd66377..0aa38a90621d5 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -468,10 +468,7 @@ void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTota mnMap = mnMap.set(dmn->proTxHash, dmn); mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash); - { - LOCK(m_cached_sml_mutex); - m_cached_sml = nullptr; - } + InvalidateSMLCache(); if (fBumpTotalCount) { // nTotalRegisteredCount acts more like a checkpoint, not as a limit, nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount); @@ -543,11 +540,9 @@ void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::s dmn->pdmnState = pdmnState; mnMap = mnMap.set(oldDmn.proTxHash, dmn); - { - LOCK(m_cached_sml_mutex); - if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) { - m_cached_sml = nullptr; - } + LOCK(m_cached_sml_mutex); + if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) { + m_cached_sml = nullptr; } } @@ -620,10 +615,7 @@ void CDeterministicMNList::RemoveMN(const uint256& proTxHash) mnMap = mnMap.erase(proTxHash); mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId()); - { - LOCK(m_cached_sml_mutex); - m_cached_sml = nullptr; - } + InvalidateSMLCache(); } bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 16a5f9b696753..bb24a177877e5 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -157,6 +157,13 @@ class CDeterministicMNList mutable Mutex m_cached_sml_mutex; mutable std::shared_ptr m_cached_sml GUARDED_BY(m_cached_sml_mutex); + // Private helper method to invalidate SML cache + void InvalidateSMLCache() + { + LOCK(m_cached_sml_mutex); + m_cached_sml = nullptr; + } + public: CDeterministicMNList() = default; explicit CDeterministicMNList(const uint256& _blockHash, int _height, uint32_t _totalRegisteredCount) : @@ -237,10 +244,7 @@ class CDeterministicMNList mnMap = MnMap(); mnUniquePropertyMap = MnUniquePropertyMap(); mnInternalIdMap = MnInternalIdMap(); - { - LOCK(m_cached_sml_mutex); - m_cached_sml = nullptr; - } + InvalidateSMLCache(); } [[nodiscard]] size_t GetAllMNsCount() const From d0a2791cf0e63f15cc15a5aef91af9dc94b8c8ef Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 6 Jul 2025 15:17:34 -0500 Subject: [PATCH 10/13] test: add comprehensive unit tests for SML caching mechanism Add extensive unit tests to verify the SML caching functionality: - Test basic cache functionality and cache hits - Test cache invalidation on AddMN operations - Test cache invalidation on RemoveMN operations - Test conditional cache invalidation on UpdateMN operations - Test cache behavior with copy constructor and assignment operator These tests ensure the cache works correctly and efficiently, validating: - Cache is properly shared when no changes occur - Cache is invalidated when MN list changes - Cache preserves optimization in UpdateMN when SML entry doesn't change - Cache is properly copied/shared during object copying This provides comprehensive coverage of the caching mechanism's correctness. --- src/test/evo_deterministicmns_tests.cpp | 118 ++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 6 deletions(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 01dbf27cf33c9..8152d3be86846 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -8,6 +8,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -20,14 +26,10 @@ #include #include -#include -#include -#include -#include -#include - #include +#include + using node::GetTransaction; using SimpleUTXOMap = std::map>; @@ -836,6 +838,98 @@ void FuncVerifyDB(TestChainSetup& setup) chainman.ActiveChainstate().CoinsTip(), *(setup.m_node.evodb), 4, 2)); } +static CDeterministicMNCPtr create_mock_mn(uint64_t internal_id) +{ + // Create a mock MN + CKey ownerKey; + ownerKey.MakeNewKey(true); + CBLSSecretKey operatorKey; + operatorKey.MakeNewKey(); + + auto dmnState = std::make_shared(); + dmnState->confirmedHash = GetRandHash(); + dmnState->keyIDOwner = ownerKey.GetPubKey().GetID(); + dmnState->pubKeyOperator.Set(operatorKey.GetPublicKey(), bls::bls_legacy_scheme.load()); + dmnState->keyIDVoting = ownerKey.GetPubKey().GetID(); + dmnState->netInfo = NetInfoInterface::MakeNetInfo( + ProTxVersion::GetMax(!bls::bls_legacy_scheme, /*is_extended_addr=*/false)); + BOOST_CHECK_EQUAL(dmnState->netInfo->AddEntry("1.1.1.1:1"), NetInfoStatus::Success); + + auto dmn = std::make_shared(internal_id, MnType::Regular); + dmn->proTxHash = GetRandHash(); + dmn->collateralOutpoint = COutPoint(GetRandHash(), 0); + dmn->nOperatorReward = 0; + dmn->pdmnState = dmnState; + + return dmn; +} + +static void SmlCache(TestChainSetup& setup) +{ + BOOST_CHECK(setup.m_node.dmnman != nullptr); + + // Create empty list and verify SML cache + CDeterministicMNList emptyList(uint256(), 0, 0); + auto sml_empty = emptyList.to_sml(); + + // Should return the same cached object + BOOST_CHECK(sml_empty == emptyList.to_sml()); + + // Should contain empty list + BOOST_CHECK_EQUAL(sml_empty->mnList.size(), 0); + + // Copy list should return the same cached object + CDeterministicMNList mn_list_1(emptyList); + BOOST_CHECK(sml_empty == mn_list_1.to_sml()); + + // Assigning list should return the same cached object + CDeterministicMNList mn_list_2 = emptyList; + BOOST_CHECK(sml_empty == mn_list_2.to_sml()); + + auto dmn = create_mock_mn(1); + + // Add MN - should invalidate cache + mn_list_1.AddMN(dmn, true); + auto sml_add = mn_list_1.to_sml(); + + // Cache should be invalidated, so different pointer but equal content after regeneration + BOOST_CHECK(sml_empty != sml_add); // Different pointer (cache invalidated) + + BOOST_CHECK_EQUAL(sml_add->mnList.size(), 1); // Should contain the added MN + + { + // Remove MN - should invalidate cache + CDeterministicMNList mn_list(mn_list_1); + BOOST_CHECK(mn_list_1.to_sml() == mn_list.to_sml()); + + mn_list.RemoveMN(dmn->proTxHash); + auto sml_remove = mn_list.to_sml(); + + // Cache should be invalidated + BOOST_CHECK(sml_remove != sml_add); + BOOST_CHECK(sml_remove != sml_empty); + BOOST_CHECK_EQUAL(sml_remove->mnList.size(), 0); // Should be empty after removal + } + + // Start with a list containing one MN mn_list_1 + // Test 1: Update with same SML entry data - cache should NOT be invalidated + auto unchangedState = std::make_shared(*dmn->pdmnState); + unchangedState->nPoSePenalty += 10; + mn_list_1.UpdateMN(*dmn, unchangedState); + + // Cache should NOT be invalidated since SML entry didn't change + BOOST_CHECK(sml_add == mn_list_1.to_sml()); // Same pointer (cache preserved) + + // Test 2: Update with different SML entry data - cache SHOULD be invalidated + auto changedState = std::make_shared(*unchangedState); + changedState->pubKeyOperator.Set(CBLSPublicKey{}, bls::bls_legacy_scheme.load()); + mn_list_1.UpdateMN(*dmn, changedState); + + // Cache should be invalidated since SML entry changed + BOOST_CHECK(sml_add != mn_list_1.to_sml()); + BOOST_CHECK_EQUAL(mn_list_1.to_sml()->mnList.size(), 1); // Still one MN but with updated data +} + BOOST_AUTO_TEST_SUITE(evo_dip3_activation_tests) struct TestChainDIP3BeforeActivationSetup : public TestChainSetup { @@ -938,4 +1032,16 @@ BOOST_AUTO_TEST_CASE(verify_db_legacy) FuncVerifyDB(setup); } +BOOST_AUTO_TEST_CASE(test_sml_cache_legacy) +{ + TestChainDIP3Setup setup; + SmlCache(setup); +} + +BOOST_AUTO_TEST_CASE(test_sml_cache_basic) +{ + TestChainV19Setup setup; + SmlCache(setup); +} + BOOST_AUTO_TEST_SUITE_END() From b75e6406c8ae066cc580d396bef06489156a6ce6 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 16 Jul 2025 14:30:48 +0700 Subject: [PATCH 11/13] refactor: add gsl::not_null annotation for cached_sml and CalcCbTxMerkleRootMNList --- src/evo/simplifiedmns.cpp | 4 ++-- src/evo/simplifiedmns.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/evo/simplifiedmns.cpp b/src/evo/simplifiedmns.cpp index d4ee9ae5b7c9c..3b2fd600e7a7a 100644 --- a/src/evo/simplifiedmns.cpp +++ b/src/evo/simplifiedmns.cpp @@ -96,7 +96,7 @@ bool CSimplifiedMNList::operator==(const CSimplifiedMNList& rhs) const ); } -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, std::shared_ptr sml, +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, gsl::not_null> sml, BlockValidationState& state) { try { @@ -105,7 +105,7 @@ bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, std::shared_ptr cached_sml GUARDED_BY(cached_mutex){ + static gsl::not_null> cached_sml GUARDED_BY(cached_mutex){ std::make_shared()}; static uint256 merkleRootCached GUARDED_BY(cached_mutex); static bool mutatedCached GUARDED_BY(cached_mutex){false}; diff --git a/src/evo/simplifiedmns.h b/src/evo/simplifiedmns.h index ed434fd9594cb..7fef3623aa953 100644 --- a/src/evo/simplifiedmns.h +++ b/src/evo/simplifiedmns.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -107,7 +108,7 @@ class CSimplifiedMNList bool operator==(const CSimplifiedMNList& rhs) const; }; -bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, std::shared_ptr sml, +bool CalcCbTxMerkleRootMNList(uint256& merkleRootRet, gsl::not_null> sml, BlockValidationState& state); #endif // BITCOIN_EVO_SIMPLIFIEDMNS_H From 8a68ec61d82bca39453c0c2b0f4224a4677f318f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 19 Jul 2025 19:50:56 +0700 Subject: [PATCH 12/13] fix: remove duplicated check for hash existance in map, avoid hash recalculation --- src/evo/deterministicmns.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 0aa38a90621d5..4554944b41ba5 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -785,11 +785,8 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlockInternal(gsl::not_n if (tipIndex) { // always keep a snapshot for the tip - if (snapshot.GetBlockHash() == tipIndex->GetBlockHash()) { - auto hash = snapshot.GetBlockHash(); - if (mnListsCache.find(hash) == mnListsCache.end()) { - mnListsCache.emplace(snapshot.GetBlockHash(), snapshot); - } + if (const auto snapshot_hash = snapshot.GetBlockHash(); snapshot_hash == tipIndex->GetBlockHash()) { + mnListsCache.emplace(snapshot_hash, snapshot); } else { // keep snapshots for yet alive quorums if (ranges::any_of(Params().GetConsensus().llmqs, @@ -799,7 +796,7 @@ CDeterministicMNList CDeterministicMNManager::GetListForBlockInternal(gsl::not_n (snapshot.GetHeight() + params.dkgInterval * (params.keepOldConnections + 1) >= tipIndex->nHeight); })) { - mnListsCache.emplace(snapshot.GetBlockHash(), snapshot); + mnListsCache.emplace(snapshot_hash, snapshot); } } } From bc01e9603f8767ac2f8c1bcb85f6500aee5529c9 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 21 Jul 2025 14:06:53 +0700 Subject: [PATCH 13/13] fmt: re-order include accorindgly to clang formatter --- src/test/evo_deterministicmns_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 8152d3be86846..7c786443e7759 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include #include #include