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

rpc: votegov: Add support for owner and operator address #1717

Merged
merged 14 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/masternodes/rpc_proposals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ UniValue votegov(const JSONRPCRequest &request) {
"\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"},
{"masternodeId/ownerAddress/operatorAdress", RPCArg::Type::STR, RPCArg::Optional::NO, "The masternode id / owner address / operator address which made the vote"},
DocteurPing marked this conversation as resolved.
Show resolved Hide resolved
{"decision", RPCArg::Type::STR, RPCArg::Optional::NO, "The vote decision (yes/no/neutral)"},
{
"inputs",
Expand Down Expand Up @@ -449,7 +449,8 @@ UniValue votegov(const JSONRPCRequest &request) {
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");
std::string id = request.params[1].get_str();
uint256 mnId;
auto vote = CProposalVoteType::VoteNeutral;

auto voteStr = ToLower(request.params[2].get_str());
Expand All @@ -474,9 +475,26 @@ UniValue votegov(const JSONRPCRequest &request) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Proposal <%s> is not in voting period", propId.GetHex()));
}
if (id.length() == 64) {
mnId = ParseHashV(id, "masternodeId");
} else {
CTxDestination dest = DecodeDestination(id);
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("The masternode id or address is not valid: %s", id));
}
const CKeyID ckeyId = dest.index() == PKHashType ? CKeyID(std::get<PKHash>(dest)) : CKeyID(std::get<WitnessV0KeyHash>(dest));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure that the provided address is either P2PKH or a P2WPKH, otherwise if someone passes a different variant then std::get will throw a std::bad_variant_access.

            const CKeyID ckeyId  = dest.index() == PKHashType ? CKeyID(std::get<PKHash>(dest))
                                 : dest.index() == WitV0KeyHashType ? CKeyID(std::get<WitnessV0KeyHash>(dest))
                                 : CKeyID();

auto masterNodeIdByOwner = view.GetMasternodeIdByOwner(ckeyId);
if (auto masterNodeIdByOwner = view.GetMasternodeIdByOwner(ckeyId)) {
mnId = masterNodeIdByOwner.value();
} else if (auto masterNodeIdByOperator = view.GetMasternodeIdByOperator(ckeyId)) {
mnId = masterNodeIdByOperator.value();
}
}
auto node = view.GetMasternode(mnId);
if (!node) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("The masternode %s does not exist", mnId.ToString()));
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("The masternode does not exist or the address doesn't own a masternode: %s", id));
}
ownerDest = node->ownerType == 1 ? CTxDestination(PKHash(node->ownerAuthAddress))
: CTxDestination(WitnessV0KeyHash(node->ownerAuthAddress));
Expand Down
87 changes: 83 additions & 4 deletions test/functional/feature_on_chain_government_voting_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from test_framework.test_framework import DefiTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error
)

APPROVAL_THRESHOLD=50
Expand All @@ -24,22 +25,22 @@ def set_test_params(self):

def setup_masternodes(self, nMasternodes = 19):
self.nodes[0].mns = []
operatorAddresses = []
self.operatorAddresses = []

for _ in range(nMasternodes):
address = self.nodes[0].getnewaddress('', 'legacy')
self.nodes[0].mns.append(self.nodes[0].createmasternode(address))
operatorAddresses.append(address)
self.operatorAddresses.append(address)
self.nodes[0].generate(1)

self.nodes[0].generate(20) # Enables all MNs
self.sync_blocks(timeout=120)

# restart node with masternode_operator addresses to be able to mint with every MNs
self.restart_node(0, self.nodes[0].extra_args + ['-masternode_operator={}'.format(address) for address in operatorAddresses])
self.restart_node(0, self.nodes[0].extra_args + ['-masternode_operator={}'.format(address) for address in self.operatorAddresses])

# Mint with every MNs to meet voting eligibility criteria
for address in operatorAddresses:
for address in self.operatorAddresses:
self.nodes[0].generatetoaddress(1, address)

def setup(self):
Expand Down Expand Up @@ -92,38 +93,114 @@ def test_vote_on_cfp(self, yesVote, noVote, neutralVote, expectedStatus):

self.rollback_to(height)

def test_vote_on_cfp_with_address(self, yesVote, noVote, neutralVote, expectedStatus):
height = self.nodes[0].getblockcount()

# Create address for CFP
address = self.nodes[0].getnewaddress()
context = "<Git issue url>"
title = "Create test community fund proposal"
amount = 100

# Create CFP
propId = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": amount, "cycles": 1, "payoutAddress": address})
self.nodes[0].generate(1)

addressIterator = iter(self.operatorAddresses)

for _ in range(yesVote):
mnId = next(addressIterator)
self.nodes[0].votegov(propId, mnId, 'yes')

