Skip to content

Commit

Permalink
[RPC] Get all required keys before signing budgets
Browse files Browse the repository at this point in the history
also add finalized budget voting for DMNs
  • Loading branch information
random-zebra committed May 23, 2021
1 parent 0591957 commit 72c2a70
Showing 1 changed file with 177 additions and 134 deletions.
311 changes: 177 additions & 134 deletions src/rpc/budget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,66 +232,106 @@ static UniValue packErrorRetStatus(const std::string& nodeType, const std::strin
return packRetStatus(nodeType, "failed", error);
}

static bool voteProposal(const COutPoint& collOut, const CKey& keyMasternode, const std::string& mnAlias,
const uint256& propHash, const CBudgetVote::VoteDirection& nVote,
UniValue& resultsObj)
static UniValue packVoteReturnValue(const UniValue& details, int success, int failed)
{
CBudgetVote vote(CTxIn(collOut), propHash, nVote);
if (!vote.Sign(keyMasternode, keyMasternode.GetPubKey().GetID())) {
resultsObj.push_back(packErrorRetStatus(mnAlias, "Failure to sign."));
return false;
}
UniValue returnObj(UniValue::VOBJ);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed));
returnObj.pushKV("detail", details);
return returnObj;
}

std::string strError;
if (!g_budgetman.AddAndRelayProposalVote(vote, strError)) {
resultsObj.push_back(packErrorRetStatus(mnAlias, strError));
return false;
}
// key, alias and collateral outpoint of a masternode. Struct used to sign proposal/budget votes
struct MnKeyData
{
std::string mnAlias;
const COutPoint* collateralOut;
CKey key;

MnKeyData(const std::string& _mnAlias, const COutPoint* _collateralOut, const CKey& _key):
mnAlias(_mnAlias),
collateralOut(_collateralOut),
key(_key)
{}
};

resultsObj.push_back(packRetStatus(mnAlias, "success", ""));
return true;
}
typedef std::list<MnKeyData> mnKeyList;

static bool voteFinalBudget(const COutPoint& collOut, const CKey& votingKey, const std::string& mnAlias,
const uint256& budgetHash, UniValue& resultsObj)
static UniValue voteProposal(const uint256& propHash, const CBudgetVote::VoteDirection& nVote,
const mnKeyList& mnKeys, UniValue resultsObj, int failed)
{
CFinalizedBudgetVote vote(CTxIn(collOut), budgetHash);
if (!vote.Sign(votingKey, votingKey.GetPubKey().GetID())) {
resultsObj.push_back(packErrorRetStatus(mnAlias, "Failure to sign."));
return false;
int success = 0;
for (const auto& k : mnKeys) {
CBudgetVote vote(CTxIn(*k.collateralOut), propHash, nVote);
if (!vote.Sign(k.key, k.key.GetPubKey().GetID())) {
resultsObj.push_back(packErrorRetStatus(k.mnAlias, "Failure to sign."));
failed++;
continue;
}
std::string strError;
if (!g_budgetman.AddAndRelayProposalVote(vote, strError)) {
resultsObj.push_back(packErrorRetStatus(k.mnAlias, strError));
failed++;
continue;
}
resultsObj.push_back(packRetStatus(k.mnAlias, "success", ""));
success++;
}

std::string strError = "";
if (!g_budgetman.UpdateFinalizedBudget(vote, nullptr, strError)) {
resultsObj.push_back(packErrorRetStatus(mnAlias, strError));
return false;
return packVoteReturnValue(resultsObj, success, failed);
}

static UniValue voteFinalBudget(const uint256& budgetHash,
const mnKeyList& mnKeys, UniValue resultsObj, int failed)
{
int success = 0;
for (const auto& k : mnKeys) {
CFinalizedBudgetVote vote(CTxIn(*k.collateralOut), budgetHash);
if (!vote.Sign(k.key, k.key.GetPubKey().GetID())) {
resultsObj.push_back(packErrorRetStatus(k.mnAlias, "Failure to sign."));
failed++;
continue;
}
std::string strError = "";
if (!g_budgetman.UpdateFinalizedBudget(vote, nullptr, strError)) {
resultsObj.push_back(packErrorRetStatus(k.mnAlias, strError));
failed++;
continue;
}
g_budgetman.AddSeenFinalizedBudgetVote(vote);
vote.Relay();
resultsObj.push_back(packRetStatus(k.mnAlias, "success", ""));
success++;
}

g_budgetman.AddSeenFinalizedBudgetVote(vote);
vote.Relay();
resultsObj.push_back(packRetStatus(mnAlias, "success", ""));
return true;
return packVoteReturnValue(resultsObj, success, failed);
}

// Legacy masternodes
static bool getMNKeysForEntry(const CMasternodeConfig::CMasternodeEntry& mne,
CKey& key, CPubKey& pubkey, COutPoint& collOut, UniValue& resultsObj)
static mnKeyList getMNKeys(const Optional<std::string>& mnAliasFilter,
UniValue& resultsObj, int& failed)
{
if (!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), key, pubkey)) {
resultsObj.push_back(
packErrorRetStatus(mne.getAlias(), "Masternode signing error, could not set key correctly."));
return false;
}
CMasternode* pmn = mnodeman.Find(pubkey);
if (!pmn) {
resultsObj.push_back(packErrorRetStatus(mne.getAlias(), "Can't find masternode by pubkey"));
return false;
mnKeyList mnKeys;
for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) {
if (mnAliasFilter && *mnAliasFilter != mne.getAlias()) continue;
CKey mnKey; CPubKey mnPubKey;
const std::string& mnAlias = mne.getAlias();
if (!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), mnKey, mnPubKey)) {
resultsObj.push_back(packErrorRetStatus(mnAlias, "Could not get key from masternode.conf"));
failed++;
continue;
}
CMasternode* pmn = mnodeman.Find(mnPubKey);
if (!pmn) {
resultsObj.push_back(packErrorRetStatus(mnAlias, "Can't find masternode by pubkey"));
failed++;
continue;
}
mnKeys.emplace_back(mnAlias, &pmn->vin.prevout, mnKey);
}
collOut = pmn->vin.prevout;
return true;
return mnKeys;
}

