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

Fix deserialization of events with entitlements and add better error logging #67

Merged
merged 19 commits into from
Nov 4, 2024
Merged
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ ENV/
env.bak/
venv.bak/
pythonenv*
myenv

# Spyder project settings
.spyderproject
Expand Down
2 changes: 1 addition & 1 deletion flow_py_sdk/cadence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
StoragePathKind,
PublicPathKind,
PrivatePathKind,
AuthAccountKind,
AccountKind,
lealobanov marked this conversation as resolved.
Show resolved Hide resolved
PublicAccountKind,
AuthAccountKeysKind,
PublicAccountKeysKind,
Expand Down
66 changes: 39 additions & 27 deletions flow_py_sdk/cadence/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from flow_py_sdk.cadence.value import Value
from flow_py_sdk.cadence.kind import Kind

import logging
import flow_py_sdk.cadence.constants as c

_cadence_decoders: dict[str, Callable[[Any], Value]] = {}
Expand All @@ -17,32 +17,44 @@ def add_cadence_kind_decoder(t: Type[Kind]):
_cadence_kind_decoders[t.kind_str()] = t.decode


def decode(obj: [dict[Any, Any]]) -> Union[Value, Kind]:
# json decoder starts from bottom up, so it's possible that this is already decoded
if isinstance(obj, Value) or isinstance(obj, Kind):
return obj

# if there is an id key, it's already decoded and it is either a field or a parameter
if c.idKey in obj:
return obj

# if there is no type key we cant decode it directly, but it could be part of a dictionary or composite or path
if c.kindKey not in obj and c.typeKey not in obj:
return obj

if c.kindKey in obj:
kind = obj[c.kindKey]
if kind in _cadence_kind_decoders:
decoder = _cadence_kind_decoders[kind]
return decoder(obj)

if c.typeKey in obj:
type_ = obj[c.typeKey]
if type_ in _cadence_decoders:
decoder = _cadence_decoders[type_]
return decoder(obj)

raise NotImplementedError()
def decode(obj: dict[Any, Any]) -> Union[Value, Kind, dict]:
try:
# Check if already decoded
if isinstance(obj, Value) or isinstance(obj, Kind):
return obj

# If obj has an idKey, treat as already decoded field or parameter
if c.idKey in obj:
return obj

# Check for kindKey or typeKey to determine appropriate decoder
if c.kindKey in obj:
kind = obj[c.kindKey]
if kind in _cadence_kind_decoders:
decoder = _cadence_kind_decoders[kind]
return decoder(obj)

if c.typeKey in obj:
type_ = obj[c.typeKey]
if type_ in _cadence_decoders:
decoder = _cadence_decoders[type_]
return decoder(obj)

return obj # Return the object if no decoder applies

except KeyError as e:
logging.error(
f"Unhandled key '{e}' during decode of {type(obj).__name__}. "
+ f"Value: {obj}"
)
raise

except NotImplementedError:
logging.error(
f"Decoding not implemented for type {type(obj).__name__}. "
+ f"Value: {obj}"
)
raise


def cadence_object_hook(obj: [dict[Any, Any]]) -> Any:
Expand Down
4 changes: 1 addition & 3 deletions flow_py_sdk/cadence/kinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,7 @@ def decode(cls, value) -> "Kind":
entitlements = [
decode(v).as_kind(EntitlementBaseKind) for v in entitlements_val
]
return cls(
entitlements,
)
return cls(entitlements)

def encode_kind(self) -> dict:
return {c.entitlementsKey: [e.encode() for e in self.entitlements]}
Expand Down
6 changes: 3 additions & 3 deletions flow_py_sdk/cadence/simple_kinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,10 @@ def kind_str(cls) -> str:
return "PrivatePath"


class AuthAccountKind(SimpleKind):
class AccountKind(SimpleKind):
@classmethod
def kind_str(cls) -> str:
return "AuthAccount"
return "Account"


