Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pagination support for listgovproposalvotes #1635

Merged
merged 11 commits into from
Jan 4, 2023
202 changes: 160 additions & 42 deletions src/masternodes/rpc_proposals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,56 +482,158 @@ UniValue votegov(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)"},
{
"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();

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

UniValue ret(UniValue::VARR);
Expand All @@ -551,15 +653,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 @@ -566,5 +566,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 ()