Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions cardano_node_tests/tests/plutus_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@

@dataclasses.dataclass(frozen=True, order=True)
class ExecutionCost:
"""A script's integer execution budget and expected execution fee, not unit prices.

Attributes:
per_time: Number of execution steps consumed by the script.
per_space: Number of memory units consumed by the script.
fixed_cost: Expected script execution fee in Lovelace.
"""

per_time: int
per_space: int
fixed_cost: int
Expand Down Expand Up @@ -945,11 +953,30 @@ def check_plutus_costs(
raise AssertionError("\n".join(errors))


def get_cost_per_unit(protocol_params: dict) -> ExecutionCost:
"""Get execution cost per unit in Lovelace."""
return ExecutionCost(
per_time=protocol_params["executionUnitPrices"]["priceSteps"] or 0,
per_space=protocol_params["executionUnitPrices"]["priceMemory"] or 0,
@dataclasses.dataclass(frozen=True, order=True)
class CostPerUnit:
"""Protocol prices used to convert an execution budget into a Lovelace fee.

Attributes:
per_time: Lovelace price per execution step, which can be fractional.
per_space: Lovelace price per memory unit, which can be fractional.
fixed_cost: Protocol's fixed transaction fee (txFeeFixed), not a script fee.
"""

per_time: float
per_space: float
fixed_cost: int


def get_cost_per_unit(protocol_params: dict) -> CostPerUnit:
"""Get execution prices in Lovelace from numeric or rational-object parameters."""
return CostPerUnit(
per_time=float(
helpers.get_rational_pparam(protocol_params["executionUnitPrices"]["priceSteps"] or 0)
),
per_space=float(
helpers.get_rational_pparam(protocol_params["executionUnitPrices"]["priceMemory"] or 0)
),
fixed_cost=protocol_params["txFeeFixed"] or 0,
)

Expand Down
16 changes: 2 additions & 14 deletions cardano_node_tests/tests/tests_conway/conway_common.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Common functionality for Conway governance tests."""

import dataclasses
import fractions
import itertools
import json
import logging
Expand Down Expand Up @@ -36,17 +35,6 @@ def is_in_bootstrap(
return bool(clusterlib_utils.get_protocol_version(cluster_obj=cluster_obj) == 9)


def get_rational_pparam(pparam: float | dict) -> fractions.Fraction:
"""Return a rational pparam as a fraction.

The ledger can show a rational either as a number or as a dict with numerator and
denominator.
"""
if isinstance(pparam, dict):
return fractions.Fraction(pparam["numerator"], pparam["denominator"])
return fractions.Fraction(str(pparam))


def check_rational_pparam(
update_proposal: clusterlib_utils.UpdateProposal, protocol_params: dict
) -> bool:
Expand All @@ -62,8 +50,8 @@ def check_rational_pparam(
Returns:
bool: True when the pparam has the proposed value.
"""
pparam_val = get_rational_pparam(protocol_params[update_proposal.name])
return bool(pparam_val == fractions.Fraction(str(update_proposal.value)))
pparam_val = helpers.get_rational_pparam(protocol_params[update_proposal.name])
return bool(pparam_val == helpers.get_rational_pparam(update_proposal.value))


def get_committee_val(data: dict[str, tp.Any]) -> dict[str, tp.Any]:
Expand Down
17 changes: 13 additions & 4 deletions cardano_node_tests/tests/tests_conway/test_committee.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for Conway governance Constitutional Committee functionality."""

import fractions
import logging
import pathlib as pl
import random
Expand Down Expand Up @@ -480,6 +481,7 @@ def test_update_committee_action(
* Add CC Members
* Update committee threshold
* Check that the proposed changes are correct in `query gov-state`
* Check `governance action view` for fraction and decimal thresholds
"""
temp_template = common.get_test_id(cluster)
cc_size = 3
Expand Down Expand Up @@ -606,6 +608,9 @@ def test_update_committee_action(
dbsync_utils.check_committee_info(gov_state=gov_state, txid=txid)
[r.success() for r in (reqc.db010, reqc.db011)]

# Check both fraction and decimal thresholds in the action view.
governance_utils.check_action_view(cluster_obj=cluster, action_data=update_action)

@allure.link(helpers.get_vcs_link())
@pytest.mark.xdist_split(markers.XdSplits.governance, markers.XdSplits.heavy)
@pytest.mark.long
Expand Down Expand Up @@ -673,13 +678,17 @@ def test_add_rm_committee_members( # noqa: C901
# would not be possible to approve any action.
delegated_stake = governance_utils.get_delegated_stake(cluster_obj=cluster)
cur_pparams = cluster.g_query.get_protocol_params()
drep_constitution_threshold = cur_pparams["dRepVotingThresholds"]["committeeNormal"]
spo_constitution_threshold = cur_pparams["poolVotingThresholds"]["committeeNormal"]
drep_constitution_threshold = helpers.get_rational_pparam(
cur_pparams["dRepVotingThresholds"]["committeeNormal"]
)
spo_constitution_threshold = helpers.get_rational_pparam(
cur_pparams["poolVotingThresholds"]["committeeNormal"]
)
is_drep_total_below_threshold = (
delegated_stake.drep / delegated_stake.total_lovelace
fractions.Fraction(delegated_stake.drep, delegated_stake.total_lovelace)
) < drep_constitution_threshold
is_spo_total_below_threshold = (
delegated_stake.spo / delegated_stake.total_lovelace
fractions.Fraction(delegated_stake.spo, delegated_stake.total_lovelace)
) < spo_constitution_threshold

# Auth keys for CC members
Expand Down
18 changes: 10 additions & 8 deletions cardano_node_tests/tests/tests_conway/test_pparam_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,8 @@ def _get_rational_str(value: float) -> str:
def _check_w_denominator(
update_proposal: clusterlib_utils.UpdateProposal, pparam: float | dict
) -> bool:
exp_val: float | dict | str = pparam
if isinstance(pparam, dict):
exp_val = f"{pparam['numerator']}/{pparam['denominator']}"
return bool(update_proposal.value == exp_val)
"""Compare rational parameters returned as decimals or numerator/denominator objects."""
return helpers.get_rational_pparam(update_proposal.value) == helpers.get_rational_pparam(pparam)


def _check_max_tx_execution_units(
Expand Down Expand Up @@ -366,13 +364,17 @@ def test_pparam_update( # noqa: C901
# missing votes would approve the action.
delegated_stake = governance_utils.get_delegated_stake(cluster_obj=cluster)
cur_pparams = cluster.g_query.get_protocol_params()
drep_constitution_threshold = cur_pparams["dRepVotingThresholds"]["ppGovGroup"]
spo_constitution_threshold = cur_pparams["poolVotingThresholds"]["ppSecurityGroup"]
drep_constitution_threshold = helpers.get_rational_pparam(
cur_pparams["dRepVotingThresholds"]["ppGovGroup"]
)
spo_constitution_threshold = helpers.get_rational_pparam(
cur_pparams["poolVotingThresholds"]["ppSecurityGroup"]
)
is_drep_total_below_threshold = (
delegated_stake.drep / delegated_stake.total_lovelace
fractions.Fraction(delegated_stake.drep, delegated_stake.total_lovelace)
) < drep_constitution_threshold
is_spo_total_below_threshold = (
delegated_stake.spo / delegated_stake.total_lovelace
fractions.Fraction(delegated_stake.spo, delegated_stake.total_lovelace)
) < spo_constitution_threshold

# PParam groups
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1563,9 +1563,9 @@ def test_quorum_loss_and_recovery(
# The quorum is read from the current protocol parameters, and the seat weights
# from the committee of the current epoch. Neither changes at an epoch boundary
# unless the pools or their stake change, which the cluster lock rules out.
quorum = fractions.Fraction(
quorum = helpers.get_rational_pparam(
cluster.g_query.get_protocol_params()["leiosQuorumStakeThreshold"]
).limit_denominator()
)
stopped_pool_id = helpers.get_pool_id_hex(
delegation.get_pool_id(
cluster_obj=cluster,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def test_committee_size_and_quorum(
quorum = fractions.Fraction(
min(pool_shares.values()) * fractions.Fraction(QUORUM_STAKE_SHARE_RATIO)
).limit_denominator(1000)
init_quorum = conway_common.get_rational_pparam(
init_quorum = helpers.get_rational_pparam(
cluster.g_query.get_protocol_params()["leiosQuorumStakeThreshold"]
)
assert 0 < quorum < init_quorum, (
Expand Down
31 changes: 16 additions & 15 deletions cardano_node_tests/tests/tests_plutus/test_spend_negative_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,8 @@ def fund_script_guessing_game_v2(
def cost_per_unit(
self,
cluster: clusterlib.ClusterLib,
) -> plutus_common.ExecutionCost:
) -> plutus_common.CostPerUnit:
"""Return the current execution unit prices and fixed transaction fee."""
return plutus_common.get_cost_per_unit(
protocol_params=cluster.g_query.get_protocol_params()
)
Expand All @@ -824,7 +825,7 @@ def _failed_tx_build(
collateral_utxos: list[clusterlib.UTXOData],
redeemer_content: str,
dst_addr: clusterlib.AddressRecord,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
) -> str:
"""Try to build a Tx and expect failure."""
Expand Down Expand Up @@ -875,7 +876,7 @@ def _int_out_of_range(
collateral_utxos: list[clusterlib.UTXOData],
redeemer_value: int,
dst_addr: clusterlib.AddressRecord,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
) -> str:
"""Try to spend a locked UTxO with redeemer int value that is not in allowed range."""
Expand Down Expand Up @@ -942,7 +943,7 @@ def test_wrong_value_inside_range(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: int,
):
Expand Down Expand Up @@ -1053,7 +1054,7 @@ def test_wrong_value_bellow_range(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: int,
):
Expand Down Expand Up @@ -1108,7 +1109,7 @@ def test_wrong_value_above_range(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: int,
):
Expand Down Expand Up @@ -1162,7 +1163,7 @@ def test_wrong_type(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: bytes,
):
Expand Down Expand Up @@ -1265,7 +1266,7 @@ def test_too_big(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: bytes,
):
Expand Down Expand Up @@ -1354,7 +1355,7 @@ def test_json_schema_typed_int_bytes_declared(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: bytes,
):
Expand Down Expand Up @@ -1407,7 +1408,7 @@ def test_json_schema_untyped_int_bytes_declared(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: bytes,
):
Expand Down Expand Up @@ -1459,7 +1460,7 @@ def test_json_schema_typed_bytes_int_declared(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: int,
):
Expand Down Expand Up @@ -1511,7 +1512,7 @@ def test_json_schema_untyped_bytes_int_declared(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: int,
):
Expand Down Expand Up @@ -1563,7 +1564,7 @@ def test_invalid_json(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_value: str,
):
Expand Down Expand Up @@ -1622,7 +1623,7 @@ def test_json_schema_typed_invalid_type(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_type: str,
):
Expand Down Expand Up @@ -1691,7 +1692,7 @@ def test_json_schema_untyped_invalid_type(
cluster: clusterlib.ClusterLib,
fund_script_guessing_game_v1: FundTupleT,
fund_script_guessing_game_v2: FundTupleT,
cost_per_unit: plutus_common.ExecutionCost,
cost_per_unit: plutus_common.CostPerUnit,
plutus_version: str,
redeemer_type: str,
):
Expand Down
11 changes: 5 additions & 6 deletions cardano_node_tests/utils/dbsync_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,13 +936,10 @@ def check_plutus_costs(


def _get_float_pparam(pparam: tp.Any) -> float | None:
"""Convert a rational parameter to a db-sync float, preserving missing values."""
if pparam is None:
return None
if isinstance(pparam, dict):
numerator = pparam.get("numerator", 0)
denominator = pparam.get("denominator", 1)
return float(numerator / denominator)
return float(pparam)
return float(helpers.get_rational_pparam(pparam))


def map_params_to_db_convention(*, pparams: dict) -> dict[str, tp.Any]:
Expand Down Expand Up @@ -974,7 +971,9 @@ def map_params_to_db_convention(*, pparams: dict) -> dict[str, tp.Any]:
"treasury_growth_rate": _get_float_pparam(pparams.get("treasuryCut")),
"min_pool_cost": pparams.get("minPoolCost"),
"coins_per_utxo_size": pparams.get("utxoCostPerByte"),
"min_fee_ref_script_cost_per_byte": pparams.get("minFeeRefScriptCostPerByte"),
"min_fee_ref_script_cost_per_byte": _get_float_pparam(
pparams.get("minFeeRefScriptCostPerByte")
),
"price_mem": price_mem,
"price_step": price_steps,
# Technical proposals group
Expand Down
8 changes: 6 additions & 2 deletions cardano_node_tests/utils/governance_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ def _get_cvkey_hash(*, member: clusterlib.CCMember) -> str:
_prev_action_ref(action_data),
removed_members or [],
added_members,
float(action_data.threshold),
helpers.get_rational_pparam(action_data.threshold),
],
"tag": ActionTags.UPDATE_COMMITTEE.value,
}
Expand Down Expand Up @@ -553,7 +553,7 @@ def check_action_view(
return_addr_vkey_hash: str = "",
recv_addr_vkey_hash: str = "",
) -> None:
"""Check `governance action view` output."""
"""Check `governance action view` output, comparing committee thresholds numerically."""
if not return_addr_vkey_hash:
if action_data.deposit_return_stake_vkey_file:
return_addr_vkey_hash = cluster_obj.g_stake_address.get_stake_vkey_hash(
Expand Down Expand Up @@ -588,6 +588,10 @@ def check_action_view(

action_view_out = cluster_obj.g_governance.action.view(action_file=action_data.action_file)

if isinstance(action_data, clusterlib.ActionUpdateCommittee):
contents = action_view_out["governance action"]["contents"]
contents[3] = helpers.get_rational_pparam(contents[3])

assert action_view_out == expected_action_out, f"{action_view_out} != {expected_action_out}"


Expand Down
Loading
Loading