diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index e757c6084b8ac..2cd03a445bea0 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -4,10 +4,14 @@ #include "cbtx.h" #include "deterministicmns.h" +#include "llmq/quorums.h" +#include "llmq/quorums_blockprocessor.h" +#include "llmq/quorums_commitment.h" #include "simplifiedmns.h" #include "specialtx.h" #include "chainparams.h" +#include "consensus/merkle.h" #include "univalue.h" #include "validation.h" @@ -34,11 +38,18 @@ bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidatio return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-height"); } + if (pindexPrev) { + bool fDIP0008Active = VersionBitsState(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == THRESHOLD_ACTIVE; + if (fDIP0008Active && cbTx.nVersion < 2) { + return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-version"); + } + } + return true; } // This can only be done after the block has been fully processed, as otherwise we won't have the finished MN list -bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, CValidationState& state) +bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, CValidationState& state) { if (block.vtx[0]->nType != TRANSACTION_COINBASE) { return true; @@ -57,6 +68,14 @@ bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, C if (calculatedMerkleRoot != cbTx.merkleRootMNList) { return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-mnmerkleroot"); } + if (cbTx.nVersion >= 2) { + if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, calculatedMerkleRoot, state)) { + return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-quorummerkleroot"); + } + if (calculatedMerkleRoot != cbTx.merkleRootQuorums) { + return state.DoS(100, false, REJECT_INVALID, "bad-cbtx-quorummerkleroot"); + } + } } return true; @@ -78,10 +97,67 @@ bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev return !mutated; } +bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state) +{ + auto quorums = llmq::quorumBlockProcessor->GetMinedAndActiveCommitmentsUntilBlock(pindexPrev); + std::map> qcHashes; + size_t hashCount = 0; + for (const auto& p : quorums) { + auto& v = qcHashes[p.first]; + v.reserve(p.second.size()); + for (const auto& p2 : p.second) { + llmq::CFinalCommitment qc; + bool found = llmq::quorumBlockProcessor->GetMinedCommitment(p.first, p2->GetBlockHash(), qc); + assert(found); + v.emplace_back(::SerializeHash(qc)); + hashCount++; + } + } + + // now add the commitments from the current block, which are not returned by GetMinedAndActiveCommitmentsUntilBlock + // due to the use of pindexPrev (we don't have the tip index here) + for (size_t i = 1; i < block.vtx.size(); i++) { + auto& tx = block.vtx[i]; + + if (tx->nVersion == 3 && tx->nType == TRANSACTION_QUORUM_COMMITMENT) { + llmq::CFinalCommitmentTxPayload qc; + if (!GetTxPayload(*tx, qc)) { + assert(false); + } + if (qc.commitment.IsNull()) { + continue; + } + auto qcHash = ::SerializeHash(qc.commitment); + const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.commitment.llmqType); + auto& v = qcHashes[params.type]; + if (v.size() == params.signingActiveQuorumCount) { + v.pop_back(); + } + v.emplace_back(qcHash); + hashCount++; + assert(v.size() <= params.signingActiveQuorumCount); + } + } + + std::vector qcHashesVec; + qcHashesVec.reserve(hashCount); + + for (const auto& p : qcHashes) { + for (const auto& h : p.second) { + qcHashesVec.emplace_back(h); + } + } + std::sort(qcHashesVec.begin(), qcHashesVec.end()); + + bool mutated = false; + merkleRootRet = ComputeMerkleRoot(qcHashesVec, &mutated); + return !mutated; +} + std::string CCbTx::ToString() const { - return strprintf("CCbTx(nHeight=%d, nVersion=%d, merkleRootMNList=%s)", - nVersion, nHeight, merkleRootMNList.ToString()); + return strprintf("CCbTx(nHeight=%d, nVersion=%d, merkleRootMNList=%s, merkleRootQuorums=%s)", + nVersion, nHeight, merkleRootMNList.ToString(), merkleRootQuorums.ToString()); } void CCbTx::ToJson(UniValue& obj) const @@ -91,4 +167,7 @@ void CCbTx::ToJson(UniValue& obj) const obj.push_back(Pair("version", (int)nVersion)); obj.push_back(Pair("height", (int)nHeight)); obj.push_back(Pair("merkleRootMNList", merkleRootMNList.ToString())); + if (nVersion >= 2) { + obj.push_back(Pair("merkleRootQuorums", merkleRootQuorums.ToString())); + } } diff --git a/src/evo/cbtx.h b/src/evo/cbtx.h index c9b1c0e1eb026..0df252d1e0889 100644 --- a/src/evo/cbtx.h +++ b/src/evo/cbtx.h @@ -16,12 +16,13 @@ class UniValue; class CCbTx { public: - static const uint16_t CURRENT_VERSION = 1; + static const uint16_t CURRENT_VERSION = 2; public: uint16_t nVersion{CURRENT_VERSION}; int32_t nHeight{0}; uint256 merkleRootMNList; + uint256 merkleRootQuorums; public: ADD_SERIALIZE_METHODS; @@ -32,6 +33,10 @@ class CCbTx READWRITE(nVersion); READWRITE(nHeight); READWRITE(merkleRootMNList); + + if (nVersion >= 2) { + READWRITE(merkleRootQuorums); + } } std::string ToString() const; @@ -40,7 +45,8 @@ class CCbTx bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); -bool CheckCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindex, CValidationState& state); +bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, CValidationState& state); bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state); +bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state); #endif //DASH_CBTX_H diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 7bd3ad8c13820..9e08b0fc60657 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -106,7 +106,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV return false; } - if (!CheckCbTxMerkleRootMNList(block, pindex, state)) { + if (!CheckCbTxMerkleRoots(block, pindex, state)) { return false; } diff --git a/src/miner.cpp b/src/miner.cpp index 015424e7f64e0..0c21b0f6c9b26 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -147,6 +147,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LOCK2(cs_main, mempool.cs); bool fDIP0003Active_context = VersionBitsState(chainActive.Tip(), chainparams.GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE; + bool fDIP0008Active_context = VersionBitsState(chainActive.Tip(), chainparams.GetConsensus(), Consensus::DEPLOYMENT_DIP0008, versionbitscache) == THRESHOLD_ACTIVE; CBlockIndex* pindexPrev = chainActive.Tip(); nHeight = pindexPrev->nHeight + 1; @@ -209,12 +210,24 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc coinbaseTx.nType = TRANSACTION_COINBASE; CCbTx cbTx; + + if (fDIP0008Active_context) { + cbTx.nVersion = 2; + } else { + cbTx.nVersion = 1; + } + cbTx.nHeight = nHeight; CValidationState state; if (!CalcCbTxMerkleRootMNList(*pblock, pindexPrev, cbTx.merkleRootMNList, state)) { throw std::runtime_error(strprintf("%s: CalcSMLMerkleRootForNewBlock failed: %s", __func__, FormatStateMessage(state))); } + if (fDIP0008Active_context) { + if (!CalcCbTxMerkleRootQuorums(*pblock, pindexPrev, cbTx.merkleRootQuorums, state)) { + throw std::runtime_error(strprintf("%s: CalcSMLMerkleRootForNewBlock failed: %s", __func__, FormatStateMessage(state))); + } + } SetTxPayload(coinbaseTx, cbTx); } diff --git a/src/test/test_dash.cpp b/src/test/test_dash.cpp index e6679a7571208..e57f10dc62aa6 100644 --- a/src/test/test_dash.cpp +++ b/src/test/test_dash.cpp @@ -177,6 +177,9 @@ CBlock TestChainSetup::CreateBlock(const std::vector& txns, if (!CalcCbTxMerkleRootMNList(block, chainActive.Tip(), cbTx.merkleRootMNList, state)) { BOOST_ASSERT(false); } + if (!CalcCbTxMerkleRootQuorums(block, chainActive.Tip(), cbTx.merkleRootQuorums, state)) { + BOOST_ASSERT(false); + } CMutableTransaction tmpTx = *block.vtx[0]; SetTxPayload(tmpTx, cbTx); block.vtx[0] = MakeTransactionRef(tmpTx);