Skip to content

Commit

Permalink
Merge pull request #2232 from IntersectMBO/test_committee_min_size
Browse files Browse the repository at this point in the history
Add `test_committee_min_size` test
  • Loading branch information
mkoura authored Feb 14, 2024
2 parents de04d34 + 26a9e5b commit 9b6da04
Show file tree
Hide file tree
Showing 3 changed files with 494 additions and 60 deletions.
227 changes: 227 additions & 0 deletions cardano_node_tests/tests/tests_conway/conway_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from cardano_node_tests.cluster_management import cluster_management
from cardano_node_tests.tests import common
from cardano_node_tests.utils import clusterlib_utils
from cardano_node_tests.utils import governance_setup
from cardano_node_tests.utils import governance_utils

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -120,3 +122,228 @@ def get_pool_user(
).address, f"Stake address is not registered: {pool_user.stake.address}"

return pool_user


def cast_vote(
cluster_obj: clusterlib.ClusterLib,
governance_data: governance_setup.DefaultGovernance,
name_template: str,
payment_addr: clusterlib.AddressRecord,
action_txid: str,
action_ix: int,
approve_cc: tp.Optional[bool] = None,
approve_drep: tp.Optional[bool] = None,
approve_spo: tp.Optional[bool] = None,
) -> VotedVotes:
"""Cast a vote."""
votes_cc = []
votes_drep = []
votes_spo = []

if approve_cc is not None:
votes_cc = [
cluster_obj.g_conway_governance.vote.create_committee(
vote_name=f"{name_template}_cc{i}",
action_txid=action_txid,
action_ix=action_ix,
vote=get_yes_abstain_vote(i) if approve_cc else clusterlib.Votes.NO,
cc_hot_vkey_file=m.hot_vkey_file,
anchor_url=f"http://www.cc-vote{i}.com",
anchor_data_hash="5d372dca1a4cc90d7d16d966c48270e33e3aa0abcb0e78f0d5ca7ff330d2245d",
)
for i, m in enumerate(governance_data.cc_members, start=1)
]
if approve_drep is not None:
votes_drep = [
cluster_obj.g_conway_governance.vote.create_drep(
vote_name=f"{name_template}_drep{i}",
action_txid=action_txid,
action_ix=action_ix,
vote=get_yes_abstain_vote(i) if approve_drep else clusterlib.Votes.NO,
drep_vkey_file=d.key_pair.vkey_file,
anchor_url=f"http://www.drep-vote{i}.com",
anchor_data_hash="5d372dca1a4cc90d7d16d966c48270e33e3aa0abcb0e78f0d5ca7ff330d2245d",
)
for i, d in enumerate(governance_data.dreps_reg, start=1)
]
if approve_spo is not None:
votes_spo = [
cluster_obj.g_conway_governance.vote.create_spo(
vote_name=f"{name_template}_pool{i}",
action_txid=action_txid,
action_ix=action_ix,
vote=get_yes_abstain_vote(i) if approve_spo else clusterlib.Votes.NO,
cold_vkey_file=p.vkey_file,
anchor_url=f"http://www.spo-vote{i}.com",
anchor_data_hash="5d372dca1a4cc90d7d16d966c48270e33e3aa0abcb0e78f0d5ca7ff330d2245d",
)
for i, p in enumerate(governance_data.pools_cold, start=1)
]

cc_keys = [r.hot_skey_file for r in governance_data.cc_members] if votes_cc else []
drep_keys = [r.key_pair.skey_file for r in governance_data.dreps_reg] if votes_drep else []
spo_keys = [r.skey_file for r in governance_data.pools_cold] if votes_spo else []

tx_files = clusterlib.TxFiles(
vote_files=[
*[r.vote_file for r in votes_cc],
*[r.vote_file for r in votes_drep],
*[r.vote_file for r in votes_spo],
],
signing_key_files=[
payment_addr.skey_file,
*cc_keys,
*drep_keys,
*spo_keys,
],
)

# Make sure we have enough time to submit the votes in one epoch
clusterlib_utils.wait_for_epoch_interval(
cluster_obj=cluster_obj, start=1, stop=common.EPOCH_STOP_SEC_BUFFER
)

tx_output = clusterlib_utils.build_and_submit_tx(
cluster_obj=cluster_obj,
name_template=f"{name_template}_vote_",
src_address=payment_addr.address,
use_build_cmd=True,
tx_files=tx_files,
)

out_utxos = cluster_obj.g_query.get_utxo(tx_raw_output=tx_output)
assert (
clusterlib.filter_utxos(utxos=out_utxos, address=payment_addr.address)[0].amount
== clusterlib.calculate_utxos_balance(tx_output.txins) - tx_output.fee
), f"Incorrect balance for source address `{payment_addr.address}`"

