From 72c2a70b1f6d5f8254f2552168194f4f6f806c07 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 18:53:22 +0100 Subject: [PATCH] [RPC] Get all required keys before signing budgets also add finalized budget voting for DMNs --- src/rpc/budget.cpp | 311 ++++++++++++++++++++++++++------------------- 1 file changed, 177 insertions(+), 134 deletions(-) diff --git a/src/rpc/budget.cpp b/src/rpc/budget.cpp index 0ece9b120f755..ea9eca00ed17a 100644 --- a/src/rpc/budget.cpp +++ b/src/rpc/budget.cpp @@ -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 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& 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) @@ -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, CKey>& votingKeys, - const uint256& propHash, const CBudgetVote::VoteDirection& nVote, - UniValue& resultsObj, int& success, int& failed) +static mnKeyList getDMNKeys(CWallet* const pwallet, const Optional& 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 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& 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); - - // --> votingKey map - std::map, 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) @@ -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 @@ -487,7 +521,7 @@ UniValue mnbudgetvote(const JSONRPCRequest& request) bool isAlias = false; if (strCommand == "many" || (isAlias = strCommand == "alias")) { Optional mnAlias = isAlias ? Optional(request.params[3].get_str()) : nullopt; - return mnBudgetVoteInner(pwallet, fLegacyMN, mnAlias, hash, nVote, false); + return mnBudgetVoteInner(pwallet, fLegacyMN, hash, false, nVote, mnAlias); } return NullUniValue; @@ -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( @@ -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") {