static bool getMNKeysForActiveMasternode(CKey& key, CPubKey& pubkey, COutPoint& collOut, UniValue& resultsObj)
static mnKeyList getMNKeysForActiveMasternode(UniValue& resultsObj)
{
// local node must be a masternode
if (!fMasterNode)
Expand All @@ -300,117 +340,111 @@ static bool getMNKeysForActiveMasternode(CKey& key, CPubKey& pubkey, COutPoint&
if (activeMasternode.vin == nullopt)
throw JSONRPCError(RPC_MISC_ERROR, _("Active Masternode not initialized."));

activeMasternode.GetKeys(key, pubkey);
CMasternode* pmn = mnodeman.Find(pubkey);
CKey mnKey; CPubKey mnPubKey;
activeMasternode.GetKeys(mnKey, mnPubKey);
CMasternode* pmn = mnodeman.Find(mnPubKey);
if (!pmn) {
resultsObj.push_back(packErrorRetStatus("local", "Can't find masternode by pubkey"));
return false;
return mnKeyList();
}
collOut = pmn->vin.prevout;
return true;

return {MnKeyData("local", &pmn->vin.prevout, mnKey)};
}

// Deterministic masternodes
static void voteProposalWithDeterministicMNs(const std::map<std::pair<uint256, COutPoint>, CKey>& votingKeys,
const uint256& propHash, const CBudgetVote::VoteDirection& nVote,
UniValue& resultsObj, int& success, int& failed)
static mnKeyList getDMNKeys(CWallet* const pwallet, const Optional<std::string>& mnAliasFilter, bool fFinal, UniValue& resultsObj, int& failed)
{
for (const auto& it : votingKeys) {
const uint256& proTxHash = it.first.first;
const COutPoint& collatOut = it.first.second;
const CKey& votingKey = it.second;
if (voteProposal(collatOut, votingKey, proTxHash.ToString(), propHash, nVote, resultsObj)) {
success++;
} else {
if (!pwallet) {
throw JSONRPCError(RPC_IN_WARMUP, "Wallet (with voting key) not found.");
}

auto mnList = deterministicMNManager->GetListAtChainTip();

CDeterministicMNCPtr mnFilter{nullptr};
if (mnAliasFilter) {
// vote with a single masternode (identified by ProTx)
const uint256& proTxHash = ParseHashV(*mnAliasFilter, "ProTX transaction hash");
mnFilter = mnList.GetValidMN(proTxHash);
if (!mnFilter) {
resultsObj.push_back(packErrorRetStatus(*mnAliasFilter, "Invalid or unknown proTxHash"));
failed++;
return mnKeyList();
}
}

LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);

mnKeyList mnKeys;
mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) {
bool filtered = mnFilter && dmn->proTxHash == mnFilter->proTxHash;
if (!mnFilter || filtered) {
const CKeyID& mnKeyID = fFinal ? dmn->pdmnState->keyIDOperator : dmn->pdmnState->keyIDVoting;
CKey mnKey;
if (pwallet->GetKey(mnKeyID, mnKey)) {
mnKeys.emplace_back(dmn->proTxHash.ToString(), &dmn->collateralOutpoint, mnKey);
} else if (filtered) {
resultsObj.push_back(packErrorRetStatus(*mnAliasFilter, strprintf(
"Private key for voting address %s not known by this wallet", EncodeDestination(mnKeyID))));
failed++;
}
}
});

return mnKeys;
}

