From 366bfd04c471092e751c99f5311737fc8f9f3844 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 6 May 2021 14:40:34 +0200 Subject: [PATCH 01/26] [RPC] Remove redundant checks for wallet existing/unlocked --- src/rpc/rpcevo.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index a593bcae1a4d..4c348f812b1f 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -207,7 +207,6 @@ static CKeyID ParsePubKeyIDFromAddress(const std::string& strAddress) template static void FundSpecialTx(CWallet* pwallet, CMutableTransaction& tx, SpecialTxPayload& payload) { - assert(pwallet != nullptr); SetTxPayload(tx, payload); static CTxOut dummyTxOut(0, CScript() << OP_RETURN); From c5b9e6ffaa2255084a9562c207f2ac1421f919ea Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Feb 2021 05:24:39 +0100 Subject: [PATCH 02/26] [RPC][Budget] Deterministic MNs: vote for proposals --- src/rpc/budget.cpp | 122 ++++++++++++++++++++++++++++++++++++++------- src/rpc/client.cpp | 1 + 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/rpc/budget.cpp b/src/rpc/budget.cpp index 7fbdf7d4d1f9..f66915e8e241 100644 --- a/src/rpc/budget.cpp +++ b/src/rpc/budget.cpp @@ -8,6 +8,7 @@ #include "db.h" #include "init.h" #include "budget/budgetmanager.h" +#include "evo/deterministicmns.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeconfig.h" @@ -231,18 +232,12 @@ UniValue packErrorRetStatus(const std::string& nodeType, const std::string& erro return packRetStatus(nodeType, "failed", error); } -bool voteProposal(CPubKey& pubKeyMasternode, CKey& keyMasternode, const std::string& mnAlias, +bool voteProposal(const COutPoint& collOut, const CKey& keyMasternode, const std::string& mnAlias, const uint256& propHash, const CBudgetVote::VoteDirection& nVote, UniValue& resultsObj) { - CMasternode* pmn = mnodeman.Find(pubKeyMasternode); - if (!pmn) { - resultsObj.push_back(packErrorRetStatus(mnAlias, "Can't find masternode by pubkey")); - return false; - } - - CBudgetVote vote(pmn->vin, propHash, nVote); - if (!vote.Sign(keyMasternode, pubKeyMasternode.GetID())) { + CBudgetVote vote(CTxIn(collOut), propHash, nVote); + if (!vote.Sign(keyMasternode, keyMasternode.GetPubKey().GetID())) { resultsObj.push_back(packErrorRetStatus(mnAlias, "Failure to sign.")); return false; } @@ -257,9 +252,11 @@ bool voteProposal(CPubKey& pubKeyMasternode, CKey& keyMasternode, const std::str return true; } +// Legacy masternodes bool voteProposalMasternodeEntry(const CMasternodeConfig::CMasternodeEntry& mne, const uint256& propHash, const CBudgetVote::VoteDirection& nVote, - UniValue& resultsObj) { + UniValue& resultsObj) +{ CPubKey pubKeyMasternode; CKey keyMasternode; @@ -269,7 +266,30 @@ bool voteProposalMasternodeEntry(const CMasternodeConfig::CMasternodeEntry& mne, return false; } - return voteProposal(pubKeyMasternode, keyMasternode, mne.getAlias(), propHash, nVote, resultsObj); + CMasternode* pmn = mnodeman.Find(pubKeyMasternode); + if (!pmn) { + resultsObj.push_back(packErrorRetStatus(mne.getAlias(), "Can't find masternode by pubkey")); + return false; + } + + return voteProposal(pmn->vin.prevout, keyMasternode, mne.getAlias(), propHash, nVote, resultsObj); +} + +// Deterministic masternodes +void voteProposalWithDeterministicMNs(const std::map, CKey>& votingKeys, + const uint256& propHash, const CBudgetVote::VoteDirection& nVote, + UniValue& resultsObj, int& success, 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 { + failed++; + } + } } UniValue packVoteReturnValue(const UniValue& details, int success, int failed) @@ -280,12 +300,57 @@ UniValue packVoteReturnValue(const UniValue& details, int success, int failed) return returnObj; } -UniValue mnBudgetVoteInner(Optional mnAliasFilter, const uint256& propHash, +UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, Optional mnAliasFilter, const uint256& propHash, const CBudgetVote::VoteDirection& nVote) { UniValue resultsObj(UniValue::VARR); int success = 0; int failed = 0; + + if (!fLegacyMN) { + // 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)); + } + }); + } + + voteProposalWithDeterministicMNs(votingKeys, propHash, nVote, resultsObj, success, failed); + return packVoteReturnValue(resultsObj, success, failed); + } + + // Legacy Masternodes for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) { if (mnAliasFilter && *mnAliasFilter != mne.getAlias()) continue; if (!voteProposalMasternodeEntry(mne, propHash, nVote, resultsObj)) { @@ -312,7 +377,12 @@ UniValue mnLocalBudgetVoteInner(const uint256& propHash, const CBudgetVote::Vote CPubKey pubKeyMasternode; CKey keyMasternode; activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); - bool ret = voteProposal(pubKeyMasternode, keyMasternode, "local", propHash, nVote, resultsObj); + CMasternode* pmn = mnodeman.Find(pubKeyMasternode); + if (!pmn) { + resultsObj.push_back(packErrorRetStatus("local", "Can't find masternode by pubkey")); + return false; + } + bool ret = voteProposal(pmn->vin.prevout, keyMasternode, "local", propHash, nVote, resultsObj); return packVoteReturnValue(resultsObj, ret, !ret); } @@ -337,17 +407,21 @@ UniValue mnbudgetvote(const JSONRPCRequest& request) if (strCommand == "vote-alias") strCommand = "alias"; } + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (request.fHelp || (request.params.size() == 3 && (strCommand != "local" && strCommand != "many")) || (request.params.size() == 4 && strCommand != "alias") || - request.params.size() > 4 || request.params.size() < 3) + request.params.size() > 5 || request.params.size() < 3) throw std::runtime_error( - "mnbudgetvote \"local|many|alias\" \"votehash\" \"yes|no\" ( \"alias\" )\n" + "mnbudgetvote \"local|many|alias\" \"votehash\" \"yes|no\" ( \"alias\" legacy )\n" "\nVote on a budget proposal\n" + "\nAfter V6 enforcement, the deterministic masternode system is used by default. Set the \"legacy\" parameter to true to vote with legacy masternodes." "\nArguments:\n" "1. \"mode\" (string, required) The voting mode. 'local' for voting directly from a masternode, 'many' for voting with a MN controller and casting the same vote for each MN, 'alias' for voting with a MN controller and casting a vote for a single MN\n" "2. \"votehash\" (string, required) The vote hash for the proposal\n" "3. \"votecast\" (string, required) Your vote. 'yes' to vote for the proposal, 'no' to vote against\n" - "4. \"alias\" (string, required for 'alias' mode) The MN alias to cast a vote for.\n" + "4. \"alias\" (string, required for 'alias' mode) The MN alias to cast a vote for (for deterministic masternodes it's the hash of the proTx transaction).\n" + "5. \"legacy\" (boolean, optional, default=false) Use the legacy masternode system after deterministic masternodes enforcement.\n" "\nResult:\n" "{\n" @@ -363,20 +437,30 @@ UniValue mnbudgetvote(const JSONRPCRequest& request) "}\n" "\nExamples:\n" + - HelpExampleCli("mnbudgetvote", "\"local\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\"") + - HelpExampleRpc("mnbudgetvote", "\"local\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\"")); + HelpExampleCli("mnbudgetvote", "\"alias\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\" \"4f9de28fca1f0574a217c5d3c59cc51125ec671de82a2f80b6ceb69673115041\"") + + HelpExampleRpc("mnbudgetvote", "\"alias\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\" \"4f9de28fca1f0574a217c5d3c59cc51125ec671de82a2f80b6ceb69673115041\"")); const uint256& hash = ParseHashV(request.params[1], "parameter 1"); CBudgetVote::VoteDirection nVote = parseVote(request.params[2].get_str()); + bool fLegacyMN = !deterministicMNManager->IsDIP3Enforced() || (request.params.size() > 4 && request.params[4].get_bool()); + if (strCommand == "local") { + 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); } + // DMN require wallet with voting key + if (!fLegacyMN && !EnsureWalletIsAvailable(pwallet, false)) { + return NullUniValue; + } + bool isAlias = false; if (strCommand == "many" || (isAlias = strCommand == "alias")) { Optional mnAlias = isAlias ? Optional(request.params[3].get_str()) : nullopt; - return mnBudgetVoteInner(mnAlias, hash, nVote); + return mnBudgetVoteInner(pwallet, fLegacyMN, mnAlias, hash, nVote); } return NullUniValue; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b70e6ae48f80..c64b48adb18b 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -144,6 +144,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { // TODO: remove this and switch over to proper arg parsing in rpc/masternode.cpp for the second argument //{"startmasternode", 1}, { "startmasternode", 3 }, + { "mnbudgetvote", 4 }, { "mnvoteraw", 1 }, { "mnvoteraw", 4 }, { "setstakesplitthreshold", 0 }, From f93bc0f40477bfb61ee329121c8156fc8fa144d8 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Feb 2021 06:03:49 +0100 Subject: [PATCH 03/26] [Budget] Validate proposal votes from deterministic masternodes --- src/budget/budgetmanager.cpp | 41 ++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index dbffb3b1ffdc..91926b59d469 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -5,6 +5,7 @@ #include "budget/budgetmanager.h" +#include "evo/deterministicmns.h" #include "masternode-sync.h" #include "masternodeman.h" #include "net_processing.h" @@ -533,7 +534,7 @@ void CBudgetManager::VoteOnFinalizedBudgets() LogPrintf("%s: Error submitting vote - %s\n", __func__, strError); continue; } - LogPrint(BCLog::MNBUDGET,"%s: new finalized budget vote signed: %s\n", __func__, vote.GetHash().ToString()); + LogPrint(BCLog::MNBUDGET, "%s: new finalized budget vote signed: %s\n", __func__, vote.GetHash().ToString()); AddSeenFinalizedBudgetVote(vote); vote.Relay(); } @@ -800,10 +801,18 @@ void CBudgetManager::RemoveStaleVotesOnProposal(CBudgetProposal* prop) auto it = prop->mapVotes.begin(); while (it != prop->mapVotes.end()) { - CMasternode* pmn = mnodeman.Find(it->first); - (*it).second.SetValid(pmn && pmn->IsEnabled()); + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(it->first); + if (dmn) { + (*it).second.SetValid(mnList.IsMNValid(dmn)); + } else { + // -- Legacy System (!TODO: remove after enforcement) -- + CMasternode* pmn = mnodeman.Find(it->first); + (*it).second.SetValid(pmn && pmn->IsEnabled()); + } ++it; } + LogPrint(BCLog::MNBUDGET, "Cleaned proposal votes for %s. After: YES=%d, NO=%d\n", prop->GetName(), prop->GetYeas(), prop->GetNays()); } @@ -967,6 +976,30 @@ int CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom) } const CTxIn& voteVin = vote.GetVin(); + + // See if this vote was signed with a deterministic masternode + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(voteVin.prevout); + if (dmn) { + if (!vote.CheckSignature(dmn->pdmnState->keyIDVoting)) { + LogPrint(BCLog::MNBUDGET, "mvote - signature invalid from dmn %s\n", dmn->proTxHash.ToString()); + return 100; + } + AddSeenProposalVote(vote); + std::string strError = "masternode not valid or PoSe banned"; + if (mnList.IsMNValid(dmn) && UpdateProposal(vote, pfrom, strError)) { + vote.Relay(); + const uint256& voteID = vote.GetHash(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n", + voteID.ToString(), vote.GetProposalHash().ToString(), dmn->proTxHash.ToString()); + } else { + LogPrint(BCLog::MNBUDGET, "mvote - error: %s", strError); + } + return 0; + } + + // -- Legacy System (!TODO: remove after enforcement) -- CMasternode* pmn = mnodeman.Find(voteVin.prevout); if (!pmn) { LogPrint(BCLog::MNBUDGET, "mvote - unknown masternode - vin: %s\n", voteVin.ToString()); @@ -978,7 +1011,7 @@ int CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom) if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) { if (masternodeSync.IsSynced()) { - LogPrintf("mvote - signature invalid\n"); + LogPrint(BCLog::MNBUDGET, "mvote - signature invalid\n"); return 20; } // it could just be a non-synced masternode From 972d236476f6dbdf50cb87ebce2a035cf53ab3c4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Feb 2021 09:23:42 +0100 Subject: [PATCH 04/26] [RPC] Init Deterministic masternode on-demand --- src/activemasternode.cpp | 14 +++++++++++++- src/budget/budgetmanager.cpp | 6 ++++++ src/messagesigner.cpp | 2 +- src/rpc/client.cpp | 1 + src/rpc/masternode.cpp | 22 +++++++++++++++++++--- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index ab82b9681953..abcc2a19d84d 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -69,8 +69,18 @@ OperationResult CActiveDeterministicMasternodeManager::SetOperatorKey(const std: void CActiveDeterministicMasternodeManager::Init() { - if (!fMasterNode || !deterministicMNManager->IsDIP3Enforced()) + // set masternode arg if called from RPC + if (!fMasterNode) { + gArgs.ForceSetArg("-masternode", "1"); + fMasterNode = true; + } + + if (!deterministicMNManager->IsDIP3Enforced()) { + state = MASTERNODE_ERROR; + strError = "Evo upgrade is not active yet."; + LogPrintf("%s -- ERROR: %s\n", __func__, strError); return; + } LOCK(cs_main); @@ -260,6 +270,7 @@ void CActiveMasternode::ManageStatus() return; } + // !TODO: Legacy masternodes - remove after enforcement LogPrint(BCLog::MASTERNODE, "CActiveMasternode::ManageStatus() - Begin\n"); // If a DMN has been registered with same collateral, disable me. @@ -427,3 +438,4 @@ void CActiveMasternode::GetKeys(CKey& _privKeyMasternode, CPubKey& _pubKeyMaster _privKeyMasternode = privKeyMasternode; _pubKeyMasternode = pubKeyMasternode; } + diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 91926b59d469..2215a18fdacf 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -486,6 +486,12 @@ void CBudgetManager::VoteOnFinalizedBudgets() return; } + // check that the active masternode is enabled + if (activeMasternode.GetStatus() != ACTIVE_MASTERNODE_STARTED) { + LogPrint(BCLog::MNBUDGET,"%s: MN not enabled (%s)\n", __func__, activeMasternode.GetStatusMessage()); + return; + } + std::vector vBudget = GetBudget(); if (vBudget.empty()) { LogPrint(BCLog::MNBUDGET,"%s: No proposal can be finalized\n", __func__); diff --git a/src/messagesigner.cpp b/src/messagesigner.cpp index 46df3e55b824..18e2d3a5e00d 100644 --- a/src/messagesigner.cpp +++ b/src/messagesigner.cpp @@ -19,7 +19,7 @@ bool CMessageSigner::GetKeysFromSecret(const std::string& strSecret, CKey& keyRe return false; pubkeyRet = keyRet.GetPubKey(); - return true; + return pubkeyRet.IsValid(); } bool CMessageSigner::GetKeysFromSecret(const std::string& strSecret, CKey& keyRet, CKeyID& keyIDRet) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c64b48adb18b..e31a6e1ea58d 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -120,6 +120,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { { "importpubkey", 2 }, { "importmulti", 0 }, { "importmulti", 1 }, + { "initmasternode", 2}, { "exportsaplingkey", 1 }, { "importsaplingkey", 2 }, { "importsaplingviewingkey", 2 }, diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index a91bf2812b30..e18cfa2b7de8 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -55,13 +55,14 @@ UniValue mnping(const JSONRPCRequest& request) UniValue initmasternode(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.empty() || request.params.size() > 2)) { + if (request.fHelp || (request.params.size() < 2|| request.params.size() > 3)) { throw std::runtime_error( - "initmasternode ( \"masternodePrivKey\" \"masternodeAddr\" )\n" + "initmasternode \"masternodePrivKey\" \"masternodeAddr\" ( deterministic )\n" "\nInitialize masternode on demand if it's not already initialized.\n" "\nArguments:\n" "1. masternodePrivKey (string, required) The masternode private key.\n" "2. masternodeAddr (string, required) The IP:Port of this masternode.\n" + "3. deterministic (boolean, optional, default=false) Init as DMN.\n" "\nResult:\n" " success (string) if the masternode initialization succeeded.\n" @@ -73,6 +74,21 @@ UniValue initmasternode(const JSONRPCRequest& request) std::string _strMasterNodePrivKey = request.params[0].get_str(); std::string _strMasterNodeAddr = request.params[1].get_str(); + bool fDeterministic = request.params.size() > 2 && request.params[2].get_bool(); + if (fDeterministic) { + if (!activeMasternodeManager) { + activeMasternodeManager = new CActiveDeterministicMasternodeManager(); + RegisterValidationInterface(activeMasternodeManager); + } + auto res = activeMasternodeManager->SetOperatorKey(_strMasterNodePrivKey); + if (!res) throw std::runtime_error(res.getError()); + activeMasternodeManager->Init(); + if (activeMasternodeManager->GetState() == CActiveDeterministicMasternodeManager::MASTERNODE_ERROR) { + throw std::runtime_error(activeMasternodeManager->GetStatus()); + } + return "success"; + } + // legacy auto res = initMasternode(_strMasterNodePrivKey, _strMasterNodeAddr, false); if (!res) throw std::runtime_error(res.getError()); return "success"; @@ -200,7 +216,7 @@ UniValue listmasternodes(const JSONRPCRequest& request) obj.pushKV("network", strNetwork); obj.pushKV("txhash", strTxHash); obj.pushKV("outidx", (uint64_t)oIdx); - obj.pushKV("pubkey", HexStr(mn.pubKeyMasternode)); + obj.pushKV("pubkey", EncodeDestination(mn.pubKeyMasternode.GetID())); obj.pushKV("status", strStatus); obj.pushKV("addr", EncodeDestination(mn.pubKeyCollateralAddress.GetID())); obj.pushKV("version", mn.protocolVersion); From 5df5067c2ede8c2a567945377562dafe22c78c06 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Feb 2021 09:24:28 +0100 Subject: [PATCH 05/26] [Tests] tiertwo_governance_basic: add deterministic masternodes --- .../test_framework/test_framework.py | 67 +++++++++++++++---- .../tiertwo_governance_sync_basic.py | 30 ++++++--- .../tiertwo_masternode_activation.py | 14 ++-- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 64d362bd6112..95e01d663182 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1069,6 +1069,35 @@ def stake_and_ping(self, node_id, num_blocks, with_ping_mns=[]): self.send_pings(with_ping_mns) + + def setupDMN(self, + mnOwner, + miner, + mnRemotePos): + self.log.info("Creating proRegTx for deterministic masternode...") + collateralAdd = mnOwner.getnewaddress("dmn1") + # send to the owner the collateral tx cost + some dust for the ProReg and fee + fundingTxId = miner.sendtoaddress(collateralAdd, Decimal('10001')) + # confirm and verify reception + self.stake_and_sync(self.nodes.index(miner), 1) + assert_greater_than(mnOwner.getrawtransaction(fundingTxId, 1)["confirmations"], 0) + # create and send the ProRegTx funding the collateral + operatorAdd = self.nodes[mnRemotePos].getnewaddress("dmn_operator") + ipport = "127.0.0.1:"+str(p2p_port(mnRemotePos)) + ownerAdd = mnOwner.getnewaddress("dmn_owner") + votingAdd = mnOwner.getnewaddress("dmn_voting") + proTxId = mnOwner.protx_register_fund(collateralAdd, ipport, ownerAdd, operatorAdd, votingAdd, collateralAdd) + self.sync_mempools([mnOwner, miner]) + # confirm and verify inclusion in list + self.stake_and_sync(self.nodes.index(miner), 1) + assert_greater_than(self.nodes[mnRemotePos].getrawtransaction(proTxId, 1)["confirmations"], 0) + assert proTxId in self.nodes[mnRemotePos].protx_list(False) + return ( + proTxId, + self.nodes[mnRemotePos].dumpprivkey(operatorAdd) + ) + + def setupMasternode(self, mnOwner, miner, @@ -1123,12 +1152,7 @@ class PivxTier2TestFramework(PivxTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 5 - self.extra_args = [[], - ["-listen", "-externalip=127.0.0.1"], - [], - ["-listen", "-externalip=127.0.0.1"], - ["-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi"]] + self.num_nodes = 6 self.enable_mocktime() self.ownerOnePos = 0 @@ -1136,6 +1160,12 @@ def set_test_params(self): self.ownerTwoPos = 2 self.remoteTwoPos = 3 self.minerPos = 4 + self.remoteDMNPos = 5 + + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250"]] * self.num_nodes + for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMNPos]: + self.extra_args[i] += ["-listen", "-externalip=127.0.0.1"] + self.extra_args[self.minerPos].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") self.masternodeOneAlias = "mnOne" self.masternodeTwoAlias = "mntwo" @@ -1143,22 +1173,25 @@ def set_test_params(self): self.mnOnePrivkey = "9247iC59poZmqBYt9iDh9wDam6v9S1rW5XekjLGyPnDhrDkP4AK" self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" - # Updated in setup_2_masternodes_network() to be called at the start of run_test + # Updated in setup_3_masternodes_network() to be called at the start of run_test self.ownerOne = None # self.nodes[self.ownerOnePos] self.remoteOne = None # self.nodes[self.remoteOnePos] self.ownerTwo = None # self.nodes[self.ownerTwoPos] self.remoteTwo = None # self.nodes[self.remoteTwoPos] self.miner = None # self.nodes[self.minerPos] + self.remoteDMN = None # self.nodes[self.remoteDMNPos] self.mnOneTxHash = "" self.mnTwoTxHash = "" + self.proRegTx = "" def send_3_pings(self): + mns = [self.remoteOne, self.remoteTwo] self.advance_mocktime(30) - self.send_pings([self.remoteOne, self.remoteTwo]) - self.stake(1, [self.remoteOne, self.remoteTwo]) + self.send_pings(mns) + self.stake(1, mns) self.advance_mocktime(30) - self.send_pings([self.remoteOne, self.remoteTwo]) + self.send_pings(mns) time.sleep(2) def stake(self, num_blocks, with_ping_mns=[]): @@ -1180,22 +1213,23 @@ def advance_mocktime_and_stake(self, secs_to_add): self.mocktime = self.generate_pos(self.minerPos, self.mocktime) time.sleep(2) - def setup_2_masternodes_network(self): + def setup_3_masternodes_network(self): self.ownerOne = self.nodes[self.ownerOnePos] self.remoteOne = self.nodes[self.remoteOnePos] self.ownerTwo = self.nodes[self.ownerTwoPos] self.remoteTwo = self.nodes[self.remoteTwoPos] self.miner = self.nodes[self.minerPos] + self.remoteDMN = self.nodes[self.remoteDMNPos] ownerOneDir = os.path.join(self.options.tmpdir, "node0") ownerTwoDir = os.path.join(self.options.tmpdir, "node2") - self.log.info("generating 259 blocks..") + self.log.info("generating 256 blocks..") # First mine 250 PoW blocks for i in range(250): self.mocktime = self.generate_pow(self.minerPos, self.mocktime) self.sync_blocks() # Then start staking - self.stake(9) + self.stake(6) self.log.info("masternodes setup..") # setup first masternode node, corresponding to nodeOne @@ -1214,6 +1248,12 @@ def setup_2_masternodes_network(self): os.path.join(ownerTwoDir, "regtest"), self.remoteTwoPos, self.mnTwoPrivkey) + # setup deterministic masternode + self.proRegTx, self.dmnPrivkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMNPos + ) self.log.info("masternodes setup completed, initializing them..") @@ -1225,6 +1265,7 @@ def setup_2_masternodes_network(self): remoteTwoPort = p2p_port(self.remoteTwoPos) self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) + self.remoteDMN.initmasternode(self.dmnPrivkey, "", True) # wait until mnsync complete on all nodes self.stake(1) diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 3ff5412cfa93..3b46330b932a 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -7,7 +7,7 @@ from test_framework.util import ( assert_equal, assert_true, - Decimal, + satoshi_round, ) import time @@ -96,7 +96,7 @@ def check_budgetprojection(self, expected): def run_test(self): self.enable_mocktime() - self.setup_2_masternodes_network() + self.setup_3_masternodes_network() # Prepare the proposal self.log.info("preparing budget proposal..") @@ -147,8 +147,8 @@ def run_test(self): self.stake(7, [self.remoteOne, self.remoteTwo]) # now let's vote for the proposal with the first MN - self.log.info("broadcasting votes for the proposal now..") - voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.masternodeOneAlias) + self.log.info("Voting with MN1...") + voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.masternodeOneAlias, True) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere @@ -157,7 +157,8 @@ def run_test(self): self.log.info("all good, MN1 vote accepted everywhere!") # now let's vote for the proposal with the second MN - voteResult = self.ownerTwo.mnbudgetvote("alias", proposalHash, "yes", self.masternodeTwoAlias) + self.log.info("Voting with MN2...") + voteResult = self.ownerTwo.mnbudgetvote("alias", proposalHash, "yes", self.masternodeTwoAlias, True) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere @@ -165,6 +166,16 @@ def run_test(self): self.check_vote_existence(firstProposalName, self.mnTwoTxHash, "YES") self.log.info("all good, MN2 vote accepted everywhere!") + # now let's vote for the proposal with the first DMN + self.log.info("Voting with DMN...") + voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx) + assert_equal(voteResult["detail"][0]["result"], "success") + + # check that the vote was accepted everywhere + self.stake(1, [self.remoteOne, self.remoteTwo]) + self.check_vote_existence(firstProposalName, self.proRegTx, "YES") + self.log.info("all good, DM1 vote accepted everywhere!") + # Now check the budget blockStart = nextSuperBlockHeight blockEnd = blockStart + firstProposalCycles * 145 @@ -174,8 +185,8 @@ def run_test(self): expected_budget = [ self.get_proposal_obj(firstProposalName, firstProposalLink, proposalHash, proposalFeeTxId, blockStart, blockEnd, firstProposalCycles, RemainingPaymentCount, firstProposalAddress, 1, - 2, 0, 0, Decimal(str(TotalPayment)), Decimal(str(firstProposalAmountPerCycle)), - True, True, Decimal(str(Allotted)), Decimal(str(Allotted))) + 3, 0, 0, satoshi_round(TotalPayment), satoshi_round(firstProposalAmountPerCycle), + True, True, satoshi_round(Allotted), satoshi_round(Allotted)) ] self.check_budgetprojection(expected_budget) @@ -217,8 +228,5 @@ def run_test(self): self.check_budgetprojection(expected_budget) - - - if __name__ == '__main__': - MasternodeGovernanceBasicTest().main() \ No newline at end of file + MasternodeGovernanceBasicTest().main() diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index ec95325cac99..c06a8842fb4a 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -5,7 +5,7 @@ from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( - assert_equal, + assert_greater_than_or_equal, connect_nodes_clique, disconnect_nodes, satoshi_round, @@ -44,9 +44,13 @@ def reconnect_and_restart_masternodes(self): self.controller_start_all_masternodes() def spend_collateral(self): - mnCollateralOutput = self.ownerOne.getmasternodeoutputs()[0] - assert_equal(mnCollateralOutput["txhash"], self.mnOneTxHash) - mnCollateralOutputIndex = mnCollateralOutput["outputidx"] + mnCollateralOutputs = self.ownerOne.getmasternodeoutputs() + mnCollateralOutputIndex = -1 + for x in mnCollateralOutputs: + if x["txhash"] == self.mnOneTxHash: + mnCollateralOutputIndex = x["outputidx"] + break + assert_greater_than_or_equal(mnCollateralOutputIndex, 0) send_value = satoshi_round(100 - 0.001) inputs = [{'txid' : self.mnOneTxHash, 'vout' : mnCollateralOutputIndex}] outputs = {} @@ -86,7 +90,7 @@ def wait_until_mn_expired(self, _timeout, removed=False): def run_test(self): self.enable_mocktime() - self.setup_2_masternodes_network() + self.setup_3_masternodes_network() # check masternode expiration self.log.info("testing expiration now.") From 6fcd53dfa4a61140146b6a966e9f0955cd09a47b Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 15 Feb 2021 00:27:28 +0100 Subject: [PATCH 06/26] [MN] Active MN manager: return key and dmn after validation --- src/activemasternode.cpp | 22 ++++++++++++++++++++-- src/activemasternode.h | 4 ++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index abcc2a19d84d..1cc372f1da6e 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -7,7 +7,6 @@ #include "addrman.h" #include "evo/providertx.h" -#include "evo/deterministicmns.h" #include "masternode-sync.h" #include "masternode.h" #include "masternodeconfig.h" @@ -67,6 +66,24 @@ OperationResult CActiveDeterministicMasternodeManager::SetOperatorKey(const std: return OperationResult(true); } +OperationResult CActiveDeterministicMasternodeManager::GetOperatorKey(CKey& key, CKeyID& keyID, CDeterministicMNCPtr& dmn) const +{ + if (!IsReady()) { + return errorOut("Active masternode not ready"); + } + dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(info.proTxHash); + if (!dmn) { + return errorOut(strprintf("Active masternode %s not registered or PoSe banned", info.proTxHash.ToString())); + } + if (info.keyIDOperator != dmn->pdmnState->keyIDOperator) { + return errorOut("Active masternode operator key changed or revoked"); + } + // return keys + key = info.keyOperator; + keyID = info.keyIDOperator; + return OperationResult(true); +} + void CActiveDeterministicMasternodeManager::Init() { // set masternode arg if called from RPC @@ -119,6 +136,8 @@ void CActiveDeterministicMasternodeManager::Init() LogPrintf("%s: proTxHash=%s, proTx=%s\n", __func__, dmn->proTxHash.ToString(), dmn->ToString()); + info.proTxHash = dmn->proTxHash; + if (info.service != dmn->pdmnState->addr) { state = MASTERNODE_ERROR; strError = strprintf("Local address %s does not match the address from ProTx (%s)", @@ -142,7 +161,6 @@ void CActiveDeterministicMasternodeManager::Init() } } - info.proTxHash = dmn->proTxHash; state = MASTERNODE_READY; } diff --git a/src/activemasternode.h b/src/activemasternode.h index 1990b94fad45..bdcc97950668 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -8,6 +8,7 @@ #include "init.h" #include "key.h" +#include "evo/deterministicmns.h" #include "masternode.h" #include "net.h" #include "operationresult.h" @@ -60,6 +61,9 @@ class CActiveDeterministicMasternodeManager : public CValidationInterface void Reset(masternode_state_t _state); // Sets the Deterministic Masternode Operator's private/public key OperationResult SetOperatorKey(const std::string& strMNOperatorPrivKey); + // If the active masternode is ready, and the keyID matches with the registered one, + // return private key, keyID, and pointer to dmn. + OperationResult GetOperatorKey(CKey& key, CKeyID& keyID, CDeterministicMNCPtr& dmn) const; void SetNullProTx() { info.proTxHash = UINT256_ZERO; } const CActiveMasternodeInfo* GetInfo() const { return &info; } From a7557f71ee8294ef4f24921715ab83b4efc764a1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 12:28:27 +0100 Subject: [PATCH 07/26] [Validation] Sign/Verify final budgets with DMNs --- src/budget/budgetmanager.cpp | 70 +++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 2215a18fdacf..13f793023a0f 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -474,9 +474,28 @@ void CBudgetManager::VoteOnFinalizedBudgets() LogPrint(BCLog::MNBUDGET,"%s: Not a masternode\n", __func__); return; } - if (activeMasternode.vin == nullopt) { - LogPrint(BCLog::MNBUDGET,"%s: Active Masternode not initialized\n", __func__); - return; + + CKey mnKey; CKeyID mnKeyID; CTxIn mnVin; + if (activeMasternodeManager != nullptr) { + // deterministic mn + CDeterministicMNCPtr dmn; + auto res = activeMasternodeManager->GetOperatorKey(mnKey, mnKeyID, dmn); + if (!res) { + LogPrint(BCLog::MNBUDGET,"%s: %s\n", __func__, res.getError()); + return; + } + mnVin = CTxIn(dmn->collateralOutpoint); + + } else { + // legacy mn + if (activeMasternode.vin == nullopt) { + LogPrint(BCLog::MNBUDGET,"%s: Active Masternode not initialized\n", __func__); + return; + } + CPubKey mnPubKey; + activeMasternode.GetKeys(mnKey, mnPubKey); + mnKeyID = mnPubKey.GetID(); + mnVin = *activeMasternode.vin; } // Do this 1 in 4 blocks -- spread out the voting activity @@ -523,15 +542,10 @@ void CBudgetManager::VoteOnFinalizedBudgets() } } - // Get masternode keys - CPubKey pubKeyMasternode; - CKey keyMasternode; - activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); - // Sign finalized budgets for (const uint256& budgetHash: vBudgetHashes) { - CFinalizedBudgetVote vote(*(activeMasternode.vin), budgetHash); - if (!vote.Sign(keyMasternode, pubKeyMasternode.GetID())) { + CFinalizedBudgetVote vote(mnVin, budgetHash); + if (!vote.Sign(mnKey, mnKeyID)) { LogPrintf("%s: Failure to sign budget %s", __func__, budgetHash.ToString()); continue; } @@ -831,8 +845,15 @@ void CBudgetManager::RemoveStaleVotesOnFinalBudget(CFinalizedBudget* fbud) auto it = fbud->mapVotes.begin(); while (it != fbud->mapVotes.end()) { - CMasternode* pmn = mnodeman.Find(it->first); - (*it).second.SetValid(pmn && pmn->IsEnabled()); + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(it->first); + if (dmn) { + (*it).second.SetValid(mnList.IsMNValid(dmn)); + } else { + // -- Legacy System (!TODO: remove after enforcement) -- + CMasternode* pmn = mnodeman.Find(it->first); + (*it).second.SetValid(pmn && pmn->IsEnabled()); + } ++it; } LogPrint(BCLog::MNBUDGET, "Cleaned finalized budget votes for [%s (%s)]. After: %d\n", @@ -1038,6 +1059,7 @@ int CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom) int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget) { + const uint256& nHash = finalbudget.GetHash(); if (HaveFinalizedBudget(nHash)) { masternodeSync.AddedBudgetItem(nHash); @@ -1063,6 +1085,30 @@ int CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode } const CTxIn& voteVin = vote.GetVin(); + + // See if this vote was signed with a deterministic masternode + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(voteVin.prevout); + if (dmn) { + if (!vote.CheckSignature(dmn->pdmnState->keyIDOperator)) { + LogPrintf("fbvote - signature invalid from dmn %s\n", dmn->proTxHash.ToString()); + return 100; + } + AddSeenFinalizedBudgetVote(vote); + std::string strError = "masternode not valid or PoSe banned"; + if (mnList.IsMNValid(dmn) && UpdateFinalizedBudget(vote, pfrom, strError)) { + vote.Relay(); + const uint256& voteID = vote.GetHash(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n", + voteID.ToString(), vote.GetBudgetHash().ToString(), dmn->proTxHash.ToString()); + } else { + LogPrint(BCLog::MNBUDGET, "fbvote - error: %s", strError); + } + return 0; + } + + // -- Legacy System (!TODO: remove after enforcement) -- CMasternode* pmn = mnodeman.Find(voteVin.prevout); if (!pmn) { LogPrint(BCLog::MNBUDGET, "fbvote - unknown masternode - vin: %s\n", voteVin.prevout.hash.ToString()); From 05919578c8dbdc49d9936675bfeedbb809ff2344 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 15:30:54 +0100 Subject: [PATCH 08/26] [RPC][Refactoring] Mn final budget / proposal voting code de-duplication --- src/rpc/budget.cpp | 235 ++++++++++++++++----------------------------- 1 file changed, 82 insertions(+), 153 deletions(-) diff --git a/src/rpc/budget.cpp b/src/rpc/budget.cpp index f66915e8e241..0ece9b120f75 100644 --- a/src/rpc/budget.cpp +++ b/src/rpc/budget.cpp @@ -218,7 +218,7 @@ UniValue submitbudget(const JSONRPCRequest& request) return proposal.GetHash().ToString(); } -UniValue packRetStatus(const std::string& nodeType, const std::string& result, const std::string& error) +static UniValue packRetStatus(const std::string& nodeType, const std::string& result, const std::string& error) { UniValue statusObj(UniValue::VOBJ); statusObj.pushKV("node", nodeType); @@ -227,14 +227,14 @@ UniValue packRetStatus(const std::string& nodeType, const std::string& result, c return statusObj; } -UniValue packErrorRetStatus(const std::string& nodeType, const std::string& error) +static UniValue packErrorRetStatus(const std::string& nodeType, const std::string& error) { return packRetStatus(nodeType, "failed", error); } -bool voteProposal(const COutPoint& collOut, const CKey& keyMasternode, const std::string& mnAlias, - const uint256& propHash, const CBudgetVote::VoteDirection& nVote, - UniValue& resultsObj) +static bool voteProposal(const COutPoint& collOut, const CKey& keyMasternode, const std::string& mnAlias, + const uint256& propHash, const CBudgetVote::VoteDirection& nVote, + UniValue& resultsObj) { CBudgetVote vote(CTxIn(collOut), propHash, nVote); if (!vote.Sign(keyMasternode, keyMasternode.GetPubKey().GetID())) { @@ -252,33 +252,68 @@ bool voteProposal(const COutPoint& collOut, const CKey& keyMasternode, const std return true; } -// Legacy masternodes -bool voteProposalMasternodeEntry(const CMasternodeConfig::CMasternodeEntry& mne, - const uint256& propHash, const CBudgetVote::VoteDirection& nVote, - UniValue& resultsObj) +static bool voteFinalBudget(const COutPoint& collOut, const CKey& votingKey, const std::string& mnAlias, + const uint256& budgetHash, UniValue& resultsObj) { - CPubKey pubKeyMasternode; - CKey keyMasternode; + CFinalizedBudgetVote vote(CTxIn(collOut), budgetHash); + if (!vote.Sign(votingKey, votingKey.GetPubKey().GetID())) { + resultsObj.push_back(packErrorRetStatus(mnAlias, "Failure to sign.")); + return false; + } - if (!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), keyMasternode, pubKeyMasternode)) { + std::string strError = ""; + if (!g_budgetman.UpdateFinalizedBudget(vote, nullptr, strError)) { + resultsObj.push_back(packErrorRetStatus(mnAlias, strError)); + return false; + } + + g_budgetman.AddSeenFinalizedBudgetVote(vote); + vote.Relay(); + resultsObj.push_back(packRetStatus(mnAlias, "success", "")); + return true; +} + +// Legacy masternodes +static bool getMNKeysForEntry(const CMasternodeConfig::CMasternodeEntry& mne, + CKey& key, CPubKey& pubkey, COutPoint& collOut, UniValue& resultsObj) +{ + 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(pubKeyMasternode); + CMasternode* pmn = mnodeman.Find(pubkey); if (!pmn) { resultsObj.push_back(packErrorRetStatus(mne.getAlias(), "Can't find masternode by pubkey")); return false; } + collOut = pmn->vin.prevout; + return true; +} - return voteProposal(pmn->vin.prevout, keyMasternode, mne.getAlias(), propHash, nVote, resultsObj); +static bool getMNKeysForActiveMasternode(CKey& key, CPubKey& pubkey, COutPoint& collOut, UniValue& resultsObj) +{ + // local node must be a masternode + if (!fMasterNode) + throw JSONRPCError(RPC_MISC_ERROR, _("This is not a masternode. 'local' option disabled.")); + + if (activeMasternode.vin == nullopt) + throw JSONRPCError(RPC_MISC_ERROR, _("Active Masternode not initialized.")); + + activeMasternode.GetKeys(key, pubkey); + CMasternode* pmn = mnodeman.Find(pubkey); + if (!pmn) { + resultsObj.push_back(packErrorRetStatus("local", "Can't find masternode by pubkey")); + return false; + } + collOut = pmn->vin.prevout; + return true; } // Deterministic masternodes -void voteProposalWithDeterministicMNs(const std::map, CKey>& votingKeys, - const uint256& propHash, const CBudgetVote::VoteDirection& nVote, - UniValue& resultsObj, int& success, int& failed) +static void voteProposalWithDeterministicMNs(const std::map, CKey>& votingKeys, + const uint256& propHash, const CBudgetVote::VoteDirection& nVote, + UniValue& resultsObj, int& success, int& failed) { for (const auto& it : votingKeys) { const uint256& proTxHash = it.first.first; @@ -292,7 +327,7 @@ void voteProposalWithDeterministicMNs(const std::map mnAliasFilter, const uint256& propHash, - const CBudgetVote::VoteDirection& nVote) +// 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) { 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."); @@ -346,47 +383,37 @@ UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, Optionalvin.prevout, keyMasternode, "local", propHash, nVote, resultsObj); + 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); } -CBudgetVote::VoteDirection parseVote(const std::string& strVote) +static CBudgetVote::VoteDirection parseVote(const std::string& strVote) { if (strVote != "yes" && strVote != "no") throw JSONRPCError(RPC_MISC_ERROR, "You can only vote 'yes' or 'no'"); CBudgetVote::VoteDirection nVote = CBudgetVote::VOTE_ABSTAIN; @@ -449,7 +476,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); + return mnLocalBudgetVoteInner(hash, nVote, false); } // DMN require wallet with voting key @@ -460,7 +487,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); + return mnBudgetVoteInner(pwallet, fLegacyMN, mnAlias, hash, nVote, false); } return NullUniValue; @@ -718,7 +745,7 @@ UniValue mnfinalbudget(const JSONRPCRequest& request) strCommand = request.params[0].get_str(); if (request.fHelp || - (strCommand != "suggest" && strCommand != "vote-many" && strCommand != "vote" && strCommand != "show" && strCommand != "getvotes")) + (strCommand != "vote-many" && strCommand != "vote" && strCommand != "show" && strCommand != "getvotes")) throw std::runtime_error( "mnfinalbudget \"command\"... ( \"passphrase\" )\n" "\nVote or show current budgets\n" @@ -729,111 +756,13 @@ UniValue mnfinalbudget(const JSONRPCRequest& request) " show - Show existing finalized budgets\n" " getvotes - Get vote information for each finalized budget\n"); - if (strCommand == "vote-many") { + if (strCommand == "vote-many" || strCommand == "vote") { if (request.params.size() != 2) - throw std::runtime_error("Correct usage is 'mnfinalbudget vote-many BUDGET_HASH'"); - - std::string strHash = request.params[1].get_str(); - uint256 hash(uint256S(strHash)); - - int success = 0; - int failed = 0; - - UniValue resultsObj(UniValue::VOBJ); - - for (CMasternodeConfig::CMasternodeEntry mne : masternodeConfig.getEntries()) { - std::vector vchMasterNodeSignature; - std::string strMasterNodeSignMessage; + throw std::runtime_error(strprintf("Correct usage is 'mnfinalbudget %s BUDGET_HASH'", strCommand)); - CPubKey pubKeyCollateralAddress; - CKey keyCollateralAddress; - CPubKey pubKeyMasternode; - CKey keyMasternode; - - UniValue statusObj(UniValue::VOBJ); - - if (!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), keyMasternode, pubKeyMasternode)) { - failed++; - statusObj.pushKV("result", "failed"); - statusObj.pushKV("errorMessage", "Masternode signing error, could not set key correctly."); - resultsObj.pushKV(mne.getAlias(), statusObj); - continue; - } - - CMasternode* pmn = mnodeman.Find(pubKeyMasternode); - if (pmn == NULL) { - failed++; - statusObj.pushKV("result", "failed"); - statusObj.pushKV("errorMessage", "Can't find masternode by pubkey"); - resultsObj.pushKV(mne.getAlias(), statusObj); - continue; - } - - - CFinalizedBudgetVote vote(pmn->vin, hash); - if (!vote.Sign(keyMasternode, pubKeyMasternode.GetID())) { - failed++; - statusObj.pushKV("result", "failed"); - statusObj.pushKV("errorMessage", "Failure to sign."); - resultsObj.pushKV(mne.getAlias(), statusObj); - continue; - } - - std::string strError = ""; - if (g_budgetman.UpdateFinalizedBudget(vote, NULL, strError)) { - g_budgetman.AddSeenFinalizedBudgetVote(vote); - vote.Relay(); - success++; - statusObj.pushKV("result", "success"); - } else { - failed++; - statusObj.pushKV("result", strError.c_str()); - } - - resultsObj.pushKV(mne.getAlias(), statusObj); - } - - UniValue returnObj(UniValue::VOBJ); - returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed)); - returnObj.pushKV("detail", resultsObj); - - return returnObj; - } - - if (strCommand == "vote") { - if (!fMasterNode) - throw JSONRPCError(RPC_MISC_ERROR, _("This is not a masternode. 'local' option disabled.")); - - if (activeMasternode.vin == nullopt) - throw JSONRPCError(RPC_MISC_ERROR, _("Active Masternode not initialized.")); - - if (request.params.size() != 2) - throw std::runtime_error("Correct usage is 'mnfinalbudget vote BUDGET_HASH'"); - - std::string strHash = request.params[1].get_str(); - uint256 hash(uint256S(strHash)); - - CPubKey pubKeyMasternode; CKey keyMasternode; - activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); - - CMasternode* pmn = mnodeman.Find(activeMasternode.vin->prevout); - if (pmn == NULL) { - return "Failure to find masternode in list : " + activeMasternode.vin->ToString(); - } - - CFinalizedBudgetVote vote(*(activeMasternode.vin), hash); - if (!vote.Sign(keyMasternode, pubKeyMasternode.GetID())) { - return "Failure to sign."; - } - - std::string strError = ""; - if (g_budgetman.UpdateFinalizedBudget(vote, NULL, strError)) { - g_budgetman.AddSeenFinalizedBudgetVote(vote); - vote.Relay(); - return "success"; - } else { - return "Error voting : " + strError; - } + 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)); } if (strCommand == "show") { From 72c2a70b1f6d5f8254f2552168194f4f6f806c07 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 18:53:22 +0100 Subject: [PATCH 09/26] [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 0ece9b120f75..ea9eca00ed17 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") { From 474e0b2d1e5da496492dd6db97d339cd98fc30b0 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 19:04:49 +0100 Subject: [PATCH 10/26] [RPC][Refactoring] Use ProcessProposal(FinalBudget)Vote directly Which adds missing Relay and AddSeen to budget votes --- src/budget/budgetmanager.cpp | 163 ++++++++++++++++++++++------------- src/budget/budgetmanager.h | 7 +- src/rpc/budget.cpp | 14 ++- 3 files changed, 114 insertions(+), 70 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 13f793023a0f..907e489cbcff 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -5,6 +5,7 @@ #include "budget/budgetmanager.h" +#include "consensus/validation.h" #include "evo/deterministicmns.h" #include "masternode-sync.h" #include "masternodeman.h" @@ -995,66 +996,80 @@ int CBudgetManager::ProcessProposal(CBudgetProposal& proposal) return 0; } -int CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom) +bool CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom, CValidationState& state) { - if (HaveSeenProposalVote(vote.GetHash())) { - masternodeSync.AddedBudgetItem(vote.GetHash()); - return 0; + const uint256& voteID = vote.GetHash(); + + if (HaveSeenProposalVote(voteID)) { + masternodeSync.AddedBudgetItem(voteID); + return false; } const CTxIn& voteVin = vote.GetVin(); // See if this vote was signed with a deterministic masternode + std::string err; auto mnList = deterministicMNManager->GetListAtChainTip(); auto dmn = mnList.GetMNByCollateral(voteVin.prevout); if (dmn) { + const std::string& mn_protx_id = dmn->proTxHash.ToString(); + if (!vote.CheckSignature(dmn->pdmnState->keyIDVoting)) { - LogPrint(BCLog::MNBUDGET, "mvote - signature invalid from dmn %s\n", dmn->proTxHash.ToString()); - return 100; + err = strprintf("invalid mvote sig from dmn: %s", mn_protx_id); + return state.DoS(100, false, REJECT_INVALID, "bad-mvote-sig", false, err); } + AddSeenProposalVote(vote); - std::string strError = "masternode not valid or PoSe banned"; - if (mnList.IsMNValid(dmn) && UpdateProposal(vote, pfrom, strError)) { - vote.Relay(); - const uint256& voteID = vote.GetHash(); - masternodeSync.AddedBudgetItem(voteID); - LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n", - voteID.ToString(), vote.GetProposalHash().ToString(), dmn->proTxHash.ToString()); - } else { - LogPrint(BCLog::MNBUDGET, "mvote - error: %s", strError); + + if (!mnList.IsMNValid(dmn)) { + err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id); + return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err); } - return 0; + + if (!UpdateProposal(vote, pfrom, err)) { + return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, strprintf("%s (%s)", err, mn_protx_id)); + } + + vote.Relay(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n", + voteID.ToString(), vote.GetProposalHash().ToString(), mn_protx_id); + return true; } // -- Legacy System (!TODO: remove after enforcement) -- + CMasternode* pmn = mnodeman.Find(voteVin.prevout); if (!pmn) { - LogPrint(BCLog::MNBUDGET, "mvote - unknown masternode - vin: %s\n", voteVin.ToString()); + err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString()); mnodeman.AskForMN(pfrom, voteVin); - return 0; + return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err); } AddSeenProposalVote(vote); if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) { if (masternodeSync.IsSynced()) { - LogPrint(BCLog::MNBUDGET, "mvote - signature invalid\n"); - return 20; + err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString()); + return state.DoS(20, false, REJECT_INVALID, "bad-fbvote", false, err); } // it could just be a non-synced masternode mnodeman.AskForMN(pfrom, voteVin); - return 0; + return false; } - std::string strError; - if (pmn->IsEnabled() && UpdateProposal(vote, pfrom, strError)) { - vote.Relay(); - masternodeSync.AddedBudgetItem(vote.GetHash()); - LogPrint(BCLog::MNBUDGET, "mvote - new budget vote for budget %s - %s\n", vote.GetProposalHash().ToString(), vote.GetHash().ToString()); - } else { - LogPrint(BCLog::MNBUDGET, "mvote error: %s", strError); + if (!pmn->IsEnabled()) { + return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, "masternode not valid"); } - return 0; + if (!UpdateProposal(vote, pfrom, err)) { + return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err); + } + + vote.Relay(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n", + voteID.ToString(), vote.GetProposalHash().ToString(), voteVin.prevout.ToString()); + return true; } int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget) @@ -1077,66 +1092,78 @@ int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget) return 0; } -int CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom) +bool CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom, CValidationState& state) { - if (HaveSeenFinalizedBudgetVote(vote.GetHash())) { - masternodeSync.AddedBudgetItem(vote.GetHash()); - return 0; + const uint256& voteID = vote.GetHash(); + + if (HaveSeenFinalizedBudgetVote(voteID)) { + masternodeSync.AddedBudgetItem(voteID); + return false; } const CTxIn& voteVin = vote.GetVin(); // See if this vote was signed with a deterministic masternode + std::string err; auto mnList = deterministicMNManager->GetListAtChainTip(); auto dmn = mnList.GetMNByCollateral(voteVin.prevout); if (dmn) { + const std::string& mn_protx_id = dmn->proTxHash.ToString(); + if (!vote.CheckSignature(dmn->pdmnState->keyIDOperator)) { - LogPrintf("fbvote - signature invalid from dmn %s\n", dmn->proTxHash.ToString()); - return 100; + err = strprintf("invalid fbvote sig from dmn: %s", mn_protx_id); + return state.DoS(100, false, REJECT_INVALID, "bad-fbvote-sig", false, err); } + AddSeenFinalizedBudgetVote(vote); - std::string strError = "masternode not valid or PoSe banned"; - if (mnList.IsMNValid(dmn) && UpdateFinalizedBudget(vote, pfrom, strError)) { - vote.Relay(); - const uint256& voteID = vote.GetHash(); - masternodeSync.AddedBudgetItem(voteID); - LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n", - voteID.ToString(), vote.GetBudgetHash().ToString(), dmn->proTxHash.ToString()); - } else { - LogPrint(BCLog::MNBUDGET, "fbvote - error: %s", strError); + + if (!mnList.IsMNValid(dmn)) { + err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id); + return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err); } - return 0; + if (!UpdateFinalizedBudget(vote, pfrom, err)) { + return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, strprintf("%s (%s)", err, mn_protx_id)); + } + + vote.Relay(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n", + voteID.ToString(), vote.GetBudgetHash().ToString(), mn_protx_id); + return true; } // -- Legacy System (!TODO: remove after enforcement) -- CMasternode* pmn = mnodeman.Find(voteVin.prevout); if (!pmn) { - LogPrint(BCLog::MNBUDGET, "fbvote - unknown masternode - vin: %s\n", voteVin.prevout.hash.ToString()); + err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString()); mnodeman.AskForMN(pfrom, voteVin); - return 0; + return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err); } AddSeenFinalizedBudgetVote(vote); if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) { if (masternodeSync.IsSynced()) { - LogPrint(BCLog::MNBUDGET, "fbvote - signature from masternode %s invalid\n", HexStr(pmn->pubKeyMasternode)); - return 20; + err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString()); + return state.DoS(20, false, REJECT_INVALID, "bad-fbvote", false, err); } // it could just be a non-synced masternode mnodeman.AskForMN(pfrom, voteVin); - return 0; + return false; } - std::string strError; - if (UpdateFinalizedBudget(vote, pfrom, strError)) { - vote.Relay(); - masternodeSync.AddedBudgetItem(vote.GetHash()); - LogPrint(BCLog::MNBUDGET, "fbvote - new finalized budget vote - %s from masternode %s\n", vote.GetHash().ToString(), HexStr(pmn->pubKeyMasternode)); - } else { - LogPrint(BCLog::MNBUDGET, "fbvote - rejected finalized budget vote - %s from masternode %s - %s\n", vote.GetHash().ToString(), HexStr(pmn->pubKeyMasternode), strError); + if (!pmn->IsEnabled()) { + return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, "masternode not valid"); } - return 0; + if (!UpdateFinalizedBudget(vote, pfrom, err)) { + return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err); + } + + vote.Relay(); + masternodeSync.AddedBudgetItem(voteID); + LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from mn %s\n", + voteID.ToString(), vote.GetBudgetHash().ToString(), voteVin.prevout.ToString()); + return true; } void CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) @@ -1174,7 +1201,15 @@ int CBudgetManager::ProcessMessageInner(CNode* pfrom, std::string& strCommand, C CBudgetVote vote; vRecv >> vote; vote.SetValid(true); - return ProcessProposalVote(vote, pfrom); + CValidationState state; + if (!ProcessProposalVote(vote, pfrom, state)) { + int nDos = 0; + if(state.IsInvalid(nDos) && nDos > 0) { + LogPrint(BCLog::NET, "%s: %s\n", __func__, FormatStateMessage(state)); + return nDos; + } + } + return 0; } if (strCommand == NetMsgType::FINALBUDGET) { @@ -1190,7 +1225,15 @@ int CBudgetManager::ProcessMessageInner(CNode* pfrom, std::string& strCommand, C CFinalizedBudgetVote vote; vRecv >> vote; vote.SetValid(true); - return ProcessFinalizedBudgetVote(vote, pfrom); + CValidationState state; + if (!ProcessFinalizedBudgetVote(vote, pfrom, state)) { + int nDos = 0; + if(state.IsInvalid(nDos) && nDos > 0) { + LogPrint(BCLog::NET, "%s: %s\n", __func__, FormatStateMessage(state)); + return nDos; + } + } + return 0; } // nothing was done diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index add1de973cee..eb1cbc3a7f5d 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -9,6 +9,8 @@ #include "budget/budgetproposal.h" #include "budget/finalizedbudget.h" +class CValidationState; + // // Budget Manager : Contains all proposals for the budget // @@ -97,9 +99,10 @@ class CBudgetManager int ProcessBudgetVoteSync(const uint256& nProp, CNode* pfrom); int ProcessProposal(CBudgetProposal& proposal); - int ProcessProposalVote(CBudgetVote& proposal, CNode* pfrom); int ProcessFinalizedBudget(CFinalizedBudget& finalbudget); - int ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom); + + bool ProcessProposalVote(CBudgetVote& proposal, CNode* pfrom, CValidationState& state); + bool ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom, CValidationState& state); // functions returning a pointer in the map. Need cs_proposals/cs_budgets locked from the caller CBudgetProposal* FindProposal(const uint256& nHash); diff --git a/src/rpc/budget.cpp b/src/rpc/budget.cpp index ea9eca00ed17..0287afabea6e 100644 --- a/src/rpc/budget.cpp +++ b/src/rpc/budget.cpp @@ -267,9 +267,9 @@ static UniValue voteProposal(const uint256& propHash, const CBudgetVote::VoteDir failed++; continue; } - std::string strError; - if (!g_budgetman.AddAndRelayProposalVote(vote, strError)) { - resultsObj.push_back(packErrorRetStatus(k.mnAlias, strError)); + CValidationState state; + if (!g_budgetman.ProcessProposalVote(vote, nullptr, state)) { + resultsObj.push_back(packErrorRetStatus(k.mnAlias, FormatStateMessage(state))); failed++; continue; } @@ -291,14 +291,12 @@ static UniValue voteFinalBudget(const uint256& budgetHash, failed++; continue; } - std::string strError = ""; - if (!g_budgetman.UpdateFinalizedBudget(vote, nullptr, strError)) { - resultsObj.push_back(packErrorRetStatus(k.mnAlias, strError)); + CValidationState state; + if (!g_budgetman.ProcessFinalizedBudgetVote(vote, nullptr, state)) { + resultsObj.push_back(packErrorRetStatus(k.mnAlias, FormatStateMessage(state))); failed++; continue; } - g_budgetman.AddSeenFinalizedBudgetVote(vote); - vote.Relay(); resultsObj.push_back(packRetStatus(k.mnAlias, "success", "")); success++; } From baf60c7f993930940592e2df8270f6a3d159c6c4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 12 Feb 2021 22:20:09 +0100 Subject: [PATCH 11/26] [Tests] governance_sync_basic sign final budget with DMN too --- test/functional/tiertwo_governance_sync_basic.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 3b46330b932a..b451d2ea5a27 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -210,12 +210,19 @@ def run_test(self): self.log.info("budget finalization synced!, now voting for the budget finalization..") - self.ownerOne.mnfinalbudget("vote-many", budgetFinHash) - self.ownerTwo.mnfinalbudget("vote-many", budgetFinHash) + voteResult = self.ownerOne.mnfinalbudget("vote-many", budgetFinHash, True) + assert_equal(voteResult["detail"][0]["result"], "success") + self.log.info("Remote One voted successfully.") + voteResult = self.ownerTwo.mnfinalbudget("vote-many", budgetFinHash, True) + assert_equal(voteResult["detail"][0]["result"], "success") + self.log.info("Remote Two voted successfully.") + voteResult = self.remoteDMN.mnfinalbudget("vote", budgetFinHash) + assert_equal(voteResult["detail"][0]["result"], "success") + self.log.info("DMN voted successfully.") self.stake(2, [self.remoteOne, self.remoteTwo]) self.log.info("checking finalization votes..") - self.check_budget_finalization_sync(2, "OK") + self.check_budget_finalization_sync(3, "OK") self.stake(8, [self.remoteOne, self.remoteTwo]) addrInfo = self.miner.listreceivedbyaddress(0, False, False, firstProposalAddress) From 8b10fc22c292604bdbb8cc8e7a757eed4120d431 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sun, 14 Feb 2021 18:22:17 +0100 Subject: [PATCH 12/26] [P2P] Stop processing mnw messages when Legacy MN system is obsolete --- src/masternode-payments.cpp | 7 ++++ src/masternode-sync.cpp | 67 ++++++++++++++++++++++++------------- src/masternode-sync.h | 3 +- src/masternodeman.cpp | 6 ---- src/net_processing.cpp | 10 ++++-- src/tiertwo_networksync.cpp | 16 ++++----- 6 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 1f2715ea2fb0..4a829949ca6f 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "masternode-payments.h" + #include "addrman.h" #include "chainparams.h" #include "evo/deterministicmns.h" @@ -414,6 +415,12 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st if (fLiteMode) return; //disable all Masternode related functionality + // Skip after legacy obsolete. !TODO: remove when transition to DMN is complete + if (deterministicMNManager->LegacyMNObsolete()) { + LogPrint(BCLog::MASTERNODE, "mnw - skip obsolete message %s\n", strCommand); + return; + } + if (strCommand == NetMsgType::GETMNWINNERS) { //Masternode Payments Request Sync if (fLiteMode) return; //disable all Masternode related functionality diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index 7fcec9276c9b..ec949593c836 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -6,6 +6,7 @@ // clang-format off #include "activemasternode.h" #include "budget/budgetmanager.h" +#include "evo/deterministicmns.h" #include "masternode-sync.h" #include "masternode-payments.h" #include "masternode.h" @@ -160,28 +161,38 @@ bool CMasternodeSync::IsBudgetFinEmpty() return sumBudgetItemFin == 0 && countBudgetItemFin > 0; } -void CMasternodeSync::GetNextAsset() +int CMasternodeSync::GetNextAsset(int currentAsset) { - switch (RequestedMasternodeAssets) { + bool fSkipObsolete = deterministicMNManager->LegacyMNObsolete(); + if (currentAsset > MASTERNODE_SYNC_FINISHED) { + LogPrintf("%s - invalid asset %d\n", __func__, currentAsset); + return MASTERNODE_SYNC_FAILED; + } + switch (currentAsset) { case (MASTERNODE_SYNC_INITIAL): - case (MASTERNODE_SYNC_FAILED): // should never be used here actually, use Reset() instead - ClearFulfilledRequest(); - RequestedMasternodeAssets = MASTERNODE_SYNC_SPORKS; - break; + case (MASTERNODE_SYNC_FAILED): + return MASTERNODE_SYNC_SPORKS; case (MASTERNODE_SYNC_SPORKS): - RequestedMasternodeAssets = MASTERNODE_SYNC_LIST; - break; + return fSkipObsolete ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_LIST; case (MASTERNODE_SYNC_LIST): - RequestedMasternodeAssets = MASTERNODE_SYNC_MNW; - break; + return fSkipObsolete ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_MNW; case (MASTERNODE_SYNC_MNW): - RequestedMasternodeAssets = MASTERNODE_SYNC_BUDGET; - break; + return MASTERNODE_SYNC_BUDGET; case (MASTERNODE_SYNC_BUDGET): - LogPrintf("CMasternodeSync::GetNextAsset - Sync has finished\n"); - RequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED; - break; + default: + return MASTERNODE_SYNC_FINISHED; + } +} + +void CMasternodeSync::SwitchToNextAsset() +{ + const int nextAsset = GetNextAsset(RequestedMasternodeAssets); + if (nextAsset == MASTERNODE_SYNC_FINISHED) { + LogPrintf("%s - Sync has finished\n", __func__); + } else if (nextAsset == MASTERNODE_SYNC_FAILED) { + ClearFulfilledRequest(); } + RequestedMasternodeAssets = nextAsset; RequestedMasternodeAttempt = 0; nAssetSyncStarted = GetTime(); } @@ -280,7 +291,7 @@ void CMasternodeSync::Process() LogPrint(BCLog::MASTERNODE, "CMasternodeSync::Process() - tick %d RequestedMasternodeAssets %d\n", tick, RequestedMasternodeAssets); - if (RequestedMasternodeAssets == MASTERNODE_SYNC_INITIAL) GetNextAsset(); + if (RequestedMasternodeAssets == MASTERNODE_SYNC_INITIAL) SwitchToNextAsset(); // sporks synced but blockchain is not, wait until we're almost at a recent block to continue if (!IsBlockchainSynced() && @@ -312,16 +323,21 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) pnode->FulfilledRequest("getspork"); g_connman->PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS)); //get current network sporks - if (RequestedMasternodeAttempt >= 2) GetNextAsset(); + if (RequestedMasternodeAttempt >= 2) SwitchToNextAsset(); RequestedMasternodeAttempt++; return false; } if (pnode->nVersion >= ActiveProtocol()) { if (RequestedMasternodeAssets == MASTERNODE_SYNC_LIST) { + if (deterministicMNManager->LegacyMNObsolete()) { + SwitchToNextAsset(); + return false; + } + LogPrint(BCLog::MASTERNODE, "CMasternodeSync::Process() - lastMasternodeList %lld (GetTime() - MASTERNODE_SYNC_TIMEOUT) %lld\n", lastMasternodeList, GetTime() - MASTERNODE_SYNC_TIMEOUT); if (lastMasternodeList > 0 && lastMasternodeList < GetTime() - MASTERNODE_SYNC_TIMEOUT * 2 && RequestedMasternodeAttempt >= MASTERNODE_SYNC_THRESHOLD) { //hasn't received a new item in the last five seconds, so we'll move to the - GetNextAsset(); + SwitchToNextAsset(); return false; } @@ -338,7 +354,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) lastFailure = GetTime(); nCountFailures++; } else { - GetNextAsset(); + SwitchToNextAsset(); } return false; } @@ -351,8 +367,13 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) } if (RequestedMasternodeAssets == MASTERNODE_SYNC_MNW) { + if (deterministicMNManager->LegacyMNObsolete()) { + SwitchToNextAsset(); + return false; + } + if (lastMasternodeWinner > 0 && lastMasternodeWinner < GetTime() - MASTERNODE_SYNC_TIMEOUT * 2 && RequestedMasternodeAttempt >= MASTERNODE_SYNC_THRESHOLD) { //hasn't received a new item in the last five seconds, so we'll move to the - GetNextAsset(); + SwitchToNextAsset(); return false; } @@ -369,7 +390,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) lastFailure = GetTime(); nCountFailures++; } else { - GetNextAsset(); + SwitchToNextAsset(); } return false; } @@ -386,7 +407,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) // We'll start rejecting votes if we accidentally get set as synced too soon if (lastBudgetItem > 0 && lastBudgetItem < GetTime() - MASTERNODE_SYNC_TIMEOUT * 2 && RequestedMasternodeAttempt >= MASTERNODE_SYNC_THRESHOLD) { // Hasn't received a new item in the last five seconds, so we'll move to the - GetNextAsset(); + SwitchToNextAsset(); // Try to activate our masternode if possible activeMasternode.ManageStatus(); @@ -397,7 +418,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) if (lastBudgetItem == 0 && (RequestedMasternodeAttempt >= MASTERNODE_SYNC_THRESHOLD * 3 || GetTime() - nAssetSyncStarted > MASTERNODE_SYNC_TIMEOUT * 5)) { // maybe there is no budgets at all, so just finish syncing - GetNextAsset(); + SwitchToNextAsset(); activeMasternode.ManageStatus(); return false; } diff --git a/src/masternode-sync.h b/src/masternode-sync.h index 41b5dd0c083d..32197285c4d8 100644 --- a/src/masternode-sync.h +++ b/src/masternode-sync.h @@ -77,7 +77,7 @@ class CMasternodeSync void AddedMasternodeList(const uint256& hash); void AddedMasternodeWinner(const uint256& hash); void AddedBudgetItem(const uint256& hash); - void GetNextAsset(); + void SwitchToNextAsset(); std::string GetSyncStatus(); void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); bool IsBudgetFinEmpty(); @@ -108,6 +108,7 @@ class CMasternodeSync // Tier two sync node state // map of nodeID --> TierTwoPeerData std::map peersSyncState; + static int GetNextAsset(int currentAsset); void SyncRegtest(CNode* pnode); diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index fd38d3e3873a..281a74baabf7 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -720,12 +720,6 @@ int CMasternodeMan::ProcessMNPing(CNode* pfrom, CMasternodePing& mnp) int CMasternodeMan::ProcessGetMNList(CNode* pfrom, CTxIn& vin) { - // Skip after legacy obsolete. !TODO: remove when transition to DMN is complete - if (deterministicMNManager->LegacyMNObsolete()) { - LogPrint(BCLog::MASTERNODE, "dseg - skip obsolete message\n"); - return 0; - } - if (vin.IsNull()) { //only should ask for this once //local network bool isLocal = (pfrom->addr.IsRFC1918() || pfrom->addr.IsLocal()); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a978b71b14e9..ae31ea4d6bbf 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -8,6 +8,7 @@ #include "budget/budgetmanager.h" #include "chain.h" +#include "evo/deterministicmns.h" #include "masternodeman.h" #include "masternode-payments.h" #include "masternode-sync.h" @@ -854,7 +855,8 @@ bool static PushTierTwoGetDataRequest(const CInv& inv, } } - if (inv.type == MSG_MASTERNODE_WINNER) { + // !TODO: remove when transition to DMN is complete + if (inv.type == MSG_MASTERNODE_WINNER && !deterministicMNManager->LegacyMNObsolete()) { if (masternodePayments.mapMasternodePayeeVotes.count(inv.hash)) { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss.reserve(1000); @@ -892,7 +894,8 @@ bool static PushTierTwoGetDataRequest(const CInv& inv, } } - if (inv.type == MSG_MASTERNODE_ANNOUNCE) { + // !TODO: remove when transition to DMN is complete + if (inv.type == MSG_MASTERNODE_ANNOUNCE && !deterministicMNManager->LegacyMNObsolete()) { if (mnodeman.mapSeenMasternodeBroadcast.count(inv.hash)) { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss.reserve(1000); @@ -902,7 +905,8 @@ bool static PushTierTwoGetDataRequest(const CInv& inv, } } - if (inv.type == MSG_MASTERNODE_PING) { + // !TODO: remove when transition to DMN is complete + if (inv.type == MSG_MASTERNODE_PING && !deterministicMNManager->LegacyMNObsolete()) { if (mnodeman.mapSeenMasternodePing.count(inv.hash)) { CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss.reserve(1000); diff --git a/src/tiertwo_networksync.cpp b/src/tiertwo_networksync.cpp index ce946550cee5..0183a5ec7f61 100644 --- a/src/tiertwo_networksync.cpp +++ b/src/tiertwo_networksync.cpp @@ -4,11 +4,11 @@ #include "masternode-sync.h" -#include "spork.h" // for sporkManager -#include "masternodeman.h" // for mnodeman +#include "masternodeman.h" // for mnodeman #include "netmessagemaker.h" -#include "net_processing.h" // for Misbehaving -#include "streams.h" // for CDataStream +#include "net_processing.h" // for Misbehaving +#include "spork.h" // for sporkManager +#include "streams.h" // for CDataStream // Update in-flight message status if needed @@ -71,7 +71,7 @@ bool CMasternodeSync::MessageDispatcher(CNode* pfrom, std::string& strCommand, C return true; } // All good, Update in-flight message status if needed - if (!UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETSPORKS, MASTERNODE_SYNC_LIST)) { + if (!UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETSPORKS, GetNextAsset(MASTERNODE_SYNC_SPORKS))) { // This could happen because of the message thread is requesting the sporks alone.. // So.. for now, can just update the peer status and move it to the next state if the end message arrives if (spork.nSporkID == SPORK_INVALID) { @@ -96,17 +96,17 @@ bool CMasternodeSync::MessageDispatcher(CNode* pfrom, std::string& strCommand, C // this means we will receive no further communication on the first sync switch (nItemID) { case MASTERNODE_SYNC_LIST: { - UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETMNLIST, MASTERNODE_SYNC_MNW); + UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETMNLIST, GetNextAsset(nItemID)); return true; } case MASTERNODE_SYNC_MNW: { - UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETMNWINNERS, MASTERNODE_SYNC_BUDGET); + UpdatePeerSyncState(pfrom->GetId(), NetMsgType::GETMNWINNERS, GetNextAsset(nItemID)); return true; } case MASTERNODE_SYNC_BUDGET_PROP: { // TODO: This could be a MASTERNODE_SYNC_BUDGET_FIN as well, possibly should decouple the finalization budget sync // from the MASTERNODE_SYNC_BUDGET_PROP (both are under the BUDGETVOTESYNC message) - UpdatePeerSyncState(pfrom->GetId(), NetMsgType::BUDGETVOTESYNC, MASTERNODE_SYNC_FINISHED); + UpdatePeerSyncState(pfrom->GetId(), NetMsgType::BUDGETVOTESYNC, GetNextAsset(nItemID)); return true; } case MASTERNODE_SYNC_BUDGET_FIN: { From 064f7745f101d0b893688612b036c1f0d86d7514 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sun, 14 Feb 2021 19:23:08 +0100 Subject: [PATCH 13/26] [RPC] getmasternodestatus for DMN --- src/rpc/masternode.cpp | 34 +++++++++++++++++-- .../tiertwo_governance_sync_basic.py | 19 +++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index e18cfa2b7de8..b542ac2d5ffa 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -645,7 +645,7 @@ UniValue getmasternodestatus (const JSONRPCRequest& request) "getmasternodestatus\n" "\nPrint masternode status\n" - "\nResult:\n" + "\nResult (if legacy masternode):\n" "{\n" " \"txhash\": \"xxxx\", (string) Collateral transaction hash\n" " \"outputidx\": n, (numeric) Collateral transaction output index number\n" @@ -654,6 +654,11 @@ UniValue getmasternodestatus (const JSONRPCRequest& request) " \"status\": \"xxxx\", (string) Masternode status\n" " \"message\": \"xxxx\" (string) Masternode status message\n" "}\n" + "\n" + "\nResult (if deterministic masternode):\n" + "{\n" + "... !TODO ...\n" + "}\n" "\nExamples:\n" + HelpExampleCli("getmasternodestatus", "") + HelpExampleRpc("getmasternodestatus", "")); @@ -661,8 +666,33 @@ UniValue getmasternodestatus (const JSONRPCRequest& request) if (!fMasterNode) throw JSONRPCError(RPC_MISC_ERROR, _("This is not a masternode.")); - if (activeMasternode.vin == nullopt) + bool fLegacyMN = (activeMasternode.vin != nullopt); + bool fDeterministicMN = (activeMasternodeManager != nullptr); + + if (!fLegacyMN && !fDeterministicMN) { throw JSONRPCError(RPC_MISC_ERROR, _("Active Masternode not initialized.")); + } + + if (fDeterministicMN) { + if (!deterministicMNManager->IsDIP3Enforced()) { + // this should never happen as ProTx transactions are not accepted yet + throw JSONRPCError(RPC_MISC_ERROR, _("Deterministic masternodes are not enforced yet")); + } + const CActiveMasternodeInfo* amninfo = activeMasternodeManager->GetInfo(); + UniValue mnObj(UniValue::VOBJ); + auto dmn = deterministicMNManager->GetListAtChainTip().GetMNByOperatorKey(amninfo->keyIDOperator); + if (dmn) { + dmn->ToJson(mnObj); + } + mnObj.pushKV("netaddr", amninfo->service.ToString()); + mnObj.pushKV("status", activeMasternodeManager->GetStatus()); + return mnObj; + } + + // Legacy code !TODO: remove when transition to DMN is complete + if (deterministicMNManager->LegacyMNObsolete()) { + throw JSONRPCError(RPC_MISC_ERROR, _("Legacy Masternode is obsolete.")); + } CMasternode* pmn = mnodeman.Find(activeMasternode.vin->prevout); diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index b451d2ea5a27..aed2a5395d57 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -23,6 +23,17 @@ class MasternodeGovernanceBasicTest(PivxTier2TestFramework): + def check_mns_status_legacy(self, node, txhash): + status = node.getmasternodestatus() + assert_equal(status["txhash"], txhash) + assert_equal(status["message"], "Masternode successfully started") + + def check_mns_status(self, node, txhash): + status = node.getmasternodestatus() + assert_equal(status["proTxHash"], txhash) + assert_equal(status["dmnstate"]["PoSePenalty"], 0) + assert_equal(status["status"], "Ready") + def check_budget_finalization_sync(self, votesCount, status): for i in range(0, len(self.nodes)): node = self.nodes[i] @@ -98,6 +109,14 @@ def run_test(self): self.enable_mocktime() self.setup_3_masternodes_network() + # check status of masternodes + self.check_mns_status_legacy(self.remoteOne, self.mnOneTxHash) + self.log.info("MN1 active") + self.check_mns_status_legacy(self.remoteTwo, self.mnTwoTxHash) + self.log.info("MN2 active") + self.check_mns_status(self.remoteDMN, self.proRegTx) + self.log.info("DMN1 active") + # Prepare the proposal self.log.info("preparing budget proposal..") firstProposalName = "super-cool" From aa867d5f89110e856a2a1533b529ee6b49c526a1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 15 Feb 2021 18:33:35 +0100 Subject: [PATCH 14/26] [RPC] Add DMN support to listmasternodes --- test/functional/tiertwo_governance_sync_basic.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index aed2a5395d57..5c3a0f61cd70 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -34,6 +34,13 @@ def check_mns_status(self, node, txhash): assert_equal(status["dmnstate"]["PoSePenalty"], 0) assert_equal(status["status"], "Ready") + def check_mn_list(self, node, txHashSet): + # check masternode list from node + mnlist = node.listmasternodes() + assert_equal(len(mnlist), 3) + foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) + assert_equal(len(foundHashes), len(txHashSet)) + def check_budget_finalization_sync(self, votesCount, status): for i in range(0, len(self.nodes)): node = self.nodes[i] @@ -108,6 +115,9 @@ def check_budgetprojection(self, expected): def run_test(self): self.enable_mocktime() self.setup_3_masternodes_network() + txHashSet = set([self.mnOneTxHash, self.mnTwoTxHash, self.proRegTx]) + # check mn list from miner + self.check_mn_list(self.miner, txHashSet) # check status of masternodes self.check_mns_status_legacy(self.remoteOne, self.mnOneTxHash) From 6ad7ea6960e07e99f3676079e47e00a6481dd20f Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 2 Mar 2021 14:59:52 +0100 Subject: [PATCH 15/26] [BUG] Fix locking order between CDeterministicMNManager/CMasternodeman Legacy manager should never call the deterministic one, while holding its own mutex (e.g. LegacyMNObsolete) fixing POTENTIAL DEADLOCK DETECTED Previous lock order was: cs_main blockassembler.cpp:236 (in thread bitcoin-httpworker) (1) cs evo/deterministicmns.cpp:517 (in thread bitcoin-httpworker) (2) cs masternodeman.cpp:456 (in thread bitcoin-httpworker) Current lock order is: (2) cs masternodeman.cpp:244 (in thread pivx-masternodeman) (1) cs evo/deterministicmns.cpp:855 (in thread pivx-masternodeman) --- src/masternode-sync.cpp | 18 ++++++++++-------- src/masternode-sync.h | 2 +- src/masternodeman.cpp | 6 +++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/masternode-sync.cpp b/src/masternode-sync.cpp index ec949593c836..072542bfa98c 100644 --- a/src/masternode-sync.cpp +++ b/src/masternode-sync.cpp @@ -163,7 +163,6 @@ bool CMasternodeSync::IsBudgetFinEmpty() int CMasternodeSync::GetNextAsset(int currentAsset) { - bool fSkipObsolete = deterministicMNManager->LegacyMNObsolete(); if (currentAsset > MASTERNODE_SYNC_FINISHED) { LogPrintf("%s - invalid asset %d\n", __func__, currentAsset); return MASTERNODE_SYNC_FAILED; @@ -173,9 +172,9 @@ int CMasternodeSync::GetNextAsset(int currentAsset) case (MASTERNODE_SYNC_FAILED): return MASTERNODE_SYNC_SPORKS; case (MASTERNODE_SYNC_SPORKS): - return fSkipObsolete ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_LIST; + return deterministicMNManager->LegacyMNObsolete() ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_LIST; case (MASTERNODE_SYNC_LIST): - return fSkipObsolete ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_MNW; + return deterministicMNManager->LegacyMNObsolete() ? MASTERNODE_SYNC_BUDGET : MASTERNODE_SYNC_MNW; case (MASTERNODE_SYNC_MNW): return MASTERNODE_SYNC_BUDGET; case (MASTERNODE_SYNC_BUDGET): @@ -297,6 +296,9 @@ void CMasternodeSync::Process() if (!IsBlockchainSynced() && RequestedMasternodeAssets > MASTERNODE_SYNC_SPORKS) return; + // Skip after legacy obsolete. !TODO: remove when transition to DMN is complete + bool fLegacyMnObsolete = deterministicMNManager->LegacyMNObsolete(); + CMasternodeSync* sync = this; // New sync architecture, regtest only for now. @@ -308,12 +310,12 @@ void CMasternodeSync::Process() } // Mainnet sync - g_connman->ForEachNodeContinueIf([sync](CNode* pnode){ - return sync->SyncWithNode(pnode); + g_connman->ForEachNodeContinueIf([sync, fLegacyMnObsolete](CNode* pnode){ + return sync->SyncWithNode(pnode, fLegacyMnObsolete); }); } -bool CMasternodeSync::SyncWithNode(CNode* pnode) +bool CMasternodeSync::SyncWithNode(CNode* pnode, bool fLegacyMnObsolete) { CNetMsgMaker msgMaker(pnode->GetSendVersion()); @@ -330,7 +332,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) if (pnode->nVersion >= ActiveProtocol()) { if (RequestedMasternodeAssets == MASTERNODE_SYNC_LIST) { - if (deterministicMNManager->LegacyMNObsolete()) { + if (fLegacyMnObsolete) { SwitchToNextAsset(); return false; } @@ -367,7 +369,7 @@ bool CMasternodeSync::SyncWithNode(CNode* pnode) } if (RequestedMasternodeAssets == MASTERNODE_SYNC_MNW) { - if (deterministicMNManager->LegacyMNObsolete()) { + if (fLegacyMnObsolete) { SwitchToNextAsset(); return false; } diff --git a/src/masternode-sync.h b/src/masternode-sync.h index 32197285c4d8..ea7b8add4d89 100644 --- a/src/masternode-sync.h +++ b/src/masternode-sync.h @@ -90,7 +90,7 @@ class CMasternodeSync * If it returns false, the Process() step is complete. * Otherwise Process() calls it again for a different node. */ - bool SyncWithNode(CNode* pnode); + bool SyncWithNode(CNode* pnode, bool fLegacyMnObsolete); bool IsSynced(); bool NotCompleted(); bool IsSporkListSynced(); diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 281a74baabf7..b2691af57b72 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -226,8 +226,6 @@ void CMasternodeMan::AskForMN(CNode* pnode, const CTxIn& vin) int CMasternodeMan::CheckAndRemove(bool forceExpiredRemoval) { - LOCK(cs); - // Skip after legacy obsolete. !TODO: remove when transition to DMN is complete if (deterministicMNManager->LegacyMNObsolete()) { LogPrint(BCLog::MASTERNODE, "Removing all legacy mn due to SPORK 21\n"); @@ -235,6 +233,8 @@ int CMasternodeMan::CheckAndRemove(bool forceExpiredRemoval) return 0; } + LOCK(cs); + //remove inactive and outdated (or replaced by DMN) auto it = mapMasternodes.begin(); while (it != mapMasternodes.end()) { @@ -838,7 +838,7 @@ void CMasternodeMan::UpdateMasternodeList(CMasternodeBroadcast& mnb) mapSeenMasternodeBroadcast.emplace(mnb.GetHash(), mnb); masternodeSync.AddedMasternodeList(mnb.GetHash()); - LogPrint(BCLog::MASTERNODE,"CMasternodeMan::UpdateMasternodeList() -- masternode=%s\n", mnb.vin.prevout.ToString()); + LogPrint(BCLog::MASTERNODE,"%s -- masternode=%s\n", __func__, mnb.vin.prevout.ToString()); CMasternode* pmn = Find(mnb.vin.prevout); if (pmn == NULL) { From acfa24b858b0a9ae0f9c0e53eef257252c68be08 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 15 Feb 2021 23:18:57 +0100 Subject: [PATCH 16/26] [Tests] Introduce tiertwo_dmn_compatibility functional test --- src/rpc/masternode.cpp | 5 +- .../test_framework/test_framework.py | 110 ++++++++----- test/functional/test_runner.py | 1 + .../tiertwo_governance_sync_basic.py | 22 +-- .../tiertwo_masternode_activation.py | 15 +- test/functional/tiertwo_mn_compatibility.py | 148 ++++++++++++++++++ 6 files changed, 239 insertions(+), 62 deletions(-) create mode 100755 test/functional/tiertwo_mn_compatibility.py diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index b542ac2d5ffa..ac9f878d99db 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -506,8 +506,9 @@ UniValue startmasternode(const JSONRPCRequest& request) pwallet->Lock(); if(!found) { - statusObj.pushKV("success", false); - statusObj.pushKV("error_message", "Could not find alias in config. Verify with listmasternodeconf."); + statusObj.pushKV("alias", alias); + statusObj.pushKV("result", "failed"); + statusObj.pushKV("error", "Could not find alias in config. Verify with listmasternodeconf."); } return statusObj; diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 95e01d663182..0d159505c805 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1069,34 +1069,67 @@ def stake_and_ping(self, node_id, num_blocks, with_ping_mns=[]): self.send_pings(with_ping_mns) - def setupDMN(self, mnOwner, miner, - mnRemotePos): - self.log.info("Creating proRegTx for deterministic masternode...") - collateralAdd = mnOwner.getnewaddress("dmn1") - # send to the owner the collateral tx cost + some dust for the ProReg and fee - fundingTxId = miner.sendtoaddress(collateralAdd, Decimal('10001')) - # confirm and verify reception - self.stake_and_sync(self.nodes.index(miner), 1) - assert_greater_than(mnOwner.getrawtransaction(fundingTxId, 1)["confirmations"], 0) - # create and send the ProRegTx funding the collateral - operatorAdd = self.nodes[mnRemotePos].getnewaddress("dmn_operator") - ipport = "127.0.0.1:"+str(p2p_port(mnRemotePos)) + mnRemotePos, + strType, # "fund"|"internal"|"external" + outpoint=None): # COutPoint, only for "external" + self.log.info("Creating%s proRegTx for deterministic masternode..." % ( + " and funding" if strType == "fFund" else "")) + collateralAdd = mnOwner.getnewaddress("dmn") + ipport = "127.0.0.1:" + str(p2p_port(mnRemotePos)) ownerAdd = mnOwner.getnewaddress("dmn_owner") + operatorAdd = mnOwner.getnewaddress("dmn_operator") + operatorKey = mnOwner.dumpprivkey(operatorAdd) votingAdd = mnOwner.getnewaddress("dmn_voting") - proTxId = mnOwner.protx_register_fund(collateralAdd, ipport, ownerAdd, operatorAdd, votingAdd, collateralAdd) + if strType == "fund": + # send to the owner the collateral tx cost + some dust for the ProReg and fee + fundingTxId = miner.sendtoaddress(collateralAdd, Decimal('101')) + # confirm and verify reception + self.stake_and_sync(self.nodes.index(miner), 1) + assert_greater_than(mnOwner.getrawtransaction(fundingTxId, 1)["confirmations"], 0) + # create and send the ProRegTx funding the collateral + proTxId = mnOwner.protx_register_fund(collateralAdd, ipport, ownerAdd, + operatorAdd, votingAdd, collateralAdd) + elif strType == "internal": + mnOwner.getnewaddress("dust") + # send to the owner the collateral tx cost + some dust for the ProReg and fee + collateralTxId = miner.sendtoaddress(collateralAdd, Decimal('100')) + miner.sendtoaddress(collateralAdd, Decimal('1')) + # confirm and verify reception + self.stake_and_sync(self.nodes.index(miner), 1) + json_tx = mnOwner.getrawtransaction(collateralTxId, True) + collateralTxId_n = -1 + for o in json_tx["vout"]: + if o["value"] == Decimal('100'): + collateralTxId_n = o["n"] + break + assert_greater_than(collateralTxId_n, -1) + assert_greater_than(json_tx["confirmations"], 0) + proTxId = mnOwner.protx_register(collateralTxId, collateralTxId_n, ipport, ownerAdd, + operatorAdd, votingAdd, collateralAdd) + elif strType == "external": + self.log.info("Setting up ProRegTx with collateral externally-signed...") + # send the tx from the miner + payoutAdd = mnOwner.getnewaddress("payout") + register_res = miner.protx_register_prepare(outpoint.hash, outpoint.n, ipport, ownerAdd, + operatorAdd, votingAdd, payoutAdd) + self.log.info("ProTx prepared") + message_to_sign = register_res["signMessage"] + collateralAdd = register_res["collateralAddress"] + signature = mnOwner.signmessage(collateralAdd, message_to_sign) + self.log.info("ProTx signed") + proTxId = miner.protx_register_submit(register_res["tx"], signature) + else: + raise Exception("Type %s not available" % strType) + self.sync_mempools([mnOwner, miner]) # confirm and verify inclusion in list self.stake_and_sync(self.nodes.index(miner), 1) assert_greater_than(self.nodes[mnRemotePos].getrawtransaction(proTxId, 1)["confirmations"], 0) assert proTxId in self.nodes[mnRemotePos].protx_list(False) - return ( - proTxId, - self.nodes[mnRemotePos].dumpprivkey(operatorAdd) - ) - + return COutPoint(proTxId, 0), operatorKey def setupMasternode(self, mnOwner, @@ -1134,8 +1167,8 @@ def setupMasternode(self, # lock collateral mnOwner.lockunspent(False, [{"txid": collateralTxId, "vout": collateralTxId_n}]) - # return the collateral id - return collateralTxId + # return the collateral outpoint + return COutPoint(collateralTxId, collateralTxId_n) class SkipTest(Exception): @@ -1160,10 +1193,10 @@ def set_test_params(self): self.ownerTwoPos = 2 self.remoteTwoPos = 3 self.minerPos = 4 - self.remoteDMNPos = 5 + self.remoteDMN1Pos = 5 self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250"]] * self.num_nodes - for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMNPos]: + for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMN1Pos]: self.extra_args[i] += ["-listen", "-externalip=127.0.0.1"] self.extra_args[self.minerPos].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") @@ -1179,10 +1212,10 @@ def set_test_params(self): self.ownerTwo = None # self.nodes[self.ownerTwoPos] self.remoteTwo = None # self.nodes[self.remoteTwoPos] self.miner = None # self.nodes[self.minerPos] - self.remoteDMN = None # self.nodes[self.remoteDMNPos] - self.mnOneTxHash = "" - self.mnTwoTxHash = "" - self.proRegTx = "" + self.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] + self.mnOneCollateral = COutPoint() + self.mnTwoCollateral = COutPoint() + self.proRegTx1 = COutPoint() def send_3_pings(self): @@ -1200,12 +1233,12 @@ def stake(self, num_blocks, with_ping_mns=[]): def controller_start_all_masternodes(self): self.controller_start_masternode(self.ownerOne, self.masternodeOneAlias) self.controller_start_masternode(self.ownerTwo, self.masternodeTwoAlias) - self.wait_until_mn_preenabled(self.mnOneTxHash, 40) - self.wait_until_mn_preenabled(self.mnTwoTxHash, 40) + self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40) + self.wait_until_mn_preenabled(self.mnTwoCollateral.hash, 40) self.log.info("masternodes started, waiting until both get enabled..") self.send_3_pings() - self.wait_until_mn_enabled(self.mnOneTxHash, 120, [self.remoteOne, self.remoteTwo]) - self.wait_until_mn_enabled(self.mnTwoTxHash, 120, [self.remoteOne, self.remoteTwo]) + self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.remoteOne, self.remoteTwo]) + self.wait_until_mn_enabled(self.mnTwoCollateral.hash, 120, [self.remoteOne, self.remoteTwo]) self.log.info("masternodes enabled and running properly!") def advance_mocktime_and_stake(self, secs_to_add): @@ -1219,9 +1252,9 @@ def setup_3_masternodes_network(self): self.ownerTwo = self.nodes[self.ownerTwoPos] self.remoteTwo = self.nodes[self.remoteTwoPos] self.miner = self.nodes[self.minerPos] - self.remoteDMN = self.nodes[self.remoteDMNPos] - ownerOneDir = os.path.join(self.options.tmpdir, "node0") - ownerTwoDir = os.path.join(self.options.tmpdir, "node2") + self.remoteDMN1 = self.nodes[self.remoteDMN1Pos] + ownerOneDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerOnePos) + ownerTwoDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerTwoPos) self.log.info("generating 256 blocks..") # First mine 250 PoW blocks @@ -1233,7 +1266,7 @@ def setup_3_masternodes_network(self): self.log.info("masternodes setup..") # setup first masternode node, corresponding to nodeOne - self.mnOneTxHash = self.setupMasternode( + self.mnOneCollateral = self.setupMasternode( self.ownerOne, self.miner, self.masternodeOneAlias, @@ -1241,7 +1274,7 @@ def setup_3_masternodes_network(self): self.remoteOnePos, self.mnOnePrivkey) # setup second masternode node, corresponding to nodeTwo - self.mnTwoTxHash = self.setupMasternode( + self.mnTwoCollateral = self.setupMasternode( self.ownerTwo, self.miner, self.masternodeTwoAlias, @@ -1249,10 +1282,11 @@ def setup_3_masternodes_network(self): self.remoteTwoPos, self.mnTwoPrivkey) # setup deterministic masternode - self.proRegTx, self.dmnPrivkey = self.setupDMN( + self.proRegTx1, self.dmn1Privkey = self.setupDMN( self.ownerOne, self.miner, - self.remoteDMNPos + self.remoteDMN1Pos, + "fund" ) self.log.info("masternodes setup completed, initializing them..") @@ -1265,7 +1299,7 @@ def setup_3_masternodes_network(self): remoteTwoPort = p2p_port(self.remoteTwoPos) self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) - self.remoteDMN.initmasternode(self.dmnPrivkey, "", True) + self.remoteDMN1.initmasternode(self.dmn1Privkey, "", True) # wait until mnsync complete on all nodes self.stake(1) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 7ba290508061..905a95593f9f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -142,6 +142,7 @@ TIERTWO_SCRIPTS = [ # Longest test should go first, to favor running tests in parallel 'tiertwo_governance_sync_basic.py', + 'tiertwo_mn_compatibility.py', 'tiertwo_masternode_activation.py', 'tiertwo_masternode_ping.py', ] diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 5c3a0f61cd70..26aaf10e6601 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -115,16 +115,16 @@ def check_budgetprojection(self, expected): def run_test(self): self.enable_mocktime() self.setup_3_masternodes_network() - txHashSet = set([self.mnOneTxHash, self.mnTwoTxHash, self.proRegTx]) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1.hash]) # check mn list from miner self.check_mn_list(self.miner, txHashSet) # check status of masternodes - self.check_mns_status_legacy(self.remoteOne, self.mnOneTxHash) + self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) self.log.info("MN1 active") - self.check_mns_status_legacy(self.remoteTwo, self.mnTwoTxHash) + self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") - self.check_mns_status(self.remoteDMN, self.proRegTx) + self.check_mns_status(self.remoteDMN1, self.proRegTx1.hash) self.log.info("DMN1 active") # Prepare the proposal @@ -182,7 +182,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.mnOneTxHash, "YES") + self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES") self.log.info("all good, MN1 vote accepted everywhere!") # now let's vote for the proposal with the second MN @@ -192,18 +192,18 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.mnTwoTxHash, "YES") + self.check_vote_existence(firstProposalName, self.mnTwoCollateral.hash, "YES") self.log.info("all good, MN2 vote accepted everywhere!") # now let's vote for the proposal with the first DMN - self.log.info("Voting with DMN...") - voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx) + self.log.info("Voting with DMN1...") + voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx1.hash) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.proRegTx, "YES") - self.log.info("all good, DM1 vote accepted everywhere!") + self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES") + self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget blockStart = nextSuperBlockHeight @@ -245,7 +245,7 @@ def run_test(self): voteResult = self.ownerTwo.mnfinalbudget("vote-many", budgetFinHash, True) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("Remote Two voted successfully.") - voteResult = self.remoteDMN.mnfinalbudget("vote", budgetFinHash) + voteResult = self.remoteDMN1.mnfinalbudget("vote", budgetFinHash) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("DMN voted successfully.") self.stake(2, [self.remoteOne, self.remoteTwo]) diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index c06a8842fb4a..c0a3b166c19c 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -44,15 +44,8 @@ def reconnect_and_restart_masternodes(self): self.controller_start_all_masternodes() def spend_collateral(self): - mnCollateralOutputs = self.ownerOne.getmasternodeoutputs() - mnCollateralOutputIndex = -1 - for x in mnCollateralOutputs: - if x["txhash"] == self.mnOneTxHash: - mnCollateralOutputIndex = x["outputidx"] - break - assert_greater_than_or_equal(mnCollateralOutputIndex, 0) send_value = satoshi_round(100 - 0.001) - inputs = [{'txid' : self.mnOneTxHash, 'vout' : mnCollateralOutputIndex}] + inputs = [{'txid': self.mnOneCollateral.hash, 'vout': self.mnOneCollateral.n}] outputs = {} outputs[self.ownerOne.getnewaddress()] = float(send_value) rawtx = self.ownerOne.createrawtransaction(inputs, outputs) @@ -66,8 +59,8 @@ def spend_collateral(self): # Similar to base class wait_until_mn_status but skipping the disconnected nodes def wait_until_mn_expired(self, _timeout, removed=False): collaterals = { - self.remoteOnePos: self.mnOneTxHash, - self.remoteTwoPos: self.mnTwoTxHash + self.remoteOnePos: self.mnOneCollateral.hash, + self.remoteTwoPos: self.mnTwoCollateral.hash } for k in collaterals: for i in range(self.num_nodes): @@ -116,7 +109,7 @@ def run_test(self): self.sync_blocks() self.log.info("checking mn status..") time.sleep(3) # wait a little bit - self.wait_until_mn_vinspent(self.mnOneTxHash, 30, [self.remoteTwo]) + self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) self.log.info("masternode list updated successfully, vin spent") diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py new file mode 100755 index 000000000000..0172f698ddf2 --- /dev/null +++ b/test/functional/tiertwo_mn_compatibility.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import PivxTier2TestFramework +from test_framework.util import ( + assert_equal, + assert_true, + satoshi_round, +) + +import time + +""" +Test checking compatibility code between MN and DMN +""" + +class MasternodeCompatibilityTest(PivxTier2TestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 7 + self.enable_mocktime() + + self.minerPos = 0 + self.ownerOnePos = self.ownerTwoPos = 1 + self.remoteOnePos = 2 + self.remoteTwoPos = 3 + self.remoteDMN1Pos = 4 + self.remoteDMN2Pos = 5 + self.remoteDMN3Pos = 6 + + self.masternodeOneAlias = "mnOne" + self.masternodeTwoAlias = "mntwo" + + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250"]] * self.num_nodes + for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMN1Pos, self.remoteDMN2Pos, self.remoteDMN3Pos]: + self.extra_args[i] += ["-listen", "-externalip=127.0.0.1"] + self.extra_args[self.minerPos].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi") + + self.mnOnePrivkey = "9247iC59poZmqBYt9iDh9wDam6v9S1rW5XekjLGyPnDhrDkP4AK" + self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" + + self.miner = None + self.ownerOne = self.ownerTwo = None + self.remoteOne = None + self.remoteTwo = None + self.remoteDMN1 = None + self.remoteDMN2 = None + self.remoteDMN3 = None + + def check_mns_status_legacy(self, node, txhash): + status = node.getmasternodestatus() + assert_equal(status["txhash"], txhash) + assert_equal(status["message"], "Masternode successfully started") + + def check_mns_status(self, node, txhash): + status = node.getmasternodestatus() + assert_equal(status["proTxHash"], txhash) + assert_equal(status["dmnstate"]["PoSePenalty"], 0) + assert_equal(status["status"], "Ready") + + def check_mn_list(self, node, txHashSet): + # check masternode list from node + mnlist = node.listmasternodes() + assert_equal(len(mnlist), len(txHashSet)) + foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) + assert_equal(len(foundHashes), len(txHashSet)) + + def run_test(self): + self.enable_mocktime() + self.setup_3_masternodes_network() + + # add two more nodes to the network + self.remoteDMN2 = self.nodes[self.remoteDMN2Pos] + self.remoteDMN3 = self.nodes[self.remoteDMN3Pos] + + # check mn list from miner + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1.hash]) + self.check_mn_list(self.miner, txHashSet) + + # check status of masternodes + self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) + self.log.info("MN1 active") + self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) + self.log.info("MN2 active") + self.check_mns_status(self.remoteDMN1, self.proRegTx1.hash) + self.log.info("DMN1 active") + + # Create another DMN, this time without funding the collateral. + # ProTx references another transaction in the owner's wallet + self.proRegTx2, self.dmn2Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN2Pos, + "internal" + ) + self.remoteDMN2.initmasternode(self.dmn2Privkey, "", True) + + # check list and status + txHashSet.add(self.proRegTx2.hash) + self.check_mn_list(self.miner, txHashSet) + self.check_mns_status(self.remoteDMN2, self.proRegTx2.hash) + self.log.info("DMN2 active") + + # Now create a DMN, reusing the collateral output of a legacy MN + self.log.info("Creating a DMN reusing the collateral of a legacy MN...") + self.proRegTx3, self.dmn3Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN3Pos, + "external", + self.mnOneCollateral, + ) + self.remoteDMN3.initmasternode(self.dmn3Privkey, "", True) + + # The remote node is shutting down the pinging service + try: + self.send_3_pings() + except: + pass + + # The legacy masternode must no longer be in the list + # and the DMN must have taken its place + txHashSet.remove(self.mnOneCollateral.hash) + txHashSet.add(self.proRegTx3.hash) + for node in self.nodes: + self.check_mn_list(node, txHashSet) + self.log.info("Masternode list correctly updated by all nodes.") + self.check_mns_status(self.remoteDMN3, self.proRegTx3.hash) + self.log.info("DMN3 active") + + # Now try to start a legacy MN with a collateral used by a DMN + self.log.info("Now trying to start a legacy MN with a collateral of a DMN...") + self.controller_start_masternode(self.ownerOne, self.masternodeOneAlias) + try: + self.send_3_pings() + except: + pass + + # the masternode list hasn't changed + for node in self.nodes: + self.check_mn_list(node, txHashSet) + self.log.info("Masternode list correctly unchanged in all nodes.") + +if __name__ == '__main__': + MasternodeCompatibilityTest().main() From 971f1da070b7d353098c338670473b611d530463 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 5 Mar 2021 21:34:17 +0100 Subject: [PATCH 17/26] [Consensus] DMN payment compatibility code Before LegacyMNObsolete we need to use the same payment logic for DMNs too. In order to do so, we initialize a temporary MastrnodeRef object for each valid deterministic masternode and apply the masternode winner logic to it. --- src/masternode-payments.cpp | 19 +++--- src/masternode-payments.h | 2 +- src/masternode.cpp | 26 ++++++++ src/masternode.h | 26 ++++++++ src/masternodeman.cpp | 69 ++++++++++++++++++--- src/masternodeman.h | 2 - src/rpc/masternode.cpp | 62 ++++++++++++------ test/functional/tiertwo_mn_compatibility.py | 33 ++++++++-- 8 files changed, 190 insertions(+), 49 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 4a829949ca6f..097d815cfdf5 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -341,7 +341,7 @@ bool CMasternodePayments::GetLegacyMasternodeTxOut(int nHeight, std::vectorpubKeyCollateralAddress.GetID()); + payee = winningNode->GetPayeeScript(); } else { LogPrint(BCLog::MASTERNODE,"CreateNewBlock: Failed to detect masternode to pay\n"); return false; @@ -490,11 +490,6 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st return; } - CTxDestination address1; - ExtractDestination(winner.payee, address1); - - // LogPrint(BCLog::MASTERNODE, "mnw - winning vote - Addr %s Height %d bestHeight %d - %s\n", address2.ToString().c_str(), winner.nBlockHeight, nHeight, winner.vinMasternode.prevout.ToStringShort()); - if (masternodePayments.AddWinningMasternode(winner)) { winner.Relay(); masternodeSync.AddedMasternodeWinner(winner.GetHash()); @@ -520,9 +515,7 @@ bool CMasternodePayments::IsScheduled(const CMasternode& mn, int nNotBlockHeight int nHeight = mnodeman.GetBestHeight(); - CScript mnpayee; - mnpayee = GetScriptForDestination(mn.pubKeyCollateralAddress.GetID()); - + const CScript& mnpayee = mn.GetPayeeScript(); CScript payee; for (int64_t h = nHeight; h <= nHeight + 8; h++) { if (h == nNotBlockHeight) continue; @@ -560,6 +553,9 @@ bool CMasternodePayments::AddWinningMasternode(CMasternodePaymentWinner& winnerI } } + CTxDestination addr; + ExtractDestination(winnerIn.payee, addr); + LogPrint(BCLog::MASTERNODE, "mnw - Adding winner %s for block %d\n", EncodeDestination(addr), winnerIn.nBlockHeight); mapMasternodeBlocks[winnerIn.nBlockHeight].AddPayee(winnerIn.payee, 1); return true; @@ -722,7 +718,6 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) return false; } - CMasternodePaymentWinner newWinner(*(activeMasternode.vin), nBlockHeight); // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough int nCount = 0; MasternodeRef pmn = mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount); @@ -732,8 +727,8 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) return false; } - const CScript& payee = GetScriptForDestination(pmn->pubKeyCollateralAddress.GetID()); - newWinner.AddPayee(payee); + CMasternodePaymentWinner newWinner(*(activeMasternode.vin), nBlockHeight); + newWinner.AddPayee(pmn->GetPayeeScript()); CPubKey pubKeyMasternode; CKey keyMasternode; activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); diff --git a/src/masternode-payments.h b/src/masternode-payments.h index a231841a4d33..52462892aee0 100644 --- a/src/masternode-payments.h +++ b/src/masternode-payments.h @@ -177,7 +177,7 @@ class CMasternodePaymentWinner : public CSignedMessage payee() {} - CMasternodePaymentWinner(CTxIn vinIn, int nHeight) : + CMasternodePaymentWinner(const CTxIn& vinIn, int nHeight): CSignedMessage(), vinMasternode(vinIn), nBlockHeight(nHeight), diff --git a/src/masternode.cpp b/src/masternode.cpp index a668f1008fb8..eea8a791acca 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -77,6 +77,7 @@ CMasternode::CMasternode() : protocolVersion = PROTOCOL_VERSION; nScanningErrorCount = 0; nLastScanningErrorBlockHeight = 0; + mnPayeeScript.clear(); } CMasternode::CMasternode(const CMasternode& other) : @@ -92,6 +93,23 @@ CMasternode::CMasternode(const CMasternode& other) : protocolVersion = other.protocolVersion; nScanningErrorCount = other.nScanningErrorCount; nLastScanningErrorBlockHeight = other.nLastScanningErrorBlockHeight; + mnPayeeScript = other.mnPayeeScript; +} + +CMasternode::CMasternode(const CDeterministicMNCPtr& dmn, int64_t registeredTime, const uint256& registeredHash) : + CSignedMessage() +{ + LOCK(cs); + vin = CTxIn(dmn->collateralOutpoint); + addr = dmn->pdmnState->addr; + pubKeyCollateralAddress = CPubKey(); + pubKeyMasternode = CPubKey(); + sigTime = registeredTime; + lastPing = CMasternodePing(vin, registeredHash, registeredTime); + protocolVersion = PROTOCOL_VERSION; + nScanningErrorCount = 0; + nLastScanningErrorBlockHeight = 0; + mnPayeeScript = dmn->pdmnState->scriptPayout; } uint256 CMasternode::GetSignatureHash() const @@ -660,3 +678,11 @@ void CMasternodePing::Relay() CInv inv(MSG_MASTERNODE_PING, GetHash()); g_connman->RelayInv(inv); } + +MasternodeRef MakeMasternodeRefForDMN(const CDeterministicMNCPtr& dmn) +{ + // create legacy masternode for DMN + int refHeight = std::max(dmn->pdmnState->nRegisteredHeight, dmn->pdmnState->nPoSeRevivedHeight); + const CBlockIndex* pindex = WITH_LOCK(cs_main, return mapBlockIndex.at(chainActive[refHeight]->GetBlockHash()); ); + return std::make_shared(CMasternode(dmn, pindex->GetBlockTime(), pindex->GetBlockHash())); +} diff --git a/src/masternode.h b/src/masternode.h index 048826fb393a..33977f3bedd5 100644 --- a/src/masternode.h +++ b/src/masternode.h @@ -22,6 +22,11 @@ class CMasternode; class CMasternodeBroadcast; class CMasternodePing; +typedef std::shared_ptr MasternodeRef; + +class CDeterministicMN; +typedef std::shared_ptr CDeterministicMNCPtr; + int MasternodeMinPingSeconds(); int MasternodeBroadcastSeconds(); int MasternodeCollateralMinConf(); @@ -116,6 +121,9 @@ class CMasternode : public CSignedMessage explicit CMasternode(); CMasternode(const CMasternode& other); + // Initialize from DMN. Used by the compatibility code. + CMasternode(const CDeterministicMNCPtr& dmn, int64_t registeredTime, const uint256& registeredHash); + // override CSignedMessage functions uint256 GetSignatureHash() const override; std::string GetStrMessage() const override; @@ -233,6 +241,20 @@ class CMasternode : public CSignedMessage /// Is the input associated with collateral public key? (and there is 10000 PIV - checking if valid masternode) bool IsInputAssociatedWithPubkey() const; + + /* + * This is used only by the compatibility code for DMN, which don't share the public key (but the keyid). + * Used by the payment-logic to include the necessary information in a temporary MasternodeRef object + * (which is not indexed in the maps of the legacy manager). + * A non-empty mnPayeeScript identifies this object as a "deterministic" masternode. + * Note: this is the single payout for the masternode (if the dmn is configured to pay a portion of the reward + * to the operator, this is done only after the disabling of the legacy system). + */ + CScript mnPayeeScript{}; + CScript GetPayeeScript() const { + return mnPayeeScript.empty() ? GetScriptForDestination(pubKeyCollateralAddress.GetID()) + : mnPayeeScript; + } }; @@ -281,4 +303,8 @@ class CMasternodeBroadcast : public CMasternode static bool CheckDefaultPort(CService service, std::string& strErrorRet, const std::string& strContext); }; +// Temporary function used for payment compatibility code. +// Returns a shared pointer to a masternode object initialized from a DMN. +MasternodeRef MakeMasternodeRefForDMN(const CDeterministicMNCPtr& dmn); + #endif diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index b2691af57b72..58d9b4fc8630 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -500,6 +500,12 @@ static bool canScheduleMN(bool fFilterSigTime, const MasternodeRef& mn, int minP // MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip) const { + // Skip after legacy obsolete. !TODO: remove when transition to DMN is complete + if (deterministicMNManager->LegacyMNObsolete()) { + LogPrintf("%s: ERROR - called after legacy system disabled\n", __func__); + return nullptr; + } + AssertLockNotHeld(cs_main); const CBlockIndex* BlockReading = (pChainTip == nullptr ? GetChainTip() : pChainTip); if (!BlockReading) return nullptr; @@ -507,14 +513,19 @@ MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeigh MasternodeRef pBestMasternode = nullptr; std::vector > vecMasternodeLastPaid; + CDeterministicMNList mnList; + if (deterministicMNManager->IsDIP3Enforced()) { + mnList = deterministicMNManager->GetListAtChainTip(); + } + /* Make a vector with all of the last paid times */ int minProtocol = ActiveProtocol(); - int nMnCount{0}; + int nMnCount = mnList.GetValidMNsCount(); { LOCK(cs); - nMnCount = CountEnabled(); + nMnCount += CountEnabled(); for (const auto& it : mapMasternodes) { if (!it.second->IsEnabled()) continue; if (canScheduleMN(fFilterSigTime, it.second, minProtocol, nMnCount, nBlockHeight)) { @@ -522,6 +533,13 @@ MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeigh } } } + // Add deterministic masternodes to the vector + mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + const MasternodeRef mn = MakeMasternodeRefForDMN(dmn); + if (canScheduleMN(fFilterSigTime, mn, minProtocol, nMnCount, nBlockHeight)) { + vecMasternodeLastPaid.emplace_back(SecondsSincePayment(mn, BlockReading), mn); + } + }); nCount = (int)vecMasternodeLastPaid.size(); @@ -537,7 +555,7 @@ MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeigh // -- (chance per block * chances before IsScheduled will fire) int nTenthNetwork = nMnCount / 10; int nCountTenth = 0; - arith_uint256 nHigh; + arith_uint256 nHigh = ARITH_UINT256_ZERO; const uint256& hash = GetHashAtHeight(nBlockHeight - 101); for (const auto& s: vecMasternodeLastPaid) { const MasternodeRef pmn = s.second; @@ -573,6 +591,21 @@ MasternodeRef CMasternodeMan::GetCurrentMasterNode(int nHeight, const uint256& h } } + // scan also dmns + if (deterministicMNManager->IsDIP3Enforced()) { + auto mnList = deterministicMNManager->GetListAtChainTip(); + mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + const MasternodeRef mn = MakeMasternodeRefForDMN(dmn); + // calculate the score of the masternode + const int64_t n = mn->CalculateScore(hash).GetCompact(false); + // determine the winner + if (n > score) { + score = n; + winner = mn; + } + }); + } + return winner; } @@ -620,6 +653,15 @@ int CMasternodeMan::GetMasternodeRank(const CTxIn& vin, int64_t nBlockHeight) co } } + // scan also dmns + if (deterministicMNManager->IsDIP3Enforced()) { + auto mnList = deterministicMNManager->GetListAtChainTip(); + mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + const MasternodeRef mn = MakeMasternodeRefForDMN(dmn); + vecMasternodeScores.emplace_back(mn->CalculateScore(hash).GetCompact(false), mn->vin); + }); + } + sort(vecMasternodeScores.rbegin(), vecMasternodeScores.rend(), CompareScoreMN()); int rank = 0; @@ -644,13 +686,21 @@ std::vector> CMasternodeMan::GetMasternodeRank // scan for winner for (const auto& it : mapMasternodes) { const MasternodeRef mn = it.second; - if (!mn->IsEnabled()) { - vecMasternodeScores.emplace_back(9999, mn); - } else { - vecMasternodeScores.emplace_back(mn->CalculateScore(hash).GetCompact(false), mn); - } + const uint32_t score = mn->IsEnabled() ? mn->CalculateScore(hash).GetCompact(false) : 9999; + + vecMasternodeScores.emplace_back(score, mn); } } + // scan also dmns + if (deterministicMNManager->IsDIP3Enforced()) { + auto mnList = deterministicMNManager->GetListAtChainTip(); + mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { + const MasternodeRef mn = MakeMasternodeRefForDMN(dmn); + const uint32_t score = mnList.IsMNValid(dmn) ? mn->CalculateScore(hash).GetCompact(false) : 9999; + + vecMasternodeScores.emplace_back(score, mn); + }); + } sort(vecMasternodeScores.rbegin(), vecMasternodeScores.rend(), CompareScoreMN()); return vecMasternodeScores; } @@ -868,8 +918,7 @@ int64_t CMasternodeMan::GetLastPaid(const MasternodeRef& mn, const CBlockIndex* { if (BlockReading == nullptr) return false; - CScript mnpayee; - mnpayee = GetScriptForDestination(mn->pubKeyCollateralAddress.GetID()); + const CScript& mnpayee = mn->GetPayeeScript(); CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << mn->vin; diff --git a/src/masternodeman.h b/src/masternodeman.h index 1859304df0ee..ed9c5276472d 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -53,8 +53,6 @@ class CMasternodeDB ReadResult Read(CMasternodeMan& mnodemanToLoad); }; -// -typedef std::shared_ptr MasternodeRef; class CMasternodeMan { diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index ac9f878d99db..e1fcc5be4903 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -150,13 +150,14 @@ UniValue listmasternodes(const JSONRPCRequest& request) "\nResult:\n" "[\n" " {\n" - " \"rank\": n, (numeric) Masternode Rank (or 0 if not enabled)\n" - " \"txhash\": \"hash\", (string) Collateral transaction hash\n" - " \"outidx\": n, (numeric) Collateral transaction output index\n" - " \"pubkey\": \"key\", (string) Masternode public key used for message broadcasting\n" - " \"status\": s, (string) Status (ENABLED/EXPIRED/REMOVE/etc)\n" - " \"addr\": \"addr\", (string) Masternode PIVX address\n" - " \"version\": v, (numeric) Masternode protocol version\n" + " \"rank\": n, (numeric) Masternode Rank (or 0 if not enabled)\n" + " \"type\": \"legacy\"|\"deterministic\", (string) type of masternode\n" + " \"txhash\": \"hash\", (string) Collateral transaction hash\n" + " \"outidx\": n, (numeric) Collateral transaction output index\n" + " \"pubkey\": \"key\", (string) Masternode public key used for message broadcasting\n" + " \"status\": s, (string) Status (ENABLED/EXPIRED/REMOVE/etc)\n" + " \"addr\": \"addr\", (string) Masternode PIVX address\n" + " \"version\": v, (numeric) Masternode protocol version\n" " \"lastseen\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last seen\n" " \"activetime\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) masternode has been active\n" " \"lastpaid\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) masternode was last paid\n" @@ -171,24 +172,24 @@ UniValue listmasternodes(const JSONRPCRequest& request) const std::string& strFilter = request.params.size() > 0 ? request.params[0].get_str() : ""; UniValue ret(UniValue::VARR); - auto mnList = deterministicMNManager->GetListAtChainTip(); - mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { - UniValue obj(UniValue::VOBJ); - dmn->ToJson(obj); - bool fEnabled = dmn->pdmnState->nPoSeBanHeight == -1; - if (filterMasternode(obj, strFilter, fEnabled)) { - // Added for backward compatibility. can be removed later. - obj.pushKV("txhash", obj["proTxHash"].get_str()); - - obj.pushKV("status", fEnabled ? "ENABLED" : "POSE_BANNED"); - ret.push_back(obj); - } - }); + if (deterministicMNManager->LegacyMNObsolete()) { + auto mnList = deterministicMNManager->GetListAtChainTip(); + mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { + UniValue obj(UniValue::VOBJ); + dmn->ToJson(obj); + bool fEnabled = dmn->pdmnState->nPoSeBanHeight == -1; + if (filterMasternode(obj, strFilter, fEnabled)) { + ret.push_back(obj); + } + }); + return ret; + } // Legacy masternodes (!TODO: remove when transition to dmn is complete) const CBlockIndex* chainTip = GetChainTip(); if (!chainTip) return "[]"; int nHeight = chainTip->nHeight; + auto mnList = deterministicMNManager->GetListAtChainTip(); std::vector> vMasternodeRanks = mnodeman.GetMasternodeRanks(nHeight); for (int pos=0; pos < (int) vMasternodeRanks.size(); pos++) { @@ -196,6 +197,26 @@ UniValue listmasternodes(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); const CMasternode& mn = *(s.second); + if (!mn.mnPayeeScript.empty()) { + // Deterministic masternode + auto dmn = mnList.GetMNByCollateral(mn.vin.prevout); + if (dmn) { + UniValue obj(UniValue::VOBJ); + dmn->ToJson(obj); + bool fEnabled = dmn->pdmnState->nPoSeBanHeight == -1; + if (filterMasternode(obj, strFilter, fEnabled)) { + // Added for backward compatibility with legacy masternodes + obj.pushKV("type", "deterministic"); + obj.pushKV("txhash", obj["proTxHash"].get_str()); + obj.pushKV("addr", obj["dmnstate"]["payoutAddress"].get_str()); + obj.pushKV("status", fEnabled ? "ENABLED" : "POSE_BANNED"); + obj.pushKV("rank", fEnabled ? pos : 0); + ret.push_back(obj); + } + } + continue; + } + std::string strVin = mn.vin.prevout.ToStringShort(); std::string strTxHash = mn.vin.prevout.hash.ToString(); uint32_t oIdx = mn.vin.prevout.n; @@ -213,6 +234,7 @@ UniValue listmasternodes(const JSONRPCRequest& request) std::string strNetwork = GetNetworkName(node.GetNetwork()); obj.pushKV("rank", (strStatus == "ENABLED" ? pos : 0)); + obj.pushKV("type", "legacy"); obj.pushKV("network", strNetwork); obj.pushKV("txhash", strTxHash); obj.pushKV("outidx", (uint64_t)oIdx); diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index 0172f698ddf2..c0e9f8581f88 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -6,11 +6,9 @@ from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( assert_equal, - assert_true, - satoshi_round, ) -import time +from decimal import Decimal """ Test checking compatibility code between MN and DMN @@ -66,9 +64,14 @@ def check_mn_list(self, node, txHashSet): mnlist = node.listmasternodes() assert_equal(len(mnlist), len(txHashSet)) foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) - assert_equal(len(foundHashes), len(txHashSet)) + if len(foundHashes) != len(txHashSet): + raise Exception(str(mnlist)) + for x in mnlist: + self.mn_addresses.add(x["addr"]) + self.log.info("MN address list has %d entries" % len(self.mn_addresses)) def run_test(self): + self.mn_addresses = set() self.enable_mocktime() self.setup_3_masternodes_network() @@ -104,6 +107,28 @@ def run_test(self): self.check_mns_status(self.remoteDMN2, self.proRegTx2.hash) self.log.info("DMN2 active") + # Check block version and coinbase payment + blk_count = self.miner.getblockcount() + self.log.info("Checking block version and coinbase payment...") + blk = self.miner.getblock(self.miner.getbestblockhash(), True) + assert_equal(blk['height'], blk_count) + assert_equal(blk['version'], 10) + cbase_tx = self.miner.getrawtransaction(blk['tx'][0], True) + assert_equal(len(cbase_tx['vin']), 1) + cbase_script = blk_count.to_bytes(1 + blk_count//256, byteorder="little") + cbase_script = len(cbase_script).to_bytes(1, byteorder="little") + cbase_script + bytearray(1) + assert_equal(cbase_tx['vin'][0]['coinbase'], cbase_script.hex()) + assert_equal(len(cbase_tx['vout']), 1) + assert_equal(cbase_tx['vout'][0]['value'], Decimal("3.0")) + payee = cbase_tx['vout'][0]['scriptPubKey']['addresses'][0] + if payee not in self.mn_addresses: + raise Exception("payee %s not found in expected list %s" % (payee, str(self.mn_addresses))) + cstake_tx = self.miner.getrawtransaction(blk['tx'][1], True) + assert_equal(len(cstake_tx['vin']), 1) + assert_equal(len(cstake_tx['vout']), 2) + assert_equal(cstake_tx['vout'][1]['value'], Decimal("497.0")) # 250 + 250 - 3 + self.log.info("Block at height %d checks out" % blk_count) + # Now create a DMN, reusing the collateral output of a legacy MN self.log.info("Creating a DMN reusing the collateral of a legacy MN...") self.proRegTx3, self.dmn3Privkey = self.setupDMN( From dbc19ff7c860bdbd1a762e4d2e84b7840f92756c Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Mar 2021 11:08:37 +0100 Subject: [PATCH 18/26] [Refactor] Decouple getting active mn keys from VoteOnFinalizedBudgets Also don't get the keys if the node is still waiting --- src/activemasternode.cpp | 29 +++++++++++++++++++++++++++++ src/activemasternode.h | 3 +++ src/budget/budgetmanager.cpp | 29 +++-------------------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 1cc372f1da6e..6e06808efb2c 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -457,3 +457,32 @@ void CActiveMasternode::GetKeys(CKey& _privKeyMasternode, CPubKey& _pubKeyMaster _pubKeyMasternode = pubKeyMasternode; } +bool GetActiveMasternodeKeys(CKey& key, CKeyID& keyID, CTxIn& vin) +{ + if (activeMasternodeManager != nullptr) { + // deterministic mn + CDeterministicMNCPtr dmn; + auto res = activeMasternodeManager->GetOperatorKey(key, keyID, dmn); + if (!res) { + LogPrint(BCLog::MNBUDGET,"%s: %s\n", __func__, res.getError()); + return false; + } + vin = CTxIn(dmn->collateralOutpoint); + return true; + } + + // legacy mn + if (activeMasternode.vin == nullopt) { + LogPrint(BCLog::MNBUDGET,"%s: Active Masternode not initialized\n", __func__); + return false; + } + if (activeMasternode.GetStatus() != ACTIVE_MASTERNODE_STARTED) { + LogPrint(BCLog::MNBUDGET,"%s: MN not started (%s)\n", __func__, activeMasternode.GetStatusMessage()); + return false; + } + CPubKey mnPubKey; + activeMasternode.GetKeys(key, mnPubKey); + keyID = mnPubKey.GetID(); + vin = *activeMasternode.vin; + return true; +} diff --git a/src/activemasternode.h b/src/activemasternode.h index bdcc97950668..5a70af467210 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -116,4 +116,7 @@ class CActiveMasternode void GetKeys(CKey& privKeyMasternode, CPubKey& pubKeyMasternode); }; +// Compatibility code: get keys for either legacy or deterministic masternode +bool GetActiveMasternodeKeys(CKey& key, CKeyID& keyID, CTxIn& vin); + #endif diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 907e489cbcff..3abdd8e61b78 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -476,29 +476,6 @@ void CBudgetManager::VoteOnFinalizedBudgets() return; } - CKey mnKey; CKeyID mnKeyID; CTxIn mnVin; - if (activeMasternodeManager != nullptr) { - // deterministic mn - CDeterministicMNCPtr dmn; - auto res = activeMasternodeManager->GetOperatorKey(mnKey, mnKeyID, dmn); - if (!res) { - LogPrint(BCLog::MNBUDGET,"%s: %s\n", __func__, res.getError()); - return; - } - mnVin = CTxIn(dmn->collateralOutpoint); - - } else { - // legacy mn - if (activeMasternode.vin == nullopt) { - LogPrint(BCLog::MNBUDGET,"%s: Active Masternode not initialized\n", __func__); - return; - } - CPubKey mnPubKey; - activeMasternode.GetKeys(mnKey, mnPubKey); - mnKeyID = mnPubKey.GetID(); - mnVin = *activeMasternode.vin; - } - // Do this 1 in 4 blocks -- spread out the voting activity // -- this function is only called every fourteenth block, so this is really 1 in 56 blocks if (GetRandInt(4) != 0) { @@ -506,9 +483,9 @@ void CBudgetManager::VoteOnFinalizedBudgets() return; } - // check that the active masternode is enabled - if (activeMasternode.GetStatus() != ACTIVE_MASTERNODE_STARTED) { - LogPrint(BCLog::MNBUDGET,"%s: MN not enabled (%s)\n", __func__, activeMasternode.GetStatusMessage()); + // Get the active masternode (operator) key + CKey mnKey; CKeyID mnKeyID; CTxIn mnVin; + if (!GetActiveMasternodeKeys(mnKey, mnKeyID, mnVin)) { return; } From 0db3f57a62f8a0ce0d71d1d8828d3017933b1a6f Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Mar 2021 11:42:53 +0100 Subject: [PATCH 19/26] [Consensus] Compatibility: sign/verify mnw with deterministic nodes --- src/masternode-payments.cpp | 42 +++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 097d815cfdf5..768bd0434b45 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -478,15 +478,31 @@ void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::st return; } - const CMasternode* pmn = mnodeman.Find(winner.vinMasternode.prevout); - if (!pmn || !winner.CheckSignature(pmn->pubKeyMasternode.GetID())) { + // See if this winner was signed with a dmn or a legacy masternode + bool fDeterministic{false}; + Optional mnKeyID = nullopt; + auto mnList = deterministicMNManager->GetListAtChainTip(); + auto dmn = mnList.GetMNByCollateral(winner.vinMasternode.prevout); + if (dmn) { + fDeterministic = true; + mnKeyID = Optional(dmn->pdmnState->keyIDOperator); + } else { + const CMasternode* pmn = mnodeman.Find(winner.vinMasternode.prevout); + if (pmn) { + mnKeyID = Optional(pmn->pubKeyMasternode.GetID()); + } + } + + if (mnKeyID == nullopt || !winner.CheckSignature(*mnKeyID)) { if (masternodeSync.IsSynced()) { LogPrintf("CMasternodePayments::ProcessMessageMasternodePayments() : mnw - invalid signature\n"); LOCK(cs_main); Misbehaving(pfrom->GetId(), 20); } // it could just be a non-synced masternode - mnodeman.AskForMN(pfrom, winner.vinMasternode); + if (!fDeterministic) { + mnodeman.AskForMN(pfrom, winner.vinMasternode); + } return; } @@ -693,13 +709,20 @@ void CMasternodePayments::CleanPaymentList(int mnCount, int nHeight) bool CMasternodePayments::ProcessBlock(int nBlockHeight) { + // No more mnw messages after transition to DMN + if (deterministicMNManager->LegacyMNObsolete()) { + return false; + } if (!fMasterNode) return false; - if (activeMasternode.vin == nullopt) - return error("%s: Active Masternode not initialized.", __func__); + // Get the active masternode (operator) key + CKey mnKey; CKeyID mnKeyID; CTxIn mnVin; + if (!GetActiveMasternodeKeys(mnKey, mnKeyID, mnVin)) { + return false; + } //reference node - hybrid mode - int n = mnodeman.GetMasternodeRank(*(activeMasternode.vin), nBlockHeight - 100); + int n = mnodeman.GetMasternodeRank(mnVin, nBlockHeight - 100); if (n == -1) { LogPrint(BCLog::MASTERNODE, "CMasternodePayments::ProcessBlock - Unknown Masternode\n"); @@ -727,12 +750,9 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) return false; } - CMasternodePaymentWinner newWinner(*(activeMasternode.vin), nBlockHeight); + CMasternodePaymentWinner newWinner(mnVin, nBlockHeight); newWinner.AddPayee(pmn->GetPayeeScript()); - - CPubKey pubKeyMasternode; CKey keyMasternode; - activeMasternode.GetKeys(keyMasternode, pubKeyMasternode); - if (!newWinner.Sign(keyMasternode, pubKeyMasternode.GetID())) { + if (!newWinner.Sign(mnKey, mnKeyID)) { LogPrintf("%s: Failed to sign masternode winner\n", __func__); return false; } From d616239c838f6e97cc14f2bbc23343b2aed8525b Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Mar 2021 11:50:58 +0100 Subject: [PATCH 20/26] [Cleanup] Remove redundant checks in CMasternodePaymentWinner::IsValid we know that the masternode who signed the mnw message has positive rank, thus it exists, it's enabled, and it has a valid active protocol. Also change return value of CMasternodePayments::ProcessBlock to void. --- src/masternode-payments.cpp | 40 +++++++++++-------------------------- src/masternode-payments.h | 2 +- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 768bd0434b45..6fab344cd196 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -165,24 +165,9 @@ std::string CMasternodePaymentWinner::GetStrMessage() const bool CMasternodePaymentWinner::IsValid(CNode* pnode, std::string& strError) { - CMasternode* pmn = mnodeman.Find(vinMasternode.prevout); - - if (!pmn) { - strError = strprintf("Unknown Masternode %s", vinMasternode.prevout.hash.ToString()); - LogPrint(BCLog::MASTERNODE,"CMasternodePaymentWinner::IsValid - %s\n", strError); - mnodeman.AskForMN(pnode, vinMasternode); - return false; - } - - if (pmn->protocolVersion < ActiveProtocol()) { - strError = strprintf("Masternode protocol too old %d - req %d", pmn->protocolVersion, ActiveProtocol()); - LogPrint(BCLog::MASTERNODE,"CMasternodePaymentWinner::IsValid - %s\n", strError); - return false; - } - int n = mnodeman.GetMasternodeRank(vinMasternode, nBlockHeight - 100); - if (n > MNPAYMENTS_SIGNATURES_TOTAL) { + if (n < 1 || n > MNPAYMENTS_SIGNATURES_TOTAL) { //It's common to have masternodes mistakenly think they are in the top 10 // We don't want to print all of these messages, or punish them unless they're way off if (n > MNPAYMENTS_SIGNATURES_TOTAL * 2) { @@ -707,18 +692,18 @@ void CMasternodePayments::CleanPaymentList(int mnCount, int nHeight) } } -bool CMasternodePayments::ProcessBlock(int nBlockHeight) +void CMasternodePayments::ProcessBlock(int nBlockHeight) { // No more mnw messages after transition to DMN if (deterministicMNManager->LegacyMNObsolete()) { - return false; + return; } - if (!fMasterNode) return false; + if (!fMasterNode) return; // Get the active masternode (operator) key CKey mnKey; CKeyID mnKeyID; CTxIn mnVin; if (!GetActiveMasternodeKeys(mnKey, mnKeyID, mnVin)) { - return false; + return; } //reference node - hybrid mode @@ -726,19 +711,19 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) if (n == -1) { LogPrint(BCLog::MASTERNODE, "CMasternodePayments::ProcessBlock - Unknown Masternode\n"); - return false; + return; } if (n > MNPAYMENTS_SIGNATURES_TOTAL) { LogPrint(BCLog::MASTERNODE, "CMasternodePayments::ProcessBlock - Masternode not in the top %d (%d)\n", MNPAYMENTS_SIGNATURES_TOTAL, n); - return false; + return; } - if (nBlockHeight <= nLastBlockHeight) return false; + if (nBlockHeight <= nLastBlockHeight) return; if (g_budgetman.IsBudgetPaymentBlock(nBlockHeight)) { //is budget payment block -- handled by the budgeting software - return false; + return; } // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough @@ -747,21 +732,20 @@ bool CMasternodePayments::ProcessBlock(int nBlockHeight) if (pmn == nullptr) { LogPrint(BCLog::MASTERNODE,"%s: Failed to find masternode to pay\n", __func__); - return false; + return; } CMasternodePaymentWinner newWinner(mnVin, nBlockHeight); newWinner.AddPayee(pmn->GetPayeeScript()); if (!newWinner.Sign(mnKey, mnKeyID)) { LogPrintf("%s: Failed to sign masternode winner\n", __func__); - return false; + return; } if (!AddWinningMasternode(newWinner)) { - return false; + return; } newWinner.Relay(); nLastBlockHeight = nBlockHeight; - return true; } void CMasternodePayments::Sync(CNode* node, int nCountNeeded) diff --git a/src/masternode-payments.h b/src/masternode-payments.h index 52462892aee0..db90697df0c1 100644 --- a/src/masternode-payments.h +++ b/src/masternode-payments.h @@ -255,7 +255,7 @@ class CMasternodePayments } bool AddWinningMasternode(CMasternodePaymentWinner& winner); - bool ProcessBlock(int nBlockHeight); + void ProcessBlock(int nBlockHeight); void Sync(CNode* node, int nCountNeeded); void CleanPaymentList(int mnCount, int nHeight); From dd3bce9be2587b035231f9f7b30ff0152bdacbd4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Mar 2021 22:33:31 +0100 Subject: [PATCH 21/26] [Tests] Raise regtest ping timeouts to 1/10th of mainnet value --- src/masternode.cpp | 4 ++-- test/functional/tiertwo_masternode_activation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/masternode.cpp b/src/masternode.cpp index eea8a791acca..1c20bc17dd45 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -18,8 +18,8 @@ #define MASTERNODE_MIN_MNP_SECONDS_REGTEST 90 #define MASTERNODE_MIN_MNB_SECONDS_REGTEST 25 #define MASTERNODE_PING_SECONDS_REGTEST 25 -#define MASTERNODE_EXPIRATION_SECONDS_REGTEST 180 -#define MASTERNODE_REMOVAL_SECONDS_REGTEST 200 +#define MASTERNODE_EXPIRATION_SECONDS_REGTEST 12 * 60 +#define MASTERNODE_REMOVAL_SECONDS_REGTEST 13 * 60 #define MASTERNODE_MIN_CONFIRMATIONS 15 #define MASTERNODE_MIN_MNP_SECONDS (10 * 60) diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index c0a3b166c19c..cba3132e6578 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -87,7 +87,7 @@ def run_test(self): # check masternode expiration self.log.info("testing expiration now.") - expiration_time = 180 # regtest expiration time + expiration_time = 12 * 60 # regtest expiration time self.log.info("disconnect remote and move time %d seconds in the future..." % expiration_time) self.disconnect_remotes() self.advance_mocktime_and_stake(expiration_time) @@ -96,7 +96,7 @@ def run_test(self): # check masternode removal self.log.info("testing removal now.") - removal_time = 200 # regtest removal time + removal_time = 13 * 60 # regtest removal time self.advance_mocktime_and_stake(removal_time - expiration_time) self.wait_until_mn_expired(30, removed=True) self.log.info("masternodes removed successfully") From ede451906a21bac6b688658d86c8722136a95cad Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 21 May 2021 09:20:15 +0200 Subject: [PATCH 22/26] [Cleanup] Remove unused parameter in GetCurrentMasternode Also pass the genesis hash instead of a null hash (as it was the original behavior before bfa628e2db8faf8f20d55e71f9357a7353df4012) when no-masternode-to-pay is found in GetLegacyMasternodeTxOut (e.g. when there are less than 9 masternodes enabled on the network, and they are all scheduled already). --- src/masternode-payments.cpp | 2 +- src/masternodeman.cpp | 4 ++-- src/masternodeman.h | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 6fab344cd196..00592e68fa9d 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -324,7 +324,7 @@ bool CMasternodePayments::GetLegacyMasternodeTxOut(int nHeight, std::vectorGetPayeeScript(); } else { diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 58d9b4fc8630..bf954a92c49e 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -572,7 +572,7 @@ MasternodeRef CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeigh return pBestMasternode; } -MasternodeRef CMasternodeMan::GetCurrentMasterNode(int nHeight, const uint256& hash) const +MasternodeRef CMasternodeMan::GetCurrentMasterNode(const uint256& hash) const { int minProtocol = ActiveProtocol(); int64_t score = 0; @@ -617,7 +617,7 @@ std::vector> CMasternodeMan::GetMnScores(int nLast for (int nHeight = nChainHeight - nLast; nHeight < nChainHeight + 20; nHeight++) { const uint256& hash = GetHashAtHeight(nHeight - 101); - MasternodeRef winner = GetCurrentMasterNode(nHeight, hash); + MasternodeRef winner = GetCurrentMasterNode(hash); if (winner) { ret.emplace_back(winner, nHeight); } diff --git a/src/masternodeman.h b/src/masternodeman.h index ed9c5276472d..9bc24a98428d 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -144,8 +144,8 @@ class CMasternodeMan /// Find an entry in the masternode list that is next to be paid MasternodeRef GetNextMasternodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount, const CBlockIndex* pChainTip = nullptr) const; - /// Get the current winner for this block - MasternodeRef GetCurrentMasterNode(int nHeight, const uint256& hash) const; + /// Get the winner for this block hash + MasternodeRef GetCurrentMasterNode(const uint256& hash) const; /// vector of pairs std::vector> GetMnScores(int nLast) const; From ad2cc30e37e75bdc4628abd01026a67ab15a0147 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 21 May 2021 10:38:35 +0200 Subject: [PATCH 23/26] [Consensus] Check against current hash when no payee is found Instead of using always the genesis hash (which is constant) --- src/masternode-payments.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 00592e68fa9d..5c029749bfc9 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -324,7 +324,10 @@ bool CMasternodePayments::GetLegacyMasternodeTxOut(int nHeight, std::vectorGetPayeeScript(); } else { From 3f1693663b83f305733e2df49959bb5d00eacd0f Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sat, 6 Mar 2021 17:29:33 +0100 Subject: [PATCH 24/26] [Test] Update tiertwo_mn_compatibility and check winners --- test/functional/test_framework/messages.py | 22 +++ .../test_framework/test_framework.py | 159 +++++++++++++++++- test/functional/test_framework/util.py | 37 +++- test/functional/tiertwo_mn_compatibility.py | 101 +++++++---- 4 files changed, 279 insertions(+), 40 deletions(-) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index ffd5d42bfe4a..2b8e684f4c95 100644 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1372,3 +1372,25 @@ def serialize(self): r += self.block_transactions.serialize(with_witness=True) return r + +# PIVX Classes +class Masternode(object): + def __init__(self, idx, owner_addr, operator_addr, voting_addr, ipport, payout_addr, operator_key): + self.idx = idx + self.owner = owner_addr + self.operator = operator_addr + self.voting = voting_addr + self.ipport = ipport + self.payee = payout_addr + self.operator_key = operator_key + self.proTx = None + self.collateral = None + + def __repr__(self): + return "Masternode(idx=%d, owner=%s, operator=%s, voting=%s, ip=%s, payee=%s, opkey=%s, protx=%s, collateral=%s)" % ( + self.idx, str(self.owner), str(self.operator), str(self.voting), str(self.ipport), + str(self.payee), str(self.operator_key), str(self.proTx), str(self.collateral) + ) + + def __str__(self): + return self.__repr__() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 0d159505c805..4e5ede169731 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -47,18 +47,19 @@ connect_nodes, connect_nodes_clique, disconnect_nodes, + get_collateral_vout, + lock_utxo, Decimal, DEFAULT_FEE, get_datadir_path, hex_str_to_bytes, bytes_to_hex_str, initialize_datadir, + create_new_dmn, p2p_port, set_node_times, SPORK_ACTIVATION_TIME, SPORK_DEACTIVATION_TIME, - vZC_DENOMS, - wait_until, ) class TestStatus(Enum): @@ -1048,9 +1049,10 @@ def controller_start_masternode(self, mnOwner, masternodeAlias): def send_pings(self, mnodes): for node in mnodes: - sent = node.mnping()["sent"] - if sent != "YES" and "Too early to send Masternode Ping" not in sent: - raise AssertionError("Unable to send ping: \"sent\" = %s" % sent) + try: + node.mnping()["sent"] + except: + pass time.sleep(1) @@ -1068,7 +1070,7 @@ def stake_and_ping(self, node_id, num_blocks, with_ping_mns=[]): if len(with_ping_mns) > 0: self.send_pings(with_ping_mns) - + # !TODO: remove after obsoleting legacy system def setupDMN(self, mnOwner, miner, @@ -1171,6 +1173,149 @@ def setupMasternode(self, return COutPoint(collateralTxId, collateralTxId_n) +### ---------------------- +### ----- DMN setup ------ +### ---------------------- + + def connect_to_all(self, nodePos): + for i in range(self.num_nodes): + if i != nodePos and self.nodes[i] is not None: + connect_nodes(self.nodes[i], nodePos) + + def assert_equal_for_all(self, expected, func_name, *args): + def not_found(): + raise Exception("function %s not found!" % func_name) + + assert_equal([getattr(x, func_name, not_found)(*args) for x in self.nodes], + [expected] * self.num_nodes) + + """ + Create a ProReg tx, which has the collateral as one of its outputs + """ + def protx_register_fund(self, miner, controller, dmn, collateral_addr, lock=True): + # send to the owner the collateral tx + some dust for the ProReg and fee + funding_txid = miner.sendtoaddress(collateral_addr, Decimal('101')) + # confirm and verify reception + miner.generate(1) + self.sync_blocks([miner, controller]) + assert_greater_than(controller.getrawtransaction(funding_txid, True)["confirmations"], 0) + # create and send the ProRegTx funding the collateral + dmn.proTx = controller.protx_register_fund(collateral_addr, dmn.ipport, dmn.owner, + dmn.operator, dmn.voting, dmn.payee) + dmn.collateral = COutPoint(int(dmn.proTx, 16), + get_collateral_vout(controller.getrawtransaction(dmn.proTx, True))) + if lock: + lock_utxo(controller, dmn.collateral) + + """ + Create a ProReg tx, which references an 100 PIV UTXO as collateral. + The controller node owns the collateral and creates the ProReg tx. + """ + def protx_register(self, miner, controller, dmn, collateral_addr, fLock): + # send to the owner the exact collateral tx amount + funding_txid = miner.sendtoaddress(collateral_addr, Decimal('100')) + # send another output to be used for the fee of the proReg tx + miner.sendtoaddress(collateral_addr, Decimal('1')) + # confirm and verify reception + miner.generate(1) + self.sync_blocks([miner, controller]) + json_tx = controller.getrawtransaction(funding_txid, True) + assert_greater_than(json_tx["confirmations"], 0) + # create and send the ProRegTx + dmn.collateral = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) + dmn.proTx = controller.protx_register(funding_txid, dmn.collateral.n, dmn.ipport, dmn.owner, + dmn.operator, dmn.voting, dmn.payee) + if fLock: + lock_utxo(controller, dmn.collateral) + + """ + Create a ProReg tx, referencing a collateral signed externally (eg. HW wallets). + Here the controller node owns the collateral (and signs), but the miner creates the ProReg tx. + """ + def protx_register_ext(self, miner, controller, dmn, outpoint, fSubmit, fLock): + # send to the owner the collateral tx if the outpoint is not specified + if outpoint is None: + funding_txid = miner.sendtoaddress(controller.getnewaddress("collateral"), Decimal('100')) + # confirm and verify reception + miner.generate(1) + self.sync_blocks([miner, controller]) + json_tx = controller.getrawtransaction(funding_txid, True) + assert_greater_than(json_tx["confirmations"], 0) + outpoint = COutPoint(int(funding_txid, 16), get_collateral_vout(json_tx)) + dmn.collateral = outpoint + # Prepare the message to be signed externally by the owner of the collateral (the controller) + reg_tx = miner.protx_register_prepare("%064x" % outpoint.hash, outpoint.n, dmn.ipport, dmn.owner, + dmn.operator, dmn.voting, dmn.payee) + sig = controller.signmessage(reg_tx["collateralAddress"], reg_tx["signMessage"]) + if fSubmit: + if fLock: + lock_utxo(controller, dmn.collateral) + dmn.proTx = miner.protx_register_submit(reg_tx["tx"], sig) + else: + return reg_tx["tx"], sig + + """ Create and register new deterministic masternode + :param idx: (int) index of the (remote) node in self.nodes + miner_idx: (int) index of the miner in self.nodes + controller_idx: (int) index of the controller in self.nodes + strType: (string) "fund"|"internal"|"external" + payout_addr: (string) payee address. If not specified, reuse the collateral address. + outpoint: (COutPoint) collateral outpoint to be used with "external". + It must be owned by the controller (proTx is sent from the miner). + If not provided, a new utxo is created, sending it from the miner. + op_addr_and_key: (list of strings) List with two entries, operator address (0) and private key (1). + If not provided, a new address-key pair is generated. + fLock: (boolean) lock the collateral output + :return: dmn: (Masternode) the deterministic masternode object + """ + def register_new_dmn(self, idx, miner_idx, controller_idx, strType, + payout_addr=None, outpoint=None, op_addr_and_key=None, fLock=True): + # Prepare remote node + assert idx != miner_idx + assert idx != controller_idx + miner_node = self.nodes[miner_idx] + controller_node = self.nodes[controller_idx] + mn_node = self.nodes[idx] + + # Generate ip and addresses/keys + collateral_addr = controller_node.getnewaddress("mncollateral-%d" % idx) + if payout_addr is None: + payout_addr = collateral_addr + dmn = create_new_dmn(idx, controller_node, payout_addr, op_addr_and_key) + + # Create ProRegTx + self.log.info("Creating%s proRegTx for deterministic masternode idx=%d..." % ( + " and funding" if strType == "fund" else "", idx)) + if strType == "fund": + self.protx_register_fund(miner_node, controller_node, dmn, collateral_addr, fLock) + elif strType == "internal": + self.protx_register(miner_node, controller_node, dmn, collateral_addr, fLock) + elif strType == "external": + self.protx_register_ext(miner_node, controller_node, dmn, outpoint, True, fLock) + else: + raise Exception("Type %s not available" % strType) + time.sleep(1) + self.sync_mempools([miner_node, controller_node]) + + # confirm and verify inclusion in list + miner_node.generate(1) + self.sync_blocks(self.nodes) + assert_greater_than(mn_node.getrawtransaction(dmn.proTx, 1)["confirmations"], 0) + assert dmn.proTx in mn_node.protx_list(False) + return dmn + + def check_mn_list_on_node(self, idx, mns): + mnlist = self.nodes[idx].listmasternodes() + if len(mnlist) != len(mns): + raise Exception("Invalid mn list on node %d:\n%s\nExpected:%s" % (idx, str(mnlist), str(mns))) + protxs = [x["proTxHash"] for x in mnlist] + for mn in mns: + if mn.proTx not in protxs: + raise Exception("ProTx for mn %d (%s) not found in the list of node %d", mn.idx, mn.proTx, idx) + + +### ------------------------------------------------------ + class SkipTest(Exception): """This exception is raised to skip a test""" def __init__(self, message): @@ -1180,7 +1325,7 @@ def __init__(self, message): ''' PivxTestFramework extensions ''' - +# !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): def set_test_params(self): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 74d805085f99..9006cebb57ef 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -16,7 +16,7 @@ from subprocess import CalledProcessError import time -from . import coverage +from . import coverage, messages from .authproxy import AuthServiceProxy, JSONRPCException logger = logging.getLogger("TestFramework.utils") @@ -581,3 +581,38 @@ def get_coinstake_address(node, expected_utxos=None): addrs = [a for a in set(addrs) if addrs.count(a) == expected_utxos] assert(len(addrs) > 0) return addrs[0] + +# Deterministic masternodes + +def lock_utxo(node, outpoint): + node.lockunspent(False, [{"txid": "%064x" % outpoint.hash, "vout": outpoint.n}]) + +def get_collateral_vout(json_tx): + funding_txidn = -1 + for o in json_tx["vout"]: + if o["value"] == Decimal('100'): + funding_txidn = o["n"] + break + assert_greater_than(funding_txidn, -1) + return funding_txidn + +# owner and voting keys are created from controller node. +# operator key and address are created, if operator_addr_and_key is None. +def create_new_dmn(idx, controller, payout_addr, operator_addr_and_key): + ipport = "127.0.0.1:" + str(p2p_port(idx)) + owner_addr = controller.getnewaddress("mnowner-%d" % idx) + voting_addr = controller.getnewaddress("mnvoting-%d" % idx) + if operator_addr_and_key is None: + operator_addr = controller.getnewaddress("mnoperator-%d" % idx) + operator_key = controller.dumpprivkey(operator_addr) + else: + operator_addr = operator_addr_and_key[0] + operator_key = operator_addr_and_key[1] + return messages.Masternode(idx, owner_addr, operator_addr, voting_addr, ipport, payout_addr, operator_key) + +def spend_mn_collateral(spender, dmn): + inputs = [{"txid": "%064x" % dmn.collateral.hash, "vout": dmn.collateral.n}] + outputs = {spender.getnewaddress(): Decimal('99.99')} + sig_res = spender.signrawtransaction(spender.createrawtransaction(inputs, outputs)) + assert_equal(sig_res['complete'], True) + return spender.sendrawtransaction(sig_res['hex']) diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index c0e9f8581f88..b048aabe35ce 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -6,6 +6,7 @@ from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( assert_equal, + connect_nodes, ) from decimal import Decimal @@ -59,25 +60,47 @@ def check_mns_status(self, node, txhash): assert_equal(status["dmnstate"]["PoSePenalty"], 0) assert_equal(status["status"], "Ready") + """ + Checks the block at specified height (it must be a v10 block). + Returns the address of the mn paid (in the coinbase), and the json coinstake tx + """ + def get_block_mnwinner(self, height): + blk = self.miner.getblock(self.miner.getblockhash(height), True) + assert_equal(blk['height'], height) + assert_equal(blk['version'], 10) + cbase_tx = self.miner.getrawtransaction(blk['tx'][0], True) + assert_equal(len(cbase_tx['vin']), 1) + cbase_script = height.to_bytes(1 + height // 256, byteorder="little") + cbase_script = len(cbase_script).to_bytes(1, byteorder="little") + cbase_script + bytearray(1) + assert_equal(cbase_tx['vin'][0]['coinbase'], cbase_script.hex()) + assert_equal(len(cbase_tx['vout']), 1) + assert_equal(cbase_tx['vout'][0]['value'], Decimal("3.0")) + return cbase_tx['vout'][0]['scriptPubKey']['addresses'][0], self.miner.getrawtransaction(blk['tx'][1], True) + def check_mn_list(self, node, txHashSet): # check masternode list from node mnlist = node.listmasternodes() - assert_equal(len(mnlist), len(txHashSet)) + if len(mnlist) != len(txHashSet): + raise Exception(str(mnlist)) foundHashes = set([mn["txhash"] for mn in mnlist if mn["txhash"] in txHashSet]) if len(foundHashes) != len(txHashSet): raise Exception(str(mnlist)) for x in mnlist: - self.mn_addresses.add(x["addr"]) - self.log.info("MN address list has %d entries" % len(self.mn_addresses)) + self.mn_addresses[x["txhash"]] = x["addr"] def run_test(self): - self.mn_addresses = set() + self.mn_addresses = {} self.enable_mocktime() self.setup_3_masternodes_network() # add two more nodes to the network self.remoteDMN2 = self.nodes[self.remoteDMN2Pos] self.remoteDMN3 = self.nodes[self.remoteDMN3Pos] + # add more direct connections to the miner + connect_nodes(self.miner, 2) + connect_nodes(self.remoteTwo, 0) + connect_nodes(self.remoteDMN2, 0) + self.sync_all() # check mn list from miner txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1.hash]) @@ -85,11 +108,11 @@ def run_test(self): # check status of masternodes self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) - self.log.info("MN1 active") + self.log.info("MN1 active. Pays %s" % self.mn_addresses[self.mnOneCollateral.hash]) self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) - self.log.info("MN2 active") + self.log.info("MN2 active Pays %s" % self.mn_addresses[self.mnTwoCollateral.hash]) self.check_mns_status(self.remoteDMN1, self.proRegTx1.hash) - self.log.info("DMN1 active") + self.log.info("DMN1 active Pays %s" % self.mn_addresses[self.proRegTx1.hash]) # Create another DMN, this time without funding the collateral. # ProTx references another transaction in the owner's wallet @@ -105,25 +128,14 @@ def run_test(self): txHashSet.add(self.proRegTx2.hash) self.check_mn_list(self.miner, txHashSet) self.check_mns_status(self.remoteDMN2, self.proRegTx2.hash) - self.log.info("DMN2 active") + self.log.info("DMN2 active Pays %s" % self.mn_addresses[self.proRegTx2.hash]) # Check block version and coinbase payment blk_count = self.miner.getblockcount() self.log.info("Checking block version and coinbase payment...") - blk = self.miner.getblock(self.miner.getbestblockhash(), True) - assert_equal(blk['height'], blk_count) - assert_equal(blk['version'], 10) - cbase_tx = self.miner.getrawtransaction(blk['tx'][0], True) - assert_equal(len(cbase_tx['vin']), 1) - cbase_script = blk_count.to_bytes(1 + blk_count//256, byteorder="little") - cbase_script = len(cbase_script).to_bytes(1, byteorder="little") + cbase_script + bytearray(1) - assert_equal(cbase_tx['vin'][0]['coinbase'], cbase_script.hex()) - assert_equal(len(cbase_tx['vout']), 1) - assert_equal(cbase_tx['vout'][0]['value'], Decimal("3.0")) - payee = cbase_tx['vout'][0]['scriptPubKey']['addresses'][0] - if payee not in self.mn_addresses: + payee, cstake_tx = self.get_block_mnwinner(blk_count) + if payee not in [self.mn_addresses[k] for k in self.mn_addresses]: raise Exception("payee %s not found in expected list %s" % (payee, str(self.mn_addresses))) - cstake_tx = self.miner.getrawtransaction(blk['tx'][1], True) assert_equal(len(cstake_tx['vin']), 1) assert_equal(len(cstake_tx['vout']), 2) assert_equal(cstake_tx['vout'][1]['value'], Decimal("497.0")) # 250 + 250 - 3 @@ -138,13 +150,10 @@ def run_test(self): "external", self.mnOneCollateral, ) - self.remoteDMN3.initmasternode(self.dmn3Privkey, "", True) - # The remote node is shutting down the pinging service - try: - self.send_3_pings() - except: - pass + self.send_3_pings() + + self.remoteDMN3.initmasternode(self.dmn3Privkey, "", True) # The legacy masternode must no longer be in the list # and the DMN must have taken its place @@ -154,20 +163,48 @@ def run_test(self): self.check_mn_list(node, txHashSet) self.log.info("Masternode list correctly updated by all nodes.") self.check_mns_status(self.remoteDMN3, self.proRegTx3.hash) - self.log.info("DMN3 active") + self.log.info("DMN3 active Pays %s" % self.mn_addresses[self.proRegTx3.hash]) # Now try to start a legacy MN with a collateral used by a DMN self.log.info("Now trying to start a legacy MN with a collateral of a DMN...") self.controller_start_masternode(self.ownerOne, self.masternodeOneAlias) - try: - self.send_3_pings() - except: - pass + self.send_3_pings() # the masternode list hasn't changed for node in self.nodes: self.check_mn_list(node, txHashSet) self.log.info("Masternode list correctly unchanged in all nodes.") + # stake 30 blocks, sync tiertwo data, and check winners + self.log.info("Staking 30 blocks...") + self.stake(30, [self.remoteTwo]) + self.sync_blocks() + self.wait_until_mnsync_finished() + + # check projection + self.log.info("Checking winners...") + winners = set([x['winner']['address'] for x in self.miner.getmasternodewinners() + if x['winner']['address'] != "Unknown"]) + # all except mn1 must be scheduled + mn_addresses = set([self.mn_addresses[k] for k in self.mn_addresses + if k != self.mnOneCollateral.hash]) + assert_equal(winners, mn_addresses) + + # check mns paid in the last 20 blocks + self.log.info("Checking masternodes paid...") + blk_count = self.miner.getblockcount() + mn_payments = {} # dict address --> payments count + for i in range(blk_count - 20 + 1, blk_count + 1): + winner, _ = self.get_block_mnwinner(i) + if winner not in mn_payments: + mn_payments[winner] = 0 + mn_payments[winner] += 1 + # two full 10-blocks schedule: all mns must be paid at least twice + assert_equal(len(mn_payments), len(mn_addresses)) + assert all([x >= 2 for x in mn_payments.values()]) + self.log.info("All good.") + + + if __name__ == '__main__': MasternodeCompatibilityTest().main() From 9aeeb761e3909655bd49aff96c531dad4a976a69 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 13 May 2021 10:35:29 -0300 Subject: [PATCH 25/26] [Test] Add test coverage for MN and DMN votes expiration removal. --- .../test_framework/test_framework.py | 14 +++++++++ .../tiertwo_governance_sync_basic.py | 30 ++++++++++++++++--- .../tiertwo_masternode_activation.py | 17 +---------- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 4e5ede169731..b6d3b0c281f5 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -60,6 +60,7 @@ set_node_times, SPORK_ACTIVATION_TIME, SPORK_DEACTIVATION_TIME, + satoshi_round ) class TestStatus(Enum): @@ -1453,3 +1454,16 @@ def setup_3_masternodes_network(self): # Now everything is set, can start both masternodes self.controller_start_all_masternodes() + + def spend_collateral(self, mnOwner, collateralOutpoint, miner): + send_value = satoshi_round(100 - 0.001) + inputs = [{'txid': collateralOutpoint.hash, 'vout': collateralOutpoint.n}] + outputs = {} + outputs[mnOwner.getnewaddress()] = float(send_value) + rawtx = mnOwner.createrawtransaction(inputs, outputs) + signedtx = mnOwner.signrawtransaction(rawtx) + txid = miner.sendrawtransaction(signedtx['hex']) + self.sync_mempools() + self.log.info("Collateral spent in %s" % txid) + self.send_pings([self.remoteTwo]) + self.stake(1, [self.remoteTwo]) diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 26aaf10e6601..5a7c317d6c4e 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -67,7 +67,7 @@ def check_proposal_existence(self, proposalName, proposalHash): assert(len(proposals) > 0) assert_equal(proposals[0]["Hash"], proposalHash) - def check_vote_existence(self, proposalName, mnCollateralHash, voteType): + def check_vote_existence(self, proposalName, mnCollateralHash, voteType, voteValid): for i in range(0, len(self.nodes)): node = self.nodes[i] votesInfo = node.getbudgetvotes(proposalName) @@ -76,6 +76,7 @@ def check_vote_existence(self, proposalName, mnCollateralHash, voteType): for voteInfo in votesInfo: if (voteInfo["mnId"].split("-")[0] == mnCollateralHash) : assert_equal(voteInfo["Vote"], voteType) + assert_equal(voteInfo["fValid"], voteValid) found = True assert_true(found, "Error checking vote existence in node " + str(i)) @@ -182,7 +183,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES") + self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, MN1 vote accepted everywhere!") # now let's vote for the proposal with the second MN @@ -192,7 +193,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.mnTwoCollateral.hash, "YES") + self.check_vote_existence(firstProposalName, self.mnTwoCollateral.hash, "YES", True) self.log.info("all good, MN2 vote accepted everywhere!") # now let's vote for the proposal with the first DMN @@ -202,7 +203,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES") + self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget @@ -263,6 +264,27 @@ def run_test(self): expected_budget[0]["RemainingPaymentCount"] -= 1 self.check_budgetprojection(expected_budget) + self.stake(1, [self.remoteOne, self.remoteTwo]) + + # now let's verify that votes expire properly. + # Drop one MN and one DMN + self.log.info("expiring MN1..") + self.spend_collateral(self.ownerOne, self.mnOneCollateral, self.miner) + self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) + self.stake(12, [self.remoteTwo]) # create blocks to remove staled votes + time.sleep(2) # wait a little bit + self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES", False) + self.log.info("MN1 vote expired after collateral spend, all good") + + self.log.info("expiring DMN..") + self.spend_collateral(self.ownerOne, self.proRegTx1, self.miner) + self.wait_until_mn_vinspent(self.proRegTx1.hash, 30, [self.remoteTwo]) + self.stake(12, [self.remoteTwo]) # create blocks to remove staled votes + time.sleep(2) # wait a little bit + self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES", False) + self.log.info("DMN vote expired after collateral spend, all good") + + if __name__ == '__main__': MasternodeGovernanceBasicTest().main() diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index cba3132e6578..47ce2480c67b 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -5,10 +5,8 @@ from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( - assert_greater_than_or_equal, connect_nodes_clique, disconnect_nodes, - satoshi_round, wait_until, ) @@ -43,19 +41,6 @@ def reconnect_and_restart_masternodes(self): self.wait_until_mnsync_finished() self.controller_start_all_masternodes() - def spend_collateral(self): - send_value = satoshi_round(100 - 0.001) - inputs = [{'txid': self.mnOneCollateral.hash, 'vout': self.mnOneCollateral.n}] - outputs = {} - outputs[self.ownerOne.getnewaddress()] = float(send_value) - rawtx = self.ownerOne.createrawtransaction(inputs, outputs) - signedtx = self.ownerOne.signrawtransaction(rawtx) - txid = self.miner.sendrawtransaction(signedtx['hex']) - self.sync_mempools() - self.log.info("Collateral spent in %s" % txid) - self.send_pings([self.remoteTwo]) - self.stake(1, [self.remoteTwo]) - # Similar to base class wait_until_mn_status but skipping the disconnected nodes def wait_until_mn_expired(self, _timeout, removed=False): collaterals = { @@ -105,7 +90,7 @@ def run_test(self): self.reconnect_and_restart_masternodes() self.advance_mocktime(30) self.log.info("spending the collateral now..") - self.spend_collateral() + self.spend_collateral(self.ownerOne, self.mnOneCollateral, self.miner) self.sync_blocks() self.log.info("checking mn status..") time.sleep(3) # wait a little bit From 04cd17e53102a268cd3aea6d0ecf0300a552e880 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 13 May 2021 23:03:34 +0200 Subject: [PATCH 26/26] [QA][BUG] Fix test and rework setupDMN to return only proTx hash --- .../test_framework/test_framework.py | 4 ++-- .../tiertwo_governance_sync_basic.py | 22 ++++++++++--------- test/functional/tiertwo_mn_compatibility.py | 18 +++++++-------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index b6d3b0c281f5..3350ba1b83d5 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1132,7 +1132,7 @@ def setupDMN(self, self.stake_and_sync(self.nodes.index(miner), 1) assert_greater_than(self.nodes[mnRemotePos].getrawtransaction(proTxId, 1)["confirmations"], 0) assert proTxId in self.nodes[mnRemotePos].protx_list(False) - return COutPoint(proTxId, 0), operatorKey + return proTxId, operatorKey def setupMasternode(self, mnOwner, @@ -1361,7 +1361,7 @@ def set_test_params(self): self.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] self.mnOneCollateral = COutPoint() self.mnTwoCollateral = COutPoint() - self.proRegTx1 = COutPoint() + self.proRegTx1 = None # hash of provider-register-tx def send_3_pings(self): diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 5a7c317d6c4e..0383afed882e 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -4,6 +4,7 @@ # file COPYING or https://www.opensource.org/licenses/mit-license.php. from test_framework.test_framework import PivxTier2TestFramework +from test_framework.messages import COutPoint from test_framework.util import ( assert_equal, assert_true, @@ -116,7 +117,7 @@ def check_budgetprojection(self, expected): def run_test(self): self.enable_mocktime() self.setup_3_masternodes_network() - txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1.hash]) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) # check mn list from miner self.check_mn_list(self.miner, txHashSet) @@ -125,7 +126,7 @@ def run_test(self): self.log.info("MN1 active") self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") - self.check_mns_status(self.remoteDMN1, self.proRegTx1.hash) + self.check_mns_status(self.remoteDMN1, self.proRegTx1) self.log.info("DMN1 active") # Prepare the proposal @@ -198,12 +199,12 @@ def run_test(self): # now let's vote for the proposal with the first DMN self.log.info("Voting with DMN1...") - voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx1.hash) + voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx1) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES", True) + self.check_vote_existence(firstProposalName, self.proRegTx1, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget @@ -271,17 +272,18 @@ def run_test(self): self.log.info("expiring MN1..") self.spend_collateral(self.ownerOne, self.mnOneCollateral, self.miner) self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) - self.stake(12, [self.remoteTwo]) # create blocks to remove staled votes + self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES", False) self.log.info("MN1 vote expired after collateral spend, all good") - self.log.info("expiring DMN..") - self.spend_collateral(self.ownerOne, self.proRegTx1, self.miner) - self.wait_until_mn_vinspent(self.proRegTx1.hash, 30, [self.remoteTwo]) - self.stake(12, [self.remoteTwo]) # create blocks to remove staled votes + self.log.info("expiring DMN1..") + lm = self.ownerOne.listmasternodes(self.proRegTx1)[0] + self.spend_collateral(self.ownerOne, COutPoint(lm["collateralHash"], lm["collateralIndex"]), self.miner) + self.wait_until_mn_vinspent(self.proRegTx1, 30, [self.remoteTwo]) + self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit - self.check_vote_existence(firstProposalName, self.proRegTx1.hash, "YES", False) + self.check_vote_existence(firstProposalName, self.proRegTx1, "YES", False) self.log.info("DMN vote expired after collateral spend, all good") diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index b048aabe35ce..fa407cb62760 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -103,7 +103,7 @@ def run_test(self): self.sync_all() # check mn list from miner - txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1.hash]) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) self.check_mn_list(self.miner, txHashSet) # check status of masternodes @@ -111,8 +111,8 @@ def run_test(self): self.log.info("MN1 active. Pays %s" % self.mn_addresses[self.mnOneCollateral.hash]) self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active Pays %s" % self.mn_addresses[self.mnTwoCollateral.hash]) - self.check_mns_status(self.remoteDMN1, self.proRegTx1.hash) - self.log.info("DMN1 active Pays %s" % self.mn_addresses[self.proRegTx1.hash]) + self.check_mns_status(self.remoteDMN1, self.proRegTx1) + self.log.info("DMN1 active Pays %s" % self.mn_addresses[self.proRegTx1]) # Create another DMN, this time without funding the collateral. # ProTx references another transaction in the owner's wallet @@ -125,10 +125,10 @@ def run_test(self): self.remoteDMN2.initmasternode(self.dmn2Privkey, "", True) # check list and status - txHashSet.add(self.proRegTx2.hash) + txHashSet.add(self.proRegTx2) self.check_mn_list(self.miner, txHashSet) - self.check_mns_status(self.remoteDMN2, self.proRegTx2.hash) - self.log.info("DMN2 active Pays %s" % self.mn_addresses[self.proRegTx2.hash]) + self.check_mns_status(self.remoteDMN2, self.proRegTx2) + self.log.info("DMN2 active Pays %s" % self.mn_addresses[self.proRegTx2]) # Check block version and coinbase payment blk_count = self.miner.getblockcount() @@ -158,12 +158,12 @@ def run_test(self): # The legacy masternode must no longer be in the list # and the DMN must have taken its place txHashSet.remove(self.mnOneCollateral.hash) - txHashSet.add(self.proRegTx3.hash) + txHashSet.add(self.proRegTx3) for node in self.nodes: self.check_mn_list(node, txHashSet) self.log.info("Masternode list correctly updated by all nodes.") - self.check_mns_status(self.remoteDMN3, self.proRegTx3.hash) - self.log.info("DMN3 active Pays %s" % self.mn_addresses[self.proRegTx3.hash]) + self.check_mns_status(self.remoteDMN3, self.proRegTx3) + self.log.info("DMN3 active Pays %s" % self.mn_addresses[self.proRegTx3]) # Now try to start a legacy MN with a collateral used by a DMN self.log.info("Now trying to start a legacy MN with a collateral of a DMN...")