Skip to content

Commit ff58178

Browse files
committed
Governance functional tests for SDMNs
1 parent 0aee9e8 commit ff58178

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

test/functional/test_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
'tiertwo_governance_invalid_budget.py', # ~ 266 sec
174174
'tiertwo_shield_deterministicmns.py', # ~ 160 sec
175175
'tiertwo_reorg_mempool.py', # ~ 97 sec
176+
'tiertwo_shield_governance.py' # ~ 80 sec
176177
]
177178

178179
SAPLING_SCRIPTS = [
@@ -249,6 +250,7 @@
249250
'wallet_multiwallet.py',
250251
'sapling_wallet_encryption.py',
251252
'tiertwo_shield_deterministicmns.py',
253+
'tiertwo_shield_governance.py',
252254
'tiertwo_dkg_errors.py',
253255
'tiertwo_chainlocks.py',
254256
'tiertwo_dkg_errors.py',
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2023 The PIVX Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or https://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
This (temporary) test checks the new vote system for SDMNs and DMNs.
7+
Once legacy are obsolete this test will be merged with tiertwo_governance_sync_basic,
8+
where we can simply change the two legacy MNs in SDMNs.
9+
"""
10+
11+
import time
12+
13+
from test_framework.test_framework import PivxTestFramework
14+
from test_framework.budget_util import (
15+
check_budget_finalization_sync,
16+
create_proposals_tx,
17+
check_budgetprojection,
18+
check_proposal_existence,
19+
check_mns_status,
20+
check_vote_existence,
21+
get_proposal_obj,
22+
Proposal,
23+
propagate_proposals
24+
)
25+
from test_framework.util import (
26+
assert_equal,
27+
satoshi_round,
28+
set_node_times
29+
)
30+
31+
32+
class SDMNsGovernanceTest(PivxTestFramework):
33+
34+
def wait_until_mnsync_completed(self):
35+
SYNC_FINISHED = [999] * self.num_nodes
36+
synced = [-1] * self.num_nodes
37+
timeout = time.time() + 120
38+
while synced != SYNC_FINISHED and time.time() < timeout:
39+
synced = [node.mnsync("status")["RequestedMasternodeAssets"]
40+
for node in self.nodes]
41+
if synced != SYNC_FINISHED:
42+
time.sleep(5)
43+
if synced != SYNC_FINISHED:
44+
raise AssertionError("Unable to complete mnsync: %s" % str(synced))
45+
46+
def broadcastbudgetfinalization(self):
47+
miner = self.nodes[0]
48+
self.log.info("suggesting the budget finalization..")
49+
assert miner.mnfinalbudgetsuggest() is not None
50+
51+
self.log.info("confirming the budget finalization..")
52+
time.sleep(1)
53+
miner.generate(4)
54+
self.sync_blocks()
55+
56+
self.log.info("broadcasting the budget finalization..")
57+
return miner.mnfinalbudgetsuggest()
58+
59+
def submit_proposals(self, props):
60+
miner = self.nodes[0]
61+
props = create_proposals_tx(miner, props)
62+
# generate 3 blocks to confirm the tx (and update the mnping)
63+
miner.generate(3)
64+
self.sync_blocks()
65+
# check fee tx existence
66+
for entry in props:
67+
txinfo = miner.gettransaction(entry.feeTxId)
68+
assert_equal(txinfo['amount'], -50.00)
69+
# propagate proposals
70+
props = propagate_proposals(miner, props)
71+
# let's wait a little bit and see if all nodes are sync
72+
time.sleep(1)
73+
for entry in props:
74+
check_proposal_existence(self.nodes, entry.name, entry.proposalHash)
75+
self.log.info("proposal %s broadcast successful!" % entry.name)
76+
return props
77+
78+
def set_test_params(self):
79+
# 1 miner, 1 controller, 1 DMNs and 1 SDMN
80+
self.num_nodes = 4
81+
self.minerPos = 0
82+
self.controllerPos = 1
83+
self.setup_clean_chain = True
84+
self.extra_args = [["-nuparams=v5_shield:1", "-nuparams=v6_evo:130"]] * self.num_nodes
85+
self.extra_args[0].append("-sporkkey=932HEevBSujW2ud7RfB1YF91AFygbBRQj3de3LyaCRqNzKKgWXi")
86+
87+
def run_test(self):
88+
# Additional connections to miner and owner
89+
for nodePos in [self.minerPos, self.controllerPos]:
90+
self.connect_to_all(nodePos)
91+
miner = self.nodes[self.minerPos]
92+
controller = self.nodes[self.controllerPos]
93+
94+
# Enforce mn payments and reject legacy mns at block 131
95+
self.activate_spork(self.minerPos, "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT")
96+
self.activate_spork(self.minerPos, "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT")
97+
self.activate_spork(self.minerPos, "SPORK_13_ENABLE_SUPERBLOCKS")
98+
assert_equal("success", self.set_spork(self.minerPos, "SPORK_21_LEGACY_MNS_MAX_HEIGHT", 130))
99+
time.sleep(1)
100+
assert_equal([130] * self.num_nodes, [self.get_spork(x, "SPORK_21_LEGACY_MNS_MAX_HEIGHT")
101+
for x in range(self.num_nodes)])
102+
mns = []
103+
104+
# DIP3 activates at block 130 but we mine until block 145 (so we are over the first budget payment).
105+
miner.generate(145 - miner.getblockcount())
106+
self.sync_blocks()
107+
self.assert_equal_for_all(145, "getblockcount")
108+
self.wait_until_mnsync_completed()
109+
110+
# Create 1 SDMN and 1 DMN:
111+
self.log.info("Initializing masternodes...")
112+
mns.append(self.register_new_dmn(2, self.minerPos, self.controllerPos, "internal", False))
113+
mns.append(self.register_new_dmn(3, self.minerPos, self.controllerPos, "internal", True))
114+
115+
for mn in mns:
116+
self.nodes[mn.idx].initmasternode(mn.operator_sk)
117+
time.sleep(1)
118+
miner.generate(1)
119+
self.sync_blocks()
120+
121+
for mn in mns:
122+
check_mns_status(self.nodes[mn.idx], mn.proTx)
123+
self.log.info("All masternodes activated")
124+
125+
nextSuperBlockHeight = miner.getnextsuperblock()
126+
127+
# Submit first proposal
128+
self.log.info("preparing budget proposal..")
129+
firstProposal = Proposal(
130+
"sdmns-are-the-best",
131+
"https://forum.pivx.org/t/fund-my-proposal",
132+
2,
133+
miner.getnewaddress(),
134+
300
135+
)
136+
self.submit_proposals([firstProposal])
137+
138+
# Check proposals existence
139+
for i in range(self.num_nodes):
140+
assert_equal(len(self.nodes[i].getbudgetinfo()), 1)
141+
142+
# now let's vote for the proposal with the first DMN
143+
self.log.info("Voting with DMN1...")
144+
voteResult = controller.mnbudgetvote("alias", firstProposal.proposalHash, "yes", mns[1].proTx)
145+
assert_equal(voteResult["detail"][0]["result"], "success")
146+
147+
# check that the vote was accepted everywhere
148+
miner.generate(1)
149+
self.sync_blocks()
150+
check_vote_existence(self.nodes, firstProposal.name, mns[1].proTx, "YES", True)
151+
self.log.info("all good, DMN1 vote accepted everywhere!")
152+
153+
# now let's vote for the proposal with the first SDMN
154+
self.log.info("Voting with SDMN1...")
155+
voteResult = controller.mnbudgetvote("alias", firstProposal.proposalHash, "yes", mns[0].proTx)
156+
assert_equal(voteResult["detail"][0]["result"], "success")
157+
158+
# check that the vote was accepted everywhere
159+
miner.generate(1)
160+
self.sync_blocks()
161+
check_vote_existence(self.nodes, firstProposal.name, mns[0].proTx, "YES", True)
162+
self.log.info("all good, SDMN1 vote accepted everywhere!")
163+
164+
# instead of waiting for 5 minutes for proposal to be established advance the nodes time of 10 minutes
165+
set_node_times(self.nodes, int(time.time()) + 10*60)
166+
167+
# check budgets
168+
blockStart = nextSuperBlockHeight
169+
blockEnd = blockStart + firstProposal.cycles * 145
170+
TotalPayment = firstProposal.amountPerCycle * firstProposal.cycles
171+
Allotted = firstProposal.amountPerCycle
172+
RemainingPaymentCount = firstProposal.cycles
173+
expected_budget = [
174+
get_proposal_obj(firstProposal.name, firstProposal.link, firstProposal.proposalHash, firstProposal.feeTxId, blockStart,
175+
blockEnd, firstProposal.cycles, RemainingPaymentCount, firstProposal.paymentAddr, 1,
176+
2, 0, 0, satoshi_round(TotalPayment), satoshi_round(firstProposal.amountPerCycle),
177+
True, True, satoshi_round(Allotted), satoshi_round(Allotted))
178+
]
179+
check_budgetprojection(self.nodes, expected_budget, self.log)
180+
181+
# Mine more block in order to finalize the budget.
182+
while (miner.getblockcount() < 279):
183+
miner.generate(1)
184+
self.sync_blocks()
185+
assert_equal(controller.getblockcount(), 279)
186+
187+
self.log.info("starting budget finalization sync test..")
188+
miner.generate(2)
189+
self.sync_blocks()
190+
191+
# assert that there is no budget finalization first.
192+
assert_equal(len(controller.mnfinalbudget("show")), 0)
193+
194+
# suggest the budget finalization and confirm the tx (+4 blocks).
195+
budgetFinHash = self.broadcastbudgetfinalization()
196+
assert budgetFinHash != ""
197+
time.sleep(2)
198+
199+
self.log.info("checking budget finalization sync..")
200+
check_budget_finalization_sync(self.nodes, 0, "OK")
201+
202+
self.log.info("budget finalization synced!, now voting for the budget finalization..")
203+
for mn in mns:
204+
voteResult = self.nodes[mn.idx].mnfinalbudget("vote", budgetFinHash)
205+
assert_equal(voteResult["detail"][0]["result"], "success")
206+
miner.generate(2)
207+
self.sync_blocks()
208+
check_budget_finalization_sync(self.nodes, 2, "OK")
209+
self.log.info("Both masternodes voted successfully.")
210+
211+
miner.generate(6)
212+
self.sync_blocks()
213+
addrInfo = miner.listreceivedbyaddress(0, False, False, firstProposal.paymentAddr)
214+
assert_equal(addrInfo[0]["amount"], firstProposal.amountPerCycle)
215+
216+
self.log.info("budget proposal paid!, all good")
217+
218+
# Check that the proposal info returns updated payment count
219+
expected_budget[0]["RemainingPaymentCount"] -= 1
220+
check_budgetprojection(self.nodes, expected_budget, self.log)
221+
222+
223+
if __name__ == '__main__':
224+
SDMNsGovernanceTest().main()

0 commit comments

Comments
 (0)