canopy-network/canopy · error · ValueError

Message type does not support deserialization

Error message

Message type does not support deserialization

What it means

unmarshal decodes raw bytes into a protobuf message using message_type.FromString(data). If the given message_type lacks FromString (i.e. it is not a generated protobuf message class, or an instance was passed instead of the class), it raises ValueError, re-raised as an err_unmarshal-wrapped error. Callers include check_tx (decoding FeeParams) and _deliver_message_send.

Source

Thrown at plugin/python/contract/contract.py:139

def marshal(message: Any) -> bytes:
    """Marshal object to protobuf bytes."""
    try:
        if hasattr(message, 'SerializeToString'):
            return message.SerializeToString()
        raise ValueError("Message does not support serialization")
    except Exception as err:
        raise err_unmarshal(err)


def unmarshal(message_type: Any, data: Optional[bytes]) -> Optional[Any]:
    """Unmarshal bytes to protobuf message."""
    if not data:
        return None
    try:
        if hasattr(message_type, 'FromString'):
            return message_type.FromString(data)
        raise ValueError("Message type does not support deserialization")
    except Exception as err:
        raise err_unmarshal(err)


class Contract:
    """
    Contract defines the smart contract that implements the extended logic of the nested chain.
    Matches Go's Contract struct.
    """

    def __init__(
        self,
        config: Optional["Config"] = None,
        fsm_config: Optional[PluginFSMConfig] = None,
        plugin: Optional["Plugin"] = None,
        fsm_id: Optional[int] = None,
    ):
        self.config = config

View on GitHub (pinned to ee8197d91d)

Solutions

  1. Pass the generated message CLASS (FeeParams), not an instance: min_fees = unmarshal(FeeParams, fee_params_bytes).
  2. Confirm the import points at the generated protobuf module that defines FromString (regenerate code if needed).
  3. Validate message_type has FromString before the call: if not hasattr(T, 'FromString'): raise TypeError(...).
  4. Catch the err_unmarshal-wrapped error in check_tx/deliver_tx and return a PluginError response to the FSM.

Example fix

// before
min_fees = unmarshal(FeeParams(), fee_params_bytes)  # instance, has no FromString
// after
min_fees = unmarshal(FeeParams, fee_params_bytes)  # class
Defensive patterns

Strategy: type-guard

Validate before calling

def is_message_class(t) -> bool:
    return isinstance(t, type) and hasattr(t, 'FromString')

Type guard

from google.protobuf.message import Message

def is_deserializable(t) -> bool:
    return isinstance(t, type) and issubclass(t, Message)

Try / catch

try:
    min_fees = unmarshal(FeeParams, fee_params_bytes)
except Exception as err:
    return PluginCheckResponse(error=err_unmarshal(err))

Prevention

When it happens

Trigger: Calling unmarshal(FeeParams_instance, data) with an instance instead of the class; passing a non-protobuf type (dict, type alias, None class) as message_type; the generated module not imported so a stub is used.

Common situations: Refactoring renamed the generated message class so the old name resolves to something else; passing `FeeParams()` (instance) rather than `FeeParams` (class); forgetting to regenerate protobuf code so FromString is missing on a hand-written stub.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/ee8563eec8efc365. Report an issue: GitHub.