Skip to content

Fix transaction certificates in Conway and handle latest cardano-cli transaction commands #446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025
Merged
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
48 changes: 37 additions & 11 deletions pycardano/backend/cardano_cli.py
Original file line number Diff line number Diff line change
@@ -44,6 +44,11 @@
Value,
)
from pycardano.types import JsonDict
from pycardano.utils import greater_than_version

if greater_than_version((3, 13)):
from enum import member # type: ignore[attr-defined]

Check warning on line 50 in pycardano/backend/cardano_cli.py

Codecov / codecov/patch

pycardano/backend/cardano_cli.py#L50

Added line #L50 was not covered by tests


__all__ = ["CardanoCliChainContext", "CardanoCliNetwork", "DockerConfig"]

@@ -70,7 +75,11 @@
PREVIEW = ["--testnet-magic", str(2)]
PREPROD = ["--testnet-magic", str(1)]
GUILDNET = ["--testnet-magic", str(141)]
CUSTOM = partial(network_magic)
CUSTOM = (
member(partial(network_magic))
if greater_than_version((3, 13))
else partial(network_magic)
)


class DockerConfig:
@@ -511,22 +520,39 @@

try:
self._run_command(
["transaction", "submit", "--tx-file", tmp_tx_file.name]
[
"latest",
"transaction",
"submit",
"--tx-file",
tmp_tx_file.name,
]
+ self._network_args
)
except CardanoCliError as err:
raise TransactionFailedException(
"Failed to submit transaction"
) from err
except CardanoCliError:
try:
self._run_command(
["transaction", "submit", "--tx-file", tmp_tx_file.name]
+ self._network_args
)
except CardanoCliError as err:
raise TransactionFailedException(
"Failed to submit transaction"
) from err

# Get the transaction ID
try:
txid = self._run_command(
["transaction", "txid", "--tx-file", tmp_tx_file.name]
["latest", "transaction", "txid", "--tx-file", tmp_tx_file.name]
)
except CardanoCliError as err:
raise PyCardanoException(
f"Unable to get transaction id for {tmp_tx_file.name}"
) from err
except CardanoCliError:
try:
txid = self._run_command(
["transaction", "txid", "--tx-file", tmp_tx_file.name]
)
except CardanoCliError as err:
raise PyCardanoException(
f"Unable to get transaction id for {tmp_tx_file.name}"
) from err

return txid
4 changes: 3 additions & 1 deletion pycardano/transaction.py
Original file line number Diff line number Diff line change
@@ -577,7 +577,9 @@ class TransactionBody(MapCBORSerializable):

ttl: Optional[int] = field(default=None, metadata={"key": 3, "optional": True})

certificates: Optional[List[Certificate]] = field(
certificates: Optional[
Union[List[Certificate], NonEmptyOrderedSet[Certificate]]
] = field(
default=None,
metadata={
"key": 4,
16 changes: 15 additions & 1 deletion pycardano/utils.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
from __future__ import annotations

import math
from typing import Dict, List, Optional, Union
import sys
from typing import Dict, List, Optional, Tuple, Union

import cbor2
from nacl.encoding import RawEncoder
@@ -24,6 +25,7 @@
"min_lovelace_post_alonzo",
"script_data_hash",
"tiered_reference_script_fee",
"greater_than_version",
]


@@ -266,3 +268,15 @@ def script_data_hash(
encoder=RawEncoder,
)
)


def greater_than_version(version: Tuple[int, int]) -> bool:
"""Check if the current Python version is greater than or equal to the specified version

Args:
version (Tuple[int, int]): Tuple of major and minor version

Returns:
True if the current Python version is greater than or equal to the specified version
"""
return sys.version_info >= version
177 changes: 159 additions & 18 deletions test/pycardano/backend/test_cardano_cli.py
Original file line number Diff line number Diff line change
@@ -8,13 +8,16 @@
from pycardano import (
ALONZO_COINS_PER_UTXO_WORD,
CardanoCliChainContext,
CardanoCliError,
CardanoCliNetwork,
DatumHash,
GenesisParameters,
MultiAsset,
PlutusV2Script,
ProtocolParameters,
PyCardanoException,
RawPlutusData,
TransactionFailedException,
TransactionInput,
)

@@ -483,31 +486,135 @@
}


def override_run_command(cmd: List[str]):
@pytest.fixture
def chain_context(genesis_file, config_file):
"""
Create a CardanoCliChainContext with a mock run_command method
Args:
genesis_file: The genesis file
config_file: The config file

Returns:
The CardanoCliChainContext
"""

def override_run_command_older_version(cmd: List[str]):
"""
Override the run_command method of CardanoCliChainContext to return a mock result of older versions of cardano-cli.
Args:
cmd: The command to run

Returns:
The mock result
"""
if "latest" in cmd:
raise CardanoCliError(
"Older versions of cardano-cli do not support the latest command"
)
if "tip" in cmd:
return json.dumps(QUERY_TIP_RESULT)
if "protocol-parameters" in cmd:
return json.dumps(QUERY_PROTOCOL_PARAMETERS_RESULT)
if "utxo" in cmd:
return json.dumps(QUERY_UTXO_RESULT)
if "txid" in cmd:
return "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
if "version" in cmd:
return "cardano-cli 8.1.2 - linux-x86_64 - ghc-8.10\ngit rev d2d90b48c5577b4412d5c9c9968b55f8ab4b9767"
else:
return None

