mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 19:42:46 +01:00
Implement and enforce DIP6 commitments (#2477)
* Add LLMQ parameters to consensus params * Add DIP6 quorum commitment special TX * Implement CQuorumBlockProcessor which validates and handles commitments * Add quorum commitments to new blocks * Propagate QFCOMMITMENT messages to all nodes * Allow special transactions in blocks which have no inputs/outputs But only for TRANSACTION_QUORUM_COMMITMENT for now. * Add quorum commitments to self-crafted blocks in DIP3 tests * Add simple fork logic for current testnet This should avoid a fork on the current testnet. It only applies to the current chain which activated DIP3 at height 264000 and block 00000048e6e71d4bd90e7c456dcb94683ae832fcad13e1760d8283f7e89f332f. When we revert the chain to retest the DIP3 deployment, this fork logic can be removed again. * Use quorumVvecHash instead of quorumHash to make null commitments unique Implementation of https://github.com/dashpay/dips/pull/31 * Re-add quorum commitments after pruning mempool selected blocks * Refactor CQuorumBlockProcessor::ProcessBlock to have less nested if/else statements Also add BEGIN/END markers for temporary code. * Add comments/documentation to LLMQParams * Move code which determines if a commitment is required into IsCommitmentRequired This should make the code easier to read and also removes some duplication. The also changes the error types that are possible from 3 to 2 now. Instead of having "bad-qc-already-mined" and "bad-qc-not-mining-phase", there is only "bad-qc-not-allowed" now. * Use new parameter from consensus parames for the temporary fork
This commit is contained in:
parent
6c190d1bb3
commit
22b5952c5a
@ -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
|
||||
)
|
||||
|
@ -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))
|
||||
|
@ -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 \
|
||||
|
@ -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<SeedSpec6>(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,6 +745,10 @@ 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)
|
||||
|
@ -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<LLMQType, LLMQParams> llmqs;
|
||||
|
||||
// This is temporary until we reset testnet for retesting of the full DIP3 deployment
|
||||
int nTemporaryTestnetForkDIP3Height{0};
|
||||
uint256 nTemporaryTestnetForkDIP3BlockHash;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -68,6 +68,8 @@
|
||||
|
||||
#include "evo/deterministicmns.h"
|
||||
|
||||
#include "llmq/quorums_init.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
@ -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);
|
||||
|
437
src/llmq/quorums_blockprocessor.cpp
Normal file
437
src/llmq/quorums_blockprocessor.cpp
Normal file
@ -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<Consensus::LLMQType, CFinalCommitment> 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<Consensus::LLMQType, CFinalCommitment> 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<Consensus::LLMQType, CFinalCommitment>& 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;
|
||||
}
|
||||
|
||||
}
|
61
src/llmq/quorums_blockprocessor.h
Normal file
61
src/llmq/quorums_blockprocessor.h
Normal file
@ -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 <map>
|
||||
|
||||
class CNode;
|
||||
class CConnman;
|
||||
|
||||
namespace llmq
|
||||
{
|
||||
|
||||
class CQuorumBlockProcessor
|
||||
{
|
||||
private:
|
||||
CEvoDB& evoDb;
|
||||
|
||||
// TODO cleanup
|
||||
CCriticalSection minableCommitmentsCs;
|
||||
std::map<std::pair<Consensus::LLMQType, uint256>, uint256> minableCommitmentsByQuorum;
|
||||
std::map<uint256, CFinalCommitment> 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<Consensus::LLMQType, CFinalCommitment>& 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
|
146
src/llmq/quorums_commitment.cpp
Normal file
146
src/llmq/quorums_commitment.cpp
Normal file
@ -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 <univalue.h>
|
||||
|
||||
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<CDeterministicMNCPtr>& 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<CBLSPublicKey> 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()));
|
||||
}
|
||||
|
||||
}
|
92
src/llmq/quorums_commitment.h
Normal file
92
src/llmq/quorums_commitment.h
Normal file
@ -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<bool> signers;
|
||||
std::vector<bool> 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<CDeterministicMNCPtr>& members) const;
|
||||
bool VerifyNull(int nHeight) const;
|
||||
bool VerifySizes(const Consensus::LLMQParams& params) const;
|
||||
|
||||
void ToJson(UniValue& obj) const;
|
||||
|
||||
public:
|
||||
ADD_SERIALIZE_METHODS
|
||||
|
||||
template<typename Stream, typename Operation>
|
||||
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
|
24
src/llmq/quorums_init.cpp
Normal file
24
src/llmq/quorums_init.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
18
src/llmq/quorums_init.h
Normal file
18
src/llmq/quorums_init.h
Normal file
@ -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
|
33
src/llmq/quorums_utils.cpp
Normal file
33
src/llmq/quorums_utils.cpp
Normal file
@ -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<CDeterministicMNCPtr> 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<bool>& 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
28
src/llmq/quorums_utils.h
Normal file
28
src/llmq/quorums_utils.h
Normal file
@ -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 <vector>
|
||||
|
||||
namespace llmq
|
||||
{
|
||||
|
||||
class CLLMQUtils
|
||||
{
|
||||
public:
|
||||
// includes members which failed DKG
|
||||
static std::vector<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqType, const uint256& blockHash);
|
||||
|
||||
static uint256 BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif//DASH_QUORUMS_UTILS_H
|
@ -33,6 +33,8 @@
|
||||
#include "evo/simplifiedmns.h"
|
||||
#include "evo/deterministicmns.h"
|
||||
|
||||
#include "llmq/quorums_blockprocessor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
@ -150,6 +152,16 @@ std::unique_ptr<CBlockTemplate> 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;
|
||||
|
@ -44,6 +44,8 @@
|
||||
|
||||
#include "evo/deterministicmns.h"
|
||||
#include "evo/simplifiedmns.h"
|
||||
#include "llmq/quorums_commitment.h"
|
||||
#include "llmq/quorums_blockprocessor.h"
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
@ -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 */
|
||||
|
@ -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<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes));
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "util.h"
|
||||
|
||||
#include "evo/deterministicmns.h"
|
||||
#include "llmq/quorums_init.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QtGlobal>
|
||||
@ -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);
|
||||
{
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "evo/specialtx.h"
|
||||
#include "evo/providertx.h"
|
||||
#include "evo/cbtx.h"
|
||||
#include "llmq/quorums_commitment.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@ -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()) {
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "evo/specialtx.h"
|
||||
#include "evo/deterministicmns.h"
|
||||
#include "evo/cbtx.h"
|
||||
#include "llmq/quorums_init.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@ -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<CMutableTransaction>& txns,
|
||||
std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey);
|
||||
CBlock& block = pblocktemplate->block;
|
||||
|
||||
std::vector<CTransactionRef> 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));
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user