static UniValue packVoteReturnValue(const UniValue& details, int success, int failed)
static mnKeyList getDMNKeysForActiveMasternode(UniValue& resultsObj)
{
UniValue returnObj(UniValue::VOBJ);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed));
returnObj.pushKV("detail", details);
return returnObj;
// local node must be a masternode
if (!activeMasternodeManager)
throw JSONRPCError(RPC_MISC_ERROR, _("This is not a deterministic masternode. 'local' option disabled."));

CKey dmnKey; CKeyID dmnKeyID; CDeterministicMNCPtr dmn;
auto res = activeMasternodeManager->GetOperatorKey(dmnKey, dmnKeyID, dmn);
if (!res) {
resultsObj.push_back(packErrorRetStatus("local", res.getError()));
return {};
}

return {MnKeyData("local", &dmn->collateralOutpoint, dmnKey)};
}

// vote on proposal (finalized budget, if final=true) with all possible keys or a single mn (mnAliasFilter)
static UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, Optional<std::string> mnAliasFilter,
const uint256& budgetHash, const CBudgetVote::VoteDirection& nVote, bool final)
// vote on proposal (finalized budget, if fFinal=true) with all possible keys or a single mn (mnAliasFilter)
static UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, const uint256& budgetHash, bool fFinal,
const CBudgetVote::VoteDirection& nVote, const Optional<std::string>& mnAliasFilter)
{
UniValue resultsObj(UniValue::VARR);
int success = 0;
int failed = 0;

if (!fLegacyMN) {
// !TODO: add voting for finalized budget
// Deterministic masternode voting. Need wallet with voting key.
if (!pwallet) {
throw JSONRPCError(RPC_IN_WARMUP, "Wallet (with voting key) not found.");
}

LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);

// <proTx, collatOut> --> votingKey map
std::map<std::pair<uint256, COutPoint>, CKey> votingKeys;
auto mnList = deterministicMNManager->GetListAtChainTip();

if (mnAliasFilter) {
// vote with a single masternode (identified by ProTx)
const uint256& proTxHash = ParseHashV(*mnAliasFilter, "ProTX transaction hash");
auto dmn = mnList.GetValidMN(proTxHash);
if (!dmn) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unknown proTxHash");
}
CKey votingKey;
if (!pwallet->GetKey(dmn->pdmnState->keyIDVoting, votingKey)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Private key for voting address %s not known by wallet", EncodeDestination(dmn->pdmnState->keyIDVoting)));
}
votingKeys.emplace(std::piecewise_construct,
std::forward_as_tuple(proTxHash, dmn->collateralOutpoint),
std::forward_as_tuple(votingKey));
} else {
// vote with all voting keys known by this wallet
mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) {
CKey votingKey;
if (pwallet->GetKey(dmn->pdmnState->keyIDVoting, votingKey)) {
votingKeys.emplace(std::piecewise_construct,
std::forward_as_tuple(dmn->proTxHash, dmn->collateralOutpoint),
std::forward_as_tuple(votingKey));
}
});
}
mnKeyList mnKeys = fLegacyMN ? getMNKeys(mnAliasFilter, resultsObj, failed)
: getDMNKeys(pwallet, mnAliasFilter, fFinal, resultsObj, failed);

voteProposalWithDeterministicMNs(votingKeys, budgetHash, nVote, resultsObj, success, failed);
return packVoteReturnValue(resultsObj, success, failed);
if (mnKeys.empty()) {
return packVoteReturnValue(resultsObj, 0, failed);
}

// Legacy Masternodes
for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) {
if (mnAliasFilter && *mnAliasFilter != mne.getAlias()) continue;
CKey keyMasternode; CPubKey pubKeyMasternode; COutPoint collOut;
bool result = getMNKeysForEntry(mne, keyMasternode, pubKeyMasternode, collOut, resultsObj) &&
(final ? voteFinalBudget(collOut, keyMasternode, mne.getAlias(), budgetHash, resultsObj)
: voteProposal(collOut, keyMasternode, mne.getAlias(), budgetHash, nVote, resultsObj));
if (result) success++;
else failed++;
}
return packVoteReturnValue(resultsObj, success, failed);
return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, failed)
: voteProposal(budgetHash, nVote, mnKeys, resultsObj, failed));
}

