Skip to content

Commit

Permalink
Add cycle filtering and pagination to listgovproposals (#1627)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Make pagination rpc consistent

* Resolve compiler warning

* Update help message

Co-authored-by: Keng Ye <[email protected]>

Co-authored-by: jouzo <[email protected]>
Co-authored-by: Jouzo <[email protected]>
Co-authored-by: Mihailo Milenkovic <[email protected]>
Co-authored-by: Peter John Bushnell <[email protected]>
Co-authored-by: Prasanna Loganathar <[email protected]>
Co-authored-by: Keng Ye <[email protected]>
  • Loading branch information
7 people authored Jan 5, 2023
1 parent 90c05a0 commit 3dbfa01
Show file tree
Hide file tree
Showing 7 changed files with 510 additions and 34 deletions.
4 changes: 2 additions & 2 deletions src/masternodes/proposals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,14 @@ std::optional<CPropVoteType> CPropsView::GetPropVote(const CPropId &propId,
return static_cast<CPropVoteType>(*vote);
}

void CPropsView::ForEachProp(std::function<bool(const CPropId &, const CPropObject &)> callback, uint8_t status) {
void CPropsView::ForEachProp(std::function<bool(const CPropId &, const CPropObject &)> callback, const CPropStatusType status, const CPropId start) {
ForEach<ByStatus, std::pair<uint8_t, uint256>, uint8_t>(
[&](const std::pair<uint8_t, uint256> &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<bool(const CPropId &, uint8_t, const uint256 &, CPropVoteType)> callback,
Expand Down
2 changes: 1 addition & 1 deletion src/masternodes/proposals.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class CPropsView : public virtual CStorageView {
Res AddPropVote(const CPropId &propId, const uint256 &masternodeId, CPropVoteType vote);
std::optional<CPropVoteType> GetPropVote(const CPropId &propId, uint8_t cycle, const uint256 &masternodeId);

void ForEachProp(std::function<bool(const CPropId &, const CPropObject &)> callback, uint8_t status = 0);
void ForEachProp(std::function<bool(const CPropId &, const CPropObject &)> callback, const CPropStatusType status, const CPropId start = {});
void ForEachPropVote(std::function<bool(const CPropId &, uint8_t, const uint256 &, CPropVoteType)> callback,
const CMnVotePerCycle &start = {});
void ForEachCycleProp(std::function<bool(const CPropId &, const CPropObject &)> callback, uint32_t height);
Expand Down
224 changes: 193 additions & 31 deletions src/masternodes/rpc_proposals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<decltype(limit)>::max();
}

UniValue ret{UniValue::VARR};
CCustomCSView view(*pcustomcsview);

using IdPropPair = std::pair<CPropId, CPropObject>;
using CycleEndHeightInt = int;
using PropBatchesMap = std::map<CycleEndHeightInt, std::vector<IdPropPair>>;

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<IdPropPair>{propPair}});
} else { // else insert to prop vector
batch->second.push_back(propPair);
}
return true;
},
static_cast<CPropStatusType>(0),
start);

auto batch = propBatches.rbegin();
if (cycle != -1) {
if (static_cast<PropBatchesMap::size_type>(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<CPropStatusType>(status),
start);

return ret;
}
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "creategovvoc", 0, "data" },
{ "creategovvoc", 1, "inputs" },
{ "listgovproposalvotes", 2, "cycle" },
{ "listgovproposals", 0, "type" },
};
// clang-format on

Expand Down
14 changes: 14 additions & 0 deletions test/functional/feature_on_chain_government.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 3dbfa01

Please sign in to comment.