// Copyright (c) 2017-2022 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 #include #include #include #include #include CSimplifiedMNListEntry::CSimplifiedMNListEntry(const CDeterministicMN& dmn) : proRegTxHash(dmn.proTxHash), confirmedHash(dmn.pdmnState->confirmedHash), service(dmn.pdmnState->addr), 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 == CProRegTx::LEGACY_BLS_VERSION ? LEGACY_BLS_VERSION : BASIC_BLS_VERSION), nType(dmn.nType) { } uint256 CSimplifiedMNListEntry::CalcHash() const { CHashWriter hw(SER_GETHASH, CLIENT_VERSION); hw << *this; return hw.GetHash(); } std::string CSimplifiedMNListEntry::ToString() const { CTxDestination dest; std::string payoutAddress = "unknown"; std::string operatorPayoutAddress = "none"; if (ExtractDestination(scriptPayout, dest)) { payoutAddress = EncodeDestination(dest); } if (ExtractDestination(scriptOperatorPayout, dest)) { operatorPayoutAddress = EncodeDestination(dest); } return strprintf("CSimplifiedMNListEntry(nVersion=%d, nType=%d, proRegTxHash=%s, confirmedHash=%s, service=%s, pubKeyOperator=%s, votingAddress=%s, isValid=%d, payoutAddress=%s, operatorPayoutAddress=%s, platformHTTPPort=%d, platformNodeID=%s)", nVersion, ToUnderlying(nType), proRegTxHash.ToString(), confirmedHash.ToString(), service.ToString(false), pubKeyOperator.ToString(), EncodeDestination(PKHash(keyIDVoting)), isValid, payoutAddress, operatorPayoutAddress, platformHTTPPort, platformNodeID.ToString()); } UniValue CSimplifiedMNListEntry::ToJson(bool extended) const { UniValue obj; obj.setObject(); obj.pushKV("nVersion", nVersion); obj.pushKV("nType", ToUnderlying(nType)); obj.pushKV("proRegTxHash", proRegTxHash.ToString()); obj.pushKV("confirmedHash", confirmedHash.ToString()); obj.pushKV("service", service.ToString(false)); 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()); 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)); }); std::sort(mnList.begin(), mnList.end(), [&](const std::unique_ptr& a, const std::unique_ptr& b) { return a->proRegTxHash.Compare(b->proRegTxHash) < 0; }); } uint256 CSimplifiedMNList::CalcMerkleRoot(bool* pmutated) const { std::vector leaves; leaves.reserve(mnList.size()); for (const auto& e : mnList) { leaves.emplace_back(e->CalcHash()); } return ComputeMerkleRoot(leaves, pmutated); } bool CSimplifiedMNList::operator==(const CSimplifiedMNList& rhs) const { return mnList.size() == rhs.mnList.size() && std::equal(mnList.begin(), mnList.end(), rhs.mnList.begin(), [](const std::unique_ptr& left, const std::unique_ptr& right) { return *left == *right; } ); } 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 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)) { auto quorum = llmq::quorumManager->GetQuorum(e.llmqType, e.quorumHash); // 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; } UniValue CSimplifiedMNListDiff::ToJson(bool extended) const { UniValue obj; obj.setObject(); 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); CCbTx cbTxPayload; if (GetTxPayload(*cbTx, cbTxPayload)) { obj.pushKV("merkleRootMNList", cbTxPayload.merkleRootMNList.ToString()); if (cbTxPayload.nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) { obj.pushKV("merkleRootQuorums", 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; 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(const uint256& baseBlockHash, const uint256& blockHash, CSimplifiedMNListDiff& mnListDiffRet, const llmq::CQuorumBlockProcessor& quorum_block_processor, std::string& errorRet, bool extended) { AssertLockHeld(cs_main); mnListDiffRet = CSimplifiedMNListDiff(); const CBlockIndex* baseBlockIndex = ::ChainActive().Genesis(); if (!baseBlockHash.IsNull()) { baseBlockIndex = g_chainman.m_blockman.LookupBlockIndex(baseBlockHash); if (!baseBlockIndex) { errorRet = strprintf("block %s not found", baseBlockHash.ToString()); return false; } } const CBlockIndex* blockIndex = g_chainman.m_blockman.LookupBlockIndex(blockHash); if (!blockIndex) { errorRet = strprintf("block %s not found", blockHash.ToString()); return false; } if (!::ChainActive().Contains(baseBlockIndex) || !::ChainActive().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 = deterministicMNManager->GetListForBlock(baseBlockIndex); auto dmnList = deterministicMNManager->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, quorum_block_processor)) { errorRet = strprintf("failed to build quorums diff"); return false; } if (DeploymentActiveAfter(blockIndex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) { if (!mnListDiffRet.BuildQuorumChainlockInfo(blockIndex)) { errorRet = strprintf("failed to build quorums chainlocks 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; }