diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e22a2e9b5..8ba410964e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,8 @@ file(GLOB SOURCE_FILES src/*.h src/evo/*.h src/evo/*.cpp + src/llmq/*.h + src/llmq/*.cpp src/rpc/*.cpp src/rpc/*.h ) diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py index 04c620fcfc..3bccd4843d 100755 --- a/qa/rpc-tests/dip3-deterministicmns.py +++ b/qa/rpc-tests/dip3-deterministicmns.py @@ -780,6 +780,13 @@ class DIP3Test(BitcoinTestFramework): block = create_block(int(tip_hash, 16), coinbase) block.vtx += vtx + + # Add quorum commitments from template + for tx in bt['transactions']: + tx2 = FromHex(CTransaction(), tx['data']) + if tx2.nType == 6: + block.vtx.append(tx2) + block.hashMerkleRoot = block.calc_merkle_root() block.solve() result = node.submitblock(ToHex(block)) diff --git a/src/Makefile.am b/src/Makefile.am index 659af594ff..ef84482ede 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -139,6 +139,10 @@ BITCOIN_CORE_H = \ keystore.h \ dbwrapper.h \ limitedmap.h \ + llmq/quorums_commitment.h \ + llmq/quorums_blockprocessor.h \ + llmq/quorums_utils.h \ + llmq/quorums_init.h \ masternode.h \ masternode-payments.h \ masternode-sync.h \ @@ -243,6 +247,10 @@ libdash_server_a_SOURCES = \ governance-validators.cpp \ governance-vote.cpp \ governance-votedb.cpp \ + llmq/quorums_commitment.cpp \ + llmq/quorums_blockprocessor.cpp \ + llmq/quorums_utils.cpp \ + llmq/quorums_init.cpp \ masternode.cpp \ masternode-payments.cpp \ masternode-sync.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b58fedff33..bc4221e2b1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -106,6 +106,61 @@ static CBlock FindDevNetGenesisBlock(const Consensus::Params& params, const CBlo assert(false); } +// this one is for testing only +static Consensus::LLMQParams llmq10_60 = { + .type = Consensus::LLMQ_10_60, + .name = "llmq_10", + .size = 10, + .minSize = 6, + .threshold = 6, + + .dkgInterval = 24, // one DKG per hour + .dkgPhaseBlocks = 2, + .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 18, +}; + +static Consensus::LLMQParams llmq50_60 = { + .type = Consensus::LLMQ_50_60, + .name = "llmq_50_60", + .size = 50, + .minSize = 40, + .threshold = 30, + + .dkgInterval = 24, // one DKG per hour + .dkgPhaseBlocks = 2, + .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 18, +}; + +static Consensus::LLMQParams llmq400_60 = { + .type = Consensus::LLMQ_400_60, + .name = "llmq_400_51", + .size = 400, + .minSize = 300, + .threshold = 240, + + .dkgInterval = 24 * 12, // one DKG every 12 hours + .dkgPhaseBlocks = 4, + .dkgMiningWindowStart = 20, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 28, +}; + +// Used for deployment and min-proto-version signalling, so it needs a higher threshold +static Consensus::LLMQParams llmq400_85 = { + .type = Consensus::LLMQ_400_85, + .name = "llmq_400_85", + .size = 400, + .minSize = 350, + .threshold = 340, + + .dkgInterval = 24 * 24, // one DKG every 24 hours + .dkgPhaseBlocks = 4, + .dkgMiningWindowStart = 20, // dkgPhaseBlocks * 5 = after finalization + .dkgMiningWindowEnd = 48, // give it a larger mining window to make sure it is mined +}; + + /** * Main network */ @@ -219,6 +274,11 @@ public: vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); + // long living quorum params + consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; + consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; + consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -378,6 +438,11 @@ public: // Testnet Dash BIP44 coin type is '1' (All coin's testnet default) nExtCoinType = 1; + // long living quorum params + consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; + consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; + consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + // This is temporary until we reset testnet for retesting of the full DIP3 deployment consensus.nTemporaryTestnetForkDIP3Height = 264000; consensus.nTemporaryTestnetForkHeight = 273000; @@ -524,6 +589,11 @@ public: // Testnet Dash BIP44 coin type is '1' (All coin's testnet default) nExtCoinType = 1; + // long living quorum params + consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; + consensus.llmqs[Consensus::LLMQ_400_60] = llmq400_60; + consensus.llmqs[Consensus::LLMQ_400_85] = llmq400_85; + fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = false; @@ -675,7 +745,11 @@ public: // Regtest Dash BIP44 coin type is '1' (All coin's testnet default) nExtCoinType = 1; - } + + // long living quorum params + consensus.llmqs[Consensus::LLMQ_10_60] = llmq10_60; + consensus.llmqs[Consensus::LLMQ_50_60] = llmq50_60; + } void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout) { diff --git a/src/consensus/params.h b/src/consensus/params.h index f5131d2b6c..705f7b9e28 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -39,6 +39,66 @@ struct BIP9Deployment { int64_t nThreshold; }; +enum LLMQType : uint8_t +{ + LLMQ_NONE = 0xff, + + LLMQ_50_60 = 1, // 50 members, 30 (60%) threshold, one per hour + LLMQ_400_60 = 2, // 400 members, 240 (60%) threshold, one every 12 hours + LLMQ_400_85 = 3, // 400 members, 340 (85%) threshold, one every 24 hours + + // for testing only + LLMQ_10_60 = 100, // 10 members, 6 (60%) threshold, one per hour +}; + +// Configures a LLMQ and its DKG +// See https://github.com/dashpay/dips/blob/master/dip-0006.md for more details +struct LLMQParams { + LLMQType type; + + // not consensus critical, only used in logging, RPC and UI + std::string name; + + // the size of the quorum, e.g. 50 or 400 + int size; + + // The minimum number of valid members after the DKK. If less members are determined valid, no commitment can be + // created. Should be higher then the threshold to allow some room for failing nodes, otherwise quorum might end up + // not being able to ever created a recovered signature if more nodes fail after the DKG + int minSize; + + // The threshold required to recover a final signature. Should be at least 50%+1 of the quorum size. This value + // also controls the size of the public key verification vector and has a large influence on the performance of + // recovery. It also influences the amount of minimum messages that need to be exchanged for a single signing session. + // This value has the most influence on the security of the quorum. The number of total malicious masternodes + // required to negatively influence signing sessions highly correlates to the threshold percentage. + int threshold; + + // The interval in number blocks for DKGs and the creation of LLMQs. If set to 24 for example, a DKG will start + // every 24 blocks, which is approximately once every hour. + int dkgInterval; + + // The number of blocks per phase in a DKG session. There are 6 phases plus the mining phase that need to be processed + // per DKG. Set this value to a number of blocks so that each phase has enough time to propagate all required + // messages to all members before the next phase starts. If blocks are produced too fast, whole DKG sessions will + // fail. + int dkgPhaseBlocks; + + // The starting block inside the DKG interval for when mining of commitments starts. The value is inclusive. + // Starting from this block, the inclusion of (possibly null) commitments is enforced until the first non-null + // commitment is mined. The chosen value should be at least 5 * dkgPhaseBlocks so that it starts right after the + // finalization phase. + int dkgMiningWindowStart; + + // The ending block inside the DKG interval for when mining of commitments ends. The value is inclusive. + // Choose a value so that miners have enough time to receive the commitment and mine it. Also take into consideration + // that miners might omit real commitments and revert to always including null commitments. The mining window should + // be large enough so that other miners have a chance to produce a block containing a non-null commitment. The window + // should at the same time not be too large so that not too much space is wasted with null commitments in case a DKG + // session failed. + int dkgMiningWindowEnd; +}; + /** * Parameters that influence chain consensus. */ @@ -96,6 +156,8 @@ struct Params { int nHighSubsidyBlocks{0}; int nHighSubsidyFactor{1}; + std::map llmqs; + // This is temporary until we reset testnet for retesting of the full DIP3 deployment int nTemporaryTestnetForkDIP3Height{0}; uint256 nTemporaryTestnetForkDIP3BlockHash; diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index cd2a1d0bb7..99e9e80bac 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -14,6 +14,9 @@ #include "deterministicmns.h" #include "specialtx.h" +#include "llmq/quorums_commitment.h" +#include "llmq/quorums_blockprocessor.h" + bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { if (tx.nVersion != 3 || tx.nType == TRANSACTION_NORMAL) @@ -34,6 +37,8 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali return CheckProUpRevTx(tx, pindexPrev, state); case TRANSACTION_COINBASE: return CheckCbTx(tx, pindexPrev, state); + case TRANSACTION_QUORUM_COMMITMENT: + return true; // can't really check much here. checks are done in ProcessBlock } return state.DoS(10, false, REJECT_INVALID, "bad-tx-type-check"); @@ -53,6 +58,8 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida return true; // handled in batches per block case TRANSACTION_COINBASE: return true; // nothing to do + case TRANSACTION_QUORUM_COMMITMENT: + return true; // handled per block } return state.DoS(100, false, REJECT_INVALID, "bad-tx-type-proc"); @@ -72,6 +79,8 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return true; // handled in batches per block case TRANSACTION_COINBASE: return true; // nothing to do + case TRANSACTION_QUORUM_COMMITMENT: + return true; // handled per block } return false; @@ -97,6 +106,10 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV return false; } + if (!llmq::quorumBlockProcessor->ProcessBlock(block, pindex->pprev, state)) { + return false; + } + return true; } @@ -109,6 +122,10 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) } } + if (!llmq::quorumBlockProcessor->UndoBlock(block, pindex)) { + return false; + } + if (!deterministicMNManager->UndoBlock(block, pindex)) { return false; } diff --git a/src/init.cpp b/src/init.cpp index 854e371dd6..118700cf6e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -68,6 +68,8 @@ #include "evo/deterministicmns.h" +#include "llmq/quorums_init.h" + #include #include #include @@ -303,6 +305,7 @@ void PrepareShutdown() pcoinsdbview = NULL; delete pblocktree; pblocktree = NULL; + llmq::DestroyLLMQSystem(); delete deterministicMNManager; deterministicMNManager = NULL; delete evoDb; @@ -1713,6 +1716,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) delete pcoinsdbview; delete pcoinscatcher; delete pblocktree; + llmq::DestroyLLMQSystem(); delete deterministicMNManager; delete evoDb; @@ -1722,6 +1726,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); + llmq::InitLLMQSystem(*evoDb); if (fReindex) { pblocktree->WriteReindexing(true); diff --git a/src/llmq/quorums_blockprocessor.cpp b/src/llmq/quorums_blockprocessor.cpp new file mode 100644 index 0000000000..c40c1a85c9 --- /dev/null +++ b/src/llmq/quorums_blockprocessor.cpp @@ -0,0 +1,437 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "quorums_blockprocessor.h" +#include "quorums_commitment.h" +#include "quorums_utils.h" + +#include "evo/specialtx.h" + +#include "chain.h" +#include "chainparams.h" +#include "consensus/validation.h" +#include "net.h" +#include "net_processing.h" +#include "primitives/block.h" +#include "validation.h" + +namespace llmq +{ + +CQuorumBlockProcessor* quorumBlockProcessor; + +static const std::string DB_MINED_COMMITMENT = "q_mc"; + +void CQuorumBlockProcessor::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) +{ + if (strCommand == NetMsgType::QFCOMMITMENT) { + CFinalCommitment qc; + vRecv >> qc; + + if (qc.IsNull()) { + LOCK(cs_main); + LogPrintf("CQuorumBlockProcessor::%s -- null commitment from peer=%d\n", __func__, pfrom->id); + Misbehaving(pfrom->id, 100); + return; + } + + if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)qc.llmqType)) { + LOCK(cs_main); + LogPrintf("CQuorumBlockProcessor::%s -- invalid commitment type %d from peer=%d\n", __func__, + qc.llmqType, pfrom->id); + Misbehaving(pfrom->id, 100); + return; + } + auto type = (Consensus::LLMQType)qc.llmqType; + const auto& params = Params().GetConsensus().llmqs.at(type); + + // Verify that quorumHash is part of the active chain and that it's the first block in the DKG interval + { + LOCK(cs_main); + if (!mapBlockIndex.count(qc.quorumHash)) { + LogPrintf("CQuorumBlockProcessor::%s -- unknown block %s in commitment, peer=%d\n", __func__, + qc.quorumHash.ToString(), pfrom->id); + // can't really punish the node here, as we might simply be the one that is on the wrong chain or not + // fully synced + return; + } + auto pquorumIndex = mapBlockIndex[qc.quorumHash]; + if (chainActive.Tip()->GetAncestor(pquorumIndex->nHeight) != pquorumIndex) { + LogPrintf("CQuorumBlockProcessor::%s -- block %s not in active chain, peer=%d\n", __func__, + qc.quorumHash.ToString(), pfrom->id); + // same, can't punish + return; + } + int quorumHeight = pquorumIndex->nHeight - (pquorumIndex->nHeight % params.dkgInterval); + if (quorumHeight != pquorumIndex->nHeight) { + LogPrintf("CQuorumBlockProcessor::%s -- block %s is not the first block in the DKG interval, peer=%d\n", __func__, + qc.quorumHash.ToString(), pfrom->id); + Misbehaving(pfrom->id, 100); + return; + } + } + + { + // Check if we already got a better one locally + // We do this before verifying the commitment to avoid DoS + LOCK(minableCommitmentsCs); + auto k = std::make_pair(type, qc.quorumHash); + auto it = minableCommitmentsByQuorum.find(k); + if (it != minableCommitmentsByQuorum.end()) { + auto jt = minableCommitments.find(it->second); + if (jt != minableCommitments.end()) { + if (jt->second.CountSigners() <= qc.CountSigners()) { + return; + } + } + } + } + + auto members = CLLMQUtils::GetAllQuorumMembers(type, qc.quorumHash); + + if (!qc.Verify(members)) { + LOCK(cs_main); + LogPrintf("CQuorumBlockProcessor::%s -- commitment for quorum %s:%d is not valid, peer=%d\n", __func__, + qc.quorumHash.ToString(), qc.llmqType, pfrom->id); + Misbehaving(pfrom->id, 100); + return; + } + + LogPrintf("CQuorumBlockProcessor::%s -- received commitment for quorum %s:%d, validMembers=%d, signers=%d, peer=%d\n", __func__, + qc.quorumHash.ToString(), qc.llmqType, qc.CountValidMembers(), qc.CountSigners(), pfrom->id); + + AddMinableCommitment(qc); + } +} + +bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state) +{ + AssertLockHeld(cs_main); + + bool fDIP0003Active = VersionBitsState(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE; + if (!fDIP0003Active) { + return true; + } + + int nHeight = pindexPrev->nHeight + 1; + + std::map qcs; + if (!GetCommitmentsFromBlock(block, pindexPrev, qcs, state)) { + return false; + } + + // The following checks make sure that there is always a (possibly null) commitment while in the mining phase + // until the first non-null commitment has been mined. After the non-null commitment, no other commitments are + // allowed, including null commitments. + for (const auto& p : Params().GetConsensus().llmqs) { + auto type = p.first; + + uint256 quorumHash = GetQuorumBlockHash(type, pindexPrev); + + // does the currently processed block contain a (possibly null) commitment for the current session? + bool hasCommitmentInNewBlock = qcs.count(type) != 0; + bool isCommitmentRequired = IsCommitmentRequired(type, pindexPrev); + + if (hasCommitmentInNewBlock && !isCommitmentRequired) { + // If we're either not in the mining phase or a non-null commitment was mined already, reject the block + return state.DoS(100, false, REJECT_INVALID, "bad-qc-not-allowed"); + } + + if (!hasCommitmentInNewBlock && isCommitmentRequired) { + // If no non-null commitment was mined for the mining phase yet and the new block does not include + // a (possibly null) commitment, the block should be rejected. + return state.DoS(100, false, REJECT_INVALID, "bad-qc-missing"); + } + } + + for (auto& p : qcs) { + auto& qc = p.second; + if (!ProcessCommitment(pindexPrev, qc, state)) { + return false; + } + } + return true; +} + +bool CQuorumBlockProcessor::ProcessCommitment(const CBlockIndex* pindexPrev, const CFinalCommitment& qc, CValidationState& state) +{ + auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.llmqType); + + uint256 quorumHash = GetQuorumBlockHash((Consensus::LLMQType)qc.llmqType, pindexPrev); + if (quorumHash.IsNull()) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-block"); + } + if (quorumHash != qc.quorumHash) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-block"); + } + + if (qc.IsNull()) { + if (!qc.VerifyNull(pindexPrev->nHeight + 1)) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid-null"); + } + return true; + } + + if (HasMinedCommitment(params.type, quorumHash)) { + // should not happen as it's already handled in ProcessBlock + return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup"); + } + + if (!IsMiningPhase(params.type, pindexPrev->nHeight + 1)) { + // should not happen as it's already handled in ProcessBlock + return state.DoS(100, false, REJECT_INVALID, "bad-qc-height"); + } + + auto members = CLLMQUtils::GetAllQuorumMembers(params.type, quorumHash); + + if (!qc.Verify(members)) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-invalid"); + } + + // Store commitment in DB + evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)params.type, quorumHash)), qc); + + LogPrintf("CQuorumBlockProcessor::%s -- processed commitment from block. type=%d, quorumHash=%s, signers=%s, validMembers=%d, quorumPublicKey=%s\n", __func__, + qc.llmqType, quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString()); + + return true; +} + +bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, const CBlockIndex* pindex) +{ + AssertLockHeld(cs_main); + + std::map qcs; + CValidationState dummy; + if (!GetCommitmentsFromBlock(block, pindex->pprev, qcs, dummy)) { + return false; + } + + for (auto& p : qcs) { + auto& qc = p.second; + if (qc.IsNull()) { + continue; + } + + evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash))); + + if (!qc.IsNull()) { + // if a reorg happened, we should allow to mine this commitment later + AddMinableCommitment(qc); + } + } + + return true; +} + +bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, std::map& ret, CValidationState& state) +{ + AssertLockHeld(cs_main); + + auto& consensus = Params().GetConsensus(); + bool fDIP0003Active = VersionBitsState(pindexPrev, consensus, Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE; + + ret.clear(); + + for (const auto& tx : block.vtx) { + if (tx->nType == TRANSACTION_QUORUM_COMMITMENT) { + CFinalCommitment qc; + if (!GetTxPayload(*tx, qc)) { + return state.DoS(100, false, REJECT_INVALID, "bad-tx-payload"); + } + + if (!consensus.llmqs.count((Consensus::LLMQType)qc.llmqType)) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-type"); + } + + // only allow one commitment per type and per block + if (ret.count((Consensus::LLMQType)qc.llmqType)) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup"); + } + + ret.emplace((Consensus::LLMQType)qc.llmqType, std::move(qc)); + } + } + + if (!fDIP0003Active && !ret.empty()) { + return state.DoS(100, false, REJECT_INVALID, "bad-qc-premature"); + } + + return true; +} + +bool CQuorumBlockProcessor::IsMiningPhase(Consensus::LLMQType llmqType, int nHeight) +{ + const auto& params = Params().GetConsensus().llmqs.at(llmqType); + int phaseIndex = nHeight % params.dkgInterval; + if (phaseIndex >= params.dkgMiningWindowStart && phaseIndex <= params.dkgMiningWindowEnd) { + return true; + } + return false; +} + +bool CQuorumBlockProcessor::IsCommitmentRequired(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev) +{ + // BEGIN TEMPORARY CODE + bool allowMissingQc = false; + { + // TODO We added the commitments code while DIP3 was already activated on testnet and we want + // to avoid reverting the chain again, as people already had many MNs registered at that time. + // So, we do a simple hardfork here at a fixed height, but only while we're on the original + // DIP3 chain. + // As we need to fork/revert the chain later to re-test all deployment stages of DIP3, we can + // remove all this temporary code later. + LOCK(cs_main); + const auto& consensus = Params().GetConsensus(); + if (consensus.nTemporaryTestnetForkDIP3Height != 0 && + pindexPrev->nHeight + 1 >= consensus.nTemporaryTestnetForkDIP3Height && + pindexPrev->nHeight + 1 < consensus.nTemporaryTestnetForkHeight && + chainActive[consensus.nTemporaryTestnetForkDIP3Height]->GetBlockHash() == consensus.nTemporaryTestnetForkDIP3BlockHash) { + allowMissingQc = true; + } + } + // END TEMPORARY CODE + + uint256 quorumHash = GetQuorumBlockHash(llmqType, pindexPrev); + + // perform extra check for quorumHash.IsNull as the quorum hash is unknown for the first block of a session + // this is because the currently processed block's hash will be the quorumHash of this session + bool isMiningPhase = !quorumHash.IsNull() && IsMiningPhase(llmqType, pindexPrev->nHeight + 1); + + // did we already mine a non-null commitment for this session? + bool hasMinedCommitment = !quorumHash.IsNull() && HasMinedCommitment(llmqType, quorumHash); + + return isMiningPhase && !hasMinedCommitment && !allowMissingQc; +} + +// WARNING: This method returns uint256() on the first block of the DKG interval (because the block hash is not known yet) +uint256 CQuorumBlockProcessor::GetQuorumBlockHash(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev) +{ + auto& params = Params().GetConsensus().llmqs.at(llmqType); + + int nHeight = pindexPrev->nHeight + 1; + int quorumStartHeight = nHeight - (nHeight % params.dkgInterval); + if (quorumStartHeight >= pindexPrev->nHeight) { + return uint256(); + } + auto quorumIndex = pindexPrev->GetAncestor(quorumStartHeight); + assert(quorumIndex); + return quorumIndex->GetBlockHash(); +} + +bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) +{ + auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)llmqType, quorumHash)); + return evoDb.Exists(key); +} + +bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& ret) +{ + auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)llmqType, quorumHash)); + return evoDb.Read(key, ret); +} + +bool CQuorumBlockProcessor::HasMinableCommitment(const uint256& hash) +{ + LOCK(minableCommitmentsCs); + return minableCommitments.count(hash) != 0; +} + +void CQuorumBlockProcessor::AddMinableCommitment(const CFinalCommitment& fqc) +{ + bool relay = false; + uint256 commitmentHash = ::SerializeHash(fqc); + + { + LOCK(minableCommitmentsCs); + + auto k = std::make_pair((Consensus::LLMQType) fqc.llmqType, fqc.quorumHash); + auto ins = minableCommitmentsByQuorum.emplace(k, commitmentHash); + if (ins.second) { + minableCommitments.emplace(commitmentHash, fqc); + relay = true; + } else { + auto& oldFqc = minableCommitments.at(ins.first->second); + if (fqc.CountSigners() > oldFqc.CountSigners()) { + // new commitment has more signers, so override the known one + ins.first->second = commitmentHash; + minableCommitments.erase(ins.first->second); + minableCommitments.emplace(commitmentHash, fqc); + relay = true; + } + } + } + + // We only relay the new commitment if it's new or better then the old one + if (relay) { + CInv inv(MSG_QUORUM_FINAL_COMMITMENT, commitmentHash); + g_connman->RelayInv(inv, DMN_PROTO_VERSION); + } +} + +bool CQuorumBlockProcessor::GetMinableCommitmentByHash(const uint256& commitmentHash, llmq::CFinalCommitment& ret) +{ + LOCK(minableCommitmentsCs); + auto it = minableCommitments.find(commitmentHash); + if (it == minableCommitments.end()) { + return false; + } + ret = it->second; + return true; +} + +// Will return false if no commitment should be mined +// Will return true and a null commitment if no minable commitment is known and none was mined yet +bool CQuorumBlockProcessor::GetMinableCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev, CFinalCommitment& ret) +{ + AssertLockHeld(cs_main); + + int nHeight = pindexPrev->nHeight + 1; + + if (!IsCommitmentRequired(llmqType, pindexPrev)) { + // no commitment required + return false; + } + + uint256 quorumHash = GetQuorumBlockHash(llmqType, pindexPrev); + if (quorumHash.IsNull()) { + return false; + } + + LOCK(minableCommitmentsCs); + + auto k = std::make_pair(llmqType, quorumHash); + auto it = minableCommitmentsByQuorum.find(k); + if (it == minableCommitmentsByQuorum.end()) { + // null commitment required + ret = CFinalCommitment(Params().GetConsensus().llmqs.at(llmqType), quorumHash); + ret.quorumVvecHash = ::SerializeHash(std::make_pair(quorumHash, nHeight)); + return true; + } + + ret = minableCommitments.at(it->second); + + return true; +} + +bool CQuorumBlockProcessor::GetMinableCommitmentTx(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev, CTransactionRef& ret) +{ + AssertLockHeld(cs_main); + + CFinalCommitment qc; + if (!GetMinableCommitment(llmqType, pindexPrev, qc)) { + return false; + } + + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = TRANSACTION_QUORUM_COMMITMENT; + SetTxPayload(tx, qc); + + ret = MakeTransactionRef(tx); + + return true; +} + +} diff --git a/src/llmq/quorums_blockprocessor.h b/src/llmq/quorums_blockprocessor.h new file mode 100644 index 0000000000..ab1f34c968 --- /dev/null +++ b/src/llmq/quorums_blockprocessor.h @@ -0,0 +1,61 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_QUORUMS_BLOCKPROCESSOR_H +#define DASH_QUORUMS_BLOCKPROCESSOR_H + +#include "llmq/quorums_commitment.h" + +#include "consensus/params.h" +#include "primitives/transaction.h" +#include "sync.h" + +#include + +class CNode; +class CConnman; + +namespace llmq +{ + +class CQuorumBlockProcessor +{ +private: + CEvoDB& evoDb; + + // TODO cleanup + CCriticalSection minableCommitmentsCs; + std::map, uint256> minableCommitmentsByQuorum; + std::map minableCommitments; + +public: + CQuorumBlockProcessor(CEvoDB& _evoDb) : evoDb(_evoDb) {} + + void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + + bool ProcessBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state); + bool UndoBlock(const CBlock& block, const CBlockIndex* pindex); + + void AddMinableCommitment(const CFinalCommitment& fqc); + bool HasMinableCommitment(const uint256& hash); + bool GetMinableCommitmentByHash(const uint256& commitmentHash, CFinalCommitment& ret); + bool GetMinableCommitment(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev, CFinalCommitment& ret); + bool GetMinableCommitmentTx(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev, CTransactionRef& ret); + + bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash); + bool GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& ret); + +private: + bool GetCommitmentsFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, std::map& ret, CValidationState& state); + bool ProcessCommitment(const CBlockIndex* pindexPrev, const CFinalCommitment& qc, CValidationState& state); + bool IsMiningPhase(Consensus::LLMQType llmqType, int nHeight); + bool IsCommitmentRequired(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev); + uint256 GetQuorumBlockHash(Consensus::LLMQType llmqType, const CBlockIndex* pindexPrev); +}; + +extern CQuorumBlockProcessor* quorumBlockProcessor; + +} + +#endif//DASH_QUORUMS_BLOCKPROCESSOR_H diff --git a/src/llmq/quorums_commitment.cpp b/src/llmq/quorums_commitment.cpp new file mode 100644 index 0000000000..f8e0a6b51a --- /dev/null +++ b/src/llmq/quorums_commitment.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "quorums_commitment.h" +#include "quorums_utils.h" + +#include "chainparams.h" + +#include + +namespace llmq +{ + +CFinalCommitment::CFinalCommitment(const Consensus::LLMQParams& params, const uint256& _quorumHash) : + llmqType(params.type), + quorumHash(_quorumHash), + signers(params.size), + validMembers(params.size) +{ +} + +#define LogPrintfFinalCommitment(...) do { \ + LogPrintStr(strprintf("CFinalCommitment::%s -- %s", __func__, tinyformat::format(__VA_ARGS__))); \ +} while(0) + +bool CFinalCommitment::Verify(const std::vector& members) const +{ + if (nVersion == 0 || nVersion > CURRENT_VERSION) { + return false; + } + + if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)llmqType)) { + LogPrintfFinalCommitment("invalid llmqType=%d\n", llmqType); + return false; + } + const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)llmqType); + + if (!VerifySizes(params)) { + return false; + } + + if (CountValidMembers() < params.minSize) { + LogPrintfFinalCommitment("invalid validMembers count. validMembersCount=%d\n", CountValidMembers()); + return false; + } + if (CountSigners() < params.minSize) { + LogPrintfFinalCommitment("invalid signers count. signersCount=%d\n", CountSigners()); + return false; + } + if (!quorumPublicKey.IsValid()) { + LogPrintfFinalCommitment("invalid quorumPublicKey\n"); + return false; + } + if (quorumVvecHash.IsNull()) { + LogPrintfFinalCommitment("invalid quorumVvecHash\n"); + return false; + } + if (!membersSig.IsValid()) { + LogPrintfFinalCommitment("invalid membersSig\n"); + return false; + } + if (!quorumSig.IsValid()) { + LogPrintfFinalCommitment("invalid vvecSig\n"); + return false; + } + + for (size_t i = members.size(); i < params.size; i++) { + if (validMembers[i]) { + LogPrintfFinalCommitment("invalid validMembers bitset. bit %d should not be set\n", i); + return false; + } + if (signers[i]) { + LogPrintfFinalCommitment("invalid signers bitset. bit %d should not be set\n", i); + return false; + } + } + + uint256 commitmentHash = CLLMQUtils::BuildCommitmentHash((uint8_t)params.type, quorumHash, validMembers, quorumPublicKey, quorumVvecHash); + + std::vector memberPubKeys; + for (size_t i = 0; i < members.size(); i++) { + if (!signers[i]) { + continue; + } + memberPubKeys.emplace_back(members[i]->pdmnState->pubKeyOperator); + } + + if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) { + LogPrintfFinalCommitment("invalid aggregated members signature\n"); + return false; + } + + if (!quorumSig.VerifyInsecure(quorumPublicKey, commitmentHash)) { + LogPrintfFinalCommitment("invalid quorum signature\n"); + return false; + } + + return true; +} + +bool CFinalCommitment::VerifyNull(int nHeight) const +{ + if (!Params().GetConsensus().llmqs.count((Consensus::LLMQType)llmqType)) { + LogPrintfFinalCommitment("invalid llmqType=%d\n", llmqType); + return false; + } + const auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)llmqType); + + if (!IsNull() || !VerifySizes(params)) { + return false; + } + + uint256 expectedQuorumVvecHash = ::SerializeHash(std::make_pair(quorumHash, nHeight)); + if (quorumVvecHash != expectedQuorumVvecHash) { + return false; + } + + return true; +} + +bool CFinalCommitment::VerifySizes(const Consensus::LLMQParams& params) const +{ + if (signers.size() != params.size) { + LogPrintfFinalCommitment("invalid signers.size=%d\n", signers.size()); + return false; + } + if (validMembers.size() != params.size) { + LogPrintfFinalCommitment("invalid signers.size=%d\n", signers.size()); + return false; + } + return true; +} + +void CFinalCommitment::ToJson(UniValue& obj) const +{ + obj.setObject(); + obj.push_back(Pair("version", (int)nVersion)); + obj.push_back(Pair("llmqType", (int)llmqType)); + obj.push_back(Pair("quorumHash", quorumHash.ToString())); + obj.push_back(Pair("signersCount", CountSigners())); + obj.push_back(Pair("validMembersCount", CountValidMembers())); + obj.push_back(Pair("quorumPublicKey", quorumPublicKey.ToString())); +} + +} diff --git a/src/llmq/quorums_commitment.h b/src/llmq/quorums_commitment.h new file mode 100644 index 0000000000..bbf702b22b --- /dev/null +++ b/src/llmq/quorums_commitment.h @@ -0,0 +1,92 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_QUORUMS_COMMITMENT_H +#define DASH_QUORUMS_COMMITMENT_H + +#include "consensus/params.h" + +#include "evo/deterministicmns.h" + +#include "bls/bls.h" + +namespace llmq +{ + +// This message is an aggregation of all received premature commitments and only valid if +// enough (>=threshold) premature commitments were aggregated +// This is mined on-chain as part of TRANSACTION_QUORUM_COMMITMENT +class CFinalCommitment +{ +public: + static const uint16_t CURRENT_VERSION = 1; + +public: + uint16_t nVersion{CURRENT_VERSION}; + uint8_t llmqType{Consensus::LLMQ_NONE}; + uint256 quorumHash; + std::vector signers; + std::vector validMembers; + + CBLSPublicKey quorumPublicKey; + uint256 quorumVvecHash; + + CBLSSignature quorumSig; // recovered threshold sig of blockHash+validMembers+pubKeyHash+vvecHash + CBLSSignature membersSig; // aggregated member sig of blockHash+validMembers+pubKeyHash+vvecHash + +public: + CFinalCommitment() {} + CFinalCommitment(const Consensus::LLMQParams& params, const uint256& _quorumHash); + + int CountSigners() const + { + return (int)std::count(signers.begin(), signers.end(), true); + } + int CountValidMembers() const + { + return (int)std::count(validMembers.begin(), validMembers.end(), true); + } + + bool Verify(const std::vector& members) const; + bool VerifyNull(int nHeight) const; + bool VerifySizes(const Consensus::LLMQParams& params) const; + + void ToJson(UniValue& obj) const; + +public: + ADD_SERIALIZE_METHODS + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nVersion); + READWRITE(llmqType); + READWRITE(quorumHash); + READWRITE(DYNBITSET(signers)); + READWRITE(DYNBITSET(validMembers)); + READWRITE(quorumPublicKey); + READWRITE(quorumVvecHash); + READWRITE(quorumSig); + READWRITE(membersSig); + } + +public: + bool IsNull() const + { + if (std::count(signers.begin(), signers.end(), true) || + std::count(validMembers.begin(), validMembers.end(), true)) { + return false; + } + if (quorumPublicKey.IsValid() || + membersSig.IsValid() || + quorumSig.IsValid()) { + return false; + } + return true; + } +}; + +} + +#endif //DASH_QUORUMS_COMMITMENT_H diff --git a/src/llmq/quorums_init.cpp b/src/llmq/quorums_init.cpp new file mode 100644 index 0000000000..f6abb5d146 --- /dev/null +++ b/src/llmq/quorums_init.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "quorums_init.h" + +#include "quorums_blockprocessor.h" +#include "quorums_commitment.h" + +namespace llmq +{ + +void InitLLMQSystem(CEvoDB& evoDb) +{ + quorumBlockProcessor = new CQuorumBlockProcessor(evoDb); +} + +void DestroyLLMQSystem() +{ + delete quorumBlockProcessor; + quorumBlockProcessor = nullptr; +} + +} diff --git a/src/llmq/quorums_init.h b/src/llmq/quorums_init.h new file mode 100644 index 0000000000..137248b885 --- /dev/null +++ b/src/llmq/quorums_init.h @@ -0,0 +1,18 @@ +// Copyright (c) 2018 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DASH_QUORUMS_INIT_H +#define DASH_QUORUMS_INIT_H + +class CEvoDB; + +namespace llmq +{ + +void InitLLMQSystem(CEvoDB& evoDb); +void DestroyLLMQSystem(); + +} + +#endif //DASH_QUORUMS_INIT_H diff --git a/src/llmq/quorums_utils.cpp b/src/llmq/quorums_utils.cpp new file mode 100644 index 0000000000..452909e07b --- /dev/null +++ b/src/llmq/quorums_utils.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2018 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 "quorums_utils.h" + +#include "chainparams.h" +#include "random.h" + +namespace llmq +{ + +std::vector CLLMQUtils::GetAllQuorumMembers(Consensus::LLMQType llmqType, const uint256& blockHash) +{ + auto& params = Params().GetConsensus().llmqs.at(llmqType); + auto allMns = deterministicMNManager->GetListForBlock(blockHash); + auto modifier = ::SerializeHash(std::make_pair((uint8_t)llmqType, blockHash)); + return allMns.CalculateQuorum(params.size, modifier); +} + +uint256 CLLMQUtils::BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash) +{ + CHashWriter hw(SER_NETWORK, 0); + hw << llmqType; + hw << blockHash; + hw << DYNBITSET(validMembers); + hw << pubKey; + hw << vvecHash; + return hw.GetHash(); +} + + +} diff --git a/src/llmq/quorums_utils.h b/src/llmq/quorums_utils.h new file mode 100644 index 0000000000..2aab41628d --- /dev/null +++ b/src/llmq/quorums_utils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2018 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 DASH_QUORUMS_UTILS_H +#define DASH_QUORUMS_UTILS_H + +#include "consensus/params.h" + +#include "evo/deterministicmns.h" + +#include + +namespace llmq +{ + +class CLLMQUtils +{ +public: + // includes members which failed DKG + static std::vector GetAllQuorumMembers(Consensus::LLMQType llmqType, const uint256& blockHash); + + static uint256 BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash); +}; + +} + +#endif//DASH_QUORUMS_UTILS_H diff --git a/src/miner.cpp b/src/miner.cpp index 3a1cf7dde2..0229cd872f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -33,6 +33,8 @@ #include "evo/simplifiedmns.h" #include "evo/deterministicmns.h" +#include "llmq/quorums_blockprocessor.h" + #include #include #include @@ -150,6 +152,16 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc ? nMedianTimePast : pblock->GetBlockTime(); + if (fDIP0003Active_context) { + for (auto& p : Params().GetConsensus().llmqs) { + CTransactionRef qcTx; + if (llmq::quorumBlockProcessor->GetMinableCommitmentTx(p.first, pindexPrev, qcTx)) { + pblock->vtx.emplace_back(qcTx); + pblocktemplate->vTxFees.emplace_back(0); + } + } + } + addPriorityTxs(); int nPackagesSelected = 0; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 908e0ba42a..e311f093fb 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -44,6 +44,8 @@ #include "evo/deterministicmns.h" #include "evo/simplifiedmns.h" +#include "llmq/quorums_commitment.h" +#include "llmq/quorums_blockprocessor.h" #include @@ -962,6 +964,9 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) case MSG_MASTERNODE_VERIFY: return mnodeman.mapSeenMasternodeVerification.count(inv.hash); + + case MSG_QUORUM_FINAL_COMMITMENT: + return llmq::quorumBlockProcessor->HasMinableCommitment(inv.hash); } // Don't know what it is, just say we already got one @@ -1275,6 +1280,14 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam } } + if (!push && (inv.type == MSG_QUORUM_FINAL_COMMITMENT)) { + llmq::CFinalCommitment o; + if (llmq::quorumBlockProcessor->GetMinableCommitmentByHash(inv.hash, o)) { + connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QFCOMMITMENT, o)); + push = true; + } + } + if (!push) vNotFound.push_back(inv); } @@ -2885,6 +2898,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr sporkManager.ProcessSpork(pfrom, strCommand, vRecv, connman); masternodeSync.ProcessMessage(pfrom, strCommand, vRecv); governance.ProcessMessage(pfrom, strCommand, vRecv, connman); + llmq::quorumBlockProcessor->ProcessMessage(pfrom, strCommand, vRecv, connman); } else { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index c2c2d1d556..ec99b589f2 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -19,6 +19,7 @@ enum { TRANSACTION_PROVIDER_UPDATE_REGISTRAR = 3, TRANSACTION_PROVIDER_UPDATE_REVOKE = 4, TRANSACTION_COINBASE = 5, + TRANSACTION_QUORUM_COMMITMENT = 6, }; /** An outpoint - a combination of a transaction hash and an index n into its vout */ diff --git a/src/protocol.cpp b/src/protocol.cpp index fac2fab6e7..d9023cdcb7 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -71,6 +71,7 @@ const char *MNGOVERNANCEOBJECTVOTE="govobjvote"; const char *MNVERIFY="mnv"; const char *GETMNLISTDIFF="getmnlistd"; const char *MNLISTDIFF="mnlistdiff"; +const char *QFCOMMITMENT="qfcommit"; }; static const char* ppszTypeName[] = @@ -98,6 +99,7 @@ static const char* ppszTypeName[] = NetMsgType::MNGOVERNANCEOBJECTVOTE, NetMsgType::MNVERIFY, "compact block", // Should never occur + NetMsgType::QFCOMMITMENT, }; /** All known message types. Keep this in the same order as the list of @@ -157,6 +159,7 @@ const static std::string allNetMessageTypes[] = { NetMsgType::MNVERIFY, NetMsgType::GETMNLISTDIFF, NetMsgType::MNLISTDIFF, + NetMsgType::QFCOMMITMENT, }; const static std::vector allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes)); diff --git a/src/protocol.h b/src/protocol.h index f1dccca68e..562d5fb9d4 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -270,6 +270,7 @@ extern const char *MNGOVERNANCEOBJECTVOTE; extern const char *MNVERIFY; extern const char *GETMNLISTDIFF; extern const char *MNLISTDIFF; +extern const char *QFCOMMITMENT; }; /* Get a vector of all valid message types (see above) */ @@ -371,6 +372,7 @@ enum GetDataMsg { // Nodes may always request a MSG_CMPCT_BLOCK in a getdata, however, // MSG_CMPCT_BLOCK should not appear in any invs except as a part of getdata. MSG_CMPCT_BLOCK = 20, //!< Defined in BIP152 + MSG_QUORUM_FINAL_COMMITMENT = 21, }; /** inv message data */ diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index eeba5d9439..e67b63ec8e 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -15,6 +15,7 @@ #include "util.h" #include "evo/deterministicmns.h" +#include "llmq/quorums_init.h" #include #include @@ -53,6 +54,9 @@ void RPCNestedTests::rpcNestedTests() pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); deterministicMNManager = new CDeterministicMNManager(*evoDb); + + llmq::InitLLMQSystem(*evoDb); + pcoinsTip = new CCoinsViewCache(pcoinsdbview); InitBlockIndex(chainparams); { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index a4dd474ca9..86b57aca74 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -32,6 +32,7 @@ #include "evo/specialtx.h" #include "evo/providertx.h" #include "evo/cbtx.h" +#include "llmq/quorums_commitment.h" #include @@ -166,6 +167,13 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) cbTx.ToJson(obj); entry.push_back(Pair("cbTx", obj)); } + } else if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) { + llmq::CFinalCommitment qcTx; + if (GetTxPayload(tx, qcTx)) { + UniValue obj; + qcTx.ToJson(obj); + entry.push_back(Pair("qcTx", obj)); + } } if (!hashBlock.IsNull()) { diff --git a/src/test/test_dash.cpp b/src/test/test_dash.cpp index 2654382264..7bc3f7e9f7 100644 --- a/src/test/test_dash.cpp +++ b/src/test/test_dash.cpp @@ -27,6 +27,7 @@ #include "evo/specialtx.h" #include "evo/deterministicmns.h" #include "evo/cbtx.h" +#include "llmq/quorums_init.h" #include @@ -73,6 +74,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); deterministicMNManager = new CDeterministicMNManager(*evoDb); + llmq::InitLLMQSystem(*evoDb); pcoinsTip = new CCoinsViewCache(pcoinsdbview); InitBlockIndex(chainparams); { @@ -95,6 +97,7 @@ TestingSetup::~TestingSetup() threadGroup.join_all(); UnloadBlockIndex(); delete pcoinsTip; + llmq::DestroyLLMQSystem(); delete deterministicMNManager; delete pcoinsdbview; delete pblocktree; @@ -144,8 +147,17 @@ CBlock TestChainSetup::CreateBlock(const std::vector& txns, std::unique_ptr pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; + std::vector llmqCommitments; + for (const auto& tx : block.vtx) { + if (tx->nVersion == 3 && tx->nType == TRANSACTION_QUORUM_COMMITMENT) { + llmqCommitments.emplace_back(tx); + } + } + // Replace mempool-selected txns with just coinbase plus passed-in txns: block.vtx.resize(1); + // Re-add quorum commitments + block.vtx.insert(block.vtx.end(), llmqCommitments.begin(), llmqCommitments.end()); BOOST_FOREACH(const CMutableTransaction& tx, txns) block.vtx.push_back(MakeTransactionRef(tx)); diff --git a/src/validation.cpp b/src/validation.cpp index 8971cff84e..69856143a1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -514,10 +514,15 @@ int GetUTXOConfirmations(const COutPoint& outpoint) bool CheckTransaction(const CTransaction& tx, CValidationState &state) { + bool allowEmptyTxInOut = false; + if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) { + allowEmptyTxInOut = true; + } + // Basic checks that don't depend on any context - if (tx.vin.empty()) + if (!allowEmptyTxInOut && tx.vin.empty()) return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); - if (tx.vout.empty()) + if (!allowEmptyTxInOut && tx.vout.empty()) return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); // Size limits if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_LEGACY_BLOCK_SIZE) @@ -580,7 +585,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, tx.nType != TRANSACTION_PROVIDER_UPDATE_SERVICE && tx.nType != TRANSACTION_PROVIDER_UPDATE_REGISTRAR && tx.nType != TRANSACTION_PROVIDER_UPDATE_REVOKE && - tx.nType != TRANSACTION_COINBASE) { + tx.nType != TRANSACTION_COINBASE && + tx.nType != TRANSACTION_QUORUM_COMMITMENT) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } if (tx.IsCoinBase() && tx.nType != TRANSACTION_COINBASE) @@ -645,6 +651,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C if (!ContextualCheckTransaction(tx, state, Params().GetConsensus(), chainActive.Tip())) return error("%s: ContextualCheckTransaction: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); + if (tx.nVersion == 3 && tx.nType == TRANSACTION_QUORUM_COMMITMENT) { + // quorum commitment is not allowed outside of blocks + return state.DoS(100, false, REJECT_INVALID, "qc-not-allowed"); + } + if (!CheckSpecialTx(tx, chainActive.Tip(), state)) return false;