From 6759c804897ebb42f1832019e024d80d672faadf Mon Sep 17 00:00:00 2001 From: Jouzo <15011228+Jouzo@users.noreply.github.com> Date: Fri, 23 Dec 2022 14:19:40 +0100 Subject: [PATCH] On-chain governance fixes (#1654) * Formatting * Remove overwriting proposal status * Fix tests * Remove valid check * Fix ForEachCycleProp returning early * Format proposals.cpp * Remove UpdatePropCycle range guard * Add attribute for cfp max cycles * Fix typo Co-authored-by: Mihailo Milenkovic --- src/masternodes/govvariables/attributes.cpp | 5 +- src/masternodes/govvariables/attributes.h | 1 + src/masternodes/mn_checks.cpp | 9 +- src/masternodes/proposals.cpp | 19 +- src/masternodes/rpc_proposals.cpp | 494 +++++++++--------- src/validation.cpp | 2 +- .../functional/feature_on_chain_government.py | 6 +- ...ature_on_chain_government_govvar_update.py | 4 +- 8 files changed, 273 insertions(+), 267 deletions(-) diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index 74303a4b80..3d0b77ea0b 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -206,6 +206,7 @@ const std::map> &ATTRIBUTES::allowedKeys {"voc_approval_threshold", GovernanceKeys::VOCApprovalThreshold}, {"quorum", GovernanceKeys::Quorum}, {"voting_period", GovernanceKeys::VotingPeriod}, + {"cfp_max_cycles", GovernanceKeys::CFPMaxCycles}, }}, }; return keys; @@ -299,6 +300,7 @@ const std::map> &ATTRIBUTES::displayKeys {GovernanceKeys::VOCApprovalThreshold, "voc_approval_threshold"}, {GovernanceKeys::Quorum, "quorum"}, {GovernanceKeys::VotingPeriod, "voting_period"}, + {GovernanceKeys::CFPMaxCycles, "cfp_max_cycles"}, }}, }; return keys; @@ -610,6 +612,7 @@ const std::map( {GovernanceKeys::VOCApprovalThreshold, VerifyPct}, {GovernanceKeys::Quorum, VerifyPct}, {GovernanceKeys::VotingPeriod, VerifyUInt32}, + {GovernanceKeys::CFPMaxCycles, VerifyUInt32}, }}, }; return parsers; @@ -822,7 +825,7 @@ Res ATTRIBUTES::ProcessVariable(const std::string &key, typeKey != GovernanceKeys::VOCFee && typeKey != GovernanceKeys::VOCApprovalThreshold && typeKey != GovernanceKeys::VOCEmergencyPeriod && typeKey != GovernanceKeys::VOCEmergencyFee && typeKey != GovernanceKeys::VOCEmergencyQuorum && typeKey != GovernanceKeys::Quorum && - typeKey != GovernanceKeys::VotingPeriod) + typeKey != GovernanceKeys::VotingPeriod && typeKey != GovernanceKeys::CFPMaxCycles) return Res::Err("Unsupported key for Governance Proposal section - {%d}", typeKey); } else { return Res::Err("Unsupported Governance ID"); diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index e875babc56..dae06e2581 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -100,6 +100,7 @@ enum GovernanceKeys : uint8_t { Quorum = 'j', VotingPeriod = 'k', VOCEmergencyQuorum = 'l', + CFPMaxCycles = 'm', }; enum TokenKeys : uint8_t { diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index faae15dcf9..a3b87643d0 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -4447,8 +4447,13 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { if (obj.contextHash.size() > MAX_PROP_CONTEXT_SIZE) return Res::Err("proposal context hash cannot be more than %d bytes", MAX_PROP_CONTEXT_SIZE); - if (obj.nCycles < 1 || obj.nCycles > MAX_CYCLES) - return Res::Err("proposal cycles can be between 1 and %d", int(MAX_CYCLES)); + auto attributes = mnview.GetAttributes(); + assert(attributes); + CDataStructureV0 cfpMaxCycles{AttributeTypes::Governance, GovernanceIDs::Proposals, GovernanceKeys::CFPMaxCycles}; + auto maxCycles = attributes->GetValue(cfpMaxCycles, static_cast(MAX_CYCLES)); + + if (obj.nCycles < 1 || obj.nCycles > maxCycles ) + return Res::Err("proposal cycles can be between 1 and %d", maxCycles); if ((obj.options & CPropOption::Emergency)) { if (obj.nCycles != 1) { diff --git a/src/masternodes/proposals.cpp b/src/masternodes/proposals.cpp index 6d5fd22d93..bcfabb99ff 100644 --- a/src/masternodes/proposals.cpp +++ b/src/masternodes/proposals.cpp @@ -53,12 +53,12 @@ Res CPropsView::CreateProp(const CPropId &propId, uint32_t height, const CCreate bool emergency = prop.options & CPropOption::Emergency; auto type = static_cast(prop.type); - prop.creationHeight = height; - prop.votingPeriod = (emergency ? GetEmergencyPeriodFromAttributes(type) : GetVotingPeriodFromAttributes()); - prop.approvalThreshold = GetApprovalThresholdFromAttributes(type); - prop.quorum = GetQuorumFromAttributes(type, emergency); - prop.fee = fee; - prop.feeBurnAmount = MultiplyAmounts(fee, GetFeeBurnPctFromAttributes()); + prop.creationHeight = height; + prop.votingPeriod = (emergency ? GetEmergencyPeriodFromAttributes(type) : GetVotingPeriodFromAttributes()); + prop.approvalThreshold = GetApprovalThresholdFromAttributes(type); + prop.quorum = GetQuorumFromAttributes(type, emergency); + prop.fee = fee; + prop.feeBurnAmount = MultiplyAmounts(fee, GetFeeBurnPctFromAttributes()); auto key = std::make_pair(uint8_t(CPropStatusType::Voting), propId); WriteBy(key, static_cast(1)); @@ -101,9 +101,6 @@ std::optional CPropsView::GetProp(const CPropId &propId) { } Res CPropsView::UpdatePropCycle(const CPropId &propId, uint8_t cycle) { - if (cycle < 1 || cycle > MAX_CYCLES) - return Res::Err("Cycle out of range"); - auto key = std::make_pair(uint8_t(CPropStatusType::Voting), propId); auto pcycle = ReadBy(key); if (!pcycle) @@ -120,8 +117,8 @@ Res CPropsView::UpdatePropCycle(const CPropId &propId, uint8_t cycle) { bool emergency = prop->options & CPropOption::Emergency; auto type = static_cast(prop->type); - prop->approvalThreshold = GetApprovalThresholdFromAttributes(type); - prop->quorum = GetQuorumFromAttributes(type, emergency); + prop->approvalThreshold = GetApprovalThresholdFromAttributes(type); + prop->quorum = GetQuorumFromAttributes(type, emergency); WriteBy(propId, *prop); return Res::Ok(); diff --git a/src/masternodes/rpc_proposals.cpp b/src/masternodes/rpc_proposals.cpp index 5a1937cfb2..4ef78b9967 100644 --- a/src/masternodes/rpc_proposals.cpp +++ b/src/masternodes/rpc_proposals.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include @@ -9,64 +9,54 @@ struct VotingInfo { int32_t votesYes; }; -UniValue -proposalToJSON(const CPropId &propId, - const CPropObject &prop, - const CCustomCSView &view, - const std::optional votingInfo) { - - auto proposalId = propId.GetHex(); - auto creationHeight = static_cast(prop.creationHeight); - auto title = prop.title; - auto context = prop.context; - auto contextHash = prop.contextHash; - auto type = static_cast(prop.type); - auto typeString = CPropTypeToString(type); - auto amountValue = ValueFromAmount(prop.nAmount); - auto payoutAddress = ScriptToString(prop.address); - auto currentCycle = static_cast(prop.cycle); - auto totalCycles = static_cast(prop.nCycles); - auto cycleEndHeight = static_cast(prop.cycleEndHeight); +UniValue proposalToJSON(const CPropId &propId, + const CPropObject &prop, + const CCustomCSView &view, + const std::optional votingInfo) { + auto proposalId = propId.GetHex(); + auto creationHeight = static_cast(prop.creationHeight); + auto title = prop.title; + auto context = prop.context; + auto contextHash = prop.contextHash; + auto type = static_cast(prop.type); + auto typeString = CPropTypeToString(type); + auto amountValue = ValueFromAmount(prop.nAmount); + auto payoutAddress = ScriptToString(prop.address); + auto currentCycle = static_cast(prop.cycle); + auto totalCycles = static_cast(prop.nCycles); + auto cycleEndHeight = static_cast(prop.cycleEndHeight); auto proposalEndHeight = static_cast(prop.proposalEndHeight); - auto votingPeriod = static_cast(prop.votingPeriod); - bool isEmergency = prop.options & CPropOption::Emergency; - auto quorum = prop.quorum; + auto votingPeriod = static_cast(prop.votingPeriod); + bool isEmergency = prop.options & CPropOption::Emergency; + auto quorum = prop.quorum; auto approvalThreshold = prop.approvalThreshold; - auto status = static_cast(prop.status); - auto statusString = CPropStatusToString(status); - auto feeTotalValue = ValueFromAmount(prop.fee); - auto feeBurnValue = ValueFromAmount(prop.feeBurnAmount); + auto status = static_cast(prop.status); + auto statusString = CPropStatusToString(status); + auto feeTotalValue = ValueFromAmount(prop.fee); + auto feeBurnValue = ValueFromAmount(prop.feeBurnAmount); - auto quorumString = strprintf("%d.%02d%%", quorum / 100, quorum % 100); + auto quorumString = strprintf("%d.%02d%%", quorum / 100, quorum % 100); auto approvalThresholdString = strprintf("%d.%02d%%", approvalThreshold / 100, approvalThreshold % 100); - auto votesPossible = -1; - auto votesPresent = -1; - auto votesPresentPct = -1; - auto votesYes = -1; - auto votesYesPct = -1; + auto votesPossible = -1; + auto votesPresent = -1; + auto votesPresentPct = -1; + auto votesYes = -1; + auto votesYesPct = -1; std::string votesPresentPctString = "-1"; - std::string votesYesPctString = "-1"; + std::string votesYesPctString = "-1"; auto isVotingInfoAvailable = votingInfo.has_value(); if (isVotingInfoAvailable) { - votesPresent = votingInfo->votesPresent; - votesYes = votingInfo->votesYes; + votesPresent = votingInfo->votesPresent; + votesYes = votingInfo->votesYes; votesPossible = votingInfo->votesPossible; votesPresentPct = lround(votesPresent * 10000.f / votesPossible); - auto valid = votesPresentPct > quorum; - if (valid) { - votesYesPct = lround(votesYes * 10000.f / votesPresent); - } - if (valid && votesYesPct > approvalThreshold) { - statusString = "Completed"; - } else { - statusString = "Rejected"; - } + votesYesPct = lround(votesYes * 10000.f / votesPresent); votesPresentPctString = strprintf("%d.%02d%%", votesPresentPct / 100, votesPresentPct % 100); - votesYesPctString = strprintf("%d.%02d%%", votesYesPct / 100, votesYesPct % 100); + votesYesPctString = strprintf("%d.%02d%%", votesYesPct / 100, votesYesPct % 100); } UniValue ret(UniValue::VOBJ); @@ -98,8 +88,7 @@ proposalToJSON(const CPropId &propId, ret.pushKV("approvalThreshold", approvalThresholdString); ret.pushKV("fee", feeTotalValue); // ret.pushKV("feeBurn", feeBurnValue); - if (prop.options) - { + if (prop.options) { UniValue opt = UniValue(UniValue::VARR); if (isEmergency) opt.push_back("emergency"); @@ -120,58 +109,71 @@ UniValue proposalVoteToJSON(const CPropId &propId, uint8_t cycle, const uint256 /* * Issued by: any -*/ -UniValue creategovcfp(const JSONRPCRequest& request) -{ + */ +UniValue creategovcfp(const JSONRPCRequest &request) { auto pwallet = GetWallet(request); - RPCHelpMan{"creategovcfp", - "\nCreates a Community Fund Proposal" + - HelpRequiringPassphrase(pwallet) + "\n", - { - {"data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "data in json-form, containing cfp data", + RPCHelpMan{ + "creategovcfp", + "\nCreates a Community Fund Proposal" + HelpRequiringPassphrase(pwallet) + "\n", + { + { + "data", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED_NAMED_ARG, + "data in json-form, containing cfp data", + { + {"title", RPCArg::Type::STR, RPCArg::Optional::NO, "The title of community fund request"}, + {"context", RPCArg::Type::STR, RPCArg::Optional::NO, "The context field of community fund request"}, + {"contextHash", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "The hash of the content which context field point to of community fund request"}, + {"cycles", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Defaulted to one cycle"}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Amount in DFI to request"}, + {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "Any valid address for receiving"}, + }, + }, { + "inputs", + RPCArg::Type::ARR, + RPCArg::Optional::OMITTED_NAMED_ARG, + "A json array of json objects", + { + { + "", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED, + "", { - {"title", RPCArg::Type::STR, RPCArg::Optional::NO, "The title of community fund request"}, - {"context", RPCArg::Type::STR, RPCArg::Optional::NO, "The context field of community fund request"}, - {"contextHash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The hash of the content which context field point to of community fund request"}, - {"cycles", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Defaulted to one cycle"}, - {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Amount in DFI to request"}, - {"payoutAddress", RPCArg::Type::STR, RPCArg::Optional::NO, "Any valid address for receiving"}, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, }, }, - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", - { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - }, - }, - }, - }, - }, - RPCResult{ - "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" - }, - RPCExamples{ - HelpExampleCli("creategovcfp", "'{\"title\":\"The cfp title\",\"context\":\"The cfp context\",\"amount\":10,\"payoutAddress\":\"address\"}' '[{\"txid\":\"id\",\"vout\":0}]'") - + HelpExampleRpc("creategovcfp", "'{\"title\":\"The cfp title\",\"context\":\"The cfp context\",\"amount\":10,\"payoutAddress\":\"address\"} '[{\"txid\":\"id\",\"vout\":0}]'") - }, - }.Check(request); + }, + }, }, + RPCResult{"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"}, + RPCExamples{ + HelpExampleCli("creategovcfp", + "'{\"title\":\"The cfp title\",\"context\":\"The cfp " + "context\",\"amount\":10,\"payoutAddress\":\"address\"}' '[{\"txid\":\"id\",\"vout\":0}]'") + + HelpExampleRpc("creategovcfp", + "'{\"title\":\"The cfp title\",\"context\":\"The cfp " + "context\",\"amount\":10,\"payoutAddress\":\"address\"} '[{\"txid\":\"id\",\"vout\":0}]'")}, + } + .Check(request); if (pwallet->chain().isInitialBlockDownload()) { - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, - "Cannot create a cfp while still in Initial Block Download"); + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot create a cfp while still in Initial Block Download"); } pwallet->BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, { UniValue::VOBJ, UniValue::VARR }, true); + RPCTypeCheck(request.params, {UniValue::VOBJ, UniValue::VARR}, true); CAmount amount; int cycles = 1; std::string title, context, contextHash, addressStr; - const UniValue& data = request.params[0].get_obj(); + const UniValue &data = request.params[0].get_obj(); if (!data["title"].isNull()) { title = data["title"].get_str(); @@ -210,30 +212,30 @@ UniValue creategovcfp(const JSONRPCRequest& request) } CCreatePropMessage pm; - pm.type = CPropType::CommunityFundProposal; - pm.address = GetScriptForDestination(address); - pm.nAmount = amount; - pm.nCycles = cycles; - pm.title = title; - pm.context = context; + pm.type = CPropType::CommunityFundProposal; + pm.address = GetScriptForDestination(address); + pm.nAmount = amount; + pm.nCycles = cycles; + pm.title = title; + pm.context = context; pm.contextHash = contextHash; - pm.options = 0; + pm.options = 0; // encode CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::CreateCfp) - << pm; + metadata << static_cast(CustomTxType::CreateCfp) << pm; CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); - auto targetHeight = pcustomcsview->GetLastHeight() + 1; + auto targetHeight = pcustomcsview->GetLastHeight() + 1; const auto txVersion = GetTransactionVersion(targetHeight); CMutableTransaction rawTx(txVersion); CTransactionRef optAuthTx; std::set auths{pm.address}; - rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[1]); + rawTx.vin = + GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[1]); auto cfpFee = GetPropsCreationFee(targetHeight, *pcustomcsview, pm); rawTx.vout.emplace_back(CTxOut(cfpFee, scriptMeta)); @@ -256,54 +258,63 @@ UniValue creategovcfp(const JSONRPCRequest& request) return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } -UniValue creategovvoc(const JSONRPCRequest& request) -{ +UniValue creategovvoc(const JSONRPCRequest &request) { auto pwallet = GetWallet(request); - RPCHelpMan{"creategovvoc", - "\nCreates a Vote of Confidence" + - HelpRequiringPassphrase(pwallet) + "\n", - { - {"data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "data in json-form, containing voc data", - { - {"title", RPCArg::Type::STR, RPCArg::Optional::NO, "The title of vote of confidence"}, - {"context", RPCArg::Type::STR, RPCArg::Optional::NO, "The context field for vote of confidence"}, - {"contextHash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The hash of the content which context field point to of vote of confidence request"}, - {"emergency", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Is this emergency VOC"}, - }, - }, - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", + RPCHelpMan{ + "creategovvoc", + "\nCreates a Vote of Confidence" + HelpRequiringPassphrase(pwallet) + "\n", + { + { + "data", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED_NAMED_ARG, + "data in json-form, containing voc data", + { + {"title", RPCArg::Type::STR, RPCArg::Optional::NO, "The title of vote of confidence"}, + {"context", RPCArg::Type::STR, RPCArg::Optional::NO, "The context field for vote of confidence"}, + {"contextHash", + RPCArg::Type::STR, + RPCArg::Optional::OMITTED, + "The hash of the content which context field point to of vote of confidence request"}, + {"emergency", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Is this emergency VOC"}, + }, + }, { + "inputs", + RPCArg::Type::ARR, + RPCArg::Optional::OMITTED_NAMED_ARG, + "A json array of json objects", + { + { + "", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED, + "", { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - }, - }, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, }, }, - }, - RPCResult{ - "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" - }, - RPCExamples{ - HelpExampleCli("creategovvoc", "'The voc title' 'The voc context' '[{\"txid\":\"id\",\"vout\":0}]'") - + HelpExampleRpc("creategovvoc", "'The voc title' 'The voc context' '[{\"txid\":\"id\",\"vout\":0}]'") - }, - }.Check(request); + }, + }, }, + RPCResult{"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"}, + RPCExamples{ + HelpExampleCli("creategovvoc", "'The voc title' 'The voc context' '[{\"txid\":\"id\",\"vout\":0}]'") + + HelpExampleRpc("creategovvoc", "'The voc title' 'The voc context' '[{\"txid\":\"id\",\"vout\":0}]'")}, + } + .Check(request); if (pwallet->chain().isInitialBlockDownload()) { - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, - "Cannot create a voc while still in Initial Block Download"); + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot create a voc while still in Initial Block Download"); } pwallet->BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, { UniValue::VOBJ, UniValue::VARR }, true); + RPCTypeCheck(request.params, {UniValue::VOBJ, UniValue::VARR}, true); std::string title, context, contextHash; bool emergency = false; - const UniValue& data = request.params[0].get_obj(); + const UniValue &data = request.params[0].get_obj(); if (!data["title"].isNull()) { title = data["title"].get_str(); @@ -320,35 +331,34 @@ UniValue creategovvoc(const JSONRPCRequest& request) if (!data["contextHash"].isNull()) contextHash = data["contextHash"].get_str(); - if (!data["emergency"].isNull()) - { + if (!data["emergency"].isNull()) { emergency = data["emergency"].get_bool(); } CCreatePropMessage pm; - pm.type = CPropType::VoteOfConfidence; - pm.nAmount = 0; - pm.nCycles = (emergency ? 1 : VOC_CYCLES); - pm.title = title; - pm.context = context; + pm.type = CPropType::VoteOfConfidence; + pm.nAmount = 0; + pm.nCycles = (emergency ? 1 : VOC_CYCLES); + pm.title = title; + pm.context = context; pm.contextHash = contextHash; - pm.options = (emergency ? CPropOption::Emergency : 0); + pm.options = (emergency ? CPropOption::Emergency : 0); // encode CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::CreateVoc) - << pm; + metadata << static_cast(CustomTxType::CreateVoc) << pm; CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); - auto targetHeight = pcustomcsview->GetLastHeight() + 1; + auto targetHeight = pcustomcsview->GetLastHeight() + 1; const auto txVersion = GetTransactionVersion(targetHeight); CMutableTransaction rawTx(txVersion); CTransactionRef optAuthTx; std::set auths; - rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[1]); + rawTx.vin = + GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[1]); auto vocFee = GetPropsCreationFee(targetHeight, *pcustomcsview, pm); rawTx.vout.emplace_back(CTxOut(vocFee, scriptMeta)); @@ -371,48 +381,50 @@ UniValue creategovvoc(const JSONRPCRequest& request) return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } -UniValue votegov(const JSONRPCRequest& request) -{ +UniValue votegov(const JSONRPCRequest &request) { auto pwallet = GetWallet(request); - RPCHelpMan{"votegov", - "\nVote for community proposal" + - HelpRequiringPassphrase(pwallet) + "\n", - { - {"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal txid"}, - {"masternodeId", RPCArg::Type::STR, RPCArg::Optional::NO, "The masternode id which made the vote"}, - {"decision", RPCArg::Type::STR, RPCArg::Optional::NO, "The vote decision (yes/no/neutral)"}, - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", + RPCHelpMan{ + "votegov", + "\nVote for community proposal" + HelpRequiringPassphrase(pwallet) + "\n", + { + {"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal txid"}, + {"masternodeId", RPCArg::Type::STR, RPCArg::Optional::NO, "The masternode id which made the vote"}, + {"decision", RPCArg::Type::STR, RPCArg::Optional::NO, "The vote decision (yes/no/neutral)"}, + { + "inputs", + RPCArg::Type::ARR, + RPCArg::Optional::OMITTED_NAMED_ARG, + "A json array of json objects", + { + { + "", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED, + "", { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - }, - }, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, }, - }, - }, - RPCResult{ - "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" - }, - RPCExamples{ - HelpExampleCli("votegov", "txid masternodeId yes") - + HelpExampleRpc("votegov", "txid masternodeId yes") - }, - }.Check(request); + }, + }, + }, }, + RPCResult{"\"hash\" (string) The hex-encoded hash of broadcasted transaction\n"}, + RPCExamples{HelpExampleCli("votegov", "txid masternodeId yes") + + HelpExampleRpc("votegov", "txid masternodeId yes")}, + } + .Check(request); if (pwallet->chain().isInitialBlockDownload()) { - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, - "Cannot vote while still in Initial Block Download"); + throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot vote while still in Initial Block Download"); } pwallet->BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, { UniValue::VSTR, UniValue::VSTR, UniValue::VSTR, UniValue::VARR }, true); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VSTR, UniValue::VARR}, true); auto propId = ParseHashV(request.params[0].get_str(), "proposalId"); - auto mnId = ParseHashV(request.params[1].get_str(), "masternodeId"); - auto vote = CPropVoteType::VoteNeutral; + auto mnId = ParseHashV(request.params[1].get_str(), "masternodeId"); + auto vote = CPropVoteType::VoteNeutral; auto voteStr = ToLower(request.params[2].get_str()); if (voteStr == "no") { @@ -433,26 +445,27 @@ UniValue votegov(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Proposal <%s> does not exist", propId.GetHex())); } if (prop->status != CPropStatusType::Voting) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Proposal <%s> is not in voting period", propId.GetHex())); + throw JSONRPCError(RPC_INVALID_PARAMETER, + strprintf("Proposal <%s> is not in voting period", propId.GetHex())); } auto node = view.GetMasternode(mnId); if (!node) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("The masternode %s does not exist", mnId.ToString())); } - ownerDest = node->ownerType == 1 ? CTxDestination(PKHash(node->ownerAuthAddress)) : CTxDestination(WitnessV0KeyHash(node->ownerAuthAddress)); + ownerDest = node->ownerType == 1 ? CTxDestination(PKHash(node->ownerAuthAddress)) + : CTxDestination(WitnessV0KeyHash(node->ownerAuthAddress)); targetHeight = view.GetLastHeight() + 1; } CPropVoteMessage msg; - msg.propId = propId; + msg.propId = propId; msg.masternodeId = mnId; - msg.vote = vote; + msg.vote = vote; // encode CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::Vote) - << msg; + metadata << static_cast(CustomTxType::Vote) << msg; CScript scriptMeta; scriptMeta << OP_RETURN << ToByteVector(metadata); @@ -461,8 +474,9 @@ UniValue votegov(const JSONRPCRequest& request) CMutableTransaction rawTx(txVersion); CTransactionRef optAuthTx; - std::set auths = { GetScriptForDestination(ownerDest) }; - rawTx.vin = GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[3]); + std::set auths = {GetScriptForDestination(ownerDest)}; + rawTx.vin = + GetAuthInputsSmart(pwallet, rawTx.nVersion, auths, false /*needFoundersAuth*/, optAuthTx, request.params[3]); rawTx.vout.emplace_back(CTxOut(0, scriptMeta)); @@ -479,24 +493,21 @@ UniValue votegov(const JSONRPCRequest& request) return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } -UniValue listgovproposalvotes(const JSONRPCRequest& request) -{ +UniValue listgovproposalvotes(const JSONRPCRequest &request) { auto pwallet = GetWallet(request); - RPCHelpMan{"listgovproposalvotes", - "\nReturns information about proposal votes.\n", - { - {"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal id)"}, - {"masternode", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "mine/all/id (default = mine)"}, - {"cycle", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "cycle: 0 (show current), cycle: N (show cycle N), cycle: -1 (show all) (default = 0)"} - }, - RPCResult{ - "{id:{...},...} (array) Json object with proposal vote information\n" - }, - RPCExamples{ - HelpExampleCli("listgovproposalvotes", "txid") - + HelpExampleRpc("listgovproposalvotes", "txid") - }, - }.Check(request); + RPCHelpMan{ + "listgovproposalvotes", + "\nReturns information about proposal votes.\n", + {{"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal id)"}, + {"masternode", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "mine/all/id (default = mine)"}, + {"cycle", + RPCArg::Type::NUM, + RPCArg::Optional::OMITTED, + "cycle: 0 (show current), cycle: N (show cycle N), cycle: -1 (show all) (default = 0)"}}, + RPCResult{"{id:{...},...} (array) Json object with proposal vote information\n"}, + RPCExamples{HelpExampleCli("listgovproposalvotes", "txid") + HelpExampleRpc("listgovproposalvotes", "txid")}, + } + .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VNUM}, true); @@ -510,7 +521,7 @@ UniValue listgovproposalvotes(const JSONRPCRequest& request) isMine = false; } else if (str != "mine") { isMine = false; - mnId = ParseHashV(str, "masternode"); + mnId = ParseHashV(str, "masternode"); } } CCustomCSView view(*pcustomcsview); @@ -520,15 +531,15 @@ UniValue listgovproposalvotes(const JSONRPCRequest& request) if (request.params.size() > 2) { inputCycle = request.params[2].get_int(); } - if (inputCycle==0){ + if (inputCycle == 0) { auto prop = view.GetProp(propId); - if (!prop){ + if (!prop) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Proposal <%s> does not exist", propId.GetHex())); } cycle = prop->cycle; - } else if (inputCycle > 0){ + } else if (inputCycle > 0) { cycle = inputCycle; - } else if (inputCycle == -1){ + } else if (inputCycle == -1) { cycle = 1; } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Incorrect cycle value"); @@ -566,21 +577,17 @@ UniValue listgovproposalvotes(const JSONRPCRequest& request) return ret; } -UniValue getgovproposal(const JSONRPCRequest& request) -{ - RPCHelpMan{"getgovproposal", - "\nReturns real time information about proposal state.\n", - { - {"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal id)"}, - }, - RPCResult{ - "{id:{...},...} (obj) Json object with proposal vote information\n" - }, - RPCExamples{ - HelpExampleCli("getgovproposal", "txid") - + HelpExampleRpc("getgovproposal", "txid") - }, - }.Check(request); +UniValue getgovproposal(const JSONRPCRequest &request) { + RPCHelpMan{ + "getgovproposal", + "\nReturns real time information about proposal state.\n", + { + {"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal id)"}, + }, + RPCResult{"{id:{...},...} (obj) Json object with proposal vote information\n"}, + RPCExamples{HelpExampleCli("getgovproposal", "txid") + HelpExampleRpc("getgovproposal", "txid")}, + } + .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}, true); @@ -632,30 +639,22 @@ UniValue getgovproposal(const JSONRPCRequest& request) VotingInfo info; info.votesPossible = activeMasternodes.size(); - info.votesPresent = voters; - info.votesYes = voteYes; + info.votesPresent = voters; + info.votesYes = voteYes; return proposalToJSON(propId, *prop, view, info); } -UniValue listgovproposals(const JSONRPCRequest& request) -{ - RPCHelpMan{"listgovproposals", - "\nReturns information about proposals.\n", - { - {"type", RPCArg::Type::STR, RPCArg::Optional::OMITTED, - "cfp/voc/all (default = all)"}, - {"status", RPCArg::Type::STR, RPCArg::Optional::OMITTED, - "voting/rejected/completed/all (default = all)"} - }, - RPCResult{ - "{id:{...},...} (array) Json object with proposals information\n" - }, - RPCExamples{ - HelpExampleCli("listgovproposals", "") - + HelpExampleRpc("listgovproposals", "") - }, - }.Check(request); +UniValue listgovproposals(const JSONRPCRequest &request) { + RPCHelpMan{ + "listgovproposals", + "\nReturns information about proposals.\n", + {{"type", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "cfp/voc/all (default = all)"}, + {"status", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "voting/rejected/completed/all (default = all)"}}, + RPCResult{"{id:{...},...} (array) Json object with proposals information\n"}, + RPCExamples{HelpExampleCli("listgovproposals", "") + HelpExampleRpc("listgovproposals", "")}, + } + .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}, true); @@ -704,19 +703,18 @@ UniValue listgovproposals(const JSONRPCRequest& request) return ret; } -static const CRPCCommand commands[] = -{ -// category name actor (function) params -// --------------- ---------------------- --------------------- ---------- - {"proposals", "creategovcfp", &creategovcfp, {"data", "inputs"} }, - {"proposals", "creategovvoc", &creategovvoc, {"data", "inputs"} }, - {"proposals", "votegov", &votegov, {"proposalId", "masternodeId", "decision", "inputs"} }, - {"proposals", "listgovproposalvotes", &listgovproposalvotes, {"proposalId", "masternode", "cycle"} }, - {"proposals", "getgovproposal", &getgovproposal, {"proposalId"} }, - {"proposals", "listgovproposals", &listgovproposals, {"type", "status"} }, +static const CRPCCommand commands[] = { + // category name actor (function) params + // --------------- ---------------------- --------------------- ---------- + {"proposals", "creategovcfp", &creategovcfp, {"data", "inputs"} }, + {"proposals", "creategovvoc", &creategovvoc, {"data", "inputs"} }, + {"proposals", "votegov", &votegov, {"proposalId", "masternodeId", "decision", "inputs"}}, + {"proposals", "listgovproposalvotes", &listgovproposalvotes, {"proposalId", "masternode", "cycle"} }, + {"proposals", "getgovproposal", &getgovproposal, {"proposalId"} }, + {"proposals", "listgovproposals", &listgovproposals, {"type", "status"} }, }; -void RegisterProposalRPCCommands(CRPCTable& tableRPC) { +void RegisterProposalRPCCommands(CRPCTable &tableRPC) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); } diff --git a/src/validation.cpp b/src/validation.cpp index e7616fe88e..a0a6a0e103 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4208,7 +4208,7 @@ void CChainState::ProcessProposalEvents(const CBlockIndex* pindex, CCustomCSView std::set activeMasternodes; cache.ForEachCycleProp([&](CPropId const& propId, CPropObject const& prop) { - if (prop.status != CPropStatusType::Voting) return false; + if (prop.status != CPropStatusType::Voting) return true; if (activeMasternodes.empty()) { cache.ForEachMasternode([&](uint256 const & mnId, CMasternode node) { diff --git a/test/functional/feature_on_chain_government.py b/test/functional/feature_on_chain_government.py index 270567459d..af795dc353 100755 --- a/test/functional/feature_on_chain_government.py +++ b/test/functional/feature_on_chain_government.py @@ -277,6 +277,7 @@ def run_test(self): cycle1 = creationHeight + (votingPeriod - creationHeight % votingPeriod) + votingPeriod proposalEndHeight = cycle1 + # Check results result = self.nodes[0].getgovproposal(tx) assert_equal(result["proposalId"], tx) @@ -284,7 +285,7 @@ def run_test(self): assert_equal(result["title"], title) assert_equal(result["context"], context) assert_equal(result["contextHash"], "") - assert_equal(result["status"], "Completed") + assert_equal(result["status"], "Voting") assert_equal(result["type"], "VoteOfConfidence") assert_equal(result["currentCycle"], 1) assert_equal(result["totalCycles"], 1) @@ -503,6 +504,7 @@ def run_test(self): cycle1 = creationHeight + (emergencyPeriod - creationHeight % emergencyPeriod) + emergencyPeriod proposalEndHeight = creationHeight + emergencyPeriod + # Check results result = self.nodes[0].getgovproposal(tx) assert_equal(result["proposalId"], tx) @@ -510,7 +512,7 @@ def run_test(self): assert_equal(result["title"], title) assert_equal(result["context"], context) assert_equal(result["contextHash"], "") - assert_equal(result["status"], "Rejected") + assert_equal(result["status"], "Voting") assert_equal(result["type"], "VoteOfConfidence") assert_equal(result["currentCycle"], 1) assert_equal(result["totalCycles"], 1) diff --git a/test/functional/feature_on_chain_government_govvar_update.py b/test/functional/feature_on_chain_government_govvar_update.py index 14871ec950..864286d606 100755 --- a/test/functional/feature_on_chain_government_govvar_update.py +++ b/test/functional/feature_on_chain_government_govvar_update.py @@ -515,10 +515,10 @@ def test_cfp_state_after_update(self): # Vote and move to next cycle self.nodes[3].votegov(propId, self.mn3, "no") - self.nodes[3].generate(VOTING_PERIOD) + proposal = self.nodes[0].getgovproposal(propId) + self.nodes[3].generate(proposal["proposalEndHeight"] - self.nodes[3].getblockcount()) self.sync_blocks(timeout=120) - # First cycle should be completed proposal = self.nodes[0].getgovproposal(propId) assert_equal(proposal['status'], 'Completed')