diff --git a/agent_docs/fixtures_caching.md b/agent_docs/fixtures_caching.md index a79d7dd5a..33a57aab7 100644 --- a/agent_docs/fixtures_caching.md +++ b/agent_docs/fixtures_caching.md @@ -52,7 +52,7 @@ Use `cluster_manager.cache_fixture()` to cache fixture values: Without a `key`, `cache_fixture()` uses the file name and line number of the fixture that calls it, so every fixture gets its own cache entry. Pass an explicit `key` only when the default doesn't fit: - Inside a helper called from several fixtures, the line number of the helper would be shared by all callers. The helper must take the key from the caller - this is why the address helpers in `cardano_node_tests/tests/addrs_common.py` take `caching_key`, and fixtures pass `caching_key=helpers.get_current_line_str()`. -- When one fixture caches different values for different parameters, include the parameter in the key (e.g. `caching_key=f"plutusv3_builtins_batch_testing_{variant}"`), otherwise all variants get the value cached for the first one. +- When one fixture caches different values for different parameters, include the parameter in the key (e.g. `caching_key=f"plutus_builtins_batch_testing_{variant}"`), otherwise all variants get the value cached for the first one. Prefer the `addrs_common` helpers (`get_payment_addrs`, `get_payment_addr`, etc.) over calling `cache_fixture` directly for funded addresses. With `caching_key`, they create the addresses once and re-fund them on later calls when the balance drops below `min_amount`. When only `amount` is passed, the addresses are funded once and never re-funded, so a cached address can run out of funds over a long run. diff --git a/agent_docs/multi_index_array.md b/agent_docs/multi_index_array.md index d5fa0466b..38f899d61 100644 --- a/agent_docs/multi_index_array.md +++ b/agent_docs/multi_index_array.md @@ -85,22 +85,56 @@ the same names and order as the PlutusV3 ones, and both languages use the same builtin semantics variant. The genesis from cardonnay gives PlutusV4 only 251 parameters. Plutus sets the missing parameters to `maxBound`, so a PlutusV4 policy that uses `multiIndexArray` exceeds any budget without this update. The -fixture proposes both languages even when the PlutusV4 scenarios are skipped, -and then checks only the cost models of the tested languages. +shared setup now uses `cost_models_list_369_v3_v4.json`, which preserves those +355 parameters and adds Value builtin costing. It proposes and checks both languages +at PV12+, or V3 alone at PV10/PV11, once for all builtin groups in the same test. +At PV10/PV11 the test installs all 369 V3 parameters, then requires batch7 +protocol rejection: updated costing must not activate the builtins. PV9 keeps +its existing costing and still runs pre-activation rejection checks; the shared +update helper's DRep votes are forbidden during Conway bootstrap. The CLI must also decode PV12 scripts in `transaction policyid` and transaction witnesses. Some development builds accept `dijkstra` commands but still decode -policies as Conway/PV11. The fixture checks policy decoding before governance setup -so this incompatibility fails immediately rather than after several epochs. +policies as Conway/PV11. For activated builtin groups, the shared test checks each +ledger/Core group's policy decoding independently before running its scenarios. +A known incompatibility skips only that variant; a V3 decoding failure cannot +suppress V4 coverage (or vice versa). Other groups still use the shared governance +setup. -With a running PV12 cluster whose node and CLI support batch7: +Before activation, an availability error is an expected rejection. An unknown +builtin tag instead skips the affected subtest: that older CLI decoder cannot +establish protocol rejection, so its incompatibility is not counted as a pass. + +Use a `local_fast` PV12 cluster whose node and CLI support batch7. These builtin +tests do not depend on Leios features such as endorsement blocks. ```sh -PROTOCOL_VERSION=12 COMMAND_ERA=dijkstra pytest -v \ - cardano_node_tests/tests/tests_plutus_v3/test_multi_index_array.py +TESTNET_VARIANT=local_fast PROTOCOL_VERSION=12 COMMAND_ERA=dijkstra pytest -v \ + cardano_node_tests/tests/tests_plutus/test_builtins.py \ + -k test_builtins_new_cost ``` -Add `TESTNET_VARIANT=leios_fast` if that is the variant of the running cluster. -Pytest discovers this module automatically; the existing `plutus` and `team_plutus` -CI selections include it. It skips before PV12. Collection or local UPLC evaluation +Run the same updated-cost selection with `PROTOCOL_VERSION=11 COMMAND_ERA=conway` +on a `local_fast` PV11 cluster for batch7 V3 pre-activation coverage. V4 groups +skip because the ledger language itself is unavailable before PV12. If an +upgrade step prevents the cost-model update, pre-activation groups still run +with the existing model and must reject the scripts. + +For negative coverage without updating cost models, select +`-k 'test_builtins_old and batch7'` instead. This covers multiIndexArray and Value +policies for each applicable ledger language and both PLC versions, using the +actual protocol version and each language's cost model (355 and 369 parameters +respectively). Fully enabled groups skip; pre-activation rejection and missing +costing remain separate cases. V4 groups require the Dijkstra transaction era +and PV12; V3 groups can also exercise builtin rejection before PV12. Use `-k v3` +or `-k v4` with the old-state test selection to select a particular language. + +When a cost-model update is needed, the shared setup checks a worst-case wait of +five epochs against the local 90-minute limit. Long-epoch variants such as +`leios_fast` can therefore skip the updated-cost test. This guard does not apply +when the cost models are already current or to the old-state negative tests. + +Pytest discovers the shared test automatically; the existing `plutus` and `team_plutus` +CI selections include it. Batch7 V3 groups also run before PV12 to check rejection. +Collection or local UPLC evaluation does not establish that the E2E tests pass or that budgets were verified on a node. diff --git a/cardano_node_tests/tests/plutus_common.py b/cardano_node_tests/tests/plutus_common.py index 90a8ab7a8..67340fc62 100644 --- a/cardano_node_tests/tests/plutus_common.py +++ b/cardano_node_tests/tests/plutus_common.py @@ -819,6 +819,7 @@ def _get_multi_index_array_script(name: str, ledger: str, core_version: str) -> class ValueBuiltinScripts(tp.NamedTuple): """CIP-0168 minting policies for one ledger/Core combination.""" + cost_model: str succeeding: tuple[PlutusScriptData, ...] failing: tuple[PlutusScriptData, ...] @@ -873,6 +874,7 @@ def _get_value_builtin_script(name: str, ledger: str, core_version: str) -> Plut VALUE_BUILTIN_SCRIPTS = { f"{ledger}_{core_version.replace('.', '')}": ValueBuiltinScripts( + cost_model=f"Plutus{ledger.upper()}", succeeding=tuple( _get_value_builtin_script(name=n, ledger=ledger, core_version=core_version) for n in _SUCCEEDING_VALUE_BUILTIN_NAMES diff --git a/cardano_node_tests/tests/tests_plutus_v3/builtins_mint_build.py b/cardano_node_tests/tests/tests_plutus/builtins_mint_build.py similarity index 89% rename from cardano_node_tests/tests/tests_plutus_v3/builtins_mint_build.py rename to cardano_node_tests/tests/tests_plutus/builtins_mint_build.py index f7d505aa7..edc763739 100644 --- a/cardano_node_tests/tests/tests_plutus_v3/builtins_mint_build.py +++ b/cardano_node_tests/tests/tests_plutus/builtins_mint_build.py @@ -1,4 +1,4 @@ -"""Shared minting scenarios for PlutusV3+ builtin E2E tests.""" +"""Shared minting scenarios for Plutus builtin E2E tests across ledger languages.""" import enum import logging @@ -6,6 +6,7 @@ import pathlib as pl import typing as tp +import pytest import pytest_subtests from cardano_clusterlib import clusterlib @@ -102,6 +103,27 @@ class PendingMint(tp.NamedTuple): plutus_costs: list[dict] +def _get_policyid( + cluster_obj: clusterlib.ClusterLib, script_file: pl.Path, is_prot_version_ok: bool +) -> str: + """Return a policy ID, or an empty string for expected pre-activation rejection. + + Skip unknown builtin tags separately: an older CLI decoder cannot establish + protocol rejection. Capable decoders must still report the availability error. + """ + try: + return cluster_obj.g_transaction.get_policyid(script_file) + except clusterlib.CLIError as exc: + if is_prot_version_ok: + raise + exc_value = str(exc) + if "Failed to decode builtin tag" in exc_value: + pytest.skip(f"CLI cannot decode builtin tag in {script_file.name}: {exc_value}") + with common.allow_unstable_error_messages(): + assert "not available" in exc_value and "protocol version" in exc_value, exc_value + return "" + + def run_scenario( cluster_obj: clusterlib.ClusterLib, temp_template: str, @@ -126,10 +148,12 @@ def run_scenario( A script expected to fail, or a script submitted with a protocol version that doesn't support it, must not be accepted. A script expected to succeed can be accepted with an old cost model, e.g. casing on constants doesn't use any new cost model parameters. + Before protocol activation, rejection can happen already during policy decoding. + Unknown builtin tags skip the scenario as a CLI incompatibility, not a pass. Returns: PendingMint | None: The submitted minting transaction, or None when the minting - failed as expected. + failed as expected. """ lovelace_amount = 2_000_000 token_amount = 5 @@ -138,7 +162,16 @@ def run_scenario( # Mint the "qacoin" - policyid = cluster_obj.g_transaction.get_policyid(plutus_v_record.script_file) + # New builtins can be rejected during policy decoding, before transaction + # construction. This is expected only before their protocol activation. + policyid = _get_policyid( + cluster_obj=cluster_obj, + script_file=plutus_v_record.script_file, + is_prot_version_ok=is_prot_version_ok, + ) + if not policyid: + return None + asset_name = f"qacoin{clusterlib.get_rand_str(4)}".encode().hex() token = f"{policyid}.{asset_name}" mint_txouts = [clusterlib.TxOut(address=issuer_addr.address, amount=token_amount, coin=token)] @@ -198,7 +231,7 @@ def _dump_cost() -> list[dict]: except clusterlib.CLIError as excp: str_excp = str(excp) if not is_prot_version_ok and ( - "not available in language PlutusV3 at and protocol version" in str_excp + ("not available" in str_excp and "protocol version" in str_excp) or "Script evaluation error" in str_excp ): return None @@ -290,7 +323,7 @@ def confirm_mints( return unconfirmed -def run_plutusv3_builtins_test( +def run_builtins_test( cluster_manager: cluster_management.ClusterManager, cluster_obj: clusterlib.ClusterLib, temp_template: str, @@ -343,7 +376,7 @@ def run_plutusv3_builtins_test( cluster_obj=cluster_obj, num=2, fund_idx=[0], - caching_key=f"plutusv3_builtins_batch_testing_{variant}", + caching_key=f"plutus_builtins_batch_testing_{variant}", amount=max(1_000_000_000, total_amount), min_amount=total_amount, ) diff --git a/cardano_node_tests/tests/tests_plutus/conftest.py b/cardano_node_tests/tests/tests_plutus/conftest.py index 45cbec13c..20be351bd 100644 --- a/cardano_node_tests/tests/tests_plutus/conftest.py +++ b/cardano_node_tests/tests/tests_plutus/conftest.py @@ -14,3 +14,18 @@ def cluster( ) -> clusterlib.ClusterLib: """Return instance of `clusterlib.ClusterLib`.""" return cluster_manager.get(use_resources=[cluster_management.Resources.PLUTUS]) + + +@pytest.fixture +def cluster_plutus( + cluster_manager: cluster_management.ClusterManager, +) -> clusterlib.ClusterLib: + """Lock governance and Plutus while keeping cluster pools available for voting.""" + return cluster_manager.get( + use_resources=cluster_management.Resources.ALL_POOLS, + lock_resources=[ + cluster_management.Resources.COMMITTEE, + cluster_management.Resources.DREPS, + cluster_management.Resources.PLUTUS, + ], + ) diff --git a/cardano_node_tests/tests/tests_plutus_v3/test_multi_index_array.py b/cardano_node_tests/tests/tests_plutus/multi_index_array.py similarity index 61% rename from cardano_node_tests/tests/tests_plutus_v3/test_multi_index_array.py rename to cardano_node_tests/tests/tests_plutus/multi_index_array.py index 02b772b17..cc3bb473e 100644 --- a/cardano_node_tests/tests/tests_plutus_v3/test_multi_index_array.py +++ b/cardano_node_tests/tests/tests_plutus/multi_index_array.py @@ -3,9 +3,7 @@ import dataclasses import logging import pathlib as pl -import typing as tp -import allure import pytest import pytest_subtests from cardano_clusterlib import clusterlib @@ -13,155 +11,33 @@ from cardano_node_tests.cluster_management import cluster_management from cardano_node_tests.tests import addrs_common from cardano_node_tests.tests import common -from cardano_node_tests.tests import markers from cardano_node_tests.tests import plutus_common -from cardano_node_tests.tests.tests_conway import conway_common -from cardano_node_tests.tests.tests_plutus_v3 import builtins_mint_build as mint_build +from cardano_node_tests.tests.tests_plutus import builtins_mint_build as mint_build from cardano_node_tests.utils import dbsync_utils -from cardano_node_tests.utils import governance_setup from cardano_node_tests.utils import helpers -from cardano_node_tests.utils.versions import VERSIONS LOGGER = logging.getLogger(__name__) -MULTI_INDEX_ARRAY_COST_MODEL_LEN = 355 - - SCRIPTS = plutus_common.MULTI_INDEX_ARRAY_SCRIPTS -pytestmark = [ - # Skip before the fixture locks governance and Plutus resources - pytest.mark.skipif( - VERSIONS.cluster_era < VERSIONS.DIJKSTRA_FIRST, - reason="multiIndexArray requires protocol version 12 or later", - ), - markers.SKIPIF_PLUTUSV3_UNUSABLE, - markers.SKIPIF_BUILD_UNUSABLE, - pytest.mark.plutus, - pytest.mark.team_plutus, -] - - -class MultiIndexArraySetup(tp.NamedTuple): - """Cluster with multiIndexArray costing for all tested ledger/Core combinations.""" - - cluster: clusterlib.ClusterLib - # Reasons to skip ledger/Core combinations, keyed like `SCRIPTS` - skip_reasons: dict[str, str] - - -def _get_decode_skip_reason(cluster_obj: clusterlib.ClusterLib, script_file: pl.Path) -> str: - """Return a skip reason for known batch7 decoding incompatibilities.""" - try: - cluster_obj.g_transaction.get_policyid(script_file) - except clusterlib.CLIError as exc: - # This test requires node/CLI built with the batch7 activation - # from IntersectMBO/plutus#8006 (da649103). - if "MultiIndexArray is not available" in str(exc) or "Failed to decode builtin tag" in str( - exc - ): - return f"CLI cannot decode multiIndexArray in {script_file.name} at PV12" - raise - return "" - - -@pytest.fixture -def multi_index_array_setup( - cluster_manager: cluster_management.ClusterManager, - cluster_plutus: clusterlib.ClusterLib, -) -> MultiIndexArraySetup: - """Enable multiIndexArray costing while locking governance and Plutus resources. - - Update the PlutusV3 and PlutusV4 cost models in a single governance action, unless - the cost models of all tested languages already price multiIndexArray. Both Core - versions reuse these models. Skip the whole test when the PlutusV3/PLC 1.1.0 - baseline has a known batch7 decoding incompatibility. Check the same decoding - incompatibilities separately for the other ledger/Core combinations. - """ - cluster = cluster_plutus - pparams = cluster.g_query.get_protocol_params() - if pparams["protocolVersion"]["major"] < 12: - pytest.skip("multiIndexArray requires protocol version 12 or later") - - # Check CLI batch7 activation before governance setup - skip_reason_v3 = _get_decode_skip_reason( - cluster_obj=cluster, script_file=SCRIPTS["v3_110"].succeeding[0].script_file - ) - if skip_reason_v3: - pytest.skip(skip_reason_v3) - skip_reasons = {} - for variant, scripts in SCRIPTS.items(): - if scripts.cost_model == "PlutusV4" and VERSIONS.transaction_era < VERSIONS.DIJKSTRA_FIRST: - skip_reasons[variant] = "Plutus V4 is available only in Dijkstra+ eras" - continue - - skip_reason = _get_decode_skip_reason( - cluster_obj=cluster, script_file=scripts.succeeding[0].script_file - ) - if skip_reason: - skip_reasons[variant] = skip_reason - - tested_cost_models = {s.cost_model for v, s in SCRIPTS.items() if v not in skip_reasons} - - def _is_costing_enabled(cost_models: dict) -> bool: - return all( - len(cost_models.get(m, ())) >= MULTI_INDEX_ARRAY_COST_MODEL_LEN - for m in tested_cost_models - ) - - setup = MultiIndexArraySetup(cluster=cluster, skip_reasons=skip_reasons) - if _is_costing_enabled(pparams["costModels"]): - return setup - - # Up to one epoch for delayed ratification, then two for enactment. - common.skip_on_long_epochs(cluster_obj=cluster, epochs=3) - name_template = f"{common.get_test_id(cluster)}_cost_model" - pool_user = addrs_common.get_registered_pool_user( - name_template=name_template, - cluster_manager=cluster_manager, - cluster_obj=cluster, - ) - governance_data = governance_setup.get_default_governance( - cluster_manager=cluster_manager, cluster_obj=cluster - ) - - # Mark before submission: even a failed check can leave an enacted update. - cluster_manager.set_needs_respin() - conway_common.update_cost_model( - cluster_obj=cluster, - name_template=name_template, - governance_data=governance_data, - # The PlutusV4 parameters have the same names and order as the PlutusV3 ones - cost_proposal_file=plutus_common.DATA_DIR / "cost_models_list_355_v3_v4.json", - pool_user=pool_user, - ) - assert _is_costing_enabled(cluster.g_query.get_protocol_params()["costModels"]), ( - "multiIndexArray cost model was not enacted" - ) - return setup - -class TestMultiIndexArray: - """Exercise multiIndexArray through complete PlutusV3 and PlutusV4 minting transactions.""" +class MultiIndexArrayChecks: + """Exercise multiIndexArray minting and budgets for any supplied ledger/Core variant.""" - @allure.link(helpers.get_vcs_link()) - @pytest.mark.order(5) - @pytest.mark.xdist_split(markers.XdSplits.governance, markers.XdSplits.heavy) - @pytest.mark.long - @pytest.mark.dbsync - def test_multi_index_array( + def run( self, cluster_manager: cluster_management.ClusterManager, - multi_index_array_setup: MultiIndexArraySetup, + cluster_obj: clusterlib.ClusterLib, + temp_template: str, + variant: str, + scripts: plutus_common.MultiIndexArrayScripts, subtests: pytest_subtests.SubTests, - ): + ) -> None: """Mint with valid selections and reject invalid requests while building transactions. - Run the existing scenarios for PlutusV3 and PlutusV4, each compiled for PLC - 1.1.0 and 1.2.0. All four combinations reuse a single cost-model setup and - minting/budget helpers. Skip known batch7 decoding incompatibilities for each - combination and both PlutusV4 variants before the Dijkstra transaction era. - Other decoder errors fail setup. + The caller checks language/protocol/CLI availability and enables costing + before selecting a ledger/Core variant. All variants reuse the shared + cost-model setup and minting/budget helpers. Check ordering, duplicate indices, empty selections, heterogeneous Data, and repeated selections of 1, 64 and 1,024 elements. The Data policy selects @@ -174,42 +50,13 @@ def test_multi_index_array( Reuse the cost-model setup to reject insufficient CPU and memory budgets, then confirm a raw transaction with sufficient execution units. """ - setup = multi_index_array_setup - temp_template = common.get_test_id(setup.cluster) - - for variant, scripts in SCRIPTS.items(): - skip_reason = setup.skip_reasons.get(variant) - if skip_reason: - with subtests.test(variant=variant): - pytest.skip(skip_reason) - continue - - self._check_variant( - cluster_manager=cluster_manager, - cluster_obj=setup.cluster, - temp_template=f"{temp_template}_{variant}", - variant=variant, - scripts=scripts, - subtests=subtests, - ) - - def _check_variant( - self, - cluster_manager: cluster_management.ClusterManager, - cluster_obj: clusterlib.ClusterLib, - temp_template: str, - variant: str, - scripts: plutus_common.MultiIndexArrayScripts, - subtests: pytest_subtests.SubTests, - ) -> None: - """Run minting and budget scenarios for one ledger/Core combination.""" cluster = cluster_obj over_limit_stem = next( s.script_file.stem for s in scripts.failing if "OverLimitPolicy" in s.script_file.stem ) # Mint with all policies and collect the execution costs of successful mints - mints = mint_build.run_plutusv3_builtins_test( + mints = mint_build.run_builtins_test( cluster_manager=cluster_manager, cluster_obj=cluster, temp_template=temp_template, diff --git a/cardano_node_tests/tests/tests_plutus/test_builtins.py b/cardano_node_tests/tests/tests_plutus/test_builtins.py new file mode 100644 index 000000000..0af6d899f --- /dev/null +++ b/cardano_node_tests/tests/tests_plutus/test_builtins.py @@ -0,0 +1,374 @@ +"""Minting tests for builtin activation and costing across Plutus ledger languages.""" + +import json +import logging +import pathlib as pl +import typing as tp + +import allure +import pytest +import pytest_subtests +from cardano_clusterlib import clusterlib + +from cardano_node_tests.cluster_management import cluster_management +from cardano_node_tests.tests import addrs_common +from cardano_node_tests.tests import common +from cardano_node_tests.tests import markers +from cardano_node_tests.tests import plutus_common +from cardano_node_tests.tests.tests_conway import conway_common +from cardano_node_tests.tests.tests_plutus import builtins_mint_build as mint_build +from cardano_node_tests.tests.tests_plutus import multi_index_array +from cardano_node_tests.tests.tests_plutus import value_builtins +from cardano_node_tests.utils import governance_setup +from cardano_node_tests.utils import helpers +from cardano_node_tests.utils.versions import VERSIONS + +LOGGER = logging.getLogger(__name__) +UPGRADE_TESTS_STEP = helpers.get_env_int("UPGRADE_TESTS_STEP", 0) + +# Transaction-era and protocol activation of each ledger language, independently +# of the later protocol versions that activate individual builtin groups. +LANGUAGE_REQUIREMENTS = { + "PlutusV1": (VERSIONS.ALONZO_FIRST, 5), + "PlutusV2": (VERSIONS.BABBAGE_FIRST, 7), + "PlutusV3": (VERSIONS.CONWAY_FIRST, 9), + "PlutusV4": (VERSIONS.DIJKSTRA_FIRST, 12), +} + + +class BuiltinGroup(tp.NamedTuple): + """Scripts and activation requirements for one builtin family/ledger/Core variant.""" + + name: str + batch: int + variant: str + language: str + protocol_version: int + cost_model_len: int + succeeding: tuple[plutus_common.PlutusScriptData, ...] + failing: tuple[plutus_common.PlutusScriptData, ...] + overspending: tuple[plutus_common.PlutusScriptData, ...] = () + + +BUILTIN_GROUPS = ( + BuiltinGroup( + name="batch5_v3", + batch=5, + variant="v3", + language="PlutusV3", + protocol_version=10, + cost_model_len=297, + succeeding=( + *plutus_common.SUCCEEDING_MINTING_RIPEMD_160_SCRIPTS_V3, + *plutus_common.SUCCEEDING_MINTING_BITWISE_SCRIPTS_V3, + ), + failing=plutus_common.FAILING_MINTING_BITWISE_SCRIPTS_V3, + ), + BuiltinGroup( + name="batch6_v3", + batch=6, + variant="v3", + language="PlutusV3", + protocol_version=11, + cost_model_len=330, + succeeding=plutus_common.SUCCEEDING_MINTING_BATCH6_SCRIPTS_V3, + failing=plutus_common.FAILING_MINTING_BATCH6_SCRIPTS_V3, + overspending=plutus_common.OVERSPENDING_MINTING_BATCH6_SCRIPTS_V3, + ), + *( + BuiltinGroup( + name=f"batch7_multi_index_array_{variant}", + batch=7, + variant=variant, + language=scripts.cost_model, + protocol_version=12, + cost_model_len=355, + succeeding=scripts.succeeding, + failing=scripts.failing, + ) + for variant, scripts in multi_index_array.SCRIPTS.items() + ), + *( + BuiltinGroup( + name=f"batch7_value_{variant}", + batch=7, + variant=variant, + language=scripts.cost_model, + protocol_version=12, + cost_model_len=369, + succeeding=scripts.succeeding, + failing=scripts.failing, + ) + for variant, scripts in value_builtins.SCRIPTS.items() + ), +) + +# Install costing before builtin activation, but never propose a ledger language +# before its own activation. The proposal is filtered to these language keys. +# Start updates at PV10: the shared voting helper casts DRep votes, which are +# forbidden for parameter changes during PV9 bootstrap. PV9 keeps its own model. +COST_MODEL_UPDATES = ( + (12, {"PlutusV3": 369, "PlutusV4": 369}, "cost_models_list_369_v3_v4.json"), + (10, {"PlutusV3": 369}, "cost_models_list_369_v3_v4.json"), +) + +pytestmark = [markers.SKIPIF_PLUTUS_UNUSABLE, markers.SKIPIF_BUILD_UNUSABLE, pytest.mark.plutus] + + +def _check_language(group: BuiltinGroup, protocol_version: int) -> None: + """Skip a ledger language unavailable in the transaction era or active protocol.""" + min_era, min_protocol = LANGUAGE_REQUIREMENTS[group.language] + if VERSIONS.transaction_era < min_era or protocol_version < min_protocol: + pytest.skip(f"{group.language} requires transaction era {min_era} and PV{min_protocol}+") + + +def _check_cli_support(cluster: clusterlib.ClusterLib, group: BuiltinGroup) -> None: + """Skip known CLI activation gaps for an otherwise activated builtin group.""" + for script in (*group.succeeding, *group.failing, *group.overspending): + try: + cluster.g_transaction.get_policyid(script.script_file) + except clusterlib.CLIError as exc: + if "is not available" in str(exc) or "Failed to decode builtin tag" in str(exc): + pytest.skip(f"CLI cannot decode {group.name}: {exc}") + raise + + +def _get_scripts(group: BuiltinGroup, protocol_version: int) -> BuiltinGroup: + """Classify batch5 rotate/shift scripts using the active protocol's semantics.""" + if group.name != "batch5_v3": + return group + if plutus_common.rotate_shift_bitwise_fails(protocol_version=protocol_version): + return group._replace( + failing=(*group.failing, *plutus_common.FAILING_MINTING_ROTATE_SHIFT_SCRIPTS_V3) + ) + return group._replace( + succeeding=(*group.succeeding, *plutus_common.SUCCEEDING_MINTING_ROTATE_SHIFT_SCRIPTS_V3) + ) + + +def update_cost_model( + cluster: clusterlib.ClusterLib, + cluster_manager: cluster_management.ClusterManager, + temp_template: str, + protocol_version: int, + cost_models: dict, +) -> None: + """Install latest costing for active languages, even before builtin activation. + + Propose V3 alone at PV10/PV11, then V3/V4 once both languages are available. + Leave PV9 bootstrap costing unchanged because the voting helper needs DReps. + Preserve the restrictions on updates and respins in upgrade tests. + """ + if UPGRADE_TESTS_STEP and UPGRADE_TESTS_STEP < 3: + LOGGER.info("Skipping cost model update on upgrade step %s", UPGRADE_TESTS_STEP) + return + + for proposal in COST_MODEL_UPDATES: + if protocol_version >= proposal[0]: + break + else: + LOGGER.info("No cost model update for protocol version %s", protocol_version) + return + + _, required_models, proposal_file = proposal + if all(len(cost_models.get(lang, ())) >= size for lang, size in required_models.items()): + return + + # Include initial and delayed-ratification waits as well as enactment. + common.skip_on_long_epochs(cluster_obj=cluster, epochs=5) + name_template = f"{temp_template}_cost_model_upd" + proposal_models = json.loads( + (plutus_common.DATA_DIR / proposal_file).read_text(encoding="utf-8") + ) + cost_proposal_file = pl.Path(f"{name_template}.json") + cost_proposal_file.write_text( + json.dumps({lang: proposal_models[lang] for lang in required_models}, indent=4), + encoding="utf-8", + ) + + pool_user = addrs_common.get_registered_pool_user( + name_template=name_template, cluster_manager=cluster_manager, cluster_obj=cluster + ) + governance_data = governance_setup.get_default_governance( + cluster_manager=cluster_manager, cluster_obj=cluster + ) + + # A failed submit/check can still leave an enacted update. Keep upgrade state intact. + if not UPGRADE_TESTS_STEP: + cluster_manager.set_needs_respin() + conway_common.update_cost_model( + cluster_obj=cluster, + name_template=name_template, + governance_data=governance_data, + cost_proposal_file=cost_proposal_file, + pool_user=pool_user, + ) + enacted_models = cluster.g_query.get_protocol_params()["costModels"] + assert all( + len(enacted_models.get(lang, ())) >= size for lang, size in required_models.items() + ), "Builtin cost models were not enacted" + + +class TestPlutusBuiltins: + """Test builtin activation and costing for every available ledger/Core script group.""" + + @allure.link(helpers.get_vcs_link()) + @pytest.mark.long + @pytest.mark.team_plutus + @pytest.mark.upgrade_step1 + @pytest.mark.upgrade_step2 + @pytest.mark.upgrade_step3 + @pytest.mark.parametrize( + "group", + [ + pytest.param( + group, + id=group.name, + marks=pytest.mark.skipif( + VERSIONS.transaction_era < LANGUAGE_REQUIREMENTS[group.language][0], + reason=f"{group.language} is unavailable in the transaction era", + ), + ) + for group in BUILTIN_GROUPS + ], + ) + def test_builtins_old( + self, + cluster_manager: cluster_management.ClusterManager, + cluster: clusterlib.ClusterLib, + subtests: pytest_subtests.SubTests, + group: BuiltinGroup, + ): + """Check pre-activation rejection and missing costing without updating parameters. + + Select the language-specific cost model and builtin activation requirements + for each supplied group. Test V3 and V4 independently when their ledger + language is available. Skip only fully enabled groups or known CLI gaps. + Unpriced batch7 success policies must overspend, preserving the batch5/6 + constant-casing exceptions. Both PLC versions use their ledger's model. + """ + temp_template = common.get_test_id(cluster) + pparams = cluster.g_query.get_protocol_params() + protocol_version = pparams["protocolVersion"]["major"] + _check_language(group, protocol_version) + group = _get_scripts(group, protocol_version) + is_cost_model_ok = ( + len(pparams["costModels"].get(group.language, ())) >= group.cost_model_len + ) + is_prot_version_ok = protocol_version >= group.protocol_version + if is_cost_model_ok and is_prot_version_ok: + pytest.skip(f"Protocol version and cost model are already current for {group.name}") + if is_prot_version_ok: + _check_cli_support(cluster, group) + + # Batch7 policies exercise unpriced builtins, unlike the batch5/6 exceptions. + if group.batch >= 7 and not is_cost_model_ok: + group = group._replace( + overspending=(*group.overspending, *group.succeeding), succeeding=() + ) + variant = f"old_{group.name}_{'prot_ok' if is_prot_version_ok else 'prot_nok'}" + mint_build.run_builtins_test( + cluster_manager=cluster_manager, + cluster_obj=cluster, + temp_template=f"{temp_template}_{variant}", + variant=variant, + success_scripts=group.succeeding, + fail_scripts=group.failing, + overspend_scripts=group.overspending, + is_cost_model_ok=is_cost_model_ok, + is_prot_version_ok=is_prot_version_ok, + subtests=subtests, + ) + + @allure.link(helpers.get_vcs_link()) + @pytest.mark.order(5) + @pytest.mark.xdist_split(markers.XdSplits.governance, markers.XdSplits.heavy) + @pytest.mark.long + @pytest.mark.dbsync + @pytest.mark.team_plutus + @pytest.mark.upgrade_step1 + @pytest.mark.upgrade_step2 + @pytest.mark.upgrade_step3 + def test_builtins_new_cost( + self, + cluster_manager: cluster_management.ClusterManager, + cluster_plutus: clusterlib.ClusterLib, + subtests: pytest_subtests.SubTests, + ): + """Share one supported cost-model update across all applicable script groups. + + Run each ledger/Core variant independently with its language-specific cost + model and protocol requirements. Unsupported languages and known CLI gaps + skip only their own groups. Reuse detailed batch7 minting/budget/db-sync + checks once fully enabled. Do not update before upgrade step3 or respin an + upgrade cluster. Newly supported groups also participate in upgrade tests. + Run batch7 V3 before PV12 with updated costing and require protocol + rejection. Still exercise that rejection if an upgrade step leaves the + old model in place; only activated groups require sufficient costing here. + """ + cluster = cluster_plutus + temp_template = common.get_test_id(cluster) + pparams = cluster.g_query.get_protocol_params() + protocol_version = pparams["protocolVersion"]["major"] + update_cost_model( + cluster=cluster, + cluster_manager=cluster_manager, + temp_template=temp_template, + protocol_version=protocol_version, + cost_models=pparams["costModels"], + ) + cost_models = cluster.g_query.get_protocol_params()["costModels"] + + for original_group in BUILTIN_GROUPS: + with subtests.test(group=original_group.name): + _check_language(original_group, protocol_version) + group = _get_scripts(original_group, protocol_version) + is_prot_version_ok = protocol_version >= group.protocol_version + is_cost_model_ok = len(cost_models.get(group.language, ())) >= group.cost_model_len + if is_prot_version_ok and not is_cost_model_ok: + pytest.skip(f"Cost model is not current for {group.name}") + if is_prot_version_ok: + _check_cli_support(cluster, group) + variant = f"upd_{group.name}_{'prot_ok' if is_prot_version_ok else 'prot_nok'}" + group_template = f"{temp_template}_{variant}" + + # These guards select specialized success/budget checks, not + # whether batch7 runs. Before activation, batch7 falls through + # to the generic runner below to require protocol rejection, + # even after installing its costing. Each ledger/Core variant + # is independent, so a CLI gap cannot suppress other variants. + if group.name.startswith("batch7_multi_index_array_") and is_prot_version_ok: + multi_index_array.MultiIndexArrayChecks().run( + cluster_manager=cluster_manager, + cluster_obj=cluster, + temp_template=group_template, + variant=group.variant, + scripts=multi_index_array.SCRIPTS[group.variant], + subtests=subtests, + ) + elif group.name.startswith("batch7_value_") and is_prot_version_ok: + value_builtins.ValueBuiltinsChecks().run( + cluster_manager=cluster_manager, + cluster_obj=cluster, + temp_template=group_template, + variant=group.variant, + scripts=value_builtins.SCRIPTS[group.variant], + subtests=subtests, + ) + else: + # Run batches 5/6 here, and batch7 before its activation. + # Deliberately do not guard this call on is_prot_version_ok: + # False tells the runner to require rejection, proving that + # updated costing alone cannot activate future builtins. + mint_build.run_builtins_test( + cluster_manager=cluster_manager, + cluster_obj=cluster, + temp_template=group_template, + variant=variant, + success_scripts=group.succeeding, + fail_scripts=group.failing, + overspend_scripts=group.overspending, + is_cost_model_ok=is_cost_model_ok, + is_prot_version_ok=is_prot_version_ok, + subtests=subtests, + ) diff --git a/cardano_node_tests/tests/tests_plutus/value_builtins.py b/cardano_node_tests/tests/tests_plutus/value_builtins.py new file mode 100644 index 000000000..64cc6c7d3 --- /dev/null +++ b/cardano_node_tests/tests/tests_plutus/value_builtins.py @@ -0,0 +1,63 @@ +"""CIP-0168 Value builtin minting scenarios for any supplied ledger/Core variant.""" + +import pytest_subtests +from cardano_clusterlib import clusterlib + +from cardano_node_tests.cluster_management import cluster_management +from cardano_node_tests.tests import plutus_common +from cardano_node_tests.tests.tests_plutus import builtins_mint_build as mint_build +from cardano_node_tests.utils import dbsync_utils + +SCRIPTS = plutus_common.VALUE_BUILTIN_SCRIPTS + + +class ValueBuiltinsChecks: + """Run CIP-0168 policies through the shared builtin minting runner.""" + + def run( + self, + cluster_manager: cluster_management.ClusterManager, + cluster_obj: clusterlib.ClusterLib, + temp_template: str, + variant: str, + scripts: plutus_common.ValueBuiltinScripts, + subtests: pytest_subtests.SubTests, + ) -> None: + """Mint with Value policies and reject invalid filter lists while building. + + The caller checks language/protocol/CLI availability and enables costing + before selecting a ledger/Core variant. Exercise policies, keepPolicies, + dropPolicies and assetCount. Cover empty/ADA values, ordering, filtering, + duplicates, absent/overlong IDs and the 256/257 input-list boundary. + The size policies are ordinary successful mints. Check successful + transactions and costs in db-sync when available. + """ + mints = mint_build.run_builtins_test( + cluster_manager=cluster_manager, + cluster_obj=cluster_obj, + temp_template=temp_template, + variant=f"value_{variant}", + success_scripts=scripts.succeeding, + fail_scripts=scripts.failing, + overspend_scripts=(), + is_cost_model_ok=True, + is_prot_version_ok=True, + subtests=subtests, + expected_errors={ + script.script_file.stem: ( + "keepPolicies" if "ValueKeep" in script.script_file.stem else "dropPolicies" + ) + + ": maximum number of policy ids (256) exceeded" + for script in scripts.failing + }, + ) + + # Verify the successful transactions and measured execution costs. + for minted in mints: + db_record = dbsync_utils.check_tx( + cluster_obj=cluster_obj, tx_raw_output=minted.tx_output + ) + if db_record: + dbsync_utils.check_plutus_costs( + redeemer_records=db_record.redeemers, cost_records=minted.plutus_costs + ) diff --git a/cardano_node_tests/tests/tests_plutus_v3/conftest.py b/cardano_node_tests/tests/tests_plutus_v3/conftest.py index 45786ec61..45cbec13c 100644 --- a/cardano_node_tests/tests/tests_plutus_v3/conftest.py +++ b/cardano_node_tests/tests/tests_plutus_v3/conftest.py @@ -14,19 +14,3 @@ def cluster( ) -> clusterlib.ClusterLib: """Return instance of `clusterlib.ClusterLib`.""" return cluster_manager.get(use_resources=[cluster_management.Resources.PLUTUS]) - - -@pytest.fixture -def cluster_plutus( - cluster_manager: cluster_management.ClusterManager, -) -> clusterlib.ClusterLib: - """Mark whole governance and Plutus as "locked".""" - cluster_obj = cluster_manager.get( - use_resources=cluster_management.Resources.ALL_POOLS, - lock_resources=[ - cluster_management.Resources.COMMITTEE, - cluster_management.Resources.DREPS, - cluster_management.Resources.PLUTUS, - ], - ) - return cluster_obj diff --git a/cardano_node_tests/tests/tests_plutus_v3/test_mint_build.py b/cardano_node_tests/tests/tests_plutus_v3/test_mint_build.py deleted file mode 100644 index bd4099f05..000000000 --- a/cardano_node_tests/tests/tests_plutus_v3/test_mint_build.py +++ /dev/null @@ -1,298 +0,0 @@ -"""Tests for minting with Plutus using `transaction build`.""" - -import logging -import pathlib as pl - -import allure -import pytest -import pytest_subtests -from cardano_clusterlib import clusterlib - -from cardano_node_tests.cluster_management import cluster_management -from cardano_node_tests.tests import addrs_common -from cardano_node_tests.tests import common -from cardano_node_tests.tests import markers -from cardano_node_tests.tests import plutus_common -from cardano_node_tests.tests.tests_conway import conway_common -from cardano_node_tests.tests.tests_plutus_v3 import builtins_mint_build as mint_build -from cardano_node_tests.utils import governance_setup -from cardano_node_tests.utils import helpers - -LOGGER = logging.getLogger(__name__) - -DATA_DIR = pl.Path(__file__).parent.parent / "data" -UPGRADE_TESTS_STEP = helpers.get_env_int("UPGRADE_TESTS_STEP", 0) - -# Minimum protocol version required for batch5 built-in functions -BATCH5_PROT_VERSION = 10 -# Minimum protocol version required for batch6 built-in functions -BATCH6_PROT_VERSION = 11 -# Cost model length including batch5 built-in functions -BATCH5_COST_MODEL_LEN = 297 -# Cost model length including batch6 built-in functions -BATCH6_COST_MODEL_LEN = 330 - -pytestmark = [ - markers.SKIPIF_PLUTUSV3_UNUSABLE, - pytest.mark.plutus, -] - - -def update_cost_model( - cluster_obj: clusterlib.ClusterLib, - cluster_manager: cluster_management.ClusterManager, - temp_template: str, - prot_version: int, - cost_model_len: int, -) -> None: - """Update cost model to include values for new Plutus Core built-in functions.""" - name_template = f"{temp_template}_cost_model_upd" - - if prot_version >= BATCH6_PROT_VERSION: - if cost_model_len >= BATCH6_COST_MODEL_LEN: - return - cost_proposal_file = DATA_DIR / "cost_models_list_332_350_v2_v3.json" - elif prot_version == BATCH5_PROT_VERSION: - if cost_model_len >= BATCH5_COST_MODEL_LEN: - return - cost_proposal_file = DATA_DIR / "cost_models_list_185_297_v2_v3.json" - else: - LOGGER.warning( - "Unsupported protocol version %s for updating cost model, skipping update.", - prot_version, - ) - return - - pool_user = addrs_common.get_registered_pool_user( - name_template=name_template, - cluster_manager=cluster_manager, - cluster_obj=cluster_obj, - ) - governance_data = governance_setup.get_default_governance( - cluster_manager=cluster_manager, cluster_obj=cluster_obj - ) - conway_common.update_cost_model( - cluster_obj=cluster_obj, - name_template=name_template, - governance_data=governance_data, - cost_proposal_file=cost_proposal_file, - pool_user=pool_user, - ) - - -class TestPlutusV3Builtins: - """Tests for new batches of Plutus Core built-in functions.""" - - batch5_success_scripts = ( - *plutus_common.SUCCEEDING_MINTING_RIPEMD_160_SCRIPTS_V3, - *plutus_common.SUCCEEDING_MINTING_BITWISE_SCRIPTS_V3, - ) - batch5_fail_scripts = plutus_common.FAILING_MINTING_BITWISE_SCRIPTS_V3 - batch5_overspend_scripts = () - - batch6_success_scripts = plutus_common.SUCCEEDING_MINTING_BATCH6_SCRIPTS_V3 - batch6_fail_scripts = plutus_common.FAILING_MINTING_BATCH6_SCRIPTS_V3 - batch6_overspend_scripts = plutus_common.OVERSPENDING_MINTING_BATCH6_SCRIPTS_V3 - - def _get_scripts( - self, batch: int, prot_version: int - ) -> tuple[ - tuple[plutus_common.PlutusScriptData, ...], tuple[plutus_common.PlutusScriptData, ...] - ]: - """Get success and fail scripts for the given batch. - - The rotate/shift bitwise scripts (batch 5) succeed or fail depending on the actual - protocol version of the cluster, so they are classified at runtime. - """ - success_scripts = tuple(getattr(self, f"batch{batch}_success_scripts")) - fail_scripts = tuple(getattr(self, f"batch{batch}_fail_scripts")) - - if batch == 5: - if plutus_common.rotate_shift_bitwise_fails(protocol_version=prot_version): - fail_scripts = ( - *fail_scripts, - *plutus_common.FAILING_MINTING_ROTATE_SHIFT_SCRIPTS_V3, - ) - else: - success_scripts = ( - *success_scripts, - *plutus_common.SUCCEEDING_MINTING_ROTATE_SHIFT_SCRIPTS_V3, - ) - - return success_scripts, fail_scripts - - @allure.link(helpers.get_vcs_link()) - @pytest.mark.long - @pytest.mark.team_plutus - @pytest.mark.upgrade_step1 - @pytest.mark.upgrade_step2 - @pytest.mark.upgrade_step3 - @pytest.mark.parametrize( - ("batch", "cost_model_required", "prot_version_required"), - ( - (5, BATCH5_COST_MODEL_LEN, BATCH5_PROT_VERSION), - (6, BATCH6_COST_MODEL_LEN, BATCH6_PROT_VERSION), - ), - ids=("batch5", "batch6"), - ) - def test_plutusv3_builtins_old( - self, - cluster_manager: cluster_management.ClusterManager, - cluster: clusterlib.ClusterLib, - subtests: pytest_subtests.SubTests, - batch: int, - cost_model_required: int, - prot_version_required: int, - ): - """Test minting with a batch of Plutus Core built-in functions. - - Run tests with the old cost model and possibly also old protocol version. - - Expect correct behavior (errors) depending on whether the protocol version - supports the new built-in functions or not. - """ - temp_template = common.get_test_id(cluster) - - pparams_init = cluster.g_query.get_protocol_params() - cost_model_len_init = len(pparams_init["costModels"]["PlutusV3"]) - prot_version = pparams_init["protocolVersion"]["major"] - - is_cost_model_ok = cost_model_len_init >= cost_model_required - is_prot_version_ok = prot_version >= prot_version_required - - # Run tests only when the corresponding cost model isn't up-to-date, otherwise - # we would be repeating the same tests in `test_plutusv3_builtins_new_cost`. - if is_cost_model_ok: - pytest.skip( - f"Cost model is already up-to-date, skipping batch{batch} old cost model tests." - ) - - success_scripts, fail_scripts = self._get_scripts(batch=batch, prot_version=prot_version) - - variant = f"old_batch{batch}_{'prot_ok' if is_prot_version_ok else 'prot_nok'}" - mint_build.run_plutusv3_builtins_test( - cluster_manager=cluster_manager, - cluster_obj=cluster, - temp_template=f"{temp_template}_{variant}", - variant=variant, - success_scripts=success_scripts, - fail_scripts=fail_scripts, - overspend_scripts=getattr(self, f"batch{batch}_overspend_scripts"), - is_cost_model_ok=is_cost_model_ok, - is_prot_version_ok=is_prot_version_ok, - subtests=subtests, - ) - - @allure.link(helpers.get_vcs_link()) - @pytest.mark.order(5) - @pytest.mark.xdist_split(markers.XdSplits.governance, markers.XdSplits.heavy) - @pytest.mark.long - @pytest.mark.team_plutus - @pytest.mark.upgrade_step1 - @pytest.mark.upgrade_step2 - @pytest.mark.upgrade_step3 - def test_plutusv3_builtins_new_cost( - self, - cluster_manager: cluster_management.ClusterManager, - cluster_plutus: clusterlib.ClusterLib, - subtests: pytest_subtests.SubTests, - ): - """Test minting with the new batches of Plutus Core built-in functions. - - * When needed, update cost model to include new built-in functions - * Run tests with the updated cost model - - Expect correct behavior (errors or success) depending on whether the protocol version - supports the new built-in functions or not. - - All batches are tested in a single test as each batch needs cost model update, and it would - not be practical to update cost model multiple times in separate tests. - """ - cluster = cluster_plutus - # 2 epochs for cost model update, up to 1 epoch for delayed ratification - common.skip_on_long_epochs(cluster_obj=cluster, epochs=3) - temp_template = common.get_test_id(cluster) - - pparams_init = cluster.g_query.get_protocol_params() - cost_model_len_init = len(pparams_init["costModels"]["PlutusV3"]) - prot_version = pparams_init["protocolVersion"]["major"] - - is_batch5_prot_version_ok = prot_version >= BATCH5_PROT_VERSION - is_batch6_prot_version_ok = prot_version >= BATCH6_PROT_VERSION - - def _get_variant(batch: int) -> str: - if batch == 5: - prot_part = "prot_ok" if is_batch5_prot_version_ok else "prot_nok" - elif batch == 6: - prot_part = "prot_ok" if is_batch6_prot_version_ok else "prot_nok" - else: - err = f"Unsupported batch number {batch}" - raise ValueError(err) - - return f"batch{batch}_{prot_part}" - - # Update cost model, if not already updated - - if UPGRADE_TESTS_STEP and UPGRADE_TESTS_STEP < 3: - LOGGER.info( - "Skipping cost model update on step %s of upgrade testing", UPGRADE_TESTS_STEP - ) - cost_model_len_updated = cost_model_len_init - else: - update_cost_model( - cluster_obj=cluster, - cluster_manager=cluster_manager, - temp_template=temp_template, - prot_version=prot_version, - cost_model_len=cost_model_len_init, - ) - cost_model_len_updated = len( - cluster.g_query.get_protocol_params()["costModels"]["PlutusV3"] - ) - # The cluster needs respin if cost model was updated. - # Don't try to respin if the test runs as part of upgrade testing. - if not UPGRADE_TESTS_STEP and cost_model_len_updated != cost_model_len_init: - cluster_manager.set_needs_respin() - - if prot_version >= BATCH6_PROT_VERSION: - assert cost_model_len_updated >= BATCH6_COST_MODEL_LEN - elif prot_version >= BATCH5_PROT_VERSION: - assert cost_model_len_updated >= BATCH5_COST_MODEL_LEN - - # Run tests with the updated cost model - - if cost_model_len_updated < BATCH5_COST_MODEL_LEN: - pytest.skip("Cost model is not updated, skipping new cost model tests.") - - batch5_success_scripts, batch5_fail_scripts = self._get_scripts( - batch=5, prot_version=prot_version - ) - - batch5_variant_updated = f"upd_{_get_variant(batch=5)}" - mint_build.run_plutusv3_builtins_test( - cluster_manager=cluster_manager, - cluster_obj=cluster, - temp_template=f"{temp_template}_{batch5_variant_updated}", - variant=batch5_variant_updated, - success_scripts=batch5_success_scripts, - fail_scripts=batch5_fail_scripts, - overspend_scripts=self.batch5_overspend_scripts, - is_cost_model_ok=True, - is_prot_version_ok=is_batch5_prot_version_ok, - subtests=subtests, - ) - - if cost_model_len_updated >= BATCH6_COST_MODEL_LEN: - batch6_variant_updated = f"upd_{_get_variant(batch=6)}" - mint_build.run_plutusv3_builtins_test( - cluster_manager=cluster_manager, - cluster_obj=cluster, - temp_template=f"{temp_template}_{batch6_variant_updated}", - variant=batch6_variant_updated, - success_scripts=self.batch6_success_scripts, - fail_scripts=self.batch6_fail_scripts, - overspend_scripts=self.batch6_overspend_scripts, - is_cost_model_ok=True, - is_prot_version_ok=is_batch6_prot_version_ok, - subtests=subtests, - ) diff --git a/cardano_node_tests/tests/tests_plutus_v3/test_value_builtins.py b/cardano_node_tests/tests/tests_plutus_v3/test_value_builtins.py deleted file mode 100644 index a0a0d8fb6..000000000 --- a/cardano_node_tests/tests/tests_plutus_v3/test_value_builtins.py +++ /dev/null @@ -1,158 +0,0 @@ -"""CIP-0168 Value builtin minting tests for PlutusV3/V4 at protocol version 12.""" - -import allure -import pytest -import pytest_subtests -from cardano_clusterlib import clusterlib - -from cardano_node_tests.cluster_management import cluster_management -from cardano_node_tests.tests import addrs_common -from cardano_node_tests.tests import common -from cardano_node_tests.tests import markers -from cardano_node_tests.tests import plutus_common -from cardano_node_tests.tests.tests_conway import conway_common -from cardano_node_tests.tests.tests_plutus_v3 import builtins_mint_build as mint_build -from cardano_node_tests.utils import dbsync_utils -from cardano_node_tests.utils import governance_setup -from cardano_node_tests.utils import helpers -from cardano_node_tests.utils.versions import VERSIONS - -VALUE_COST_MODEL_LEN = 369 -SCRIPTS = plutus_common.VALUE_BUILTIN_SCRIPTS - -pytestmark = [ - pytest.mark.skipif( - VERSIONS.cluster_era < VERSIONS.DIJKSTRA_FIRST, - reason="CIP-0168 Value builtins require protocol version 12 or later", - ), - markers.SKIPIF_PLUTUSV3_UNUSABLE, - markers.SKIPIF_PLUTUSV4_UNUSABLE, - markers.SKIPIF_BUILD_UNUSABLE, - pytest.mark.plutus, - pytest.mark.team_plutus, -] - - -@pytest.fixture -def value_builtins_setup( - cluster_manager: cluster_management.ClusterManager, - cluster_plutus: clusterlib.ClusterLib, -) -> clusterlib.ClusterLib: - """Enable CIP-0168 costing once under governance/Plutus locks. - - Check PV12 and CLI policy decoding before changing governance state. Require - V3/V4 cost models to have at least 369 parameters, as in the existing batch7 - tests. Mark the cluster for respin before submitting an update. Known CLI - activation gaps skip the test before governance setup. - """ - cluster = cluster_plutus - pparams = cluster.g_query.get_protocol_params() - if pparams["protocolVersion"]["major"] < 12: - pytest.skip("CIP-0168 Value builtins require protocol version 12 or later") - - # Check every ledger/Core combination before governance setup. - for scripts in SCRIPTS.values(): - for script in (*scripts.succeeding, *scripts.failing): - try: - cluster.g_transaction.get_policyid(script.script_file) - except clusterlib.CLIError as exc: - if "is not available" in str(exc) or "Failed to decode builtin tag" in str(exc): - pytest.skip( - f"CLI lacks batch7/PV12 support for {script.script_file.name}: {exc}" - ) - raise - - def _is_costing_enabled(cost_models: dict) -> bool: - """Require sufficient parameters for both tested ledger languages.""" - return all( - len(cost_models.get(language, ())) >= VALUE_COST_MODEL_LEN - for language in ("PlutusV3", "PlutusV4") - ) - - if _is_costing_enabled(pparams["costModels"]): - return cluster - - # One initial epoch, up to two delayed waits, then ratification/enactment. - common.skip_on_long_epochs(cluster_obj=cluster, epochs=5) - name_template = f"{common.get_test_id(cluster)}_cost_model" - pool_user = addrs_common.get_registered_pool_user( - name_template=name_template, - cluster_manager=cluster_manager, - cluster_obj=cluster, - ) - governance_data = governance_setup.get_default_governance( - cluster_manager=cluster_manager, cluster_obj=cluster - ) - - # Mark before submission, since a failing check can leave an enacted update. - cluster_manager.set_needs_respin() - conway_common.update_cost_model( - cluster_obj=cluster, - name_template=name_template, - governance_data=governance_data, - cost_proposal_file=plutus_common.DATA_DIR / "cost_models_list_369_v3_v4.json", - pool_user=pool_user, - ) - assert _is_costing_enabled(cluster.g_query.get_protocol_params()["costModels"]), ( - "CIP-0168 cost models were not enacted" - ) - return cluster - - -class TestValueBuiltins: - """Run CIP-0168 policies through the shared batch7 minting runner.""" - - @allure.link(helpers.get_vcs_link()) - @pytest.mark.order(5) - @pytest.mark.xdist_split(markers.XdSplits.governance, markers.XdSplits.heavy) - @pytest.mark.long - @pytest.mark.dbsync - def test_value_builtins( - self, - cluster_manager: cluster_management.ClusterManager, - value_builtins_setup: clusterlib.ClusterLib, - subtests: pytest_subtests.SubTests, - ): - """Mint with Value policies and reject invalid filter lists while building. - - Exercise policies, keepPolicies, dropPolicies and assetCount in PlutusV3 - and PlutusV4, each compiled for PLC 1.1.0 and 1.2.0. Cover empty/ADA - values, ordering, filtering, duplicates, absent/overlong IDs and the - 256/257 input-list boundary. The size policies are ordinary successful - mints. All combinations share one cost-model setup and the existing - minting runner. Check successful transactions and costs in db-sync when - available. - """ - cluster = value_builtins_setup - temp_template = common.get_test_id(cluster) - - for variant, scripts in SCRIPTS.items(): - mints = mint_build.run_plutusv3_builtins_test( - cluster_manager=cluster_manager, - cluster_obj=cluster, - temp_template=f"{temp_template}_{variant}", - variant=f"value_{variant}", - success_scripts=scripts.succeeding, - fail_scripts=scripts.failing, - overspend_scripts=(), - is_cost_model_ok=True, - is_prot_version_ok=True, - subtests=subtests, - expected_errors={ - script.script_file.stem: ( - "keepPolicies" if "ValueKeep" in script.script_file.stem else "dropPolicies" - ) - + ": maximum number of policy ids (256) exceeded" - for script in scripts.failing - }, - ) - - # Reuse the transaction/cost checks from the existing batch7 test. - for minted in mints: - db_record = dbsync_utils.check_tx( - cluster_obj=cluster, tx_raw_output=minted.tx_output - ) - if db_record: - dbsync_utils.check_plutus_costs( - redeemer_records=db_record.redeemers, cost_records=minted.plutus_costs - )