class PublicAccountKind(SimpleKind):
Expand Down Expand Up @@ -372,7 +372,7 @@ def kind_str(cls) -> str:
StoragePathKind,
PublicPathKind,
PrivatePathKind,
AuthAccountKind,
AccountKind,
PublicAccountKind,
AuthAccountKeysKind,
PublicAccountKeysKind,
Expand Down
49 changes: 40 additions & 9 deletions flow_py_sdk/client/entities.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
lealobanov marked this conversation as resolved.
Show resolved Hide resolved
from datetime import datetime
from typing import Dict, List

Expand Down Expand Up @@ -149,17 +150,36 @@ def __init__(
self.transaction_index: int = transaction_index
self.event_index: int = event_index
self.payload: bytes = payload
self.value: cadence.Event = json.loads(payload, object_hook=cadence_object_hook)

try:
# Attempt to decode the payload
self.value: cadence.Event = json.loads(
payload, object_hook=cadence_object_hook
)
except json.JSONDecodeError as e:
logging.error(
f"JSON decode error for event {event_index} with payload: {payload[:100]}... Error: {str(e)}"
)
raise
except Exception as e:
logging.error(
f"Unexpected error deserializing payload for event {event_index} with payload: {payload[:100]}... Error: {str(e)}"
)
raise
lealobanov marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def from_proto(cls, proto: entities.Event) -> "Event":
return Event(
_type=proto.type,
transaction_id=proto.transaction_id,
transaction_index=proto.transaction_index,
event_index=proto.event_index,
payload=proto.payload,
)
try:
return Event(
_type=proto.type,
transaction_id=proto.transaction_id,
transaction_index=proto.transaction_index,
event_index=proto.event_index,
payload=proto.payload,
)
except Exception as e:
logging.error(f"Failed to deserialize event {proto.event_index}: {str(e)}")
raise
lealobanov marked this conversation as resolved.
Show resolved Hide resolved


class Transaction(object):
Expand Down Expand Up @@ -288,12 +308,23 @@ def from_proto(
proto: access.TransactionResultResponse,
id: bytes,
) -> "TransactionResultResponse":
events = []
for i, event_proto in enumerate(proto.events):
try:
event = Event.from_proto(event_proto)
events.append(event)
except Exception as e:
logging.error(
f"Failed to deserialize event {i}/{len(proto.events)}: {str(e)}"
)
raise

return TransactionResultResponse(
id_=id,
status=proto.status,
status_code=proto.status_code,
error_message=proto.error_message,
events=[Event.from_proto(e) for e in proto.events],
events=events,
)


Expand Down
62 changes: 62 additions & 0 deletions tests/cadence/encode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,68 @@ def testInclusiveRangeKind(self):
)
self._encodeAndDecodeAll([kind])

def testAccountCapabilityControllerIssued(self):
kind = _EncodeTestParams(
"AccountCapabilityControllerIssued event",
cadence.Event(
"flow.AccountCapabilityControllerIssued",
[
(
"type",
cadence.TypeValue(
cadence.ReferenceKind(
cadence.EntitlementConjunctionSetKind(
[
cadence.EntitlementKind("Storage"),
cadence.EntitlementKind("Contracts"),
cadence.EntitlementKind("Keys"),
cadence.EntitlementKind("Inbox"),
cadence.EntitlementKind("Capabilities"),
]
),
cadence.AccountKind(),
),
),
)
],
),
"""
{
"value": {
"id": "flow.AccountCapabilityControllerIssued",
"fields": [
{
"name": "type",
"value": {
"value": {
"staticType": {
"type": {
"kind": "Account"
},
"kind": "Reference",
"authorization": {
"kind": "EntitlementConjunctionSet",
"entitlements": [
{ "kind": "Entitlement", "typeID": "Storage" },
{ "kind": "Entitlement", "typeID": "Contracts" },
{ "kind": "Entitlement", "typeID": "Keys" },
{ "kind": "Entitlement", "typeID": "Inbox" },
{ "kind": "Entitlement", "typeID": "Capabilities" }
]
}
}
},
"type": "Type"
}
}
]
},
"type": "Event"
}
""",
)
self._encodeAndDecodeAll([kind])

def testStorefrontEvent(self):
self.maxDiff = None
event = _EncodeTestParams(
Expand Down
Loading