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:
Alexander Block 2018-11-23 15:42:09 +01:00 committed by GitHub
parent 6c190d1bb3
commit 22b5952c5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1085 additions and 4 deletions

View File

@ -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
)

View File

@ -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))

View File

@ -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 \

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View 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;
}
}

View 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

View 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()));
}
}

View 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
View 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
View 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

View 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
View 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

View File

@ -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;

View File

@ -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
{

View File

@ -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 */

View File

@ -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));

View File

@ -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 */

View File

@ -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);
{

View File

@ -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()) {

View File

@ -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));

View File

@ -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;