From e30f1818fac1f8e479c02a60b25a501aabdc57c0 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:53:53 -0300 Subject: [PATCH 01/11] test: extending base tier two MN network. --- .../test_framework/test_framework.py | 38 +++++++++++++++---- .../tiertwo_governance_sync_basic.py | 2 +- .../tiertwo_masternode_activation.py | 2 +- test/functional/tiertwo_mn_compatibility.py | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 877eb03340512..bf946c4417dcc 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1618,9 +1618,9 @@ def mine_quorum(self, invalidate_func=None, invalidated_idx=None, skip_bad_membe # !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): - def set_test_params(self): + def set_test_params(self, v6_enforcement_height = 250): self.setup_clean_chain = True - self.num_nodes = 6 + self.num_nodes = 8 self.enable_mocktime() self.ownerOnePos = 0 @@ -1629,17 +1629,20 @@ def set_test_params(self): self.remoteTwoPos = 3 self.minerPos = 4 self.remoteDMN1Pos = 5 - - self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250", "-whitelist=127.0.0.1"]] * self.num_nodes + self.ownerThreePos = 6 + self.remoteThreePos = 7 + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes 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") self.masternodeOneAlias = "mnOne" self.masternodeTwoAlias = "mntwo" + self.masternodeThreeAlias = "mnThree" self.mnOnePrivkey = "9247iC59poZmqBYt9iDh9wDam6v9S1rW5XekjLGyPnDhrDkP4AK" self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" + self.mnThreePrivkey = "91qP855JNR3aWv6Z71BcFjqhkeizchSDjKSi7BdqMSSirEVDTEk" # Updated in setup_3_masternodes_network() to be called at the start of run_test self.ownerOne = None # self.nodes[self.ownerOnePos] @@ -1647,14 +1650,17 @@ 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.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] + self.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] + self.ownerThree = None # self.nodes[self.ownerThreePos] + self.remoteThree = None # self.nodes[self.remoteThreePos] self.mnOneCollateral = COutPoint() self.mnTwoCollateral = COutPoint() + self.mnThreeCollateral = COutPoint() self.proRegTx1 = None # hash of provider-register-tx def send_3_pings(self): - mns = [self.remoteOne, self.remoteTwo] + mns = [self.remoteOne, self.remoteTwo, self.remoteThree] self.advance_mocktime(30) self.send_pings(mns) self.stake(1, mns) @@ -1668,12 +1674,15 @@ 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.controller_start_masternode(self.ownerThree, self.masternodeThreeAlias) self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40) self.wait_until_mn_preenabled(self.mnTwoCollateral.hash, 40) + self.wait_until_mn_preenabled(self.mnThreeCollateral.hash, 40) self.log.info("masternodes started, waiting until both get enabled..") self.send_3_pings() - 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.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.wait_until_mn_enabled(self.mnTwoCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.wait_until_mn_enabled(self.mnThreeCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("masternodes enabled and running properly!") def advance_mocktime_and_stake(self, secs_to_add): @@ -1686,10 +1695,13 @@ def setup_3_masternodes_network(self): self.remoteOne = self.nodes[self.remoteOnePos] self.ownerTwo = self.nodes[self.ownerTwoPos] self.remoteTwo = self.nodes[self.remoteTwoPos] + self.ownerThree = self.nodes[self.ownerThreePos] + self.remoteThree = self.nodes[self.remoteThreePos] self.miner = self.nodes[self.minerPos] 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) + ownerThreeDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerThreePos) self.log.info("generating 256 blocks..") # First mine 250 PoW blocks @@ -1716,6 +1728,14 @@ def setup_3_masternodes_network(self): os.path.join(ownerTwoDir, "regtest"), self.remoteTwoPos, self.mnTwoPrivkey) + # setup third masternode node, corresponding to nodeTwo + self.mnThreeCollateral = self.setupMasternode( + self.ownerThree, + self.miner, + self.masternodeThreeAlias, + os.path.join(ownerThreeDir, "regtest"), + self.remoteThreePos, + self.mnThreePrivkey) # setup deterministic masternode self.proRegTx1, self.dmn1Privkey = self.setupDMN( self.ownerOne, @@ -1732,8 +1752,10 @@ def setup_3_masternodes_network(self): self.advance_mocktime(10) remoteOnePort = p2p_port(self.remoteOnePos) remoteTwoPort = p2p_port(self.remoteTwoPos) + remoteThreePort = p2p_port(self.remoteThreePos) self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) + self.remoteThree.initmasternode(self.mnThreePrivkey, "127.0.0.1:"+str(remoteThreePort)) self.remoteDMN1.initmasternode(self.dmn1Privkey) # wait until mnsync complete on all nodes diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index f61e7ea761e3f..0b3f15147a452 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -178,7 +178,7 @@ def submit_proposals(self, props): def run_test(self): self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) # check mn list from miner self.check_mn_list(self.miner, txHashSet) diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index 2fdc631512eb6..e5c2b3cd9d6b6 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -68,7 +68,7 @@ def wait_until_mn_expired(self, _timeout, removed=False): def run_test(self): self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() # check masternode expiration self.log.info("testing expiration now.") diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index 670696c420709..6dcf91f5b6aef 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -96,7 +96,7 @@ def check_mn_list(self, node, txHashSet): def run_test(self): self.mn_addresses = {} self.enable_mocktime() - self.setup_3_masternodes_network() + self.setup_masternodes_network() # start with 3 masternodes (2 legacy + 1 DMN) self.check_mn_enabled_count(3, 3) From 90ba29a40b38010828c206f8ea6e279c13958795 Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:55:14 -0300 Subject: [PATCH 02/11] test: decouple budget functions from governance_sync test to a budget util file --- test/functional/test_framework/budget_util.py | 130 ++++++++++++ .../tiertwo_governance_sync_basic.py | 186 ++++-------------- 2 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 test/functional/test_framework/budget_util.py diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py new file mode 100644 index 0000000000000..56c60bc414189 --- /dev/null +++ b/test/functional/test_framework/budget_util.py @@ -0,0 +1,130 @@ +from .util import ( + assert_equal, + assert_true, + satoshi_round, +) + +class Proposal: + def __init__(self, name, link, cycles, payment_addr, amount_per_cycle): + self.name = name + self.link = link + self.cycles = cycles + self.paymentAddr = payment_addr + self.amountPerCycle = amount_per_cycle + self.feeTxId = "" + self.proposalHash = "" + +def get_proposal_obj(Name, URL, Hash, FeeHash, BlockStart, BlockEnd, + TotalPaymentCount, RemainingPaymentCount, PaymentAddress, + Ratio, Yeas, Nays, Abstains, TotalPayment, MonthlyPayment, + IsEstablished, IsValid, Allotted, TotalBudgetAllotted, IsInvalidReason = ""): + obj = {} + obj["Name"] = Name + obj["URL"] = URL + obj["Hash"] = Hash + obj["FeeHash"] = FeeHash + obj["BlockStart"] = BlockStart + obj["BlockEnd"] = BlockEnd + obj["TotalPaymentCount"] = TotalPaymentCount + obj["RemainingPaymentCount"] = RemainingPaymentCount + obj["PaymentAddress"] = PaymentAddress + obj["Ratio"] = Ratio + obj["Yeas"] = Yeas + obj["Nays"] = Nays + obj["Abstains"] = Abstains + obj["TotalPayment"] = TotalPayment + obj["MonthlyPayment"] = MonthlyPayment + obj["IsEstablished"] = IsEstablished + obj["IsValid"] = IsValid + if IsInvalidReason != "": + obj["IsInvalidReason"] = IsInvalidReason + obj["Allotted"] = Allotted + obj["TotalBudgetAllotted"] = TotalBudgetAllotted + return obj + +def get_proposal(prop, block_start, alloted, total_budget_alloted, positive_votes): + blockEnd = block_start + prop.cycles * 145 + total_payment = prop.amountPerCycle * prop.cycles + return get_proposal_obj(prop.name, prop.link, prop.proposalHash, prop.feeTxId, block_start, + blockEnd, prop.cycles, prop.cycles, prop.paymentAddr, 1, + positive_votes, 0, 0, satoshi_round(total_payment), satoshi_round(prop.amountPerCycle), + True, True, satoshi_round(alloted), satoshi_round(total_budget_alloted)) + +def check_mns_status_legacy(node, txhash): + status = node.getmasternodestatus() + assert_equal(status["txhash"], txhash) + assert_equal(status["message"], "Masternode successfully started") + +def check_mns_status(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(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(nodes, votesCount, status): + for i in range(0, len(nodes)): + node = nodes[i] + budFin = node.mnfinalbudget("show") + assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) + budget = budFin[next(iter(budFin))] + assert_equal(budget["VoteCount"], votesCount) + assert_equal(budget["Status"], status) + +def check_proposal_existence(nodes, proposalName, proposalHash): + for node in nodes: + proposals = node.getbudgetinfo(proposalName) + assert(len(proposals) > 0) + assert_equal(proposals[0]["Hash"], proposalHash) + +def check_vote_existence(nodes, proposalName, mnCollateralHash, voteType, voteValid): + for i in range(0, len(nodes)): + node = nodes[i] + node.syncwithvalidationinterfacequeue() + votesInfo = node.getbudgetvotes(proposalName) + assert(len(votesInfo) > 0) + found = False + 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)) + +def check_budgetprojection(nodes, expected, log): + for i in range(len(nodes)): + assert_equal(nodes[i].getbudgetprojection(), expected) + log.info("Budget projection valid for node %d" % i) + +def create_proposals_tx(miner, props): + nextSuperBlockHeight = miner.getnextsuperblock() + for entry in props: + proposalFeeTxId = miner.preparebudget( + entry.name, + entry.link, + entry.cycles, + nextSuperBlockHeight, + entry.paymentAddr, + entry.amountPerCycle) + entry.feeTxId = proposalFeeTxId + return props + +def propagate_proposals(miner, props): + nextSuperBlockHeight = miner.getnextsuperblock() + for entry in props: + proposalHash = miner.submitbudget( + entry.name, + entry.link, + entry.cycles, + nextSuperBlockHeight, + entry.paymentAddr, + entry.amountPerCycle, + entry.feeTxId) + entry.proposalHash = proposalHash + return props diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 0b3f15147a452..1bcb9ef3adaf4 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -15,9 +15,21 @@ from test_framework.messages import COutPoint from test_framework.test_framework import PivxTier2TestFramework +from test_framework.budget_util import ( + check_budget_finalization_sync, + create_proposals_tx, + check_budgetprojection, + check_proposal_existence, + check_mn_list, + check_mns_status_legacy, + check_mns_status, + check_vote_existence, + get_proposal_obj, + Proposal, + propagate_proposals +) from test_framework.util import ( assert_equal, - assert_true, connect_nodes, get_datadir_path, satoshi_round @@ -25,45 +37,8 @@ import shutil import os -class Proposal: - def __init__(self, name, link, cycles, payment_addr, amount_per_cycle): - self.name = name - self.link = link - self.cycles = cycles - self.paymentAddr = payment_addr - self.amountPerCycle = amount_per_cycle - self.feeTxId = "" - self.proposalHash = "" - 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_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] - budFin = node.mnfinalbudget("show") - assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) - budget = budFin[next(iter(budFin))] - assert_equal(budget["VoteCount"], votesCount) - assert_equal(budget["Status"], status) - def broadcastbudgetfinalization(self, node, with_ping_mns=[]): self.log.info("suggesting the budget finalization..") assert (node.mnfinalbudgetsuggest() is not None) @@ -75,92 +50,12 @@ def broadcastbudgetfinalization(self, node, with_ping_mns=[]): self.log.info("broadcasting the budget finalization..") return node.mnfinalbudgetsuggest() - def check_proposal_existence(self, proposalName, proposalHash): - for node in self.nodes: - proposals = node.getbudgetinfo(proposalName) - assert(len(proposals) > 0) - assert_equal(proposals[0]["Hash"], proposalHash) - - def check_vote_existence(self, proposalName, mnCollateralHash, voteType, voteValid): - for i in range(0, len(self.nodes)): - node = self.nodes[i] - node.syncwithvalidationinterfacequeue() - votesInfo = node.getbudgetvotes(proposalName) - assert(len(votesInfo) > 0) - found = False - 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)) - - def get_proposal_obj(self, Name, URL, Hash, FeeHash, BlockStart, BlockEnd, - TotalPaymentCount, RemainingPaymentCount, PaymentAddress, - Ratio, Yeas, Nays, Abstains, TotalPayment, MonthlyPayment, - IsEstablished, IsValid, Allotted, TotalBudgetAllotted, IsInvalidReason = ""): - obj = {} - obj["Name"] = Name - obj["URL"] = URL - obj["Hash"] = Hash - obj["FeeHash"] = FeeHash - obj["BlockStart"] = BlockStart - obj["BlockEnd"] = BlockEnd - obj["TotalPaymentCount"] = TotalPaymentCount - obj["RemainingPaymentCount"] = RemainingPaymentCount - obj["PaymentAddress"] = PaymentAddress - obj["Ratio"] = Ratio - obj["Yeas"] = Yeas - obj["Nays"] = Nays - obj["Abstains"] = Abstains - obj["TotalPayment"] = TotalPayment - obj["MonthlyPayment"] = MonthlyPayment - obj["IsEstablished"] = IsEstablished - obj["IsValid"] = IsValid - if IsInvalidReason != "": - obj["IsInvalidReason"] = IsInvalidReason - obj["Allotted"] = Allotted - obj["TotalBudgetAllotted"] = TotalBudgetAllotted - return obj - - def check_budgetprojection(self, expected): - for i in range(self.num_nodes): - assert_equal(self.nodes[i].getbudgetprojection(), expected) - self.log.info("Budget projection valid for node %d" % i) - def connect_nodes_bi(self, nodes, a, b): connect_nodes(nodes[a], b) connect_nodes(nodes[b], a) - def create_proposals_tx(self, props): - nextSuperBlockHeight = self.miner.getnextsuperblock() - for entry in props: - proposalFeeTxId = self.miner.preparebudget( - entry.name, - entry.link, - entry.cycles, - nextSuperBlockHeight, - entry.paymentAddr, - entry.amountPerCycle) - entry.feeTxId = proposalFeeTxId - return props - - def propagate_proposals(self, props): - nextSuperBlockHeight = self.miner.getnextsuperblock() - for entry in props: - proposalHash = self.miner.submitbudget( - entry.name, - entry.link, - entry.cycles, - nextSuperBlockHeight, - entry.paymentAddr, - entry.amountPerCycle, - entry.feeTxId) - entry.proposalHash = proposalHash - return props - def submit_proposals(self, props): - props = self.create_proposals_tx(props) + props = create_proposals_tx(self.miner, props) # generate 3 blocks to confirm the tx (and update the mnping) self.stake(3, [self.remoteOne, self.remoteTwo]) # check fee tx existence @@ -168,11 +63,11 @@ def submit_proposals(self, props): txinfo = self.miner.gettransaction(entry.feeTxId) assert_equal(txinfo['amount'], -50.00) # propagate proposals - props = self.propagate_proposals(props) + props = propagate_proposals(self.miner, props) # let's wait a little bit and see if all nodes are sync time.sleep(1) for entry in props: - self.check_proposal_existence(entry.name, entry.proposalHash) + check_proposal_existence(self.nodes, entry.name, entry.proposalHash) self.log.info("proposal %s broadcast successful!" % entry.name) return props @@ -181,14 +76,14 @@ def run_test(self): self.setup_masternodes_network() txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) # check mn list from miner - self.check_mn_list(self.miner, txHashSet) + check_mn_list(self.miner, txHashSet) # check status of masternodes - self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) + check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) self.log.info("MN1 active") - self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) + check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") - self.check_mns_status(self.remoteDMN1, self.proRegTx1) + check_mns_status(self.remoteDMN1, self.proRegTx1) self.log.info("DMN1 active") # activate sporks @@ -232,7 +127,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, MN1 vote accepted everywhere!") # before broadcast the second vote, let's drop the budget data of ownerOne. @@ -250,13 +145,13 @@ def run_test(self): # check orphan vote proposal re-sync self.log.info("checking orphan vote based proposal re-sync...") time.sleep(5) # wait a bit before check it - self.check_proposal_existence(firstProposal.name, firstProposal.proposalHash) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_proposal_existence(self.nodes, firstProposal.name, firstProposal.proposalHash) + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, orphan vote based proposal re-sync succeeded") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.mnTwoCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, 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 @@ -266,7 +161,7 @@ def run_test(self): # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) - self.check_vote_existence(firstProposal.name, self.proRegTx1, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget @@ -275,13 +170,12 @@ def run_test(self): TotalPayment = firstProposal.amountPerCycle * firstProposal.cycles Allotted = firstProposal.amountPerCycle RemainingPaymentCount = firstProposal.cycles - expected_budget = [ - self.get_proposal_obj(firstProposal.name, firstProposal.link, firstProposal.proposalHash, firstProposal.feeTxId, blockStart, + expected_budget = [get_proposal_obj(firstProposal.name, firstProposal.link, firstProposal.proposalHash, firstProposal.feeTxId, blockStart, blockEnd, firstProposal.cycles, RemainingPaymentCount, firstProposal.paymentAddr, 1, 3, 0, 0, satoshi_round(TotalPayment), satoshi_round(firstProposal.amountPerCycle), True, True, satoshi_round(Allotted), satoshi_round(Allotted)) ] - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) # Quick block count check. assert_equal(self.ownerOne.getblockcount(), 279) @@ -299,7 +193,7 @@ def run_test(self): time.sleep(2) self.log.info("checking budget finalization sync..") - self.check_budget_finalization_sync(0, "OK") + check_budget_finalization_sync(self.nodes, 0, "OK") self.log.info("budget finalization synced!, now voting for the budget finalization..") # Connecting owner to all the other nodes. @@ -309,7 +203,7 @@ def run_test(self): assert_equal(voteResult["detail"][0]["result"], "success") time.sleep(2) # wait a bit self.stake(2, [self.remoteOne, self.remoteTwo]) - self.check_budget_finalization_sync(1, "OK") + check_budget_finalization_sync(self.nodes, 1, "OK") self.log.info("Remote One voted successfully.") # before broadcast the second finalization vote, let's drop the budget data of remoteOne. @@ -330,7 +224,7 @@ def run_test(self): self.stake(2, [self.remoteOne, self.remoteTwo]) self.log.info("checking finalization votes..") - self.check_budget_finalization_sync(3, "OK") + check_budget_finalization_sync(self.nodes, 3, "OK") self.log.info("orphan vote based finalization re-sync succeeded") self.stake(6, [self.remoteOne, self.remoteTwo]) @@ -341,7 +235,7 @@ def run_test(self): # Check that the proposal info returns updated payment count expected_budget[0]["RemainingPaymentCount"] -= 1 - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) self.stake(1, [self.remoteOne, self.remoteTwo]) @@ -354,7 +248,7 @@ def run_test(self): self.log.info("budget cleaned, starting resync") self.wait_until_mnsync_finished() - self.check_budgetprojection(expected_budget) + check_budgetprojection(self.nodes, expected_budget, self.log) for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 16) @@ -393,10 +287,10 @@ def run_test(self): self.log.info("Checking budget sync..") for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 16) - self.check_vote_existence(firstProposal.name, self.mnOneCollateral.hash, "YES", True) - self.check_vote_existence(firstProposal.name, self.mnTwoCollateral.hash, "YES", True) - self.check_vote_existence(firstProposal.name, self.proRegTx1, "YES", True) - self.check_budget_finalization_sync(3, "OK") + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.mnTwoCollateral.hash, "YES", True) + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) + check_budget_finalization_sync(self.nodes, 3, "OK") self.log.info("Remote incremental sync succeeded") # now let's verify that votes expire properly. @@ -406,8 +300,8 @@ def run_test(self): self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 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(firstProposal.name, self.mnOneCollateral.hash, "YES", False) - self.check_budget_finalization_sync(2, "OK") # budget finalization vote removal + check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", False) + check_budget_finalization_sync(self.nodes, 2, "OK") # budget finalization vote removal self.log.info("MN1 vote expired after collateral spend, all good") self.log.info("expiring DMN1..") @@ -416,8 +310,8 @@ def run_test(self): 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(firstProposal.name, self.proRegTx1, "YES", False) - self.check_budget_finalization_sync(1, "OK") # budget finalization vote removal + check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", False) + check_budget_finalization_sync(self.nodes, 1, "OK") # budget finalization vote removal self.log.info("DMN vote expired after collateral spend, all good") # Check that the budget is removed 200 blocks after the last payment From 88200af07701eb6d659361acd81878bf61e31e0a Mon Sep 17 00:00:00 2001 From: furszy Date: Sun, 27 Feb 2022 19:55:39 -0300 Subject: [PATCH 03/11] test: implement single block payments budget test --- test/functional/test_framework/budget_util.py | 3 +- .../test_framework/test_framework.py | 30 +- test/functional/tiertwo_budget.py | 290 ++++++++++++++++++ 3 files changed, 310 insertions(+), 13 deletions(-) create mode 100755 test/functional/tiertwo_budget.py diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py index 56c60bc414189..52e95cb183cf9 100644 --- a/test/functional/test_framework/budget_util.py +++ b/test/functional/test_framework/budget_util.py @@ -1,5 +1,6 @@ from .util import ( assert_equal, + assert_greater_than_or_equal, assert_true, satoshi_round, ) @@ -72,7 +73,7 @@ def check_budget_finalization_sync(nodes, votesCount, status): for i in range(0, len(nodes)): node = nodes[i] budFin = node.mnfinalbudget("show") - assert_true(len(budFin) == 1, "MN budget finalization not synced in node" + str(i)) + assert_greater_than_or_equal(len(budFin), 1) budget = budFin[next(iter(budFin))] assert_equal(budget["VoteCount"], votesCount) assert_equal(budget["Status"], status) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index bf946c4417dcc..fc8eee88c56c4 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1618,7 +1618,11 @@ def mine_quorum(self, invalidate_func=None, invalidated_idx=None, skip_bad_membe # !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): - def set_test_params(self, v6_enforcement_height = 250): + def __init__(self): + super().__init__() + self.v6_enforcement_height = 250 + + def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 8 self.enable_mocktime() @@ -1631,7 +1635,7 @@ def set_test_params(self, v6_enforcement_height = 250): self.remoteDMN1Pos = 5 self.ownerThreePos = 6 self.remoteThreePos = 7 - self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(self.v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes 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") @@ -1644,7 +1648,7 @@ def set_test_params(self, v6_enforcement_height = 250): self.mnTwoPrivkey = "92Hkebp3RHdDidGZ7ARgS4orxJAGyFUPDXNqtsYsiwho1HGVRbF" self.mnThreePrivkey = "91qP855JNR3aWv6Z71BcFjqhkeizchSDjKSi7BdqMSSirEVDTEk" - # Updated in setup_3_masternodes_network() to be called at the start of run_test + # Updated in setup_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] @@ -1690,7 +1694,7 @@ def advance_mocktime_and_stake(self, secs_to_add): self.mocktime = self.generate_pos(self.minerPos, self.mocktime) time.sleep(2) - def setup_3_masternodes_network(self): + def setup_masternodes_network(self, setup_dmn=True): self.ownerOne = self.nodes[self.ownerOnePos] self.remoteOne = self.nodes[self.remoteOnePos] self.ownerTwo = self.nodes[self.ownerTwoPos] @@ -1736,13 +1740,14 @@ def setup_3_masternodes_network(self): os.path.join(ownerThreeDir, "regtest"), self.remoteThreePos, self.mnThreePrivkey) - # setup deterministic masternode - self.proRegTx1, self.dmn1Privkey = self.setupDMN( - self.ownerOne, - self.miner, - self.remoteDMN1Pos, - "fund" - ) + if setup_dmn: + # setup deterministic masternode + self.proRegTx1, self.dmn1Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN1Pos, + "fund" + ) self.log.info("masternodes setup completed, initializing them..") @@ -1756,7 +1761,8 @@ def setup_3_masternodes_network(self): self.remoteOne.initmasternode(self.mnOnePrivkey, "127.0.0.1:"+str(remoteOnePort)) self.remoteTwo.initmasternode(self.mnTwoPrivkey, "127.0.0.1:"+str(remoteTwoPort)) self.remoteThree.initmasternode(self.mnThreePrivkey, "127.0.0.1:"+str(remoteThreePort)) - self.remoteDMN1.initmasternode(self.dmn1Privkey) + if setup_dmn: + self.remoteDMN1.initmasternode(self.dmn1Privkey) # wait until mnsync complete on all nodes self.stake(1) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py new file mode 100755 index 0000000000000..133e6919f7e50 --- /dev/null +++ b/test/functional/tiertwo_budget.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +""" +Test checking: + 1) pre-v6 multi-block payments + 2) different payment ordering validation (proposals paid in different order) + 3) duplicated payments validation --> wrong path + 4) post-v6 single block payments + 5) single block payments ordering validation (proposals paid in different order) + 6) duplicated payments validation --> wrong path +""" + +import time + +from test_framework.test_framework import PivxTier2TestFramework +from test_framework.util import ( + assert_equal +) +from test_framework.budget_util import ( + check_budget_finalization_sync, + create_proposals_tx, + check_budgetprojection, + check_proposal_existence, + check_mn_list, + check_mns_status_legacy, + check_vote_existence, + get_proposal, + Proposal, + propagate_proposals +) + +class BudgetTest(PivxTier2TestFramework): + + def set_test_params(self): + self.v6_enforcement_height = 300 + super().set_test_params() + + def broadcastbudgetfinalization(self, node, with_ping_mns=[]): + self.log.info("suggesting the budget finalization..") + assert (node.mnfinalbudgetsuggest() is not None) + + self.log.info("confirming the budget finalization..") + time.sleep(1) + self.stake(4, with_ping_mns) + + self.log.info("broadcasting the budget finalization..") + return node.mnfinalbudgetsuggest() + + def submit_proposals(self, props): + props = create_proposals_tx(self.miner, props) + # generate 3 blocks to confirm the tx (and update the mnping) + self.stake(3, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # check fee tx existence + for entry in props: + txinfo = self.miner.gettransaction(entry.feeTxId) + assert_equal(txinfo['amount'], -50.00) + # propagate proposals + props = propagate_proposals(self.miner, props) + # let's wait a little bit and see if all nodes are sync + time.sleep(1) + for entry in props: + check_proposal_existence(self.nodes, entry.name, entry.proposalHash) + self.log.info("proposal %s broadcast successful!" % entry.name) + return props + + def vote_legacy(self, node_voter, proposal, vote_direction, mn_voter_alias): + self.log.info("Voting with " + mn_voter_alias + ", for: " + proposal.name) + voteResult = node_voter.mnbudgetvote("alias", proposal.proposalHash, vote_direction, mn_voter_alias, True) + assert_equal(voteResult["detail"][0]["result"], "success") + time.sleep(1) + + def vote(self, node_voter, proposal, vote_direction, pro_reg_tx): + self.log.info("Voting with DMN " + pro_reg_tx + ", for: " + proposal.name) + voteResult = node_voter.mnbudgetvote("alias", proposal.proposalHash, vote_direction, pro_reg_tx) + assert_equal(voteResult["detail"][0]["result"], "success") + time.sleep(1) + + def vote_finalization(self, voting_node, budget_fin_hash, legacy): + voteResult = voting_node.mnfinalbudget("vote-many" if legacy else "vote", budget_fin_hash, legacy) + assert_equal(voteResult["detail"][0]["result"], "success") + + def check_address_balance(self, addr, expected_balance, has_balance=True): + addrInfo = self.miner.listreceivedbyaddress(0, False, False, addr) + if has_balance: + assert_equal(addrInfo[0]["amount"], expected_balance) + else: + assert_equal(len(addrInfo), 0) + + def check_block_proposal_payment(self, block_hash, expected_to_address, expected_to_value, expected_out_index, is_v6_active): + block = self.miner.getblock(block_hash) + if is_v6_active: + # Get the coinbase second output that is the proposal payment + coinbase_tx = self.miner.getrawtransaction(block["tx"][0], True) + proposal_out = coinbase_tx["vout"][expected_out_index] + assert_equal(proposal_out["value"], expected_to_value) + assert_equal(proposal_out["scriptPubKey"]["addresses"][0], expected_to_address) + else: + # Get the coinstake third output + coinstake_tx = self.miner.getrawtransaction(block["tx"][1], True) + proposal_out = coinstake_tx["vout"][expected_out_index] + assert_equal(proposal_out["value"], expected_to_value) + assert_equal(proposal_out["scriptPubKey"]["addresses"][0], expected_to_address) + + def finalize_and_vote_budget(self): + # suggest the budget finalization and confirm the tx (+4 blocks). + budgetFinHash = self.broadcastbudgetfinalization(self.miner, + with_ping_mns=[self.remoteOne, self.remoteTwo, self.remoteThree]) + assert (budgetFinHash != "") + time.sleep(2) + print(budgetFinHash) + self.log.info("voting budget finalization..") + for node in [self.ownerOne, self.ownerTwo, self.ownerThree]: + self.vote_finalization(node, budgetFinHash, True) + time.sleep(2) # wait a bit + check_budget_finalization_sync(self.nodes, 3, "OK") + + def run_test(self): + self.enable_mocktime() + self.setup_masternodes_network(setup_dmn=False) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.mnThreeCollateral.hash]) + # check mn list from miner + check_mn_list(self.miner, txHashSet) + + # check status of masternodes + check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) + check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) + check_mns_status_legacy(self.remoteThree, self.mnThreeCollateral.hash) + + # activate sporks + self.activate_spork(self.minerPos, "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT") + self.activate_spork(self.minerPos, "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT") + self.activate_spork(self.minerPos, "SPORK_13_ENABLE_SUPERBLOCKS") + nextSuperBlockHeight = self.miner.getnextsuperblock() + + # Submit first proposal + self.log.info("preparing budget proposal..") + # Create 15 more proposals to have a higher tier two net gossip movement + props = [] + for i in range(16): + props.append(Proposal("prop_"+str(i), + "https://link_"+str(i)+".com", + 3, + self.miner.getnewaddress(), + 11 * (i + 1))) + self.submit_proposals(props) + + # Proposals are established after 5 minutes. Mine 7 blocks + # Proposal needs to be on the chain > 5 min. + self.stake(7, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # Check proposals existence + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].getbudgetinfo()), 16) + + # now let's vote for the two first proposals + expected_budget = [] + blockStart = nextSuperBlockHeight + alloted = 0 + for i in range(2): + prop = props[i] + self.vote_legacy(self.ownerOne, prop, "yes", self.masternodeOneAlias) + check_vote_existence(self.nodes, prop.name, self.mnOneCollateral.hash, "YES", True) + self.vote_legacy(self.ownerTwo, prop, "yes", self.masternodeTwoAlias) + check_vote_existence(self.nodes, prop.name, self.mnTwoCollateral.hash, "YES", True) + if i < 1: + self.vote_legacy(self.ownerThree, prop, "yes", self.masternodeThreeAlias) + check_vote_existence(self.nodes, prop.name, self.mnThreeCollateral.hash, "YES", True) + alloted += prop.amountPerCycle + expected_budget.append(get_proposal(prop, blockStart, prop.amountPerCycle, alloted, 3 - i)) + + # Now check the budget + check_budgetprojection(self.nodes, expected_budget, self.log) + + # Quick block count check. + assert_equal(self.ownerOne.getblockcount(), 272) + self.stake(10, [self.remoteOne, self.remoteTwo, self.remoteThree]) + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Check first proposal payment + prop1 = props[0] + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop1.paymentAddr, prop1.amountPerCycle, 2, False) + self.check_address_balance(prop1.paymentAddr, prop1.amountPerCycle) + + # Check second proposal payment + prop2 = props[1] + assert prop2.paymentAddr is not prop1.paymentAddr + self.check_address_balance(prop2.paymentAddr, 0, has_balance=False) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop2.paymentAddr, prop2.amountPerCycle, 2, False) + self.check_address_balance(prop2.paymentAddr, prop2.amountPerCycle) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + self.log.info("pre-v6 budget proposal paid, all good. Testing enforcement now..") + + ################################################################## + # Now test post enforcement, active from block 300 + for _ in range(4): + self.miner.generate(30) + self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + next_super_block = self.miner.getnextsuperblock() + block_count = self.miner.getblockcount() + self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) + assert_equal(self.ownerOne.getblockcount(), 426) + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("checking single block payments..") + assert_equal(self.ownerOne.getblockcount(), 432) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop1.paymentAddr, prop1.amountPerCycle, 1, True) + self.check_block_proposal_payment(self.miner.getbestblockhash(), prop2.paymentAddr, prop2.amountPerCycle, 2, True) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("post-v6 budget proposal paid, all good.") + + ################################################################## + self.log.info("Now test proposal with duplicate script and value") + + self.proRegTx1, self.dmn1Privkey = self.setupDMN( + self.ownerOne, + self.miner, + self.remoteDMN1Pos, + "fund" + ) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + time.sleep(3) + self.advance_mocktime(10) + self.remoteDMN1.initmasternode(self.dmn1Privkey) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Now test creating a new proposal paying to the same script and value as prop1 + blockStart = self.miner.getnextsuperblock() + prop17 = Proposal("prop_17", "https://link_"+str(17)+".com", 2, prop1.paymentAddr, 11) + self.submit_proposals([prop17]) + self.stake(5, [self.remoteOne, self.remoteTwo, self.remoteThree]) + for i in range(self.num_nodes): + assert_equal(len(self.nodes[i].getbudgetinfo()), 17) + # vote prop17 + self.vote_legacy(self.ownerOne, prop17, "yes", self.masternodeOneAlias) + check_vote_existence(self.nodes, prop17.name, self.mnOneCollateral.hash, "YES", True) + self.vote_legacy(self.ownerTwo, prop17, "yes", self.masternodeTwoAlias) + check_vote_existence(self.nodes, prop17.name, self.mnTwoCollateral.hash, "YES", True) + self.vote_legacy(self.ownerThree, prop17, "yes", self.masternodeThreeAlias) + check_vote_existence(self.nodes, prop17.name, self.mnThreeCollateral.hash, "YES", True) + self.vote(self.ownerOne, prop17, "yes", self.proRegTx1) + check_vote_existence(self.nodes, prop17.name, self.proRegTx1, "YES", True) + + alloted += prop17.amountPerCycle + expected_budget.insert(0, get_proposal(prop17, blockStart, prop17.amountPerCycle, prop17.amountPerCycle, 4)) + expected_budget[1]["TotalBudgetAllotted"] = Decimal('22.00000000') + expected_budget[2]["TotalBudgetAllotted"] = Decimal('44.00000000') + # Now check the budget + check_budgetprojection(self.nodes, expected_budget, self.log) + + for _ in range(4): + self.miner.generate(30) + self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + next_super_block = self.miner.getnextsuperblock() + block_count = self.miner.getblockcount() + self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) + assert_equal(self.ownerOne.getblockcount(), 570) + + # Finalize budget + self.finalize_and_vote_budget() + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.check_address_balance(prop1.paymentAddr, prop1.amountPerCycle * 4) # prop1.amountPerCycle * 3 + prop17.amountPerCycle + self.check_address_balance(prop2.paymentAddr, prop2.amountPerCycle * 3) + + # Check that the proposal info returns updated payment count + expected_budget[0]["RemainingPaymentCount"] -= 1 + expected_budget[1]["RemainingPaymentCount"] -= 1 + expected_budget[2]["RemainingPaymentCount"] -= 1 + check_budgetprojection(self.nodes, expected_budget, self.log) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + self.log.info("post-v6 duplicate proposals payouts paid.") + + +if __name__ == '__main__': + BudgetTest().main() From 19353f7003d47adb54a8b95cb9ed91f169c423e7 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 3 Mar 2022 10:35:27 -0300 Subject: [PATCH 04/11] test: post-v6 enforcement, check coinstake output value --- test/functional/tiertwo_budget.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index 133e6919f7e50..28e196e5e5df4 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -14,6 +14,7 @@ import time +from decimal import Decimal from test_framework.test_framework import PivxTier2TestFramework from test_framework.util import ( assert_equal @@ -30,6 +31,9 @@ Proposal, propagate_proposals ) +from test_framework.messages import ( + COIN +) class BudgetTest(PivxTier2TestFramework): @@ -201,10 +205,28 @@ def run_test(self): self.log.info("pre-v6 budget proposal paid, all good. Testing enforcement now..") ################################################################## + self.log.info("checking post enforcement coinstake value..") # Now test post enforcement, active from block 300 for _ in range(4): self.miner.generate(30) self.stake_and_ping(self.minerPos, 1, [self.remoteOne, self.remoteTwo, self.remoteThree]) + + # Post-v6 enforcement + # Get the coinstake and check that the input value is equal to + # the output value + block reward - MN payment. + BLOCK_REWARD = Decimal(250 * COIN) + MN_BLOCK_REWARD = Decimal(3 * COIN) + tx_coinstake_id = self.miner.getblock(self.miner.getbestblockhash(), True)["tx"][1] + tx_coinstake = self.miner.getrawtransaction(tx_coinstake_id, True) + tx_coinstake_out_value = Decimal(tx_coinstake["vout"][1]["value"]) * COIN + tx_coinstake_vin = tx_coinstake["vin"][0] + tx_coinstake_input = self.miner.getrawtransaction(tx_coinstake_vin["txid"], True) + tx_coinstake_input_value = Decimal(tx_coinstake_input["vout"][int(tx_coinstake_vin["vout"])]["value"]) * COIN + assert(tx_coinstake_out_value == tx_coinstake_input_value + BLOCK_REWARD - MN_BLOCK_REWARD) + + ############################################################## + # Check single block payments + self.log.info("mining until next superblock..") next_super_block = self.miner.getnextsuperblock() block_count = self.miner.getblockcount() self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) From 472071e4b2e49c821d9ef457d5d4595ad5c66d4e Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 3 Mar 2022 22:44:10 +0100 Subject: [PATCH 05/11] Chainparams: implement IsSuperBlock function --- src/budget/budgetproposal.cpp | 4 ++-- src/budget/budgetutil.cpp | 2 +- src/budget/finalizedbudget.cpp | 2 +- src/consensus/params.h | 1 + test/functional/tiertwo_budget.py | 6 +++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/budget/budgetproposal.cpp b/src/budget/budgetproposal.cpp index 08fa76a00774f..255a0196394a7 100644 --- a/src/budget/budgetproposal.cpp +++ b/src/budget/budgetproposal.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "budget/budgetproposal.h" + #include "chainparams.h" #include "script/standard.h" @@ -85,8 +86,7 @@ bool CBudgetProposal::IsHeavilyDownvoted(int mnCount) bool CBudgetProposal::CheckStartEnd() { // block start must be a superblock - if (nBlockStart < 0 || - nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) { + if (nBlockStart < 0 || !Params().GetConsensus().IsSuperBlock(nBlockStart)) { strInvalid = "Invalid nBlockStart"; return false; } diff --git a/src/budget/budgetutil.cpp b/src/budget/budgetutil.cpp index ec9ca8aa78b88..52aec9c35cb59 100644 --- a/src/budget/budgetutil.cpp +++ b/src/budget/budgetutil.cpp @@ -269,4 +269,4 @@ UniValue mnLocalBudgetVoteInner(bool fLegacyMN, const uint256& budgetHash, bool return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, 0) : voteProposal(budgetHash, nVote, mnKeys, resultsObj, 0)); -} \ No newline at end of file +} diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 52091a4fce817..429ccf5e6d87d 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -187,7 +187,7 @@ bool CFinalizedBudget::CheckStartEnd() } // Must be the correct block for payment to happen (once a month) - if (nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) { + if (!Params().GetConsensus().IsSuperBlock(nBlockStart)) { strInvalid = "Invalid BlockStart"; return false; } diff --git a/src/consensus/params.h b/src/consensus/params.h index 13c1acd490cd8..3a6ca4247d624 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -241,6 +241,7 @@ struct Params { return (contextHeight - utxoFromBlockHeight >= nStakeMinDepth); } + bool IsSuperBlock(int height) const { return height % nBudgetCycleBlocks == 0; } /* * (Legacy) Zerocoin consensus params diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index 28e196e5e5df4..c6393ed57aa50 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -86,7 +86,7 @@ def vote_finalization(self, voting_node, budget_fin_hash, legacy): assert_equal(voteResult["detail"][0]["result"], "success") def check_address_balance(self, addr, expected_balance, has_balance=True): - addrInfo = self.miner.listreceivedbyaddress(0, False, False, addr) + addrInfo = self.nodes[self.ownerOnePos].listreceivedbyaddress(0, False, False, addr) if has_balance: assert_equal(addrInfo[0]["amount"], expected_balance) else: @@ -145,8 +145,8 @@ def run_test(self): for i in range(16): props.append(Proposal("prop_"+str(i), "https://link_"+str(i)+".com", - 3, - self.miner.getnewaddress(), + 4, + self.nodes[self.ownerOnePos].getnewaddress(), 11 * (i + 1))) self.submit_proposals(props) From 3d4d88bc9ed380eed4bd7a5cd323b80d6d32daff Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 10:18:33 +0100 Subject: [PATCH 06/11] Refactor: decouple IsBlockPayeeValid/IsBlockValueValid from their legacy implementation --- src/budget/budgetmanager.cpp | 16 ++++++++++ src/budget/budgetmanager.h | 5 +-- src/masternode-payments.cpp | 59 +++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index c6e3719161c3f..1cc0ed3e603fe 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -509,6 +509,22 @@ bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet); } +CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +{ + if (!Params().GetConsensus().IsSuperBlock(chainHeight)) { + return 0; + } + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(chainHeight); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. + return 0; + } + return pfb->GetTotalPayout(); +} + bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const { if (nHeight <= 0) return false; diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index 200ad20718080..a784777fcd1d8 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -141,8 +141,9 @@ class CBudgetManager : public CValidationInterface std::vector GetAllProposalsOrdered(); std::vector GetFinalizedBudgets(); bool GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet) const; - bool IsBudgetPaymentBlock(int nBlockHeight) const; - bool IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const; + CAmount GetFinalizedBudgetTotalPayout(int chainHeight) const; + bool IsBudgetPaymentBlock(int nBlockHeight) const; // legacy (multiple SB) + bool IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const; // legacy (multiple SB) bool AddProposal(CBudgetProposal& budgetProposal); bool AddFinalizedBudget(CFinalizedBudget& finalizedBudget, CNode* pfrom = nullptr); void ForceAddFinalizedBudget(const uint256& nHash, const uint256& feeTxId, const CFinalizedBudget& finalizedBudget); diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index e99b22e4e4316..7bbc07e83ed3a 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -194,7 +194,7 @@ void DumpMasternodePayments() LogPrint(BCLog::MASTERNODE,"Budget dump finished %dms\n", GetTimeMillis() - nStart); } -bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) +static bool IsBlockValueValid_legacy(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) { const Consensus::Params& consensus = Params().GetConsensus(); if (!g_tiertwo_sync_state.IsSynced()) { @@ -224,9 +224,31 @@ bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CA return nMinted <= nExpectedValue; } -bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) +bool IsBlockValueValid(int nHeight, CAmount& nExpectedValue, CAmount nMinted, CAmount& nBudgetAmt) +{ + const Consensus::Params& consensus = Params().GetConsensus(); + if (!consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0)) { + return IsBlockValueValid_legacy(nHeight, nExpectedValue, nMinted, nBudgetAmt); + } + + if (consensus.IsSuperBlock(nHeight)) { + if (!g_tiertwo_sync_state.IsSynced()) { + // there is no budget data to use to check anything + nExpectedValue += g_budgetman.GetTotalBudget(nHeight); + } else if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + // we're synced and the superblock spork is enabled + nBudgetAmt = g_budgetman.GetFinalizedBudgetTotalPayout(nHeight); + nExpectedValue += nBudgetAmt; + } + } + + return nMinted >= 0 && nMinted <= nExpectedValue; +} + +static bool IsBlockPayeeValid_legacy(const CBlock& block, const CBlockIndex* pindexPrev) { int nBlockHeight = pindexPrev->nHeight + 1; + assert(!Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0)); TrxValidationStatus transactionStatus = TrxValidationStatus::InValid; if (!g_tiertwo_sync_state.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain @@ -234,8 +256,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } - const bool fPayCoinstake = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS) && - !Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0); + const bool fPayCoinstake = Params().GetConsensus().NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_POS); const CTransaction& txNew = *(fPayCoinstake ? block.vtx[1] : block.vtx[0]); //check if it's a budget block @@ -272,6 +293,36 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } +bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) +{ + const Consensus::Params& consensus = Params().GetConsensus(); + int nBlockHeight = pindexPrev->nHeight + 1; + if (!consensus.NetworkUpgradeActive(nBlockHeight, Consensus::UPGRADE_V6_0)) { + return IsBlockPayeeValid_legacy(block, pindexPrev); + } + + if (!g_tiertwo_sync_state.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain + // !TODO: after transition to v6, restrict this to budget-checks only + LogPrint(BCLog::MASTERNODE, "Client not synced, skipping block payee checks\n"); + return true; + } + + const CTransaction& coinbase_tx = *block.vtx[0]; + + // Check masternode payment + if (sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) && + !masternodePayments.IsTransactionValid(coinbase_tx, pindexPrev)) { + LogPrint(BCLog::MASTERNODE, "Missing required masternode payment\n"); + return false; + } + + // Check budget payments during superblocks + if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && consensus.IsSuperBlock(nBlockHeight)) { + // !TODO... + } + + return true; +} void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) { From f047f46094ecc42ff3a941168c019f0108c19d82 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 11:45:47 +0100 Subject: [PATCH 07/11] Budget: Add function to check if ALL budgets are paid in a tx --- src/budget/budgetmanager.cpp | 15 +++++++++++++++ src/budget/budgetmanager.h | 4 +++- src/budget/finalizedbudget.cpp | 33 +++++++++++++++++++++++++++++++++ src/budget/finalizedbudget.h | 3 +++ src/masternode-payments.cpp | 2 +- 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 1cc0ed3e603fe..d53f1d6ec7e1e 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -757,6 +757,21 @@ TrxValidationStatus CBudgetManager::IsTransactionValid(const CTransaction& txNew return fThreshold ? TrxValidationStatus::InValid : TrxValidationStatus::VoteThreshold; } +bool CBudgetManager::IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const +{ + assert(Params().GetConsensus().IsSuperBlock(nBlockHeight)); + + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(nBlockHeight); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. Nothing to check. + return true; + } + return pfb->AllBudgetsPaid(txNew); +} + std::vector CBudgetManager::GetAllProposalsOrdered() { LOCK(cs_proposals); diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index a784777fcd1d8..de8549acd0884 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -151,7 +151,9 @@ class CBudgetManager : public CValidationInterface bool UpdateProposal(const CBudgetVote& vote, CNode* pfrom, std::string& strError); bool UpdateFinalizedBudget(const CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError); - TrxValidationStatus IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const; + TrxValidationStatus IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const; // legacy (multiple SB) + bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB + std::string GetRequiredPaymentsString(int nBlockHeight); bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 429ccf5e6d87d..2f90243fc4aad 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -6,6 +6,7 @@ #include "budget/finalizedbudget.h" #include "masternodeman.h" +#include "utilmoneystr.h" #include "validation.h" CFinalizedBudget::CFinalizedBudget() : @@ -389,6 +390,38 @@ bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, C return true; } +bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const +{ + // make a map for faster lookup and deal with duplicate payees + struct cmp { + bool operator()(const CTxOut& a, const CTxOut& b) const { + return a.scriptPubKey < b.scriptPubKey || + (a.scriptPubKey == b.scriptPubKey && a.nValue < b.nValue); + } + }; + std::map txouts; + for (const CTxOut& o: tx.vout) { + txouts[o]++; + } + + for (const CTxBudgetPayment& payment : vecBudgetPayments) { + auto it = txouts.find(CTxOut(payment.nAmount, payment.payee)); + if (it == txouts.end() || it->second == 0) { + // Payment not found + CTxDestination addr; + const std::string& payee = ExtractDestination(payment.payee, addr) ? EncodeDestination(addr) + : HexStr(payment.payee); + LogPrint(BCLog::MNBUDGET, "Missing payment of %s for %s (proposal hash: %s)\n", + FormatMoney(payment.nAmount), payee, payment.nProposalHash.ToString()); + return false; + } + it->second--; + } + + // all budgets are paid by tx + return true; +} + // return broadcast serialization CDataStream CFinalizedBudget::GetBroadcast() const { diff --git a/src/budget/finalizedbudget.h b/src/budget/finalizedbudget.h index d936dabb92af0..0aea8601580d7 100644 --- a/src/budget/finalizedbudget.h +++ b/src/budget/finalizedbudget.h @@ -93,6 +93,9 @@ class CFinalizedBudget bool GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) const; bool GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) const; + // Check if ALL the budgets are paid by transaction tx + bool AllBudgetsPaid(const CTransaction& tx) const; + // Check finalized budget proposals. Masternodes only (when voting on finalized budgets) bool CheckProposals(const std::map& mapWinningProposals) const; // Total amount paid out by this budget diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 7bbc07e83ed3a..5f0c205a76637 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -318,7 +318,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) // Check budget payments during superblocks if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && consensus.IsSuperBlock(nBlockHeight)) { - // !TODO... + return g_budgetman.IsValidSuperBlockTx(coinbase_tx, nBlockHeight); } return true; From 77cd6e91512abb00c51281925e32a589e4639ed2 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 13:14:39 +0100 Subject: [PATCH 08/11] BlockAssembler: pay all budgets (and masternode) in single SuperBlock --- src/budget/budgetmanager.cpp | 16 ++++++++++++++++ src/budget/budgetmanager.h | 3 ++- src/budget/finalizedbudget.cpp | 7 +++++++ src/budget/finalizedbudget.h | 3 +++ src/masternode-payments.cpp | 19 ++++++++++++++++++- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index d53f1d6ec7e1e..0302c98d47296 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -568,6 +568,22 @@ bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTra return true; } +void CBudgetManager::FillBlockPayees(CMutableTransaction& tx, int height) const +{ + if (!Params().GetConsensus().IsSuperBlock(height)) { + return; + } + int nFivePercent = mnodeman.CountEnabled() / 20; + + const auto highest = GetBudgetWithHighestVoteCount(height); + const CFinalizedBudget* pfb = highest.m_budget_fin; + if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + // No finalization or not enough votes. + return; + } + pfb->PayAllBudgets(tx); +} + void CBudgetManager::VoteOnFinalizedBudgets() { // function called only from initialized masternodes diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index de8549acd0884..bdc6d01a5de32 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -155,7 +155,8 @@ class CBudgetManager : public CValidationInterface bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB std::string GetRequiredPaymentsString(int nBlockHeight); - bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; + bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; // legacy (multiple SB) + void FillBlockPayees(CMutableTransaction& tx, int height) const; // v6.0: single SB // Only initialized masternodes: sign and submit votes on valid finalized budgets void VoteOnFinalizedBudgets(); diff --git a/src/budget/finalizedbudget.cpp b/src/budget/finalizedbudget.cpp index 2f90243fc4aad..8b139648e82a1 100644 --- a/src/budget/finalizedbudget.cpp +++ b/src/budget/finalizedbudget.cpp @@ -422,6 +422,13 @@ bool CFinalizedBudget::AllBudgetsPaid(const CTransaction& tx) const return true; } +void CFinalizedBudget::PayAllBudgets(CMutableTransaction& tx) const +{ + for (const CTxBudgetPayment& payment : vecBudgetPayments) { + tx.vout.emplace_back(payment.nAmount, payment.payee); + } +} + // return broadcast serialization CDataStream CFinalizedBudget::GetBroadcast() const { diff --git a/src/budget/finalizedbudget.h b/src/budget/finalizedbudget.h index 0aea8601580d7..79dcb5075fd11 100644 --- a/src/budget/finalizedbudget.h +++ b/src/budget/finalizedbudget.h @@ -96,6 +96,9 @@ class CFinalizedBudget // Check if ALL the budgets are paid by transaction tx bool AllBudgetsPaid(const CTransaction& tx) const; + // Add payments for ALL budgets to tx outs + void PayAllBudgets(CMutableTransaction& tx) const; + // Check finalized budget proposals. Masternodes only (when voting on finalized budgets) bool CheckProposals(const std::map& mapWinningProposals) const; // Total amount paid out by this budget diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index 5f0c205a76637..d044149b74aa0 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -324,7 +324,7 @@ bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindexPrev) return true; } -void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) +static void FillBlockPayee_legacy(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) { if (!sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) || // if superblocks are not enabled // ... or this is not a superblock @@ -334,6 +334,23 @@ void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoin } } +void FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const CBlockIndex* pindexPrev, bool fProofOfStake) +{ + int height = pindexPrev->nHeight + 1; + if (!Params().GetConsensus().NetworkUpgradeActive(height, Consensus::UPGRADE_V6_0)) { + // legacy - !TODO: remove after transition + return FillBlockPayee_legacy(txCoinbase, txCoinstake, pindexPrev, fProofOfStake); + } + + // Add masternode payment + masternodePayments.FillBlockPayee(txCoinbase, txCoinstake, pindexPrev, fProofOfStake); + + // Add budget payments (if superblock, and SPORK_13 is active) + if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + g_budgetman.FillBlockPayees(txCoinbase, height); + } +} + std::string GetRequiredPaymentsString(int nBlockHeight) { if (sporkManager.IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && g_budgetman.IsBudgetPaymentBlock(nBlockHeight)) { From 83ec3d8363435ef69715372a7138312b294d15a3 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 13:27:40 +0100 Subject: [PATCH 09/11] Refactor: de-duplicate code with CBudgetManager::GetBestFinalizedBudget --- src/budget/budgetmanager.cpp | 37 +++++++++++++++++------------------- src/budget/budgetmanager.h | 1 + 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/budget/budgetmanager.cpp b/src/budget/budgetmanager.cpp index 0302c98d47296..dbb64d920924d 100644 --- a/src/budget/budgetmanager.cpp +++ b/src/budget/budgetmanager.cpp @@ -509,17 +509,25 @@ bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet); } -CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +const CFinalizedBudget* CBudgetManager::GetBestFinalizedBudget(int chainHeight) const { if (!Params().GetConsensus().IsSuperBlock(chainHeight)) { - return 0; + return nullptr; } int nFivePercent = mnodeman.CountEnabled() / 20; const auto highest = GetBudgetWithHighestVoteCount(chainHeight); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + if (highest.m_budget_fin == nullptr || highest.m_vote_count <= nFivePercent) { // No finalization or not enough votes. + return nullptr; + } + return highest.m_budget_fin; +} + +CAmount CBudgetManager::GetFinalizedBudgetTotalPayout(int chainHeight) const +{ + const CFinalizedBudget* pfb = GetBestFinalizedBudget(chainHeight); + if (pfb == nullptr) { return 0; } return pfb->GetTotalPayout(); @@ -570,18 +578,10 @@ bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTra void CBudgetManager::FillBlockPayees(CMutableTransaction& tx, int height) const { - if (!Params().GetConsensus().IsSuperBlock(height)) { - return; + const CFinalizedBudget* pfb = GetBestFinalizedBudget(height); + if (pfb != nullptr) { + pfb->PayAllBudgets(tx); } - int nFivePercent = mnodeman.CountEnabled() / 20; - - const auto highest = GetBudgetWithHighestVoteCount(height); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { - // No finalization or not enough votes. - return; - } - pfb->PayAllBudgets(tx); } void CBudgetManager::VoteOnFinalizedBudgets() @@ -777,11 +777,8 @@ bool CBudgetManager::IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHe { assert(Params().GetConsensus().IsSuperBlock(nBlockHeight)); - int nFivePercent = mnodeman.CountEnabled() / 20; - - const auto highest = GetBudgetWithHighestVoteCount(nBlockHeight); - const CFinalizedBudget* pfb = highest.m_budget_fin; - if (pfb == nullptr || highest.m_vote_count <= nFivePercent) { + const CFinalizedBudget* pfb = GetBestFinalizedBudget(nBlockHeight); + if (pfb == nullptr) { // No finalization or not enough votes. Nothing to check. return true; } diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index bdc6d01a5de32..7b7ff98de4c8b 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -155,6 +155,7 @@ class CBudgetManager : public CValidationInterface bool IsValidSuperBlockTx(const CTransaction& txNew, int nBlockHeight) const; // v6.0: single SB std::string GetRequiredPaymentsString(int nBlockHeight); + const CFinalizedBudget* GetBestFinalizedBudget(int chainHeight) const; bool FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const; // legacy (multiple SB) void FillBlockPayees(CMutableTransaction& tx, int height) const; // v6.0: single SB From af8c88100b163657130436fb50b57152649caecc Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 18 Feb 2022 18:11:23 +0100 Subject: [PATCH 10/11] Validation: consider mn payment for superblocks in IsCoinbaseValueValid --- src/masternode-payments.cpp | 37 ++++++++++++++----------------------- src/test/budget_tests.cpp | 32 ++++++++++++++++++++++++-------- src/validation.cpp | 2 +- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index d044149b74aa0..0fe6ea01fa21d 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -893,30 +893,21 @@ bool IsCoinbaseValueValid(const CTransactionRef& tx, CAmount nBudgetAmt, CValida { assert(tx->IsCoinBase()); if (g_tiertwo_sync_state.IsSynced()) { - const CAmount nCBaseOutAmt = tx->GetValueOut(); - if (nBudgetAmt > 0) { - // Superblock - if (nCBaseOutAmt != nBudgetAmt) { - const std::string strError = strprintf("%s: invalid coinbase payment for budget (%s vs expected=%s)", - __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nBudgetAmt)); - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-superblock-cb-amt"); - } - return true; - } else { - // regular block - CAmount nMnAmt = GetMasternodePayment(); - // if enforcement is disabled, there could be no masternode payment - bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); - const std::string strError = strprintf("%s: invalid coinbase payment for masternode (%s vs expected=%s)", - __func__, FormatMoney(nCBaseOutAmt), FormatMoney(nMnAmt)); - if (sporkEnforced && nCBaseOutAmt != nMnAmt) { - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt"); - } - if (!sporkEnforced && nCBaseOutAmt > nMnAmt) { - return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, "bad-cb-amt-spork8-disabled"); - } - return true; + const CAmount paid = tx->GetValueOut(); + const CAmount expected = GetMasternodePayment() + nBudgetAmt; + // if enforcement is disabled, there could be no masternode payment + bool sporkEnforced = sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT); + + const std::string strError = strprintf("%s: invalid coinbase payment (%s vs expected=%s)", + __func__, FormatMoney(paid), FormatMoney(expected)); + std::string rej_reason = (nBudgetAmt > 0 ? "bad-superblock-cb-amt" : "bad-cb-amt"); + if (sporkEnforced && paid != expected) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, rej_reason); } + if (!sporkEnforced && paid > expected) { + return _state.DoS(100, error(strError.c_str()), REJECT_INVALID, rej_reason+"-spork8-disabled"); + } + return true; } return true; } diff --git a/src/test/budget_tests.cpp b/src/test/budget_tests.cpp index 2ec3485d5778c..7e93d436d984b 100644 --- a/src/test/budget_tests.cpp +++ b/src/test/budget_tests.cpp @@ -417,36 +417,52 @@ BOOST_FIXTURE_TEST_CASE(IsCoinbaseValueValid_test, TestingSetup) // Exact cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); - cbase.vout[0].nValue /= 2; - cbase.vout.emplace_back(cbase.vout[0]); + cbase.vout[1].nValue /= 2; + cbase.vout.emplace_back(cbase.vout[1]); BOOST_CHECK(IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); // Underpaying cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt - 1, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); - cbase.vout[0].nValue = budgAmt/2; - cbase.vout.emplace_back(cbase.vout[0]); - cbase.vout[1].nValue = budgAmt/2 - 1; + cbase.vout[1].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[1]); + cbase.vout[2].nValue = budgAmt/2 - 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt - 1, cbaseScript); + cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); // Overpaying cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt, cbaseScript); cbase.vout.emplace_back(budgAmt + 1, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); state = CValidationState(); - cbase.vout[0].nValue = budgAmt/2; - cbase.vout.emplace_back(cbase.vout[0]); - cbase.vout[1].nValue = budgAmt/2 + 1; + cbase.vout[1].nValue = budgAmt/2; + cbase.vout.emplace_back(cbase.vout[1]); + cbase.vout[2].nValue = budgAmt/2 + 1; + BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); + cbase.vout.clear(); + cbase.vout.emplace_back(mnAmt + 1, cbaseScript); + cbase.vout.emplace_back(budgAmt, cbaseScript); BOOST_CHECK(!IsCoinbaseValueValid(MakeTransactionRef(cbase), budgAmt, state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-superblock-cb-amt"); + state = CValidationState(); } BOOST_AUTO_TEST_CASE(fbv_signverify_bls) diff --git a/src/validation.cpp b/src/validation.cpp index b757e1d894052..b69e1ffd60a42 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1614,7 +1614,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd nExpectedMint += nFees; //Check that the block does not overmint - CAmount nBudgetAmt = 0; // If this is a superblock, amount to be paid to the winning proposal, otherwise 0 + CAmount nBudgetAmt = 0; // If this is a superblock, amount to be paid to the winning proposals, otherwise 0 if (!IsBlockValueValid(pindex->nHeight, nExpectedMint, nMint, nBudgetAmt)) { return state.DoS(100, error("%s: reward pays too much (actual=%s vs limit=%s)", __func__, FormatMoney(nMint), FormatMoney(nExpectedMint)), From 764e29c5942e7aee1d3e6053be90815a6af8b459 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 7 Mar 2022 14:51:28 +0100 Subject: [PATCH 11/11] QA: fix broken tiertwo tests --- test/functional/test_framework/budget_util.py | 2 +- .../test_framework/test_framework.py | 56 +++++------ test/functional/tiertwo_budget.py | 33 ++++--- .../tiertwo_governance_sync_basic.py | 93 ++++++++++--------- .../tiertwo_masternode_activation.py | 2 +- test/functional/tiertwo_mn_compatibility.py | 53 ++++------- 6 files changed, 105 insertions(+), 134 deletions(-) diff --git a/test/functional/test_framework/budget_util.py b/test/functional/test_framework/budget_util.py index 52e95cb183cf9..917b9a295f828 100644 --- a/test/functional/test_framework/budget_util.py +++ b/test/functional/test_framework/budget_util.py @@ -65,7 +65,7 @@ def check_mns_status(node, txhash): def check_mn_list(node, txHashSet): # check masternode list from node mnlist = node.listmasternodes() - assert_equal(len(mnlist), 3) + 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)) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index fc8eee88c56c4..228c0c9e8a92a 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1618,24 +1618,18 @@ def mine_quorum(self, invalidate_func=None, invalidated_idx=None, skip_bad_membe # !TODO: remove after obsoleting legacy system class PivxTier2TestFramework(PivxTestFramework): - def __init__(self): - super().__init__() - self.v6_enforcement_height = 250 - - def set_test_params(self): + def set_test_params(self, v6_enforcement_height=250): self.setup_clean_chain = True - self.num_nodes = 8 + self.num_nodes = 6 self.enable_mocktime() - self.ownerOnePos = 0 + self.ownerPos = 0 self.remoteOnePos = 1 - self.ownerTwoPos = 2 - self.remoteTwoPos = 3 - self.minerPos = 4 - self.remoteDMN1Pos = 5 - self.ownerThreePos = 6 - self.remoteThreePos = 7 - self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(self.v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes + self.remoteTwoPos = 2 + self.minerPos = 3 + self.remoteDMN1Pos = 4 + self.remoteThreePos = 5 + self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:"+str(v6_enforcement_height), "-whitelist=127.0.0.1"]] * self.num_nodes 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") @@ -1649,13 +1643,11 @@ def set_test_params(self): self.mnThreePrivkey = "91qP855JNR3aWv6Z71BcFjqhkeizchSDjKSi7BdqMSSirEVDTEk" # Updated in setup_masternodes_network() to be called at the start of run_test - self.ownerOne = None # self.nodes[self.ownerOnePos] + self.owner = None # self.nodes[self.ownerPos] 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.remoteDMN1 = None # self.nodes[self.remoteDMN1Pos] - self.ownerThree = None # self.nodes[self.ownerThreePos] self.remoteThree = None # self.nodes[self.remoteThreePos] self.mnOneCollateral = COutPoint() self.mnTwoCollateral = COutPoint() @@ -1676,13 +1668,13 @@ def stake(self, num_blocks, with_ping_mns=[]): self.stake_and_ping(self.minerPos, 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.controller_start_masternode(self.ownerThree, self.masternodeThreeAlias) + self.controller_start_masternode(self.owner, self.masternodeOneAlias) + self.controller_start_masternode(self.owner, self.masternodeTwoAlias) + self.controller_start_masternode(self.owner, self.masternodeThreeAlias) self.wait_until_mn_preenabled(self.mnOneCollateral.hash, 40) self.wait_until_mn_preenabled(self.mnTwoCollateral.hash, 40) self.wait_until_mn_preenabled(self.mnThreeCollateral.hash, 40) - self.log.info("masternodes started, waiting until both get enabled..") + self.log.info("masternodes started, waiting until they get enabled..") self.send_3_pings() self.wait_until_mn_enabled(self.mnOneCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.wait_until_mn_enabled(self.mnTwoCollateral.hash, 120, [self.remoteOne, self.remoteTwo, self.remoteThree]) @@ -1695,17 +1687,13 @@ def advance_mocktime_and_stake(self, secs_to_add): time.sleep(2) def setup_masternodes_network(self, setup_dmn=True): - self.ownerOne = self.nodes[self.ownerOnePos] + self.owner = self.nodes[self.ownerPos] self.remoteOne = self.nodes[self.remoteOnePos] - self.ownerTwo = self.nodes[self.ownerTwoPos] self.remoteTwo = self.nodes[self.remoteTwoPos] - self.ownerThree = self.nodes[self.ownerThreePos] self.remoteThree = self.nodes[self.remoteThreePos] self.miner = self.nodes[self.minerPos] 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) - ownerThreeDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerThreePos) + ownerDir = os.path.join(self.options.tmpdir, "node%d" % self.ownerPos) self.log.info("generating 256 blocks..") # First mine 250 PoW blocks @@ -1718,32 +1706,32 @@ def setup_masternodes_network(self, setup_dmn=True): self.log.info("masternodes setup..") # setup first masternode node, corresponding to nodeOne self.mnOneCollateral = self.setupMasternode( - self.ownerOne, + self.owner, self.miner, self.masternodeOneAlias, - os.path.join(ownerOneDir, "regtest"), + os.path.join(ownerDir, "regtest"), self.remoteOnePos, self.mnOnePrivkey) # setup second masternode node, corresponding to nodeTwo self.mnTwoCollateral = self.setupMasternode( - self.ownerTwo, + self.owner, self.miner, self.masternodeTwoAlias, - os.path.join(ownerTwoDir, "regtest"), + os.path.join(ownerDir, "regtest"), self.remoteTwoPos, self.mnTwoPrivkey) # setup third masternode node, corresponding to nodeTwo self.mnThreeCollateral = self.setupMasternode( - self.ownerThree, + self.owner, self.miner, self.masternodeThreeAlias, - os.path.join(ownerThreeDir, "regtest"), + os.path.join(ownerDir, "regtest"), self.remoteThreePos, self.mnThreePrivkey) if setup_dmn: # setup deterministic masternode self.proRegTx1, self.dmn1Privkey = self.setupDMN( - self.ownerOne, + self.owner, self.miner, self.remoteDMN1Pos, "fund" diff --git a/test/functional/tiertwo_budget.py b/test/functional/tiertwo_budget.py index c6393ed57aa50..44b00d1c1dc2a 100755 --- a/test/functional/tiertwo_budget.py +++ b/test/functional/tiertwo_budget.py @@ -38,8 +38,7 @@ class BudgetTest(PivxTier2TestFramework): def set_test_params(self): - self.v6_enforcement_height = 300 - super().set_test_params() + super().set_test_params(300) def broadcastbudgetfinalization(self, node, with_ping_mns=[]): self.log.info("suggesting the budget finalization..") @@ -86,7 +85,7 @@ def vote_finalization(self, voting_node, budget_fin_hash, legacy): assert_equal(voteResult["detail"][0]["result"], "success") def check_address_balance(self, addr, expected_balance, has_balance=True): - addrInfo = self.nodes[self.ownerOnePos].listreceivedbyaddress(0, False, False, addr) + addrInfo = self.nodes[self.ownerPos].listreceivedbyaddress(0, False, False, addr) if has_balance: assert_equal(addrInfo[0]["amount"], expected_balance) else: @@ -115,7 +114,7 @@ def finalize_and_vote_budget(self): time.sleep(2) print(budgetFinHash) self.log.info("voting budget finalization..") - for node in [self.ownerOne, self.ownerTwo, self.ownerThree]: + for node in [self.remoteOne, self.remoteTwo, self.remoteThree]: self.vote_finalization(node, budgetFinHash, True) time.sleep(2) # wait a bit check_budget_finalization_sync(self.nodes, 3, "OK") @@ -146,7 +145,7 @@ def run_test(self): props.append(Proposal("prop_"+str(i), "https://link_"+str(i)+".com", 4, - self.nodes[self.ownerOnePos].getnewaddress(), + self.nodes[self.ownerPos].getnewaddress(), 11 * (i + 1))) self.submit_proposals(props) @@ -163,12 +162,12 @@ def run_test(self): alloted = 0 for i in range(2): prop = props[i] - self.vote_legacy(self.ownerOne, prop, "yes", self.masternodeOneAlias) + self.vote_legacy(self.owner, prop, "yes", self.masternodeOneAlias) check_vote_existence(self.nodes, prop.name, self.mnOneCollateral.hash, "YES", True) - self.vote_legacy(self.ownerTwo, prop, "yes", self.masternodeTwoAlias) + self.vote_legacy(self.owner, prop, "yes", self.masternodeTwoAlias) check_vote_existence(self.nodes, prop.name, self.mnTwoCollateral.hash, "YES", True) if i < 1: - self.vote_legacy(self.ownerThree, prop, "yes", self.masternodeThreeAlias) + self.vote_legacy(self.owner, prop, "yes", self.masternodeThreeAlias) check_vote_existence(self.nodes, prop.name, self.mnThreeCollateral.hash, "YES", True) alloted += prop.amountPerCycle expected_budget.append(get_proposal(prop, blockStart, prop.amountPerCycle, alloted, 3 - i)) @@ -177,7 +176,7 @@ def run_test(self): check_budgetprojection(self.nodes, expected_budget, self.log) # Quick block count check. - assert_equal(self.ownerOne.getblockcount(), 272) + assert_equal(self.owner.getblockcount(), 272) self.stake(10, [self.remoteOne, self.remoteTwo, self.remoteThree]) # Finalize budget self.finalize_and_vote_budget() @@ -230,12 +229,12 @@ def run_test(self): next_super_block = self.miner.getnextsuperblock() block_count = self.miner.getblockcount() self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) - assert_equal(self.ownerOne.getblockcount(), 426) + assert_equal(self.owner.getblockcount(), 426) # Finalize budget self.finalize_and_vote_budget() self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("checking single block payments..") - assert_equal(self.ownerOne.getblockcount(), 432) + assert_equal(self.owner.getblockcount(), 432) self.check_block_proposal_payment(self.miner.getbestblockhash(), prop1.paymentAddr, prop1.amountPerCycle, 1, True) self.check_block_proposal_payment(self.miner.getbestblockhash(), prop2.paymentAddr, prop2.amountPerCycle, 2, True) @@ -250,7 +249,7 @@ def run_test(self): self.log.info("Now test proposal with duplicate script and value") self.proRegTx1, self.dmn1Privkey = self.setupDMN( - self.ownerOne, + self.owner, self.miner, self.remoteDMN1Pos, "fund" @@ -269,13 +268,13 @@ def run_test(self): for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 17) # vote prop17 - self.vote_legacy(self.ownerOne, prop17, "yes", self.masternodeOneAlias) + self.vote_legacy(self.owner, prop17, "yes", self.masternodeOneAlias) check_vote_existence(self.nodes, prop17.name, self.mnOneCollateral.hash, "YES", True) - self.vote_legacy(self.ownerTwo, prop17, "yes", self.masternodeTwoAlias) + self.vote_legacy(self.owner, prop17, "yes", self.masternodeTwoAlias) check_vote_existence(self.nodes, prop17.name, self.mnTwoCollateral.hash, "YES", True) - self.vote_legacy(self.ownerThree, prop17, "yes", self.masternodeThreeAlias) + self.vote_legacy(self.owner, prop17, "yes", self.masternodeThreeAlias) check_vote_existence(self.nodes, prop17.name, self.mnThreeCollateral.hash, "YES", True) - self.vote(self.ownerOne, prop17, "yes", self.proRegTx1) + self.vote(self.owner, prop17, "yes", self.proRegTx1) check_vote_existence(self.nodes, prop17.name, self.proRegTx1, "YES", True) alloted += prop17.amountPerCycle @@ -291,7 +290,7 @@ def run_test(self): next_super_block = self.miner.getnextsuperblock() block_count = self.miner.getblockcount() self.stake_and_ping(self.minerPos, next_super_block - block_count - 6, [self.remoteOne, self.remoteTwo, self.remoteThree]) - assert_equal(self.ownerOne.getblockcount(), 570) + assert_equal(self.owner.getblockcount(), 570) # Finalize budget self.finalize_and_vote_budget() diff --git a/test/functional/tiertwo_governance_sync_basic.py b/test/functional/tiertwo_governance_sync_basic.py index 1bcb9ef3adaf4..b7def8607f015 100755 --- a/test/functional/tiertwo_governance_sync_basic.py +++ b/test/functional/tiertwo_governance_sync_basic.py @@ -57,7 +57,7 @@ def connect_nodes_bi(self, nodes, a, b): def submit_proposals(self, props): props = create_proposals_tx(self.miner, props) # generate 3 blocks to confirm the tx (and update the mnping) - self.stake(3, [self.remoteOne, self.remoteTwo]) + self.stake(3, [self.remoteOne, self.remoteTwo, self.remoteThree]) # check fee tx existence for entry in props: txinfo = self.miner.gettransaction(entry.feeTxId) @@ -74,7 +74,7 @@ def submit_proposals(self, props): def run_test(self): self.enable_mocktime() self.setup_masternodes_network() - txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.mnThreeCollateral.hash, self.proRegTx1]) # check mn list from miner check_mn_list(self.miner, txHashSet) @@ -83,6 +83,8 @@ def run_test(self): self.log.info("MN1 active") check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") + check_mns_status_legacy(self.remoteThree, self.mnThreeCollateral.hash) + self.log.info("MN3 active") check_mns_status(self.remoteDMN1, self.proRegTx1) self.log.info("DMN1 active") @@ -115,31 +117,31 @@ def run_test(self): # Proposals are established after 5 minutes. Mine 7 blocks # Proposal needs to be on the chain > 5 min. - self.stake(7, [self.remoteOne, self.remoteTwo]) + self.stake(7, [self.remoteOne, self.remoteTwo, self.remoteThree]) # Check proposals existence for i in range(self.num_nodes): assert_equal(len(self.nodes[i].getbudgetinfo()), 16) # now let's vote for the proposal with the first MN self.log.info("Voting with MN1...") - voteResult = self.ownerOne.mnbudgetvote("alias", firstProposal.proposalHash, "yes", self.masternodeOneAlias, True) + voteResult = self.owner.mnbudgetvote("alias", firstProposal.proposalHash, "yes", self.masternodeOneAlias, True) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere - self.stake(1, [self.remoteOne, self.remoteTwo]) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, MN1 vote accepted everywhere!") - # before broadcast the second vote, let's drop the budget data of ownerOne. + # before broadcast the second vote, let's drop the budget data of owner. # so the node is forced to send a single proposal sync when the, now orphan, proposal vote is received. self.log.info("Testing single proposal re-sync based on an orphan vote, dropping budget data...") - self.ownerOne.cleanbudget(try_sync=False) - assert_equal(self.ownerOne.getbudgetprojection(), []) # empty - assert_equal(self.ownerOne.getbudgetinfo(), []) + self.owner.cleanbudget(try_sync=False) + assert_equal(self.owner.getbudgetprojection(), []) # empty + assert_equal(self.owner.getbudgetinfo(), []) # now let's vote for the proposal with the second MN self.log.info("Voting with MN2...") - voteResult = self.ownerTwo.mnbudgetvote("alias", firstProposal.proposalHash, "yes", self.masternodeTwoAlias, True) + voteResult = self.remoteTwo.mnbudgetvote("local", firstProposal.proposalHash, "yes", "", True) assert_equal(voteResult["detail"][0]["result"], "success") # check orphan vote proposal re-sync @@ -150,17 +152,17 @@ def run_test(self): self.log.info("all good, orphan vote based proposal re-sync succeeded") # check that the vote was accepted everywhere - self.stake(1, [self.remoteOne, self.remoteTwo]) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) check_vote_existence(self.nodes, firstProposal.name, 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 self.log.info("Voting with DMN1...") - voteResult = self.ownerOne.mnbudgetvote("alias", firstProposal.proposalHash, "yes", self.proRegTx1) + voteResult = self.owner.mnbudgetvote("alias", firstProposal.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.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") @@ -178,13 +180,13 @@ def run_test(self): check_budgetprojection(self.nodes, expected_budget, self.log) # Quick block count check. - assert_equal(self.ownerOne.getblockcount(), 279) + assert_equal(self.owner.getblockcount(), 280) self.log.info("starting budget finalization sync test..") - self.stake(2, [self.remoteOne, self.remoteTwo]) + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) # assert that there is no budget finalization first. - assert_equal(len(self.ownerOne.mnfinalbudget("show")), 0) + assert_equal(len(self.owner.mnfinalbudget("show")), 0) # suggest the budget finalization and confirm the tx (+4 blocks). budgetFinHash = self.broadcastbudgetfinalization(self.miner, @@ -197,16 +199,19 @@ def run_test(self): self.log.info("budget finalization synced!, now voting for the budget finalization..") # Connecting owner to all the other nodes. - self.connect_to_all(self.ownerOnePos) + self.connect_to_all(self.ownerPos) - voteResult = self.ownerOne.mnfinalbudget("vote-many", budgetFinHash, True) + voteResult = self.remoteOne.mnfinalbudget("vote", budgetFinHash, True) assert_equal(voteResult["detail"][0]["result"], "success") time.sleep(2) # wait a bit - self.stake(2, [self.remoteOne, self.remoteTwo]) - check_budget_finalization_sync(self.nodes, 1, "OK") + voteResult = self.remoteTwo.mnfinalbudget("vote", budgetFinHash, True) + assert_equal(voteResult["detail"][0]["result"], "success") + time.sleep(2) # wait a bit + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) + check_budget_finalization_sync(self.nodes, 2, "OK") self.log.info("Remote One voted successfully.") - # before broadcast the second finalization vote, let's drop the budget data of remoteOne. + # before broadcast the third finalization vote, let's drop the budget data of remoteOne. # so the node is forced to send a single fin sync when the, now orphan, vote is received. self.log.info("Testing single fin re-sync based on an orphan vote, dropping budget data...") self.remoteOne.cleanbudget(try_sync=False) @@ -214,20 +219,20 @@ def run_test(self): assert_equal(self.remoteOne.getbudgetinfo(), []) # vote for finalization with MN2 and the DMN - voteResult = self.ownerTwo.mnfinalbudget("vote-many", budgetFinHash, True) + voteResult = self.remoteThree.mnfinalbudget("vote", budgetFinHash, True) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("Remote Two voted successfully.") voteResult = self.remoteDMN1.mnfinalbudget("vote", budgetFinHash) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("DMN voted successfully.") time.sleep(2) # wait a bit - self.stake(2, [self.remoteOne, self.remoteTwo]) + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("checking finalization votes..") - check_budget_finalization_sync(self.nodes, 3, "OK") + check_budget_finalization_sync(self.nodes, 4, "OK") self.log.info("orphan vote based finalization re-sync succeeded") - self.stake(6, [self.remoteOne, self.remoteTwo]) + self.stake(6, [self.remoteOne, self.remoteTwo, self.remoteThree]) addrInfo = self.miner.listreceivedbyaddress(0, False, False, firstProposal.paymentAddr) assert_equal(addrInfo[0]["amount"], firstProposal.amountPerCycle) @@ -237,7 +242,7 @@ def run_test(self): expected_budget[0]["RemainingPaymentCount"] -= 1 check_budgetprojection(self.nodes, expected_budget, self.log) - self.stake(1, [self.remoteOne, self.remoteTwo]) + self.stake(1, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("checking resync (1): cleaning budget data only..") # now let's drop budget data and try to re-sync it. @@ -256,17 +261,17 @@ def run_test(self): self.log.info("checking resync (2): stop node, delete chain data and resync from scratch..") # stop and remove everything - self.stop_node(self.ownerTwoPos) - ownerTwoDir = os.path.join(get_datadir_path(self.options.tmpdir, self.ownerTwoPos), "regtest") + self.stop_node(self.ownerPos) + ownerDir = os.path.join(get_datadir_path(self.options.tmpdir, self.ownerPos), "regtest") for entry in ['chainstate', 'blocks', 'sporks', 'evodb', 'zerocoin', "mncache.dat", "budget.dat", "mnpayments.dat", "peers.dat"]: - rem_path = os.path.join(ownerTwoDir, entry) + rem_path = os.path.join(ownerDir, entry) shutil.rmtree(rem_path) if os.path.isdir(rem_path) else os.remove(rem_path) self.log.info("restarting node..") - self.start_node(self.ownerTwoPos) - self.ownerTwo.setmocktime(self.mocktime) - self.connect_to_all(self.ownerTwoPos) - self.stake(2, [self.remoteOne, self.remoteTwo]) + self.start_node(self.ownerPos) + self.owner.setmocktime(self.mocktime) + self.connect_to_all(self.ownerPos) + self.stake(2, [self.remoteOne, self.remoteTwo, self.remoteThree]) self.log.info("syncing node..") self.wait_until_mnsync_finished() @@ -282,7 +287,7 @@ def run_test(self): assert_equal(self.remoteDMN1.getbudgetprojection(), []) # empty assert_equal(self.remoteDMN1.getbudgetinfo(), []) self.log.info("Generating blocks until someone syncs the node..") - self.stake(40, [self.remoteOne, self.remoteTwo]) + self.stake(40, [self.remoteOne, self.remoteTwo, self.remoteThree]) time.sleep(5) # wait a little bit self.log.info("Checking budget sync..") for i in range(self.num_nodes): @@ -290,40 +295,40 @@ def run_test(self): check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", True) check_vote_existence(self.nodes, firstProposal.name, self.mnTwoCollateral.hash, "YES", True) check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", True) - check_budget_finalization_sync(self.nodes, 3, "OK") + check_budget_finalization_sync(self.nodes, 4, "OK") self.log.info("Remote incremental sync succeeded") # 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.spend_collateral(self.owner, self.mnOneCollateral, self.miner) self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) - self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes + self.stake(15, [self.remoteTwo, self.remoteThree]) # create blocks to remove staled votes time.sleep(2) # wait a little bit check_vote_existence(self.nodes, firstProposal.name, self.mnOneCollateral.hash, "YES", False) - check_budget_finalization_sync(self.nodes, 2, "OK") # budget finalization vote removal + check_budget_finalization_sync(self.nodes, 3, "OK") # budget finalization vote removal self.log.info("MN1 vote expired after collateral spend, all good") self.log.info("expiring DMN1..") - lm = self.ownerOne.listmasternodes(self.proRegTx1)[0] - self.spend_collateral(self.ownerOne, COutPoint(lm["collateralHash"], lm["collateralIndex"]), self.miner) + lm = self.owner.listmasternodes(self.proRegTx1)[0] + self.spend_collateral(self.owner, 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 + self.stake(15, [self.remoteTwo, self.remoteThree]) # create blocks to remove staled votes time.sleep(2) # wait a little bit check_vote_existence(self.nodes, firstProposal.name, self.proRegTx1, "YES", False) - check_budget_finalization_sync(self.nodes, 1, "OK") # budget finalization vote removal + check_budget_finalization_sync(self.nodes, 2, "OK") # budget finalization vote removal self.log.info("DMN vote expired after collateral spend, all good") # Check that the budget is removed 200 blocks after the last payment assert_equal(len(self.miner.mnfinalbudget("show")), 1) blocks_to_mine = nextSuperBlockHeight + 200 - self.miner.getblockcount() self.log.info("Mining %d more blocks to check expired budget removal..." % blocks_to_mine) - self.stake(blocks_to_mine - 1, [self.remoteTwo]) + self.stake(blocks_to_mine - 1, [self.remoteTwo, self.remoteThree]) # finalized budget must still be there self.miner.checkbudgets() assert_equal(len(self.miner.mnfinalbudget("show")), 1) # after one more block it must be removed - self.stake(1, [self.remoteTwo]) + self.stake(1, [self.remoteTwo, self.remoteThree]) self.miner.checkbudgets() assert_equal(len(self.miner.mnfinalbudget("show")), 0) self.log.info("All good.") diff --git a/test/functional/tiertwo_masternode_activation.py b/test/functional/tiertwo_masternode_activation.py index e5c2b3cd9d6b6..32d97c3a44e87 100755 --- a/test/functional/tiertwo_masternode_activation.py +++ b/test/functional/tiertwo_masternode_activation.py @@ -90,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.ownerOne, self.mnOneCollateral, self.miner) + self.spend_collateral(self.owner, self.mnOneCollateral, self.miner) self.sync_blocks() self.log.info("checking mn status..") time.sleep(3) # wait a little bit diff --git a/test/functional/tiertwo_mn_compatibility.py b/test/functional/tiertwo_mn_compatibility.py index 6dcf91f5b6aef..b9ab4584e4406 100755 --- a/test/functional/tiertwo_mn_compatibility.py +++ b/test/functional/tiertwo_mn_compatibility.py @@ -16,39 +16,16 @@ 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" - + super().set_test_params() + self.num_nodes = 8 + self.remoteDMN2Pos = 6 + self.remoteDMN3Pos = 7 self.extra_args = [["-nuparams=v5_shield:249", "-nuparams=v6_evo:250", "-whitelist=127.0.0.1"]] * self.num_nodes - for i in [self.remoteOnePos, self.remoteTwoPos, self.remoteDMN1Pos, self.remoteDMN2Pos, self.remoteDMN3Pos]: + 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") - 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) @@ -98,8 +75,8 @@ def run_test(self): self.enable_mocktime() self.setup_masternodes_network() - # start with 3 masternodes (2 legacy + 1 DMN) - self.check_mn_enabled_count(3, 3) + # start with 4 masternodes (3 legacy + 1 DMN) + self.check_mn_enabled_count(4, 4) # add two more nodes to the network self.remoteDMN2 = self.nodes[self.remoteDMN2Pos] @@ -111,7 +88,7 @@ def run_test(self): self.sync_all() # check mn list from miner - txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1]) + txHashSet = set([self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.mnThreeCollateral.hash, self.proRegTx1]) self.check_mn_list(self.miner, txHashSet) # check status of masternodes @@ -119,13 +96,15 @@ 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_legacy(self.remoteThree, self.mnThreeCollateral.hash) + self.log.info("MN3 active Pays %s" % self.mn_addresses[self.mnThreeCollateral.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 self.proRegTx2, self.dmn2Privkey = self.setupDMN( - self.ownerOne, + self.owner, self.miner, self.remoteDMN2Pos, "internal" @@ -133,7 +112,7 @@ def run_test(self): self.remoteDMN2.initmasternode(self.dmn2Privkey) # check list and status - self.check_mn_enabled_count(4, 4) # 2 legacy + 2 DMN + self.check_mn_enabled_count(5, 5) # 3 legacy + 2 DMN txHashSet.add(self.proRegTx2) self.check_mn_list(self.miner, txHashSet) self.check_mns_status(self.remoteDMN2, self.proRegTx2) @@ -153,7 +132,7 @@ def run_test(self): # 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.owner, self.miner, self.remoteDMN3Pos, "external", @@ -166,7 +145,7 @@ def run_test(self): # The legacy masternode must no longer be in the list # and the DMN must have taken its place - self.check_mn_enabled_count(4, 4) # 1 legacy + 3 DMN + self.check_mn_enabled_count(5, 5) # 2 legacy + 3 DMN txHashSet.remove(self.mnOneCollateral.hash) txHashSet.add(self.proRegTx3) for node in self.nodes: @@ -177,11 +156,11 @@ def run_test(self): # 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) + self.controller_start_masternode(self.owner, self.masternodeOneAlias) self.send_3_pings() # the masternode list hasn't changed - self.check_mn_enabled_count(4, 4) + self.check_mn_enabled_count(5, 5) for node in self.nodes: self.check_mn_list(node, txHashSet) self.log.info("Masternode list correctly unchanged in all nodes.")