gov_state = cluster_obj.g_conway_governance.query.gov_state()
_cur_epoch = cluster_obj.g_query.get_epoch()
save_gov_state(
gov_state=gov_state,
name_template=f"{name_template}_vote_{_cur_epoch}",
)
prop_vote = governance_utils.lookup_proposal(gov_state=gov_state, action_txid=action_txid)
assert not votes_cc or prop_vote["committeeVotes"], "No committee votes"
assert not votes_drep or prop_vote["dRepVotes"], "No DRep votes"
assert not votes_spo or prop_vote["stakePoolVotes"], "No stake pool votes"

return VotedVotes(cc=votes_cc, drep=votes_drep, spo=votes_spo)


def resign_ccs(
cluster_obj: clusterlib.ClusterLib,
name_template: str,
ccs_to_resign: tp.List[clusterlib.CCMember],
payment_addr: clusterlib.AddressRecord,
) -> clusterlib.TxRawOutput:
"""Resign multiple CC Members."""
res_certs = [
cluster_obj.g_conway_governance.committee.gen_cold_key_resignation_cert(
key_name=f"{name_template}_{i}",
cold_vkey_file=r.cold_vkey_file,
resignation_metadata_url=f"http://www.cc-resign{i}.com",
resignation_metadata_hash="5d372dca1a4cc90d7d16d966c48270e33e3aa0abcb0e78f0d5ca7ff330d2245d",
)
for i, r in enumerate(ccs_to_resign, start=1)
]

cc_cold_skeys = [r.cold_skey_file for r in ccs_to_resign]
tx_files = clusterlib.TxFiles(
certificate_files=res_certs,
signing_key_files=[payment_addr.skey_file, *cc_cold_skeys],
)

tx_output = clusterlib_utils.build_and_submit_tx(
cluster_obj=cluster_obj,
name_template=f"{name_template}_res",
src_address=payment_addr.address,
use_build_cmd=True,
tx_files=tx_files,
)

cluster_obj.wait_for_new_block(new_blocks=2)
res_committee_state = cluster_obj.g_conway_governance.query.committee_state()
for cc_member in ccs_to_resign:
member_key = f"keyHash-{cc_member.cold_vkey_hash}"
assert (
res_committee_state["committee"][member_key]["hotCredsAuthStatus"]["tag"]
== "MemberResigned"
), "CC Member not resigned"

return tx_output


def propose_change_constitution(
cluster_obj: clusterlib.ClusterLib,
name_template: str,
pool_user: clusterlib.PoolUser,
) -> tp.Tuple[clusterlib.ActionConstitution, str, int]:
"""Propose a constitution change."""
deposit_amt = cluster_obj.conway_genesis["govActionDeposit"]

anchor_url = "http://www.const-action.com"
anchor_data_hash = cluster_obj.g_conway_governance.get_anchor_data_hash(text=anchor_url)

constitution_url = "http://www.const-new.com"
constitution_hash = cluster_obj.g_conway_governance.get_anchor_data_hash(text=constitution_url)

prev_action_rec = governance_utils.get_prev_action(
action_type=governance_utils.PrevGovActionIds.CONSTITUTION,
gov_state=cluster_obj.g_conway_governance.query.gov_state(),
)

constitution_action = cluster_obj.g_conway_governance.action.create_constitution(
action_name=f"{name_template}_constitution",
deposit_amt=deposit_amt,
anchor_url=anchor_url,
anchor_data_hash=anchor_data_hash,
constitution_url=constitution_url,
constitution_hash=constitution_hash,
prev_action_txid=prev_action_rec.txid,
prev_action_ix=prev_action_rec.ix,
deposit_return_stake_vkey_file=pool_user.stake.vkey_file,
)

tx_files = clusterlib.TxFiles(
proposal_files=[constitution_action.action_file],
signing_key_files=[pool_user.payment.skey_file],
)

# Make sure we have enough time to submit the proposal in one epoch
clusterlib_utils.wait_for_epoch_interval(
cluster_obj=cluster_obj, start=1, stop=common.EPOCH_STOP_SEC_BUFFER
)

tx_output = clusterlib_utils.build_and_submit_tx(
cluster_obj=cluster_obj,
name_template=f"{name_template}_constitution_action",
src_address=pool_user.payment.address,
use_build_cmd=True,
tx_files=tx_files,
)

out_utxos = cluster_obj.g_query.get_utxo(tx_raw_output=tx_output)
assert (
clusterlib.filter_utxos(utxos=out_utxos, address=pool_user.payment.address)[0].amount
== clusterlib.calculate_utxos_balance(tx_output.txins) - tx_output.fee - deposit_amt
), f"Incorrect balance for source address `{pool_user.payment.address}`"