with patch(
"pycardano.backend.cardano_cli.CardanoCliChainContext._run_command",
side_effect=override_run_command_older_version,
):
context = CardanoCliChainContext(
binary=Path("cardano-cli"),
socket=Path("node.socket"),
config_file=config_file,
network=CardanoCliNetwork.PREPROD,
)
context._run_command = override_run_command_older_version
return context


@pytest.fixture
def chain_context_latest(genesis_file, config_file):
"""
Override the run_command method of CardanoCliChainContext to return a mock result
Create a CardanoCliChainContext with a mock run_command method
Args:
cmd: The command to run
genesis_file: The genesis file
config_file: The config file

Returns:
The mock result
The CardanoCliChainContext
"""
if "tip" in cmd:
return json.dumps(QUERY_TIP_RESULT)
if "protocol-parameters" in cmd:
return json.dumps(QUERY_PROTOCOL_PARAMETERS_RESULT)
if "utxo" in cmd:
return json.dumps(QUERY_UTXO_RESULT)
if "txid" in cmd:
return "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
if "version" in cmd:
return "cardano-cli 8.1.2 - linux-x86_64 - ghc-8.10\ngit rev d2d90b48c5577b4412d5c9c9968b55f8ab4b9767"
else:

def override_run_command_latest(cmd: List[str]):
"""
Override the run_command method of CardanoCliChainContext to return a mock result of the latest versions of cardano-cli.
Args:
cmd: The command to run

Returns:
The mock result
"""
if "tip" in cmd:
return json.dumps(QUERY_TIP_RESULT)
if "txid" in cmd:
return "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
else:
return None

with patch(
"pycardano.backend.cardano_cli.CardanoCliChainContext._run_command",
side_effect=override_run_command_latest,
):
context = CardanoCliChainContext(
binary=Path("cardano-cli"),
socket=Path("node.socket"),
config_file=config_file,
network=CardanoCliNetwork.PREPROD,
)
context._run_command = override_run_command_latest
return context


@pytest.fixture
def chain_context_tx_fail(genesis_file, config_file):
"""
Create a CardanoCliChainContext with a mock run_command method
Args:
genesis_file: The genesis file
config_file: The config file

Returns:
The CardanoCliChainContext
"""

def override_run_command_fail(cmd: List[str]):
if "transaction" in cmd:
raise CardanoCliError("Intentionally raised error for testing purposes")
if "tip" in cmd:
return json.dumps(QUERY_TIP_RESULT)
return None

with patch(
"pycardano.backend.cardano_cli.CardanoCliChainContext._run_command",
side_effect=override_run_command_fail,
):
context = CardanoCliChainContext(
binary=Path("cardano-cli"),
socket=Path("node.socket"),
config_file=config_file,
network=CardanoCliNetwork.PREPROD,
)
context._run_command = override_run_command_fail
return context


@pytest.fixture
def chain_context(genesis_file, config_file):
def chain_context_tx_id_fail(genesis_file, config_file):
"""
Create a CardanoCliChainContext with a mock run_command method
Args:
@@ -517,17 +624,25 @@ def chain_context(genesis_file, config_file):
Returns:
The CardanoCliChainContext
"""

def override_run_command_fail(cmd: List[str]):
if "txid" in cmd:
raise CardanoCliError("Intentionally raised error for testing purposes")
if "tip" in cmd:
return json.dumps(QUERY_TIP_RESULT)
return None

with patch(
"pycardano.backend.cardano_cli.CardanoCliChainContext._run_command",
side_effect=override_run_command,
side_effect=override_run_command_fail,
):
context = CardanoCliChainContext(
binary=Path("cardano-cli"),
socket=Path("node.socket"),
config_file=config_file,
network=CardanoCliNetwork.PREPROD,
)
context._run_command = override_run_command
context._run_command = override_run_command_fail
return context


@@ -722,6 +837,14 @@ def test_utxo(self, chain_context):
"55fe36f482e21ff6ae2caf2e33c3565572b568852dccd3f317ddecb91463d780"
)

def test_submit_tx_bytes(self, chain_context):
results = chain_context.submit_tx("testcborhexfromtransaction".encode("utf-8"))

assert (
results
== "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
)

def test_submit_tx(self, chain_context):
results = chain_context.submit_tx("testcborhexfromtransaction")

@@ -730,5 +853,23 @@ def test_submit_tx(self, chain_context):
== "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
)

def test_submit_tx_latest(self, chain_context_latest):
results = chain_context_latest.submit_tx("testcborhexfromtransaction")

assert (
results
== "270be16fa17cdb3ef683bf2c28259c978d4b7088792074f177c8efda247e23f7"
)

def test_submit_tx_fail(self, chain_context_tx_fail):
with pytest.raises(TransactionFailedException) as exc_info:
chain_context_tx_fail.submit_tx("testcborhexfromtransaction")
assert str(exc_info.value) == "Failed to submit transaction"

def test_submit_tx_id_fail(self, chain_context_tx_id_fail):
with pytest.raises(PyCardanoException) as exc_info:
chain_context_tx_id_fail.submit_tx("testcborhexfromtransaction")
assert str(exc_info.value).startswith("Unable to get transaction id for")

def test_epoch(self, chain_context):
assert chain_context.epoch == 98