Skip to content

Commit

Permalink
Add pagination support for listgovproposalvotes (#1635)
Browse files Browse the repository at this point in the history
* Add pagination support for `listgovproposalvotes`

* Update listgovproposalvotes help dialogue

* Respect filters in listgovproposalvotes pagination

* Alter argument ordering

Prevents optional arguments from becoming mandatory if pagination has to be set

* Make listgovproposalvotes backwards compatible

* Remove empty line

* Format rpc_proposals.cpp

Co-authored-by: jouzo <[email protected]>
Co-authored-by: Prasanna Loganathar <[email protected]>
  • Loading branch information
3 people authored Jan 4, 2023
1 parent 04b7e5f commit 18cb979
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 31 deletions.
181 changes: 150 additions & 31 deletions src/masternodes/rpc_proposals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,51 +520,154 @@ UniValue listgovproposalvotes(const JSONRPCRequest &request) {
RPCHelpMan{
"listgovproposalvotes",
"\nReturns information about proposal votes.\n",
{{"proposalId", RPCArg::Type::STR, RPCArg::Optional::NO, "The proposal id)"},
{
{"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)"}},
RPCArg::Type::NUM,
RPCArg::Optional::OMITTED,
"cycle: 0 (show current), cycle: N (show cycle N), cycle: -1 (show all) (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 votes to return, 100 by default"},
},
}, },
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);
if (request.params[0].isObject())
RPCTypeCheck(request.params, {UniValue::VOBJ}, true);
else
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR, UniValue::VNUM, UniValue::VOBJ}, true);

auto propId = ParseHashV(request.params[0].get_str(), "proposalId");
CCustomCSView view(*pcustomcsview);

uint256 mnId;
uint256 propId;
bool isMine = true;
if (request.params.size() > 1) {
auto str = request.params[1].get_str();
if (str == "all") {
isMine = false;
} else if (str != "mine") {
isMine = false;
mnId = ParseHashV(str, "masternode");
}
}
CCustomCSView view(*pcustomcsview);

uint8_t cycle{1};
int8_t inputCycle{0};
if (request.params.size() > 2) {
inputCycle = request.params[2].get_int();
}
if (inputCycle == 0) {
auto prop = view.GetProp(propId);
if (!prop) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Proposal <%s> does not exist", propId.GetHex()));

size_t limit = 100;
size_t start = 0;
bool including_start = true;

if (request.params[0].isObject()) {
auto optionsObj = request.params[0].get_obj();
propId = ParseHashV(optionsObj["proposalId"].get_str(), "proposalId");

if (!optionsObj["masternode"].isNull()) {
if (optionsObj["masternode"].get_str() == "all") {
isMine = false;
} else if (optionsObj["masternode"].get_str() != "mine") {
isMine = false;
mnId = ParseHashV(optionsObj["masternode"].get_str(), "masternode");
}
}

if (!optionsObj["cycle"].isNull()) {
inputCycle = optionsObj["cycle"].get_int();
if (inputCycle == 0) {
auto prop = view.GetProp(propId);
if (!prop) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Proposal <%s> does not exist", propId.GetHex()));
}
cycle = prop->cycle;
} else if (inputCycle > 0) {
cycle = inputCycle;
} else if (inputCycle == -1) {
cycle = 1;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incorrect cycle value");
}
}

if (!optionsObj["pagination"].isNull()) {
UniValue paginationObj = optionsObj["pagination"].get_obj();
if (!paginationObj["limit"].isNull()) {
limit = (size_t)paginationObj["limit"].get_int64();
}
if (!paginationObj["start"].isNull()) {
including_start = false;
start = (size_t)paginationObj["start"].get_int();
}
if (!paginationObj["including_start"].isNull()) {
including_start = paginationObj["including_start"].getBool();
}
if (!including_start) {
++start;
}
}
cycle = prop->cycle;
} else if (inputCycle > 0) {
cycle = inputCycle;
} else if (inputCycle == -1) {
cycle = 1;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incorrect cycle value");
propId = ParseHashV(request.params[0].get_str(), "proposalId");

if (request.params.size() > 1) {
auto str = request.params[1].get_str();
if (str == "all") {
isMine = false;
} else if (str != "mine") {
isMine = false;
mnId = ParseHashV(str, "masternode");
}
}

if (request.params.size() > 2) {
inputCycle = request.params[2].get_int();

if (inputCycle == 0) {
auto prop = view.GetProp(propId);
if (!prop) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Proposal <%s> does not exist", propId.GetHex()));
}
cycle = prop->cycle;
} else if (inputCycle > 0) {
cycle = inputCycle;
} else if (inputCycle == -1) {
cycle = 1;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Incorrect cycle value");
}
}

if (request.params.size() > 3) {
UniValue 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 = (size_t)paginationObj["start"].get_int();
}
if (!paginationObj["including_start"].isNull()) {
including_start = paginationObj["including_start"].getBool();
}
if (!including_start) {
++start;
}
}
if (limit == 0) {
limit = std::numeric_limits<decltype(limit)>::max();
}
}

UniValue ret(UniValue::VARR);
Expand All @@ -584,15 +687,31 @@ UniValue listgovproposalvotes(const JSONRPCRequest &request) {
if (!node) {
return true;
}

// skip entries until we reach start index
if (start != 0) {
--start;
return true;
}

auto ownerDest = node->ownerType == 1 ? CTxDestination(PKHash(node->ownerAuthAddress))
: CTxDestination(WitnessV0KeyHash(node->ownerAuthAddress));
if (::IsMineCached(*pwallet, GetScriptForDestination(ownerDest))) {
ret.push_back(proposalVoteToJSON(propId, propCycle, id, vote));
limit--;
}
} else if (mnId.IsNull() || mnId == id) {
// skip entries until we reach start index
if (start != 0) {
--start;
return true;
}

ret.push_back(proposalVoteToJSON(propId, propCycle, id, vote));
limit--;
}
return true;

return limit != 0;
},
CMnVotePerCycle{propId, cycle, mnId});

Expand Down
29 changes: 29 additions & 0 deletions test/functional/feature_on_chain_government.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,5 +582,34 @@ def run_test(self):
assert_equal(len(self.nodes[0].listgovproposals("all", "completed")), 1)
assert_equal(len(self.nodes[0].listgovproposals("all", "rejected")), 4)

# 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)
assert_equal(len(self.nodes[1].listgovproposalvotes(
{"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 0, "including_start": True}})),
3)
assert_equal(
len(self.nodes[1].listgovproposalvotes({"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 0, "including_start": True, "limit": 2}})),
2)
assert_equal(
len(self.nodes[1].listgovproposalvotes({"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 0, "including_start": True, "limit": 1}})),
1)

# should be empty if start > number of entries
assert_equal(
len(self.nodes[1].listgovproposalvotes({"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 10, "including_start": True, "limit": 1}})),
0)

# should return all entries if limit is 0
assert_equal(
len(self.nodes[1].listgovproposalvotes({"proposalId": tx, "masternode": "all", "cycle": -1, "pagination": {"start": 0, "including_start": True, "limit": 0}})),
3)

# should respect filters
assert_equal(len(self.nodes[1].listgovproposalvotes({"proposalId": tx, "masternode": mn1, "cycle": -1, "pagination": {"start": 0}})), 0)

# test non-object RPC arguments
assert_equal(len(self.nodes[0].listgovproposalvotes(propId, 'all', -1, {"limit": 2})), 2)

if __name__ == '__main__':
OnChainGovernanceTest().main ()

0 comments on commit 18cb979

Please sign in to comment.