action_txid = cluster_obj.g_transaction.get_txid(tx_body_file=tx_output.out_file)
action_gov_state = cluster_obj.g_conway_governance.query.gov_state()
_cur_epoch = cluster_obj.g_query.get_epoch()
save_gov_state(
gov_state=action_gov_state,
name_template=f"{name_template}_constitution_action_{_cur_epoch}",
)
prop_action = governance_utils.lookup_proposal(
gov_state=action_gov_state, action_txid=action_txid
)
assert prop_action, "Create constitution action not found"
assert (
prop_action["action"]["tag"] == governance_utils.ActionTags.NEW_CONSTITUTION.value
), "Incorrect action tag"

action_ix = prop_action["actionId"]["govActionIx"]

return constitution_action, action_txid, action_ix
100 changes: 100 additions & 0 deletions cardano_node_tests/tests/tests_conway/test_no_confidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,103 @@ def _cast_vote(
message="; ".join(xfail_ledger_3979_msgs),
check_on_devel=False,
).finish_test()

@allure.link(helpers.get_vcs_link())
@pytest.mark.skipif(not configuration.HAS_CC, reason="Runs only on setup with CC")
@pytest.mark.long
def test_committee_min_size(
self,
cluster_manager: cluster_management.ClusterManager,
cluster_lock_governance: governance_setup.GovClusterT,
pool_user_lg: clusterlib.PoolUser,
):
"""Test that actions cannot be ratified when the number of CC members < committeeMinSize.
Only update-committee and no-confidence governance actions can be ratified.
* resign all CC Members but one
* try to ratify a "create constitution" action
* check that the action is not ratified
* reinstate the original CC members
"""
# pylint: disable=too-many-locals,too-many-statements
cluster, governance_data = cluster_lock_governance
temp_template = common.get_test_id(cluster)

# Linked user stories
req_cip14 = requirements.Req(id="CIP014", group=requirements.GroupsKnown.CHANG_US)

# Resign all CC Members but one

req_cip14.start(url=helpers.get_vcs_link())

cc_members_to_resign = governance_data.cc_members[1:]
conway_common.resign_ccs(
cluster_obj=cluster,
name_template=temp_template,
ccs_to_resign=cc_members_to_resign,
payment_addr=pool_user_lg.payment,
)

# Testnet will be in (sort of) state of no confidence, respin is needed in case of
# failure.
with cluster_manager.respin_on_failure():
# Try to ratify a "create constitution" action
(
__,
const_action_txid,
const_action_ix,
) = conway_common.propose_change_constitution(
cluster_obj=cluster,
name_template=f"{temp_template}_constitution",
pool_user=pool_user_lg,
)
conway_common.cast_vote(
cluster_obj=cluster,
governance_data=governance_data,
name_template=f"{temp_template}_constitution",
payment_addr=pool_user_lg.payment,
action_txid=const_action_txid,
action_ix=const_action_ix,
approve_cc=True,
approve_drep=True,
)

_cur_epoch = cluster.wait_for_new_epoch(padding_seconds=5)
approved_gov_state = cluster.g_conway_governance.query.gov_state()
conway_common.save_gov_state(
gov_state=approved_gov_state, name_template=f"{temp_template}_approved_{_cur_epoch}"
)
prop_const_action = governance_utils.lookup_proposal(
gov_state=approved_gov_state,
action_txid=const_action_txid,
action_ix=const_action_ix,
)
expire_epoch = prop_const_action["expiresAfter"] + 1

# Check that the action is not ratified
_cur_epoch = cluster.wait_for_new_epoch(
new_epochs=expire_epoch - _cur_epoch, padding_seconds=5
)
expire_gov_state = cluster.g_conway_governance.query.gov_state()
conway_common.save_gov_state(
gov_state=expire_gov_state, name_template=f"{temp_template}_expire_{_cur_epoch}"
)
assert governance_utils.lookup_expired_actions(
gov_state=expire_gov_state, action_txid=const_action_txid, action_ix=const_action_ix
), "Action not found in removed actions"

req_cip14.success()

# Reinstate the original CC members
governance_data = governance_setup.refresh_cc_keys(
cluster_obj=cluster,
cc_members=cc_members_to_resign,
governance_data=governance_data,
)
governance_setup.reinstate_committee(
cluster_obj=cluster,
governance_data=governance_data,
name_template=f"{temp_template}_reinstate",
pool_user=pool_user_lg,
)
Loading

0 comments on commit 9b6da04

Please sign in to comment.