Skip to content

Commit

Permalink
Merge pull request #67 from janezpodhostnik/bug-66
Browse files Browse the repository at this point in the history
#66/ Deserializing of events from flow_proto fails with transactions with many events
  • Loading branch information
janezpodhostnik authored Nov 4, 2024
2 parents ac0bbb9 + d8c9f6a commit d6d04d6
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 43 deletions.
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,
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
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

@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


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

0 comments on commit d6d04d6

Please sign in to comment.