Skip to content

Commit 765c6b8

Browse files
authored
Merge pull request #2891 from IntersectMBO/add_test_invalid_cc_member_vote
feat(tests): add invalid CC member vote test
2 parents fbb9c54 + 4b4e282 commit 765c6b8

File tree

2 files changed

+271
-1
lines changed

2 files changed

+271
-1
lines changed

cardano_node_tests/tests/tests_conway/conway_common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def submit_vote(
178178
cluster_obj: clusterlib.ClusterLib,
179179
name_template: str,
180180
payment_addr: clusterlib.AddressRecord,
181-
votes: list[governance_utils.VotesAllT],
181+
votes: tp.Sequence[governance_utils.VotesAllT],
182182
keys: list[clusterlib.FileType],
183183
script_votes: clusterlib.OptionalScriptVotes = (),
184184
submit_method: str = "",

cardano_node_tests/tests/tests_conway/test_committee.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import logging
44
import pathlib as pl
5+
import random
6+
import re
57
import typing as tp
68

79
import allure
810
import hypothesis
911
import hypothesis.strategies as st
1012
import pytest
13+
import pytest_subtests
1114
from _pytest.fixtures import FixtureRequest
1215
from cardano_clusterlib import clusterlib
1316

@@ -84,6 +87,23 @@ def pool_user_lg(
8487
)
8588

8689

90+
@pytest.fixture
91+
def pool_user_ug(
92+
cluster_manager: cluster_management.ClusterManager,
93+
cluster_use_governance: governance_utils.GovClusterT,
94+
) -> clusterlib.PoolUser:
95+
"""Create a pool user for "use governance"."""
96+
cluster, __ = cluster_use_governance
97+
key = helpers.get_current_line_str()
98+
name_template = common.get_test_id(cluster)
99+
return conway_common.get_registered_pool_user(
100+
cluster_manager=cluster_manager,
101+
name_template=name_template,
102+
cluster_obj=cluster,
103+
caching_key=key,
104+
)
105+
106+
87107
class TestCommittee:
88108
"""Tests for Constitutional Committee."""
89109

@@ -128,6 +148,256 @@ def test_register_hot_key_no_cc_member(
128148
err_str = str(excinfo.value)
129149
assert "ConwayCommitteeIsUnknown" in err_str, err_str
130150

151+
@allure.link(helpers.get_vcs_link())
152+
@pytest.mark.smoke
153+
def test_invalid_cc_member_vote( # noqa: C901
154+
self,
155+
cluster_use_governance: governance_utils.GovClusterT,
156+
pool_user_ug: clusterlib.PoolUser,
157+
subtests: pytest_subtests.SubTests,
158+
):
159+
"""Try to vote with invalid CC member.
160+
161+
Expect failure.
162+
163+
* Create CC memeber hot and cold keys
164+
* Create a pparam update proposal
165+
* Try to vote on the pparam update proposal with the unauthorized hot key
166+
* Expect `VotersDoNotExist` error on submit
167+
* Submit a proposal to add the new CC member, do not vote on it
168+
* Submit authorization certificate for the proposed CC member
169+
* Try to vote on the pparam update proposal with the hot keys of the proposed CC member
170+
* Expect `ConwayMempoolFailure "Unelected committee members are not allowed to cast votes`
171+
error on submit
172+
"""
173+
cluster, governance_data = cluster_use_governance
174+
temp_template = common.get_test_id(cluster)
175+
176+
submit_methods = [submit_utils.SubmitMethods.CLI]
177+
if submit_utils.is_submit_api_available():
178+
submit_methods.append(submit_utils.SubmitMethods.API)
179+
180+
cc_auth_record = governance_utils.get_cc_member_auth_record(
181+
cluster_obj=cluster,
182+
name_template=temp_template,
183+
)
184+
cc_member = clusterlib.CCMember(
185+
epoch=10_000,
186+
cold_vkey_file=cc_auth_record.cold_key_pair.vkey_file,
187+
cold_skey_file=cc_auth_record.cold_key_pair.skey_file,
188+
)
189+
cc_member_key = f"keyHash-{cc_auth_record.key_hash}"
190+
cc_key_member = governance_utils.CCKeyMember(
191+
cc_member=cc_member,
192+
hot_keys=governance_utils.CCHotKeys(
193+
hot_skey_file=cc_auth_record.hot_key_pair.skey_file,
194+
hot_vkey_file=cc_auth_record.hot_key_pair.vkey_file,
195+
),
196+
)
197+
198+
def _propose_pparam_change() -> conway_common.PParamPropRec:
199+
anchor_data = governance_utils.get_default_anchor_data()
200+
prev_action_rec = governance_utils.get_prev_action(
201+
action_type=governance_utils.PrevGovActionIds.PPARAM_UPDATE,
202+
gov_state=cluster.g_conway_governance.query.gov_state(),
203+
)
204+
205+
proposals = [
206+
clusterlib_utils.UpdateProposal(
207+
arg="--drep-activity",
208+
value=random.randint(1, 255),
209+
name="dRepActivity",
210+
),
211+
]
212+
213+
prop_rec = conway_common.propose_pparams_update(
214+
cluster_obj=cluster,
215+
name_template=f"{temp_template}_drep_activity",
216+
anchor_url=anchor_data.url,
217+
anchor_data_hash=anchor_data.hash,
218+
pool_user=pool_user_ug,
219+
proposals=proposals,
220+
prev_action_rec=prev_action_rec,
221+
)
222+
223+
return prop_rec
224+
225+
prop_rec = _propose_pparam_change()
226+
227+
vote_cc_all = [
228+
cluster.g_conway_governance.vote.create_committee(
229+
vote_name=f"{temp_template}_all_cc{i}",
230+
action_txid=prop_rec.action_txid,
231+
action_ix=prop_rec.action_ix,
232+
vote=clusterlib.Votes.YES,
233+
cc_hot_vkey_file=m.hot_keys.hot_vkey_file,
234+
)
235+
for i, m in enumerate((*governance_data.cc_key_members, cc_key_member), start=1)
236+
]
237+
vote_keys_all = [
238+
*[r.hot_keys.hot_skey_file for r in (*governance_data.cc_key_members, cc_key_member)],
239+
]
240+
241+
vote_cc_one = cluster.g_conway_governance.vote.create_committee(
242+
vote_name=f"{temp_template}_one_cc",
243+
action_txid=prop_rec.action_txid,
244+
action_ix=prop_rec.action_ix,
245+
vote=clusterlib.Votes.YES,
246+
cc_hot_vkey_file=cc_key_member.hot_keys.hot_vkey_file,
247+
)
248+
vote_key_one = cc_key_member.hot_keys.hot_skey_file
249+
250+
def _submit_vote(scenario: str, build_method: str, submit_method: str) -> None:
251+
conway_common.submit_vote(
252+
cluster_obj=cluster,
253+
name_template=f"{temp_template}_{scenario}_{build_method}_{submit_method}",
254+
payment_addr=pool_user_ug.payment,
255+
votes=vote_cc_all if "_all_" in scenario else [vote_cc_one],
256+
keys=vote_keys_all if "_all_" in scenario else [vote_key_one],
257+
submit_method=submit_method,
258+
use_build_cmd=build_method == "build",
259+
)
260+
261+
for smethod in submit_methods:
262+
for build_method in ("build_raw", "build"):
263+
for scenario in ("all_cc_one_nonexistent", "all_cc_all_nonexistent"):
264+
with subtests.test(id=f"{scenario}_{build_method}_{smethod}"):
265+
with pytest.raises(
266+
(clusterlib.CLIError, submit_api.SubmitApiError)
267+
) as excinfo:
268+
_submit_vote(
269+
scenario=scenario,
270+
build_method=build_method,
271+
submit_method=smethod,
272+
)
273+
err_str = str(excinfo.value)
274+
assert "(VotersDoNotExist" in err_str, err_str
275+
276+
# Update committee action is not supported in bootstrap period
277+
if conway_common.is_in_bootstrap(cluster_obj=cluster):
278+
return
279+
280+
def _propose_new_member() -> None:
281+
deposit_amt = cluster.conway_genesis["govActionDeposit"]
282+
anchor_data = governance_utils.get_default_anchor_data()
283+
prev_action_rec = governance_utils.get_prev_action(
284+
action_type=governance_utils.PrevGovActionIds.COMMITTEE,
285+
gov_state=cluster.g_conway_governance.query.gov_state(),
286+
)
287+
288+
update_action = cluster.g_conway_governance.action.update_committee(
289+
action_name=temp_template,
290+
deposit_amt=deposit_amt,
291+
anchor_url=anchor_data.url,
292+
anchor_data_hash=anchor_data.hash,
293+
threshold="2/3",
294+
add_cc_members=[cc_member],
295+
prev_action_txid=prev_action_rec.txid,
296+
prev_action_ix=prev_action_rec.ix,
297+
deposit_return_stake_vkey_file=pool_user_ug.stake.vkey_file,
298+
)
299+
300+
tx_files = clusterlib.TxFiles(
301+
proposal_files=[update_action.action_file],
302+
signing_key_files=[
303+
pool_user_ug.payment.skey_file,
304+
cc_auth_record.cold_key_pair.skey_file,
305+
],
306+
)
307+
308+
tx_output = clusterlib_utils.build_and_submit_tx(
309+
cluster_obj=cluster,
310+
name_template=temp_template,
311+
src_address=pool_user_ug.payment.address,
312+
use_build_cmd=True,
313+
tx_files=tx_files,
314+
deposit=deposit_amt,
315+
)
316+
317+
out_utxos = cluster.g_query.get_utxo(tx_raw_output=tx_output)
318+
assert (
319+
clusterlib.filter_utxos(utxos=out_utxos, address=pool_user_ug.payment.address)[
320+
0
321+
].amount
322+
== clusterlib.calculate_utxos_balance(tx_output.txins) - tx_output.fee - deposit_amt
323+
), f"Incorrect balance for source address `{pool_user_ug.payment.address}`"
324+
325+
txid = cluster.g_transaction.get_txid(tx_body_file=tx_output.out_file)
326+
gov_state = cluster.g_conway_governance.query.gov_state()
327+
prop = governance_utils.lookup_proposal(gov_state=gov_state, action_txid=txid)
328+
assert prop, "Update committee action not found"
329+
assert (
330+
prop["proposalProcedure"]["govAction"]["tag"]
331+
== governance_utils.ActionTags.UPDATE_COMMITTEE.value
332+
), "Incorrect action tag"
333+
334+
def _auth_hot_keys() -> None:
335+
"""Authorize the hot keys."""
336+
tx_files_auth = clusterlib.TxFiles(
337+
certificate_files=[cc_auth_record.auth_cert],
338+
signing_key_files=[
339+
pool_user_ug.payment.skey_file,
340+
cc_auth_record.cold_key_pair.skey_file,
341+
],
342+
)
343+
344+
tx_output_auth = clusterlib_utils.build_and_submit_tx(
345+
cluster_obj=cluster,
346+
name_template=f"{temp_template}_auth",
347+
src_address=pool_user_ug.payment.address,
348+
use_build_cmd=True,
349+
tx_files=tx_files_auth,
350+
)
351+
352+
out_utxos_auth = cluster.g_query.get_utxo(tx_raw_output=tx_output_auth)
353+
assert (
354+
clusterlib.filter_utxos(utxos=out_utxos_auth, address=pool_user_ug.payment.address)[
355+
0
356+
].amount
357+
== clusterlib.calculate_utxos_balance(tx_output_auth.txins) - tx_output_auth.fee
358+
), f"Incorrect balance for source address `{pool_user_ug.payment.address}`"
359+
360+
cluster.wait_for_new_block(new_blocks=2)
361+
auth_committee_state = cluster.g_conway_governance.query.committee_state()
362+
auth_epoch = cluster.g_query.get_epoch()
363+
conway_common.save_committee_state(
364+
committee_state=auth_committee_state,
365+
name_template=f"{temp_template}_auth_{auth_epoch}",
366+
)
367+
auth_member_rec = auth_committee_state["committee"][cc_member_key]
368+
assert auth_member_rec["hotCredsAuthStatus"]["tag"] == "MemberAuthorized", (
369+
"CC Member was NOT authorized"
370+
)
371+
assert not auth_member_rec["expiration"], "CC Member should not be elected"
372+
assert auth_member_rec["status"] == "Unrecognized", "CC Member should not be recognized"
373+
374+
# Make sure we have enough time to submit the proposals and vote in one epoch
375+
clusterlib_utils.wait_for_epoch_interval(
376+
cluster_obj=cluster, start=1, stop=common.EPOCH_STOP_SEC_BUFFER
377+
)
378+
379+
_propose_new_member()
380+
_auth_hot_keys()
381+
382+
for smethod in submit_methods:
383+
for build_method in ("build_raw", "build"):
384+
for scenario in ("all_cc_one_unelected", "all_cc_all_unelected"):
385+
with subtests.test(id=f"{scenario}_{build_method}_{smethod}"):
386+
with pytest.raises(
387+
(clusterlib.CLIError, submit_api.SubmitApiError)
388+
) as excinfo:
389+
_submit_vote(
390+
scenario=scenario,
391+
build_method=build_method,
392+
submit_method=smethod,
393+
)
394+
err_str = str(excinfo.value)
395+
assert re.search(
396+
"ConwayMempoolFailure .*Unelected committee members are not allowed "
397+
"to cast votes:",
398+
err_str,
399+
), err_str
400+
131401
@allure.link(helpers.get_vcs_link())
132402
@submit_utils.PARAM_SUBMIT_METHOD
133403
@common.PARAM_USE_BUILD_CMD

0 commit comments

Comments
 (0)