{"record":{"id":"16c3183f2a547bc8","repo":"canopy-network/canopy","slug":"message-does-not-support-serialization","errorCode":null,"errorMessage":"Message does not support serialization","messagePattern":"Message does not support serialization","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"plugin/python/contract/contract.py","lineNumber":127,"sourceCode":"\ndef key_for_fee_params() -> bytes:\n    \"\"\"Generate state database key for fee parameters.\"\"\"\n    return join_len_prefix(PARAMS_PREFIX, b\"/f/\")\n\n\ndef key_for_fee_pool(chain_id: int) -> bytes:\n    \"\"\"Generate state database key for fee pool.\"\"\"\n    return join_len_prefix(POOL_PREFIX, format_uint64(chain_id))\n\n\n# Proto marshal/unmarshal utilities\n\ndef marshal(message: Any) -> bytes:\n    \"\"\"Marshal object to protobuf bytes.\"\"\"\n    try:\n        if hasattr(message, 'SerializeToString'):\n            return message.SerializeToString()\n        raise ValueError(\"Message does not support serialization\")\n    except Exception as err:\n        raise err_unmarshal(err)\n\n\ndef unmarshal(message_type: Any, data: Optional[bytes]) -> Optional[Any]:\n    \"\"\"Unmarshal bytes to protobuf message.\"\"\"\n    if not data:\n        return None\n    try:\n        if hasattr(message_type, 'FromString'):\n            return message_type.FromString(data)\n        raise ValueError(\"Message type does not support deserialization\")\n    except Exception as err:\n        raise err_unmarshal(err)\n\n\nclass Contract:\n    \"\"\"","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/canopy-network/canopy/blob/ee8197d91dd410f6592cb650a94c925ee6dc8bad/plugin/python/contract/contract.py#L109-L145","documentation":"marshal serializes a protobuf message to bytes via SerializeToString(). If the passed object lacks that method (i.e. it is not a protobuf Message), the function raises ValueError('Message does not support serialization'), which the except block then re-raises wrapped via err_unmarshal. This guards against accidentally passing plain dicts, dataclasses, or None where a protobuf message is required.","triggerScenarios":"Calling marshal() with a plain dict, dataclass, bytes, str, or None instead of a protobuf Message instance — e.g. in _deliver_message_send when building a state-write value from a non-protobuf object.","commonSituations":"Constructing the payload by hand as a dict after refactoring away from generated protobuf classes; a message type that failed to import/generate so a placeholder object is passed; passing the message class instead of an instance.","solutions":["Ensure you pass an instance of a generated protobuf message (e.g. Account(...) from the generated module), not a dict or the class itself.","Convert plain data first with a helper that constructs the protobuf message (Account(address=..., amount=...)) before calling marshal.","Verify the protobuf code generation ran (npm/py protoc step) so the real message classes with SerializeToString exist.","Catch the wrapped err_unmarshal error at the deliver_tx boundary and return a PluginError response rather than crashing."],"exampleFix":"// before\nresp = await plugin.state_write(self, sets=[{'key': k, 'value': marshal({'address': addr, 'amount': amt})}])\n// after\nmsg = Account(address=addr, amount=amt)\nresp = await plugin.state_write(self, sets=[{'key': k, 'value': marshal(msg)}])","handlingStrategy":"type-guard","validationCode":"def is_protobuf_message(obj) -> bool:\n    return hasattr(obj, 'SerializeToString') and callable(obj.SerializeToString)","typeGuard":"from google.protobuf.message import Message\n\ndef is_message(obj) -> bool:\n    return isinstance(obj, Message)","tryCatchPattern":"try:\n    value = marshal(msg)\nexcept Exception as err:\n    return PluginDeliverResponse(error=err_unmarshal(err))","preventionTips":["Always construct generated protobuf classes, not dicts, for state values.","Import message types from the generated module, never stubs.","Run proto code generation in CI so classes always exist.","Add isinstance checks at boundaries where messages cross modules."],"tags":["python","protobuf","serialization","type-mismatch"],"backgroundTag":"json-serialization-failed","analyzedSha":"ee8197d91dd410f6592cb650a94c925ee6dc8bad","analyzedAt":"2026-09-06T09:30:15.973Z","contentChangedAt":"2026-09-06T09:30:15.973Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}