diff --git a/cardano_node_tests/tests/test_chain_transactions.py b/cardano_node_tests/tests/test_chain_transactions.py index 04d1de3d2..637811951 100644 --- a/cardano_node_tests/tests/test_chain_transactions.py +++ b/cardano_node_tests/tests/test_chain_transactions.py @@ -74,11 +74,7 @@ def _repeat_submit(cluster_obj: clusterlib.ClusterLib, tx_file: pl.Path) -> str: try: cluster_obj.g_transaction.submit_tx_bare(tx_file=tx_file) except clusterlib.CLIError as exc: - exc_str = str(exc) - inputs_spent = ( - "All inputs are spent" in exc_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in exc_str - ) + inputs_spent = helpers.is_inputs_spent_err(str(exc)) if r == 0 and inputs_spent: err_str = "Tx input is missing, maybe temporary fork happened?" elif inputs_spent: diff --git a/cardano_node_tests/tests/test_tx_many_utxos.py b/cardano_node_tests/tests/test_tx_many_utxos.py index 1d9c21267..5711d5ad4 100644 --- a/cardano_node_tests/tests/test_tx_many_utxos.py +++ b/cardano_node_tests/tests/test_tx_many_utxos.py @@ -96,7 +96,7 @@ def many_utxos( less_than_1_ada = int(float(multiple / 20) * 1_000_000) amount = less_than_1_ada + 1_000_000 - # Repeat transaction when "BadInputsUTxO" error happens + # Repeat transaction when the Tx inputs were already spent excp: clusterlib.CLIError | None = None for r in range(2): if r > 0: @@ -110,16 +110,11 @@ def many_utxos( amount=amount, ) except clusterlib.CLIError as err: - # The "BadInputsUTxO" error happens when a single UTxO is used in two - # transactions. This can happen from time to time, we stress + # The "inputs already spent" error happens when a single UTxO is used + # in two transactions. This can happen from time to time, we stress # the network here and waiting for 2 blocks may not be enough to get a # transaction through. - exc_str = str(err) - inputs_spent = ( - "All inputs are spent" in exc_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in exc_str - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str(err)): raise excp = err else: diff --git a/cardano_node_tests/tests/test_tx_mempool.py b/cardano_node_tests/tests/test_tx_mempool.py index fda3f1ffe..bdcb5ddc1 100644 --- a/cardano_node_tests/tests/test_tx_mempool.py +++ b/cardano_node_tests/tests/test_tx_mempool.py @@ -97,11 +97,7 @@ def test_query_mempool_txin( try: cluster.g_transaction.submit_tx_bare(tx_file=out_file_signed) except clusterlib.CLIError as exc: - exc_str = str(exc) - inputs_spent = ( - "All inputs are spent" in exc_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in exc_str - ) + inputs_spent = helpers.is_inputs_spent_err(str(exc)) if r == 0 or not inputs_spent: raise break diff --git a/cardano_node_tests/tests/test_tx_negative.py b/cardano_node_tests/tests/test_tx_negative.py index 819c49142..79ea2d6da 100644 --- a/cardano_node_tests/tests/test_tx_negative.py +++ b/cardano_node_tests/tests/test_tx_negative.py @@ -773,8 +773,7 @@ def test_duplicated_tx( exc_value = str(excinfo.value) with common.allow_unstable_error_messages(): assert ( - "All inputs are spent" in exc_value # In cardano-node >= 10.6.0 - or "(ValueNotConservedUTxO" in exc_value + helpers.is_inputs_spent_err(exc_value) or "(ValueNotConservedUTxO" in exc_value ), exc_value @allure.link(helpers.get_vcs_link()) @@ -1310,7 +1309,7 @@ def test_nonexistent_utxo_ix( * Get valid UTxO from payment address * Modify UTxO index to nonexistent value (5) * Attempt to build or submit transaction using invalid UTxO index - * Check that transaction fails with empty UTxO or BadInputsUTxO error + * Check that transaction fails with empty UTxO or inputs already spent error """ temp_template = common.get_test_id(cluster) @@ -1336,10 +1335,7 @@ def test_nonexistent_utxo_ix( ), err_str elif build_method == clusterlib_utils.BuildMethods.BUILD_RAW: with common.allow_unstable_error_messages(): - assert ( - "All inputs are spent" in err_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in err_str - ), err_str + assert helpers.is_inputs_spent_err(err_str), err_str else: msg = f"Unsupported build method: {build_method}" raise ValueError(msg) @@ -1363,7 +1359,7 @@ def test_nonexistent_utxo_hash( * Get valid UTxO from payment address * Modify last 4 characters of UTxO hash to create nonexistent hash * Attempt to build or submit transaction using invalid UTxO hash - * Check that transaction fails with empty UTxO or BadInputsUTxO error + * Check that transaction fails with empty UTxO or inputs already spent error """ temp_template = common.get_test_id(cluster) @@ -1390,10 +1386,7 @@ def test_nonexistent_utxo_hash( ), err_str elif build_method == clusterlib_utils.BuildMethods.BUILD_RAW: with common.allow_unstable_error_messages(): - assert ( - "All inputs are spent" in err_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in err_str - ), err_str + assert helpers.is_inputs_spent_err(err_str), err_str else: msg = f"Unsupported build method: {build_method}" raise ValueError(msg) diff --git a/cardano_node_tests/tests/tests_plutus/spend_build.py b/cardano_node_tests/tests/tests_plutus/spend_build.py index efec1134e..25306589f 100644 --- a/cardano_node_tests/tests/tests_plutus/spend_build.py +++ b/cardano_node_tests/tests/tests_plutus/spend_build.py @@ -8,6 +8,7 @@ from cardano_node_tests.tests import plutus_common from cardano_node_tests.utils import clusterlib_utils from cardano_node_tests.utils import dbsync_utils +from cardano_node_tests.utils import helpers from cardano_node_tests.utils import tx_view from cardano_node_tests.utils.versions import VERSIONS @@ -302,11 +303,7 @@ def _build_spend_locked_txin( # noqa: C901 ): issues.ledger_4198.finish_test() # Check if resubmitting failed because an input UTxO was already spent - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str_exc): raise else: pytest.fail("Transaction was not submitted successfully") diff --git a/cardano_node_tests/tests/tests_plutus/spend_raw.py b/cardano_node_tests/tests/tests_plutus/spend_raw.py index 89a99c017..8b3799e93 100644 --- a/cardano_node_tests/tests/tests_plutus/spend_raw.py +++ b/cardano_node_tests/tests/tests_plutus/spend_raw.py @@ -8,6 +8,7 @@ from cardano_node_tests.tests import plutus_common from cardano_node_tests.utils import clusterlib_utils from cardano_node_tests.utils import dbsync_utils +from cardano_node_tests.utils import helpers from cardano_node_tests.utils import tx_view from cardano_node_tests.utils.versions import VERSIONS @@ -297,12 +298,7 @@ def _spend_locked_txin( # noqa: C901 cluster_obj.g_transaction.submit_tx_bare(tx_file=tx_signed) except clusterlib.CLIError as exc: # Check if resubmitting failed because an input UTxO was already spent - str_exc = str(exc) - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str(exc)): raise else: pytest.fail("Transaction was not submitted successfully") diff --git a/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_build.py b/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_build.py index aa4b829ac..bffcc3306 100644 --- a/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_build.py +++ b/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_build.py @@ -210,11 +210,7 @@ def test_with_total_return_collateral( ): issues.ledger_4198.finish_test() # Check if resubmitting failed because an input UTxO was already spent - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str_exc): raise else: pytest.fail("Transaction was not submitted successfully") @@ -361,11 +357,7 @@ def test_collateral_with_tokens( ): issues.ledger_4198.finish_test() # Check if resubmitting failed because an input UTxO was already spent - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str_exc): raise else: pytest.fail("Transaction was not submitted successfully") diff --git a/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_raw.py b/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_raw.py index fe57e540e..ab10cf422 100644 --- a/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_raw.py +++ b/cardano_node_tests/tests/tests_plutus_v2/test_spend_collateral_raw.py @@ -171,11 +171,7 @@ def test_with_total_return_collateral( ): issues.ledger_4198.finish_test() # Check if resubmitting failed because an input UTxO was already spent - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str_exc): raise else: pytest.fail("Transaction was not submitted successfully") @@ -336,11 +332,7 @@ def test_collateral_with_tokens( ): issues.ledger_4198.finish_test() # Check if resubmitting failed because an input UTxO was already spent - inputs_spent = ( - "All inputs are spent" in str_exc # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in str_exc - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str_exc): raise else: pytest.fail("Transaction was not submitted successfully") diff --git a/cardano_node_tests/utils/helpers.py b/cardano_node_tests/utils/helpers.py index 98b683956..adc624a36 100644 --- a/cardano_node_tests/utils/helpers.py +++ b/cardano_node_tests/utils/helpers.py @@ -342,6 +342,26 @@ def is_in_interval(num1: float, num2: float, *, frac: float = 0.1) -> bool: return _min <= num1 <= _max +def is_inputs_spent_err(err_str: str) -> bool: + """Check if the error message indicates that the Tx inputs were already spent. + + The ledger reports this condition with a different error in each era, so all the known + variants are accepted. In the Dijkstra era the Conway mempool failure is injected as the + dedicated `AllInputsAreSpent` failure. + + Args: + err_str: An error message to check. + + Returns: + bool: `True` if the error was caused by already spent Tx inputs. + """ + return ( + "All inputs are spent" in err_str # Conway era, in cardano-node >= 10.6.0 + or "AllInputsAreSpent" in err_str # Dijkstra era + or "BadInputsUTxO" in err_str + ) + + @functools.lru_cache(maxsize=100) def tool_has(command: str) -> bool: """Check if a tool has a subcommand or argument available. diff --git a/cardano_node_tests/utils/submit_api.py b/cardano_node_tests/utils/submit_api.py index 71b5936ed..9360987cc 100644 --- a/cardano_node_tests/utils/submit_api.py +++ b/cardano_node_tests/utils/submit_api.py @@ -14,6 +14,7 @@ from cardano_node_tests.utils import cluster_nodes from cardano_node_tests.utils import custom_clusterlib +from cardano_node_tests.utils import helpers from cardano_node_tests.utils import http_client LOGGER = logging.getLogger(__name__) @@ -150,12 +151,7 @@ def submit_tx( err = None except SubmitApiError as exc: # Check if resubmitting failed because an input UTxO was already spent - exc_str = str(exc) - inputs_spent = ( - "All inputs are spent" in exc_str # In cardano-node >= 10.6.0 - or "BadInputsUTxO" in exc_str - ) - if not inputs_spent: + if not helpers.is_inputs_spent_err(str(exc)): raise err = exc # If here, the TX is likely still in mempool and we need to wait diff --git a/framework_tests/test_helpers.py b/framework_tests/test_helpers.py index 23d032425..64771bb97 100644 --- a/framework_tests/test_helpers.py +++ b/framework_tests/test_helpers.py @@ -497,6 +497,34 @@ def test_custom_frac(self): assert helpers.is_in_interval(79, 100, frac=0.2) is False +class TestIsInputsSpentErr: + """Tests for `is_inputs_spent_err`.""" + + @pytest.mark.parametrize( + "err_str", + ( + 'ConwayApplyTxError (ConwayMempoolFailure "All inputs are spent." :| [])', + "DijkstraApplyTxError (AllInputsAreSpent :| [])", + "ShelleyTxValidationError (UtxowFailure (UtxoFailure (BadInputsUTxO ...", + ), + ) + def test_known_variants(self, err_str: str): + """Recognize the error variant of each era.""" + assert helpers.is_inputs_spent_err(err_str) is True + + @pytest.mark.parametrize( + "err_str", + ( + "", + 'ConwayApplyTxError (ConwayMempoolFailure "Insufficient collateral" :| [])', + "ValueNotConservedUTxO", + ), + ) + def test_unrelated_errors(self, err_str: str): + """Don't match errors with an unrelated cause.""" + assert helpers.is_inputs_spent_err(err_str) is False + + class TestToolHas: """Tests for `tool_has`.