// vote on proposal (finalized budget, if final=true) with the active local masternode
// vote on proposal (finalized budget, if fFinal=true) with the active local masternode
// Note: for DMNs only finalized budget voting is allowed with the operator key
// (proposal voting requires the voting key)
static UniValue mnLocalBudgetVoteInner(const uint256& budgetHash, const CBudgetVote::VoteDirection& nVote, bool final)
static UniValue mnLocalBudgetVoteInner(bool fLegacyMN, const uint256& budgetHash, bool fFinal,
const CBudgetVote::VoteDirection& nVote)
{
UniValue resultsObj(UniValue::VARR);
CKey keyMasternode; CPubKey pubKeyMasternode; COutPoint collOut;
bool ret = getMNKeysForActiveMasternode(keyMasternode, pubKeyMasternode, collOut, resultsObj) &&
(final ? voteFinalBudget(collOut, keyMasternode, "local", budgetHash, resultsObj)
: voteProposal(collOut, keyMasternode, "local", budgetHash, nVote, resultsObj));
return packVoteReturnValue(resultsObj, ret, !ret);

mnKeyList mnKeys = fLegacyMN ? getMNKeysForActiveMasternode(resultsObj)
: getDMNKeysForActiveMasternode(resultsObj);

if (mnKeys.empty()) {
return packVoteReturnValue(resultsObj, 0, 1);
}

return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, 0)
: voteProposal(budgetHash, nVote, mnKeys, resultsObj, 0));
}

static CBudgetVote::VoteDirection parseVote(const std::string& strVote)
Expand Down Expand Up @@ -476,7 +510,7 @@ UniValue mnbudgetvote(const JSONRPCRequest& request)
if (!fLegacyMN) {
throw JSONRPCError(RPC_MISC_ERROR, _("\"local\" vote is no longer available with DMNs. Use \"alias\" from the wallet with the voting key."));
}
return mnLocalBudgetVoteInner(hash, nVote, false);
return mnLocalBudgetVoteInner(true, hash, false, nVote);
}

// DMN require wallet with voting key
Expand All @@ -487,7 +521,7 @@ UniValue mnbudgetvote(const JSONRPCRequest& request)
bool isAlias = false;
if (strCommand == "many" || (isAlias = strCommand == "alias")) {
Optional<std::string> mnAlias = isAlias ? Optional<std::string>(request.params[3].get_str()) : nullopt;
return mnBudgetVoteInner(pwallet, fLegacyMN, mnAlias, hash, nVote, false);
return mnBudgetVoteInner(pwallet, fLegacyMN, hash, false, nVote, mnAlias);
}

return NullUniValue;
Expand Down Expand Up @@ -744,6 +778,8 @@ UniValue mnfinalbudget(const JSONRPCRequest& request)
if (request.params.size() >= 1)
strCommand = request.params[0].get_str();

CWallet * const pwallet = GetWalletForJSONRPCRequest(request);

if (request.fHelp ||
(strCommand != "vote-many" && strCommand != "vote" && strCommand != "show" && strCommand != "getvotes"))
throw std::runtime_error(
Expand All @@ -757,12 +793,19 @@ UniValue mnfinalbudget(const JSONRPCRequest& request)
" getvotes - Get vote information for each finalized budget\n");

if (strCommand == "vote-many" || strCommand == "vote") {
if (request.params.size() != 2)
throw std::runtime_error(strprintf("Correct usage is 'mnfinalbudget %s BUDGET_HASH'", strCommand));

if (request.params.size() < 2 || request.params.size() > 3) {
throw std::runtime_error(strprintf("Correct usage is 'mnfinalbudget %s BUDGET_HASH (fLegacy)'", strCommand));
}
const uint256& hash = ParseHashV(request.params[1], "BUDGET_HASH");
return (strCommand == "vote-many" ? mnBudgetVoteInner(nullptr, true, nullopt, hash, CBudgetVote::VOTE_YES, true)
: mnLocalBudgetVoteInner(hash, CBudgetVote::VOTE_YES, true));
bool fLegacyMN = !deterministicMNManager->IsDIP3Enforced() || (request.params.size() > 2 && request.params[2].get_bool());

// DMN require wallet with operator keys for vote-many
if (!fLegacyMN && strCommand == "vote-many" && !EnsureWalletIsAvailable(pwallet, false)) {
return NullUniValue;
}

return (strCommand == "vote-many" ? mnBudgetVoteInner(pwallet, fLegacyMN, hash, true, CBudgetVote::VOTE_YES, nullopt)
: mnLocalBudgetVoteInner(fLegacyMN, hash, true, CBudgetVote::VOTE_YES));
}

if (strCommand == "show") {
Expand Down

0 comments on commit 72c2a70

Please sign in to comment.