Skip to content

Support decoding list/indefinite list from cbor #331

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fdfd3e2
Added extended signing key support for cip8
theeldermillenial Oct 6, 2023
01230ac
Fixed unused imports, flake8 checks pass.
theeldermillenial Oct 6, 2023
143e843
Fixed mypy error for overloaded variable
theeldermillenial Oct 11, 2023
049c6bf
Remove extraneous parameter for verify
theeldermillenial Oct 18, 2023
434a163
Merge branch 'main' of https://github.com/Python-Cardano/pycardano in…
theeldermillenial Nov 3, 2023
1bf3f81
Added ByteString to _restored_typed_primitive
theeldermillenial Nov 3, 2023
67add7a
Added type checking
theeldermillenial Nov 3, 2023
f0644e7
Merge pull request #1 from theeldermillenial/bugfix/bytestring
theeldermillenial Nov 3, 2023
1edb549
Merge branch 'main' of https://github.com/Python-Cardano/pycardano
theeldermillenial Jan 10, 2024
2424c64
Merge branch 'main' of https://github.com/Python-Cardano/pycardano
theeldermillenial Mar 12, 2024
971237a
Added support for fixed vs indefinite list on cbor decoding
theeldermillenial Mar 14, 2024
e2a1c9d
Fixed bug in verification key witness set deserialization
theeldermillenial Mar 15, 2024
af0c665
Fix type errors
nielstron Mar 18, 2024
f3786f1
Fix types
nielstron Mar 18, 2024
df356ed
Install setuptools
nielstron Mar 18, 2024
639cc97
Fix main.yml
nielstron Mar 18, 2024
03f653d
Revert "Fix main.yml"
nielstron Mar 18, 2024
8fca0f3
Revert "Install setuptools"
nielstron Mar 18, 2024
2e0cddd
Add setuptools to dev dependencies
nielstron Mar 18, 2024
c9a2d85
Add noqa
nielstron Mar 18, 2024
cfb7f2a
Added catch for indefinitelist on deserialization
theeldermillenial Apr 10, 2024
15c806d
Merge branch 'main' of https://github.com/python-cardano/pycardano in…
theeldermillenial Jul 12, 2024
996de30
Passing tests and CI checks
theeldermillenial Jul 12, 2024
dec33a8
Added RawPlutusData to PlutusData primitive types
theeldermillenial Jul 18, 2024
cccf0fe
Added RawPlutusData to PlutusData primitive types
theeldermillenial Jul 18, 2024
b7756ca
Added unit test for RawPlutusData
theeldermillenial Jul 18, 2024
092595c
Merge branch 'main' of https://github.com/python-cardano/pycardano in…
theeldermillenial Jul 19, 2024
b06c153
Merge branch 'feat/rawplutusdata' of https://github.com/theeldermille…
theeldermillenial Jul 19, 2024
c293704
Merge branch 'chang' of https://github.com/python-cardano/pycardano i…
theeldermillenial Sep 1, 2024
7555d03
Ignore venv
theeldermillenial Sep 1, 2024
5e4b813
Added Conway parameters to blockfrost
theeldermillenial Sep 1, 2024
5ce4d04
Changed Conway range to 0
theeldermillenial Sep 1, 2024
6cb839c
Set range to 10
theeldermillenial Sep 2, 2024
42ce792
Changed range to 1
theeldermillenial Sep 2, 2024
83b17a4
Merge branch 'chang' of https://github.com/python-cardano/pycardano i…
theeldermillenial Sep 2, 2024
3dba36b
Changed redeemers to empty map
theeldermillenial Sep 2, 2024
ea03c63
Refactor Redeemer handling in TransactionWitnessSet
bhatt-deep Sep 11, 2024
7a80ff1
Refactor KupoChainContextExtension in kupo.py
bhatt-deep Sep 13, 2024
176314e
Merge pull request #2 from bhatt-deep/chang
bhatt-deep Sep 13, 2024
0d6dd87
Refactor KupoChainContextExtension in kupo.py
bhatt-deep Sep 13, 2024
32a11cc
Changed ogmios version
theeldermillenial Sep 18, 2024
3fd91d0
Merge branch 'chang' of https://github.com/python-cardano/pycardano i…
theeldermillenial Sep 18, 2024
3785614
Changed ogmios dependency
theeldermillenial Sep 18, 2024
f0c15aa
Merge branch 'chang' of https://github.com/charli3-official/pycardano…
theeldermillenial Sep 18, 2024
7b38ec1
Fixed bug in redeemers
theeldermillenial Sep 19, 2024
3890db8
Merge branch 'chang' of https://github.com/python-cardano/pycardano i…
theeldermillenial Sep 21, 2024
af62b7f
Revert plutus changes
theeldermillenial Sep 21, 2024
9094f5e
Reverted witness changes
theeldermillenial Sep 21, 2024
85b703b
Set empty vkey witness to None when empty
theeldermillenial Sep 22, 2024
fca0337
Set vkey witnesses to None when empty
theeldermillenial Sep 22, 2024
8db56f2
Merge branch 'chang' of https://github.com/theeldermillenial/pycardan…
theeldermillenial Sep 22, 2024
d67acf2
Merge branch 'chang' of https://github.com/python-cardano/pycardano i…
theeldermillenial Sep 22, 2024
f1e3f80
Merge branch 'main' of https://github.com/python-cardano/pycardano in…
theeldermillenial Oct 8, 2024
9938b37
Add path to Ogmios v6 backend
theeldermillenial Nov 16, 2024
f203fc7
Merge branch 'feat/ogmios-path' of https://github.com/theeldermilleni…
theeldermillenial Nov 16, 2024
d76ac04
Reverted change for plutus v2 scripts
theeldermillenial Nov 16, 2024
bc6916b
Merge branch 'feat/ogmios-path' of https://github.com/theeldermilleni…
theeldermillenial Nov 16, 2024
3be8b5e
Merge branch 'main' of https://github.com/python-cardano/pycardano in…
theeldermillenial Dec 9, 2024
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ coverage.xml
# IDE
.idea
.code
.venv
/integration-test/.env
/integration-test/tmp_configs/*
53 changes: 11 additions & 42 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions pycardano/backend/kupo.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
MultiAsset,
TransactionInput,
TransactionOutput,
TransactionId,
UTxO,
Value,
)
Expand Down Expand Up @@ -197,6 +198,8 @@ def _utxos_kupo(self, address: str) -> List[UTxO]:
)
if datum_hash and result.get("datum_type", "inline"):
datum = self._get_datum_from_kupo(result["datum_hash"])
if datum:
datum_hash = None

if not result["value"]["assets"]:
tx_out = TransactionOutput(
Expand Down Expand Up @@ -253,3 +256,27 @@ def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]
:class:`TransactionFailedException`: When fails to evaluate the transaction.
"""
return self._wrapped_backend.evaluate_tx_cbor(cbor)

def get_metadata_cbor(
self, tx_id: TransactionId, slot: int
) -> Optional[RawCBOR]:
"""Get metadata cbor from Kupo.

Args:
tx_id (TransactionId): Transaction id for metadata to query.
slot (int): Slot number.

Returns:
Optional[RawCBOR]: Metadata cbor."""
if self._kupo_url is None:
raise AssertionError(
"kupo_url object attribute has not been assigned properly."
)

kupo_metadata_url = self._kupo_url + f"/metadata/{slot}?transaction_id={tx_id}"
metadata_result = requests.get(kupo_metadata_url).json()

if metadata_result and metadata_result[0]["transaction_id"] == tx_id:
return RawCBOR(bytes.fromhex(metadata_result[0]["raw"]))

return None
20 changes: 11 additions & 9 deletions pycardano/backend/ogmios_v6.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __init__(
self,
host: str = "localhost",
port: int = 1337,
path: str = "",
secure: bool = False,
refetch_chain_tip_interval: Optional[float] = None,
utxo_cache_size: int = 10000,
Expand All @@ -67,6 +68,7 @@ def __init__(
):
self.host = host
self.port = port
self.path = path
self.secure = secure
self._network = network
self._service_name = "ogmios"
Expand All @@ -86,26 +88,26 @@ def __init__(
self._datum_cache = LRUCache(maxsize=datum_cache_size)

def _query_current_era(self) -> OgmiosEra:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
return get_current_era(client)

def _query_current_epoch(self) -> int:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
epoch, _ = client.query_epoch.execute()
return epoch

def _query_chain_tip(self) -> OgmiosTip:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
tip, _ = client.query_network_tip.execute()
return tip

def _query_utxos_by_address(self, address: Address) -> List[OgmiosUtxo]:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
utxos, _ = client.query_utxo.execute([address])
return utxos

def _query_utxos_by_tx_id(self, tx_id: str, index: int) -> List[OgmiosUtxo]:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
utxos, _ = client.query_utxo.execute(
[OgmiosTxOutputReference(tx_id, index)]
)
Expand Down Expand Up @@ -135,7 +137,7 @@ def protocol_param(self) -> ProtocolParameters:
return self._protocol_param

def _fetch_protocol_param(self) -> ProtocolParameters:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
protocol_parameters, _ = client.query_protocol_parameters.execute()
pyc_protocol_params = ProtocolParameters(
min_fee_constant=protocol_parameters.min_fee_constant.lovelace,
Expand Down Expand Up @@ -205,7 +207,7 @@ def genesis_param(self) -> GenesisParameters:
return self._genesis_param # type: ignore[return-value]

def _fetch_genesis_param(self) -> OgmiosGenesisParameters:
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
return OgmiosGenesisParameters(client, self._query_current_era())

@property
Expand Down Expand Up @@ -311,13 +313,13 @@ def utxo_by_tx_id(self, tx_id: str, index: int) -> Optional[UTxO]:
def submit_tx_cbor(self, cbor: Union[bytes, str]):
if isinstance(cbor, bytes):
cbor = cbor.hex()
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
client.submit_transaction.execute(cbor)

def evaluate_tx_cbor(self, cbor: Union[bytes, str]) -> Dict[str, ExecutionUnits]:
if isinstance(cbor, bytes):
cbor = cbor.hex()
with OgmiosClient(self.host, self.port, self.secure) as client:
with OgmiosClient(self.host, self.port, self.path, self.secure) as client:
result, _ = client.evaluate_transaction.execute(cbor)
result_dict = {}
for res in result:
Expand Down
3 changes: 3 additions & 0 deletions pycardano/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def __hash__(self):
def payload(self) -> bytes:
return self._payload

def to_shallow_primitive(self) -> bytes:
return self.payload

def to_primitive(self) -> bytes:
return self.payload

Expand Down
75 changes: 65 additions & 10 deletions pycardano/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import re
import typing
from collections import OrderedDict, UserList, defaultdict
from collections.abc import Sequence
from copy import deepcopy
from dataclasses import Field, dataclass, fields
from datetime import datetime
from decimal import Decimal
from functools import wraps
from inspect import isclass
from io import BytesIO
from typing import (
Any,
Callable,
Expand All @@ -24,7 +26,14 @@
get_type_hints,
)

from cbor2 import CBOREncoder, CBORSimpleValue, CBORTag, dumps, loads, undefined
from cbor2 import (
CBORDecoder,
CBOREncoder,
CBORSimpleValue,
CBORTag,
dumps,
)
from cbor2._types import UndefinedType, break_marker
from frozendict import frozendict
from frozenlist import FrozenList
from pprintpp import pformat
Expand Down Expand Up @@ -86,6 +95,7 @@ class RawCBOR:
Primitive = Union[
bytes,
bytearray,
ByteString,
str,
int,
float,
Expand All @@ -98,6 +108,7 @@ class RawCBOR:
dict,
defaultdict,
OrderedDict,
UndefinedType,
datetime,
re.Pattern,
CBORSimpleValue,
Expand All @@ -113,6 +124,7 @@ class RawCBOR:
PRIMITIVE_TYPES = (
bytes,
bytearray,
ByteString,
str,
int,
float,
Expand All @@ -125,7 +137,7 @@ class RawCBOR:
dict,
defaultdict,
OrderedDict,
type(undefined),
UndefinedType,
datetime,
re.Pattern,
CBORSimpleValue,
Expand Down Expand Up @@ -169,6 +181,38 @@ def wrapper(cls, value: Primitive):
CBORBase = TypeVar("CBORBase", bound="CBORSerializable")


class IndefiniteDecoder(CBORDecoder):
def decode_array(self, subtype: int) -> Sequence[Any]:
# Major tag 4
length = self._decode_length(subtype, allow_indefinite=True)
if length is None:
# Indefinite length
items: list = []
if not self._immutable:
self.set_shareable(items)
while True:
value = self._decode()
if value is break_marker:
break
else:
items.append(value)

return IndefiniteList(items)
else:
return super().decode_array(subtype=subtype)

major_decoders: dict[int, Callable[[Any, int], Any]] = {
0: CBORDecoder.decode_uint,
1: CBORDecoder.decode_negint,
2: CBORDecoder.decode_bytestring,
3: CBORDecoder.decode_string,
4: decode_array,
5: CBORDecoder.decode_map,
6: CBORDecoder.decode_semantic,
7: CBORDecoder.decode_special,
}


def default_encoder(
encoder: CBOREncoder, value: Union[CBORSerializable, IndefiniteList]
):
Expand Down Expand Up @@ -235,7 +279,7 @@ class CBORSerializable:
does not refer to itself, which could cause infinite loops.
"""

def to_shallow_primitive(self) -> Primitive:
def to_shallow_primitive(self) -> Union[Primitive, "CBORSerializable"]:
"""
Convert the instance to a CBOR primitive. If the primitive is a container, e.g. list, dict, the type of
its elements could be either a Primitive or a CBORSerializable.
Expand Down Expand Up @@ -479,9 +523,13 @@ def from_cbor(cls, payload: Union[str, bytes]) -> CBORSerializable:
TestParent(3, Test(1, 2))

"""
if type(payload) is str:
if isinstance(payload, str):
payload = bytes.fromhex(payload)
value = loads(payload) # type: ignore

with BytesIO(payload) as fp:
value = IndefiniteDecoder(fp).decode()

# value = loads(payload) # type: ignore
return cls.from_primitive(value)

def __repr__(self):
Expand All @@ -503,6 +551,7 @@ def _restore_dataclass_field(

if "object_hook" in f.metadata:
return f.metadata["object_hook"](v)

return _restore_typed_primitive(f.type, v)


Expand All @@ -528,10 +577,14 @@ def _restore_typed_primitive(
raise DeserializeException(
f"List types need exactly one type argument, but got {t_args}"
)
t = t_args[0]
if not isinstance(v, list):
t_subtype = t_args[0]
if not isinstance(v, (list, IndefiniteList)):
raise DeserializeException(f"Expected type list but got {type(v)}")
return IndefiniteList([_restore_typed_primitive(t, w) for w in v])
v_list = [_restore_typed_primitive(t_subtype, w) for w in v]
if t == IndefiniteList:
return IndefiniteList(v_list)
else:
return v_list
elif isclass(t) and t == ByteString:
if not isinstance(v, bytes):
raise DeserializeException(f"Expected type bytes but got {type(v)}")
Expand Down Expand Up @@ -657,8 +710,10 @@ def to_shallow_primitive(self) -> Primitive:
return primitives

@classmethod
@limit_primitive_type(list, tuple)
def from_primitive(cls: Type[ArrayBase], values: Union[list, tuple]) -> ArrayBase:
@limit_primitive_type(list, tuple, IndefiniteList)
def from_primitive(
cls: Type[ArrayBase], values: Union[list, tuple, IndefiniteList]
) -> ArrayBase:
"""Restore a primitive value to its original class type.

Args:
Expand Down
Loading
Loading