diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index b542ac2d5ffa1..ac9f878d99dbe 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 95e01d6631820..0d159505c8052 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 7ba290508061a..905a95593f9fe 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 5c3a0f61cd70f..26aaf10e66014 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 c06a8842fb4a0..c0a3b166c19c2 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 0000000000000..0172f698ddf29 --- /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()