2019-01-29 15:53:14 +01:00
|
|
|
// Copyright (c) 2018-2019 The Dash Core developers
|
2018-11-23 15:42:09 +01:00
|
|
|
// 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"
|
2019-01-21 05:45:37 +01:00
|
|
|
#include "quorums_debug.h"
|
2018-11-23 15:42:09 +01:00
|
|
|
#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);
|
|
|
|
|
2018-11-27 08:04:08 +01:00
|
|
|
if (!qc.Verify(members, true)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-10 06:05:29 +01:00
|
|
|
bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
2018-12-10 06:05:29 +01:00
|
|
|
bool fDIP0003Active = VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE;
|
2018-11-23 15:42:09 +01:00
|
|
|
if (!fDIP0003Active) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<Consensus::LLMQType, CFinalCommitment> qcs;
|
2018-12-10 06:05:29 +01:00
|
|
|
if (!GetCommitmentsFromBlock(block, pindex->pprev, qcs, state)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
// does the currently processed block contain a (possibly null) commitment for the current session?
|
|
|
|
bool hasCommitmentInNewBlock = qcs.count(type) != 0;
|
2018-12-13 09:04:08 +01:00
|
|
|
bool isCommitmentRequired = IsCommitmentRequired(type, pindex->nHeight);
|
2018-11-23 15:42:09 +01:00
|
|
|
|
|
|
|
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;
|
2018-12-13 09:04:08 +01:00
|
|
|
if (!ProcessCommitment(pindex, qc, state)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
bool CQuorumBlockProcessor::ProcessCommitment(const CBlockIndex* pindex, const CFinalCommitment& qc, CValidationState& state)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
|
|
|
auto& params = Params().GetConsensus().llmqs.at((Consensus::LLMQType)qc.llmqType);
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
uint256 quorumHash = GetQuorumBlockHash((Consensus::LLMQType)qc.llmqType, pindex->nHeight);
|
2018-11-23 15:42:09 +01:00
|
|
|
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()) {
|
2018-11-27 08:04:08 +01:00
|
|
|
if (!qc.VerifyNull()) {
|
2018-11-23 15:42:09 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
if (!IsMiningPhase(params.type, pindex->nHeight)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
// 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);
|
|
|
|
|
2018-11-27 08:04:08 +01:00
|
|
|
if (!qc.Verify(members, true)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
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) {
|
2018-11-27 08:04:08 +01:00
|
|
|
CFinalCommitmentTxPayload qc;
|
2018-11-23 15:42:09 +01:00
|
|
|
if (!GetTxPayload(*tx, qc)) {
|
2018-11-27 08:04:08 +01:00
|
|
|
// should not happen as it was verified before processing the block
|
2018-11-23 15:42:09 +01:00
|
|
|
return state.DoS(100, false, REJECT_INVALID, "bad-tx-payload");
|
|
|
|
}
|
|
|
|
|
|
|
|
// only allow one commitment per type and per block
|
2018-11-27 08:04:08 +01:00
|
|
|
if (ret.count((Consensus::LLMQType)qc.commitment.llmqType)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
return state.DoS(100, false, REJECT_INVALID, "bad-qc-dup");
|
|
|
|
}
|
|
|
|
|
2018-11-27 08:04:08 +01:00
|
|
|
ret.emplace((Consensus::LLMQType)qc.commitment.llmqType, std::move(qc.commitment));
|
2018-11-23 15:42:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
bool CQuorumBlockProcessor::IsCommitmentRequired(Consensus::LLMQType llmqType, int nHeight)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
2018-12-13 09:04:08 +01:00
|
|
|
uint256 quorumHash = GetQuorumBlockHash(llmqType, nHeight);
|
2018-11-23 15:42:09 +01:00
|
|
|
|
|
|
|
// 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
|
2018-12-13 09:04:08 +01:00
|
|
|
bool isMiningPhase = !quorumHash.IsNull() && IsMiningPhase(llmqType, nHeight);
|
2018-11-23 15:42:09 +01:00
|
|
|
|
|
|
|
// did we already mine a non-null commitment for this session?
|
|
|
|
bool hasMinedCommitment = !quorumHash.IsNull() && HasMinedCommitment(llmqType, quorumHash);
|
|
|
|
|
2018-12-13 13:55:46 +01:00
|
|
|
return isMiningPhase && !hasMinedCommitment;
|
2018-11-23 15:42:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// WARNING: This method returns uint256() on the first block of the DKG interval (because the block hash is not known yet)
|
2018-12-13 09:04:08 +01:00
|
|
|
uint256 CQuorumBlockProcessor::GetQuorumBlockHash(Consensus::LLMQType llmqType, int nHeight)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
2018-12-13 09:04:08 +01:00
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
2018-11-23 15:42:09 +01:00
|
|
|
auto& params = Params().GetConsensus().llmqs.at(llmqType);
|
|
|
|
|
|
|
|
int quorumStartHeight = nHeight - (nHeight % params.dkgInterval);
|
2018-12-13 09:04:08 +01:00
|
|
|
uint256 quorumBlockHash;
|
|
|
|
if (!GetBlockHash(quorumBlockHash, quorumStartHeight)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
return uint256();
|
|
|
|
}
|
2018-12-13 09:04:08 +01:00
|
|
|
return quorumBlockHash;
|
2018-11-23 15:42:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2018-12-13 09:04:08 +01:00
|
|
|
bool CQuorumBlockProcessor::GetMinableCommitment(Consensus::LLMQType llmqType, int nHeight, CFinalCommitment& ret)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
if (!IsCommitmentRequired(llmqType, nHeight)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
// no commitment required
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
uint256 quorumHash = GetQuorumBlockHash(llmqType, nHeight);
|
2018-11-23 15:42:09 +01:00
|
|
|
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);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = minableCommitments.at(it->second);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
bool CQuorumBlockProcessor::GetMinableCommitmentTx(Consensus::LLMQType llmqType, int nHeight, CTransactionRef& ret)
|
2018-11-23 15:42:09 +01:00
|
|
|
{
|
|
|
|
AssertLockHeld(cs_main);
|
|
|
|
|
2018-11-27 08:04:08 +01:00
|
|
|
CFinalCommitmentTxPayload qc;
|
2018-12-13 09:04:08 +01:00
|
|
|
if (!GetMinableCommitment(llmqType, nHeight, qc.commitment)) {
|
2018-11-23 15:42:09 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-13 09:04:08 +01:00
|
|
|
qc.nHeight = nHeight;
|
2018-11-27 08:04:08 +01:00
|
|
|
|
2018-11-23 15:42:09 +01:00
|
|
|
CMutableTransaction tx;
|
|
|
|
tx.nVersion = 3;
|
|
|
|
tx.nType = TRANSACTION_QUORUM_COMMITMENT;
|
|
|
|
SetTxPayload(tx, qc);
|
|
|
|
|
|
|
|
ret = MakeTransactionRef(tx);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|