for _ in range(noVote):
mnId = next(addressIterator)
self.nodes[0].votegov(propId, mnId, 'no')

for _ in range(neutralVote):
mnId = next(addressIterator)
self.nodes[0].votegov(propId, mnId, 'neutral')

self.nodes[0].generate(1)

self.nodes[0].generate(VOTING_PERIOD * 2)
proposal = self.nodes[0].getgovproposal(propId)

assert_equal(proposal['status'], expectedStatus)

self.rollback_to(height)

def test_vote_with_address_without_masternode(self):
# Create address for CFP
address = self.nodes[0].getnewaddress()
context = "<Git issue url>"
title = "Create test community fund proposal"
amount = 100

# Create CFP
propId = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": amount, "cycles": 1, "payoutAddress": address})
self.nodes[0].generate(1)

address = self.nodes[0].getnewaddress('', 'legacy')

assert_raises_rpc_error(-8, "The masternode does not exist or the address doesn't own a masternode: {}".format(address), self.nodes[0].votegov, propId, address, 'yes')

def test_vote_with_invalid_address(self):
# Create address for CFP
address = self.nodes[0].getnewaddress()
context = "<Git issue url>"
title = "Create test community fund proposal"
amount = 100

# Create CFP
propId = self.nodes[0].creategovcfp({"title": title, "context": context, "amount": amount, "cycles": 1, "payoutAddress": address})
self.nodes[0].generate(1)

address = "fake_address"
assert_raises_rpc_error(-8, "The masternode id or address is not valid: {}".format(address), self.nodes[0].votegov, propId, address, 'yes')

def test_scenario_below_approval_threshold(self, expectedStatus):
self.test_vote_on_cfp(yesVote=4, noVote=6, neutralVote=2, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=4, noVote=6, neutralVote=2, expectedStatus=expectedStatus)

def test_scenario_at_approval_threshold(self, expectedStatus):
self.test_vote_on_cfp(yesVote=8, noVote=8, neutralVote=0, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=8, noVote=8, neutralVote=0, expectedStatus=expectedStatus)

def test_scenario_above_approval_threshold(self, expectedStatus):
self.test_vote_on_cfp(yesVote=10, noVote=6, neutralVote=2, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=10, noVote=6, neutralVote=2, expectedStatus=expectedStatus)

def test_scenario_below_quorum(self, expectedStatus):
self.test_vote_on_cfp(yesVote=6, noVote=2, neutralVote=1, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=6, noVote=2, neutralVote=1, expectedStatus=expectedStatus)

def test_scenario_at_quorum(self, expectedStatus):
self.test_vote_on_cfp(yesVote=6, noVote=2, neutralVote=2, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=6, noVote=2, neutralVote=2, expectedStatus=expectedStatus)

def test_scenario_above_quorum(self, expectedStatus):
self.test_vote_on_cfp(yesVote=6, noVote=3, neutralVote=2, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=6, noVote=3, neutralVote=2, expectedStatus=expectedStatus)

def test_scenario_high_neutral_vote(self, expectedStatus):
self.test_vote_on_cfp(yesVote=8, noVote=3, neutralVote=5, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=8, noVote=3, neutralVote=5, expectedStatus=expectedStatus)

def test_scenario_only_yes_and_neutral(self, expectedStatus):
self.test_vote_on_cfp(yesVote=8, noVote=0, neutralVote=8, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=8, noVote=0, neutralVote=8, expectedStatus=expectedStatus)

def test_scenario_66_6_percent_approval_full_yes_votes(self, expectedStatus):
self.test_vote_on_cfp(yesVote=len(self.nodes[0].mns), noVote=0, neutralVote=0, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=len(self.nodes[0].mns), noVote=0, neutralVote=0, expectedStatus=expectedStatus)

def test_scenario_66_6_percent_approval_full_no_votes(self, expectedStatus):
self.test_vote_on_cfp(yesVote=0, noVote=len(self.nodes[0].mns), neutralVote=0, expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=0, noVote=len(self.nodes[0].mns), neutralVote=0, expectedStatus=expectedStatus)

def test_scenario_66_6_percent_approval_full_neutral_votes(self, expectedStatus):
self.test_vote_on_cfp(yesVote=0, noVote=0, neutralVote=len(self.nodes[0].mns), expectedStatus=expectedStatus)
self.test_vote_on_cfp_with_address(yesVote=0, noVote=0, neutralVote=len(self.nodes[0].mns), expectedStatus=expectedStatus)

def scenarios_test(self):
self.nodes[0].setgov({"ATTRIBUTES":{
Expand Down Expand Up @@ -166,6 +243,8 @@ def run_test(self):
self.setup()

self.scenarios_test()
self.test_vote_with_address_without_masternode()
self.test_vote_with_invalid_address()

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