|
2 | 2 |
|
3 | 3 | import logging |
4 | 4 | import pathlib as pl |
| 5 | +import random |
| 6 | +import re |
5 | 7 | import typing as tp |
6 | 8 |
|
7 | 9 | import allure |
8 | 10 | import hypothesis |
9 | 11 | import hypothesis.strategies as st |
10 | 12 | import pytest |
| 13 | +import pytest_subtests |
11 | 14 | from _pytest.fixtures import FixtureRequest |
12 | 15 | from cardano_clusterlib import clusterlib |
13 | 16 |
|
@@ -84,6 +87,23 @@ def pool_user_lg( |
84 | 87 | ) |
85 | 88 |
|
86 | 89 |
|
| 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 | + |
87 | 107 | class TestCommittee: |
88 | 108 | """Tests for Constitutional Committee.""" |
89 | 109 |
|
@@ -128,6 +148,256 @@ def test_register_hot_key_no_cc_member( |
128 | 148 | err_str = str(excinfo.value) |
129 | 149 | assert "ConwayCommitteeIsUnknown" in err_str, err_str |
130 | 150 |
|
| 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 | + |
131 | 401 | @allure.link(helpers.get_vcs_link()) |
132 | 402 | @submit_utils.PARAM_SUBMIT_METHOD |
133 | 403 | @common.PARAM_USE_BUILD_CMD |
|
0 commit comments