Skip to content
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

Add Transaction Metadata CBOR Retrieval Functionality #384

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions pycardano/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
from .base import *
from .blockfrost import *
from .cardano_cli import *
from .kupo import *
from .ogmios_v5 import *
from .ogmios_v6 import *
18 changes: 18 additions & 0 deletions pycardano/backend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pycardano.plutus import ExecutionUnits
from pycardano.transaction import Transaction, UTxO
from pycardano.types import typechecked
from pycardano.serialization import RawCBOR

__all__ = [
"GenesisParameters",
Expand Down Expand Up @@ -218,3 +219,20 @@ def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]
List[ExecutionUnits]: A list of execution units calculated for each of the transaction's redeemers
"""
raise NotImplementedError()

def tx_metadata_cbor(
self, tx_id: str, slot: Optional[int] = None
) -> Optional[RawCBOR]:
"""Get metadata CBOR for a transaction.

Args:
tx_id (str): Transaction id for metadata to query.
slot (Optional[int]): Slot number. Required for some backends (e.g., Kupo).

Returns:
Optional[RawCBOR]: Metadata CBOR if found, None otherwise.

Raises:
NotImplementedError: If the method is not implemented in the subclass.
"""
raise NotImplementedError("tx_metadata_cbor is not implemented in the subclass")
26 changes: 26 additions & 0 deletions pycardano/backend/blockfrost.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,29 @@
getattr(result.EvaluationResult, k).steps,
)
return return_val

def tx_metadata_cbor(
self, tx_id: str, slot: Optional[int] = None
) -> Optional[RawCBOR]:
"""Get metadata CBOR for a transaction using BlockFrost.

Args:
tx_id (str): Transaction id for metadata to query.
slot (Optional[int]): Slot number (not used in BlockFrost implementation).

Returns:
Optional[RawCBOR]: Metadata CBOR if found, None otherwise.

Raises:
:class:`ApiError`: When fails to get metadata.
"""
try:
response = self.api.transaction_metadata_cbor(tx_id)

Check warning on line 342 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L341-L342

Added lines #L341 - L342 were not covered by tests
if response:
return RawCBOR(bytes.fromhex(response[0].metadata))
return None
except ApiError as e:

Check warning on line 346 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L344-L346

Added lines #L344 - L346 were not covered by tests
if e.status_code == 404:
return None

Check warning on line 348 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L348

Added line #L348 was not covered by tests
else:
raise e

Check warning on line 350 in pycardano/backend/blockfrost.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/blockfrost.py#L350

Added line #L350 was not covered by tests
47 changes: 47 additions & 0 deletions pycardano/backend/kupo.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
_kupo_url: Optional[str]
_utxo_cache: Cache
_datum_cache: Cache
_metadata_cache: Cache
_refetch_chain_tip_interval: int

def __init__(
Expand All @@ -58,6 +59,7 @@
refetch_chain_tip_interval: int = 10,
utxo_cache_size: int = 1000,
datum_cache_size: int = 1000,
metadata_cache_size: int = 1000,
):
self._kupo_url = kupo_url
self._wrapped_backend = wrapped_backend
Expand All @@ -66,6 +68,7 @@
ttl=self._refetch_chain_tip_interval, maxsize=utxo_cache_size
)
self._datum_cache = LRUCache(maxsize=datum_cache_size)
self._metadata_cache = LRUCache(maxsize=metadata_cache_size)

@property
def genesis_param(self) -> GenesisParameters:
Expand Down Expand Up @@ -253,3 +256,47 @@
:class:`TransactionFailedException`: When fails to evaluate the transaction.
"""
return self._wrapped_backend.evaluate_tx_cbor(cbor)

def tx_metadata_cbor(
self, tx_id: str, slot: Optional[int] = None
) -> Optional[RawCBOR]:
"""Get metadata CBOR from Kupo or fallback to wrapped backend.

Args:
tx_id (str): Transaction id for metadata to query.
slot (Optional[int]): Slot number. Required for Kupo backend.

Returns:
Optional[RawCBOR]: Metadata CBOR if found, None otherwise.
"""
if self._kupo_url is None:

Check warning on line 272 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L272

Added line #L272 was not covered by tests
raise AssertionError(
"kupo_url object attribute has not been assigned properly."
)

if slot is None:
raise ValueError("Slot number is required for Kupo backend.")

Check warning on line 278 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L278

Added line #L278 was not covered by tests

cache_key = (tx_id, slot)

Check warning on line 280 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L280

Added line #L280 was not covered by tests
if cache_key in self._metadata_cache:
return self._metadata_cache[cache_key]

Check warning on line 282 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L282

Added line #L282 was not covered by tests

kupo_metadata_url = f"{self._kupo_url}/metadata/{slot}?transaction_id={tx_id}"

Check warning on line 284 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L284

Added line #L284 was not covered by tests

try:
response = requests.get(kupo_metadata_url, timeout=10)
response.raise_for_status()
metadata_result = response.json()

Check warning on line 289 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L286-L289

Added lines #L286 - L289 were not covered by tests

if metadata_result and "raw" in metadata_result[0]:
metadata = RawCBOR(bytes.fromhex(metadata_result[0]["raw"]))
self._metadata_cache[cache_key] = metadata
return metadata

Check warning on line 294 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L292-L294

Added lines #L292 - L294 were not covered by tests

return None

Check warning on line 296 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L296

Added line #L296 was not covered by tests

except requests.exceptions.HTTPError as e:

Check warning on line 298 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L298

Added line #L298 was not covered by tests
if e.response.status_code == 404:
return None

Check warning on line 300 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L300

Added line #L300 was not covered by tests

raise

Check warning on line 302 in pycardano/backend/kupo.py

View check run for this annotation

Codecov / codecov/patch

pycardano/backend/kupo.py#L302

Added line #L302 was not covered by tests
20 changes: 20 additions & 0 deletions pycardano/backend/ogmios_v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,25 @@ def _parse_cost_models(self, plutus_cost_models):
cost_models["PlutusV3"][f"{i:0{width}d}"] = v
return cost_models

def tx_metadata_cbor(
self, tx_id: str, slot: Optional[int] = None
) -> Optional[RawCBOR]:
"""Get metadata CBOR for a transaction using Ogmios.

Args:
tx_id (str): Transaction id for metadata to query.
slot (Optional[int]): Slot number (not used in Ogmios implementation).

Returns:
Optional[RawCBOR]: Metadata CBOR if found, None otherwise.

Raises:
NotImplementedError: This method is not yet implemented for Ogmios V6 backend.
"""
raise NotImplementedError(
"get_metadata_cbor is not yet implemented for Ogmios V6 backend"
)


class OgmiosChainContext(OgmiosV6ChainContext):
"""An alias of OgmiosV6ChainContext for backwards compatibility."""
Expand All @@ -373,6 +392,7 @@ def KupoOgmiosV6ChainContext(
network: Network = Network.TESTNET,
kupo_url: Optional[str] = None,
) -> KupoChainContextExtension:
"""Create a KupoChainContextExtension with an OgmiosV6ChainContext backend and Kupo URL."""
return KupoChainContextExtension(
OgmiosV6ChainContext(
host,
Expand Down
Loading