From 3dbfa0163440247049aa420dcf77a29c9bd309d9 Mon Sep 17 00:00:00 2001 From: dCorral <55594560+dcorral@users.noreply.github.com> Date: Thu, 5 Jan 2023 10:00:41 +0100 Subject: [PATCH] Add cycle filtering and pagination to listgovproposals (#1627) * Add cycle filtering to listgovproposals * Fix cycle param description * Remove unused variable retMap * Replace uint with unsigned int to fix windows compile error * Add tests to listgovproposals * Removes trailing whitespaces and unused imports * Fix ForEachCycleProp returning early * Add pagination to listgovproposal * Fix lint * Format src/masternodes/rpc_proposals.cpp * Pagination nesting as in #1635 Co-authored-by: Jouzo <15011228+Jouzo@users.noreply.github.com> * Make pagination rpc consistent * Resolve compiler warning * Update help message Co-authored-by: Keng Ye <40191153+kyleleow@users.noreply.github.com> Co-authored-by: jouzo Co-authored-by: Jouzo <15011228+Jouzo@users.noreply.github.com> Co-authored-by: Mihailo Milenkovic Co-authored-by: Peter John Bushnell Co-authored-by: Prasanna Loganathar Co-authored-by: Keng Ye <40191153+kyleleow@users.noreply.github.com> --- src/masternodes/proposals.cpp | 4 +- src/masternodes/proposals.h | 2 +- src/masternodes/rpc_proposals.cpp | 224 +++++++++++-- src/rpc/client.cpp | 1 + .../functional/feature_on_chain_government.py | 14 + test/functional/rpc_listgovproposals.py | 298 ++++++++++++++++++ test/functional/test_runner.py | 1 + 7 files changed, 510 insertions(+), 34 deletions(-) mode change 100755 => 100644 test/functional/feature_on_chain_government.py create mode 100755 test/functional/rpc_listgovproposals.py diff --git a/src/masternodes/proposals.cpp b/src/masternodes/proposals.cpp index bcfabb99ff..508aa67fda 100644 --- a/src/masternodes/proposals.cpp +++ b/src/masternodes/proposals.cpp @@ -178,14 +178,14 @@ std::optional CPropsView::GetPropVote(const CPropId &propId, return static_cast(*vote); } -void CPropsView::ForEachProp(std::function callback, uint8_t status) { +void CPropsView::ForEachProp(std::function callback, const CPropStatusType status, const CPropId start) { ForEach, uint8_t>( [&](const std::pair &key, uint8_t i) { auto prop = GetProp(key.second); assert(prop); return callback(key.second, *prop); }, - std::make_pair(status, uint256{})); + std::make_pair(status, start)); } void CPropsView::ForEachPropVote(std::function callback, diff --git a/src/masternodes/proposals.h b/src/masternodes/proposals.h index 6600b1f602..d8e511bbbb 100644 --- a/src/masternodes/proposals.h +++ b/src/masternodes/proposals.h @@ -143,7 +143,7 @@ class CPropsView : public virtual CStorageView { Res AddPropVote(const CPropId &propId, const uint256 &masternodeId, CPropVoteType vote); std::optional GetPropVote(const CPropId &propId, uint8_t cycle, const uint256 &masternodeId); - void ForEachProp(std::function callback, uint8_t status = 0); + void ForEachProp(std::function callback, const CPropStatusType status, const CPropId start = {}); void ForEachPropVote(std::function callback, const CMnVotePerCycle &start = {}); void ForEachCycleProp(std::function callback, uint32_t height); diff --git a/src/masternodes/rpc_proposals.cpp b/src/masternodes/rpc_proposals.cpp index ddefd4abcd..01ecca5159 100644 --- a/src/masternodes/rpc_proposals.cpp +++ b/src/masternodes/rpc_proposals.cpp @@ -790,56 +790,218 @@ 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)"}}, + { + {"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)"}, + {"cycle", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "cycle: 0 (all), cycle: N (show cycle N), cycle: -1 (show previous cycle) (default = 0)"}, + { + "pagination", + RPCArg::Type::OBJ, + RPCArg::Optional::OMITTED, + "", + { + {"start", + RPCArg::Type::NUM, + RPCArg::Optional::OMITTED, + "Vote index to iterate from." + "Typically it's set to last ID from previous request."}, + {"including_start", + RPCArg::Type::BOOL, + RPCArg::Optional::OMITTED, + "If true, then iterate including starting position. False by default"}, + {"limit", + RPCArg::Type::NUM, + RPCArg::Optional::OMITTED, + "Maximum number of proposals to return, 100 by default"}, + }, + }, + }, RPCResult{"{id:{...},...} (array) Json object with proposals information\n"}, RPCExamples{HelpExampleCli("listgovproposals", "") + HelpExampleRpc("listgovproposals", "")}, } .Check(request); - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}, true); - - uint8_t type = 0; - if (request.params.size() > 0) { - auto str = request.params[0].get_str(); - if (str == "cfp") { - type = uint8_t(CPropType::CommunityFundProposal); - } else if (str == "voc") { - type = uint8_t(CPropType::VoteOfConfidence); - } else if (str != "all") { - throw JSONRPCError(RPC_INVALID_PARAMETER, "type supports cfp/voc/all"); + if (request.params[0].isObject()) + RPCTypeCheck(request.params, {UniValue::VOBJ}, true); + else + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VNUM, UniValue::VOBJ}, true); + + uint8_t type{0}, status{0}; + int cycle{0}; + size_t limit = 100; + CPropId start = {}; + bool including_start = true; + + if (request.params[0].isObject()) { + auto optionsObj = request.params[0].get_obj(); + + if (optionsObj.exists("type")) { + auto str = optionsObj["type"].get_str(); + if (str == "cfp") { + type = CPropType::CommunityFundProposal; + } else if (str == "voc") { + type = CPropType::VoteOfConfidence; + } else if (str != "all") { + throw JSONRPCError(RPC_INVALID_PARAMETER, "type supports cfp/voc/all"); + } + } + + if (optionsObj.exists("status")) { + auto str = optionsObj["status"].get_str(); + if (str == "voting") { + status = CPropStatusType::Voting; + } else if (str == "rejected") { + status = CPropStatusType::Rejected; + } else if (str == "completed") { + status = CPropStatusType::Completed; + } else if (str != "all") { + throw JSONRPCError(RPC_INVALID_PARAMETER, "status supports voting/rejected/completed/all"); + } + } + + if (optionsObj.exists("cycle")) { + cycle = optionsObj["cycle"].get_int(); + if (cycle < -1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Incorrect cycle value (0 -> all cycles, -1 -> previous cycle, N -> nth cycle"); + } + } + + if (!optionsObj["pagination"].isNull()) { + auto paginationObj = optionsObj["pagination"].get_obj(); + if (!paginationObj["limit"].isNull()) { + limit = (size_t)paginationObj["limit"].get_int64(); + } + if (!paginationObj["start"].isNull()) { + including_start = false; + start = ParseHashV(paginationObj["start"], "start"); + } + if (!paginationObj["including_start"].isNull()) { + including_start = paginationObj["including_start"].getBool(); + } + } + } else { + if (request.params.size() > 0) { + auto str = request.params[0].get_str(); + if (str == "cfp") { + type = uint8_t(CPropType::CommunityFundProposal); + } else if (str == "voc") { + type = uint8_t(CPropType::VoteOfConfidence); + } else if (str != "all") { + throw JSONRPCError(RPC_INVALID_PARAMETER, "type supports cfp/voc/all"); + } + } + + if (request.params.size() > 1) { + auto str = request.params[1].get_str(); + if (str == "voting") { + status = uint8_t(CPropStatusType::Voting); + } else if (str == "rejected") { + status = uint8_t(CPropStatusType::Rejected); + } else if (str == "completed") { + status = uint8_t(CPropStatusType::Completed); + } else if (str != "all") { + throw JSONRPCError(RPC_INVALID_PARAMETER, "status supports voting/rejected/completed/all"); + } } - } - uint8_t status{0}; - if (request.params.size() > 1) { - auto str = request.params[1].get_str(); - if (str == "voting") { - status = uint8_t(CPropStatusType::Voting); - } else if (str == "rejected") { - status = uint8_t(CPropStatusType::Rejected); - } else if (str == "completed") { - status = uint8_t(CPropStatusType::Completed); - } else if (str != "all") { - throw JSONRPCError(RPC_INVALID_PARAMETER, "status supports voting/rejected/completed/all"); + if (request.params.size() > 2) { + cycle = request.params[2].get_int(); + if (cycle < -1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Incorrect cycle value (0 -> all cycles, -1 -> previous cycle, N -> nth cycle"); + } + } + + if (request.params.size() > 3) { + auto paginationObj = request.params[3].get_obj(); + if (!paginationObj["limit"].isNull()) { + limit = (size_t)paginationObj["limit"].get_int64(); + } + if (!paginationObj["start"].isNull()) { + including_start = false; + start = ParseHashV(paginationObj["start"], "start"); + } + if (!paginationObj["including_start"].isNull()) { + including_start = paginationObj["including_start"].getBool(); + } } } - UniValue ret(UniValue::VARR); + if (limit == 0) { + limit = std::numeric_limits::max(); + } + + UniValue ret{UniValue::VARR}; CCustomCSView view(*pcustomcsview); + using IdPropPair = std::pair; + using CycleEndHeightInt = int; + using PropBatchesMap = std::map>; + + PropBatchesMap propBatches; + + if (cycle != 0) { + // populate map + view.ForEachProp( + [&](const CPropId &propId, const CPropObject &prop) { + auto batch = propBatches.find(prop.cycleEndHeight); + auto propPair = std::make_pair(propId, prop); + // if batch is not found create it + if (batch == propBatches.end()) { + propBatches.insert({prop.cycleEndHeight, std::vector{propPair}}); + } else { // else insert to prop vector + batch->second.push_back(propPair); + } + return true; + }, + static_cast(0), + start); + + auto batch = propBatches.rbegin(); + if (cycle != -1) { + if (static_cast(cycle) > propBatches.size()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find cycle"); + for (unsigned int i = 1; i <= (propBatches.size() - cycle); i++) { + batch++; + } + } else { + batch++; + } + // Filter batch + for (const auto &prop : batch->second) { + if (status && status != prop.second.status) { + continue; + } + if (type && type != prop.second.type) { + continue; + } + limit--; + ret.push_back(proposalToJSON(prop.first, prop.second, view, std::nullopt)); + if (!limit) + break; + } + return ret; + } + view.ForEachProp( [&](const CPropId &propId, const CPropObject &prop) { - if (status && status != uint8_t(prop.status)) { + if (!including_start) { + including_start = true; + return (true); + } + if (status && status != prop.status) { return false; } - if (type && type != uint8_t(prop.type)) { + if (type && type != prop.type) { return true; } + limit--; ret.push_back(proposalToJSON(propId, prop, view, std::nullopt)); - return true; + return limit != 0; }, - status); + static_cast(status), + start); return ret; } @@ -852,7 +1014,7 @@ static const CRPCCommand commands[] = { {"proposals", "votegov", &votegov, {"proposalId", "masternodeId", "decision", "inputs"}}, {"proposals", "listgovproposalvotes", &listgovproposalvotes, {"proposalId", "masternode", "cycle"} }, {"proposals", "getgovproposal", &getgovproposal, {"proposalId"} }, - {"proposals", "listgovproposals", &listgovproposals, {"type", "status"} }, + {"proposals", "listgovproposals", &listgovproposals, {"type", "status", "cycle"} }, }; void RegisterProposalRPCCommands(CRPCTable &tableRPC) { diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 77289612ad..f9796e781f 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -356,6 +356,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "creategovvoc", 0, "data" }, { "creategovvoc", 1, "inputs" }, { "listgovproposalvotes", 2, "cycle" }, + { "listgovproposals", 0, "type" }, }; // clang-format on diff --git a/test/functional/feature_on_chain_government.py b/test/functional/feature_on_chain_government.py old mode 100755 new mode 100644 index d5018ab5f3..c4b4c60a82 --- a/test/functional/feature_on_chain_government.py +++ b/test/functional/feature_on_chain_government.py @@ -582,6 +582,20 @@ def run_test(self): assert_equal(len(self.nodes[0].listgovproposals("all", "completed")), 1) assert_equal(len(self.nodes[0].listgovproposals("all", "rejected")), 4) + assert_equal(len(self.nodes[0].listgovproposals("all", "rejected", 0, {"limit":1})), 1) + assert_equal(len(self.nodes[0].listgovproposals("all", "rejected", 0, {"limit":0})), 4) + assert_equal(len(self.nodes[0].listgovproposals("all", "rejected", 0, {"limit":10})), 4) + assert_equal(self.nodes[0].listgovproposals("all", "rejected", 0, {"start": tx, "including_start": True})[0]["proposalId"], tx) + + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"voting"})), 0) + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"completed"})), 1) + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"rejected"})), 4) + + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"rejected", "pagination": {"limit":1}})), 1) + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"rejected", "pagination": {"limit":0}})), 4) + assert_equal(len(self.nodes[0].listgovproposals({"type":"all", "status":"rejected", "pagination": {"limit":10}})), 4) + assert_equal(self.nodes[0].listgovproposals({"type":"all", "status":"rejected", "pagination": {"start": tx, "including_start": True}})[0]["proposalId"], tx) + # Test pagination, total number of votes is 3 assert_equal(len(self.nodes[1].listgovproposalvotes( {"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 0}})), 2) diff --git a/test/functional/rpc_listgovproposals.py b/test/functional/rpc_listgovproposals.py new file mode 100755 index 0000000000..9c06118794 --- /dev/null +++ b/test/functional/rpc_listgovproposals.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test on chain government behaviour""" + +from test_framework.test_framework import DefiTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error +) + +from decimal import Decimal + +class ListGovProposalsTest(DefiTestFramework): + def set_test_params(self): + self.num_nodes = 4 + self.setup_clean_chain = True + self.extra_args = [ + ['-dummypos=0', '-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', '-fortcanningspringheight=1', '-fortcanninggreatworldheight=1', '-fortcanningepilogueheight=1', '-grandcentralheight=1', '-subsidytest=1', '-txindex=1'], + ['-dummypos=0', '-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', '-fortcanningspringheight=1', '-fortcanninggreatworldheight=1', '-fortcanningepilogueheight=1', '-grandcentralheight=1', '-subsidytest=1'], + ['-dummypos=0', '-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', '-fortcanningspringheight=1', '-fortcanninggreatworldheight=1', '-fortcanningepilogueheight=1', '-grandcentralheight=1', '-subsidytest=1'], + ['-dummypos=0', '-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', '-fortcanningspringheight=1', '-fortcanninggreatworldheight=1', '-fortcanningepilogueheight=1', '-grandcentralheight=1', '-subsidytest=1'] + ] + + def setup(self): + self.address1 = self.nodes[1].get_genesis_keys().ownerAuthAddress + self.address2 = self.nodes[2].get_genesis_keys().ownerAuthAddress + self.address3 = self.nodes[3].get_genesis_keys().ownerAuthAddress + + # Get MN IDs + self.mn0 = self.nodes[0].getmininginfo()['masternodes'][0]['id'] + self.mn1 = self.nodes[1].getmininginfo()['masternodes'][0]['id'] + self.mn2 = self.nodes[2].getmininginfo()['masternodes'][0]['id'] + self.mn3 = self.nodes[3].getmininginfo()['masternodes'][0]['id'] + + # Generate chain + self.nodes[0].generate(150) + self.sync_blocks() + + self.nodes[0].sendtoaddress(self.address1, Decimal("1.0")) + self.nodes[0].sendtoaddress(self.address2, Decimal("1.0")) + self.nodes[0].sendtoaddress(self.address3, Decimal("1.0")) + self.nodes[0].generate(1) + + # mine at least one block with each mn to be able to vote + self.nodes[1].generate(1) + self.nodes[2].generate(1) + self.nodes[3].generate(1) + self.sync_blocks() + + def create_proposal(self, title="default title", context="default", amount=100, cycles=1): + payoutAddress = self.nodes[0].getnewaddress() + tx = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": amount, "cycles": cycles, "payoutAddress": payoutAddress}) + self.nodes[0].generate(1) + self.sync_blocks() + + proposal = self.nodes[0].getgovproposal(tx) + assert_equal(proposal["proposalId"], tx) + return proposal + + def activate_onchain_gov_attributes(self): + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/feature/gov':'true'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/gov/proposals/fee_redistribution':'true'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/feature/gov-payout':'true'}}) + # VOC emergency Attributes + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/feature/gov-payout':'true', + 'v0/gov/proposals/voc_emergency_period': '25', + 'v0/gov/proposals/voc_emergency_fee':'20.00000000', + 'v0/gov/proposals/voc_approval_threshold':'50.00%' + }}) + self.nodes[0].generate(1) + self.sync_blocks() + + def create_proposals(self, num_props=1, cycles=1): + tmp_proposals = [] + for _ in range(num_props): + tmp_proposal = self.create_proposal(cycles=cycles) + tmp_proposals.append(tmp_proposal) + + return tmp_proposals + + def vote_on_proposals(self, proposals, vote="yes"): + for proposal in proposals: + self.nodes[0].votegov(proposal["proposalId"], self.mn0, vote) + self.nodes[0].generate(1) + self.nodes[1].votegov(proposal["proposalId"], self.mn1, vote) + self.nodes[1].generate(1) + self.nodes[2].votegov(proposal["proposalId"], self.mn2, vote) + self.nodes[2].generate(1) + self.sync_blocks() + + def create_100_proposals_and_revert(self): + revertHeight = self.nodes[0].getblockcount() + self.create_proposals(100) + + self.rollback_to(revertHeight) + prop_list = self.nodes[0].listgovproposals() + assert_equal(len(prop_list), 0) + + def create_20_proposals_go_to_end_cycle_1(self): + tmp_proposals = self.create_proposals(num_props=20) + + # check all inside same cycle + tmp_end_cycle_height = tmp_proposals[0]["cycleEndHeight"] + for prop in tmp_proposals: + assert_equal(prop["cycleEndHeight"], tmp_end_cycle_height) + + self.nodes[0].generate((tmp_end_cycle_height - self.nodes[0].getblockcount()) + 1) + self.sync_blocks() + + prop_list = self.nodes[0].listgovproposals() + + assert_equal(len(prop_list), 20) + + def create_10_proposals_go_to_end_cycle_2(self): + tmp_proposals = self.create_proposals(num_props=10, cycles=2) + + # check all inside same cycle + tmp_end_cycle_height = tmp_proposals[0]["cycleEndHeight"] + for prop in tmp_proposals: + assert_equal(prop["cycleEndHeight"], tmp_end_cycle_height) + + prop_list = self.nodes[0].listgovproposals() + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all", "all", 0) + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all", "all", -1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "all", 1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "all", 2) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "rejected") + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "rejected", -1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "rejected", 1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "rejected", 2) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed") + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", 2) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting") + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "voting", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting", 2) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("voc", "all", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("voc", "all", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("voc") + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("cfp", "all") + assert_equal(len(prop_list), 30) + + # Go to endCycleHeight and check rejected props + self.nodes[0].generate((tmp_end_cycle_height - self.nodes[0].getblockcount()) + 3) + self.sync_blocks() + + prop_list = self.nodes[0].listgovproposals("all", "rejected", 0) + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all", "rejected") + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all", "all") + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all") + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("cfp") + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("cfp", "voting") + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("cfp", "rejected", 2) + assert_equal(len(prop_list), 10) + + def create_10_proposals_and_aprove_half(self): + tmp_proposals = self.create_proposals(num_props=10, cycles=2) + + # check all inside same cycle + tmp_end_cycle_height = tmp_proposals[0]["cycleEndHeight"] + for prop in tmp_proposals: + assert_equal(prop["cycleEndHeight"], tmp_end_cycle_height) + + self.vote_on_proposals(tmp_proposals[0 : int(len(tmp_proposals)/2)], vote="yes") + + prop_list = self.nodes[0].listgovproposals() + assert_equal(len(prop_list), 40) + prop_list = self.nodes[0].listgovproposals("all", "all", 0) + assert_equal(len(prop_list), 40) + prop_list = self.nodes[0].listgovproposals("all", "all", -1) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "all", 1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "all", 2) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "rejected") + assert_equal(len(prop_list), 30) + prop_list = self.nodes[0].listgovproposals("all", "rejected", -1) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "rejected", 1) + assert_equal(len(prop_list), 20) + prop_list = self.nodes[0].listgovproposals("all", "rejected", 2) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "completed") + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", 2) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting") + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("all", "voting", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting", 2) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "voting", 3) + assert_equal(len(prop_list), 10) + prop_list = self.nodes[0].listgovproposals("voc", "all", -1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("voc", "all", 1) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("voc") + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("cfp", "all") + assert_equal(len(prop_list), 40) + + # Go to endCycleHeight and check rejected props + self.nodes[0].generate((tmp_end_cycle_height - self.nodes[0].getblockcount()) + 3) + self.sync_blocks() + + prop_list = self.nodes[0].listgovproposals("all", "rejected", 0) + assert_equal(len(prop_list), 35) + prop_list = self.nodes[0].listgovproposals("all", "rejected") + assert_equal(len(prop_list), 35) + prop_list = self.nodes[0].listgovproposals("all", "all") + assert_equal(len(prop_list), 40) + prop_list = self.nodes[0].listgovproposals("all") + assert_equal(len(prop_list), 40) + prop_list = self.nodes[0].listgovproposals("cfp") + assert_equal(len(prop_list), 40) + prop_list = self.nodes[0].listgovproposals("cfp", "voting") + assert_equal(len(prop_list), 5) + prop_list = self.nodes[0].listgovproposals("cfp", "rejected", 3) + assert_equal(len(prop_list), 5) + prop_list = self.nodes[0].listgovproposals("cfp", "completed", 3) + assert_equal(len(prop_list), 0) + + # check voting CFPs are in second cycle + prop_list = self.nodes[0].listgovproposals("all", "all", 4) + for prop in prop_list: + assert_equal(prop["currentCycle"], 2) + + # approve CFPs and go to next cycle, checl completed state + self.vote_on_proposals(prop_list, "yes") + next_end_height = prop_list[0]["cycleEndHeight"] + self.nodes[0].generate((next_end_height - self.nodes[0].getblockcount()) + 3) + self.sync_blocks() + prop_list = self.nodes[0].listgovproposals("all", "all", 4) + assert_equal(len(prop_list), 5) + prop_list = self.nodes[0].listgovproposals("all", "voting", 4) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "rejected", 4) + assert_equal(len(prop_list), 0) + prop_list = self.nodes[0].listgovproposals("all", "completed", 4) + assert_equal(len(prop_list), 5) + + def run_test(self): + self.setup() + + # check that on-chain governance is disabled + assert_raises_rpc_error(-32600, "Cannot create tx, on-chain governance is not enabled", self.create_proposal) + + # activate onchain gov + self.activate_onchain_gov_attributes() + + # self.create_100_proposals_and_revert() + self.create_20_proposals_go_to_end_cycle_1() + self.create_10_proposals_go_to_end_cycle_2() + self.create_10_proposals_and_aprove_half() + +if __name__ == '__main__': + ListGovProposalsTest().main () diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 27c99b24f9..a28121b27a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -302,6 +302,7 @@ 'feature_on_chain_government.py', 'feature_on_chain_government_voting_period_alignment.py', 'feature_on_chain_government_fee_distribution.py', + 'rpc_listgovproposals.py', 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py',