# Manejo de Mensajes para LC --- En este texto introducimos y analizamos la forma en la que se manejan los mensajes para los Light Clients dentro de Lumino. * ¿Qué son los mensajes? * ¿Cómo se almacenan? * ¿Cómo se obtienen? * Code walkthrough (almacenamiento) * Code walkthrough (consulta) --- ### ¿Qué son los mensajes? Los mensajes funcionan como forma de notificar al LC que debe realizar una acción o bien simplemente para informar de algun cambio en los datos. ### ¿Cómo se almacenan? Se almacenan en la base de datos bajo la tabla ```light_client_protocol_message```. A nivel de código como el tipo de mensajes que tenemos actualmente es de un tipo unicamente se transportan bajo la estructura ```PaymentHubMessage``` ubicada en el archivo **raiden/lightclient/lightclientmessages/payment_hub_message.py** que a su vez es encapsulada por la estructura ```HubResponseMessage``` ubicada en **raiden/lightclient/lightclientmessages/hub_response_message.py**. Esta última se utiliza para crear la respuesta de la API. Los mensajes son manejados por ```LightClientMessageHandler``` ubicado en **raiden/lightclient/handlers/light_client_message_handler.py**, allí tenemos todos los métodos para almacenar y obtener los mensajes. ### ¿Cómo se obtienen? El método para obtener los mensajes es con Long Polling. Lumino posee un endpoint para obtener los mensajes, el mismo se ubica bajo la url ```/api/v1/light_client_messages```. Dicho endpoint es manejado por ```LightClientMessageResource``` que utiliza ```get_light_client_protocol_message``` del archivo **raiden/api/rest.py:2089** para obtener los mensajes. ### Code walkthrough (almacenamiento) En cualquier parte donde se quiera o bien obtener o bien almacenar los mensajes se utiliza el manejador de mensajes ```LightClientMessageHandler``` mencionado anteriormente. En este caso nos enfocaremos en el almacenamiento por lo que nos va a interesar la llamada a la función ```store_light_client_protocol_message``` del mismo y todas sus ocurrencias. Analizando el codigo podemos encontrar las siguientes ocurrencias de ésta llamada: ![](https://i.imgur.com/a6uXkIA.png) A grandes rasgos podemos ver que se almacenan mensajes cuando: * se recibe un evento que nos dice que debemos almacenar el mensaje, esto es para cuando se quiere almacenar mensajes de estado dentro del nodo, se crea un evento llamado ```StoreMessageEvent``` que dispara el almacenamiento. * se manejan mensajes del protocolo de pagos, ya sea delivered o processed. * se crea un pago En todos los casos el almacenamiento se hace utilizando el handler que tiene un manejo básico, recibe los datos y los almacena en la base para ser consultados más adelante. El almacenamiento lo podemos ver aquí **raiden/lightclient/handlers/light_client_message_handler.py:43**: ```python @classmethod def store_light_client_protocol_message(cls, identifier: int, message: Message, signed: bool, payment_id: int, order: int, message_type: LightClientProtocolMessageType, wal: WriteAheadLog): return wal.storage.write_light_client_protocol_message( message, build_light_client_protocol_message(identifier, message, signed, payment_id, order, message_type) ) ``` Que como podemos ver en el fragmento la función es bastante simple, se reciben los parámetros y se almacenan en la base creando el mensaje génerico del tipo ```LightClientProtocolMessage```. Dicho mensaje es almacenado a continuación por la base de datos: ```python def write_light_client_protocol_message(self, new_message, msg_dto): serialized_data = self.serializer.serialize(new_message) if msg_dto.is_signed: msg_dto.signed_message = serialized_data else: msg_dto.unsigned_message = serialized_data return super().write_light_client_protocol_message(msg_dto) ``` en el archivo **raiden/storage/sqlite.py:1466**, que como vemos utiliza otra función para el guardado final de los datos en el mismo archivo linea 190: ```python def write_light_client_protocol_message(self, msg_dto): with self.write_lock, self.conn: cursor = self.conn.execute( "INSERT INTO light_client_protocol_message(" "identifier, " "message_order, " "message_type, " "unsigned_message, " "signed_message, " "light_client_payment_id " ")" "VALUES(?, ?, ?, ?, ?, ?)", (str(msg_dto.identifier), msg_dto.message_order, str(msg_dto.message_type.value), msg_dto.unsigned_message, msg_dto.signed_message, str(msg_dto.light_client_payment_id) if msg_dto.light_client_payment_id else None ), ) last_id = cursor.lastrowid return last_id ``` ### Code walkthrough (consulta) Los mensajes como mencionamos anteriormente se consultan utilizando Long Polling en el recurso mencionado más arriba. Aqui mostramos un breve análisis de ese código y de como funciona. El recurso que maneja el long polling está ubicado en el archivo **raiden/api/rest.py:232**: ```("/light_client_messages", LightClientMessageResource, "Message polling"),``` Dicho recurso es manejado por ```LightClientMessageResource``` ubicado en **raiden/api/v1/resources.py:306**: ```python class LightClientMessageResource(BaseResource): get_schema = LightClientMessageGetSchema @use_kwargs(get_schema, locations=("query",)) def get(self, from_message): return self.rest_api.get_light_client_protocol_message(from_message) ``` que termina utilizando ```get_light_client_protocol_message``` dentro del archivo **raiden/api/rest.py:2089**: ```python def get_light_client_protocol_message(self, from_message: int): headers = request.headers api_key = headers.get("x-api-key") if not api_key: return ApiErrorBuilder.build_and_log_error(errors="Missing api_key auth header", status_code=HTTPStatus.BAD_REQUEST, log=log) light_client = LightClientService.get_by_api_key(api_key, self.raiden_api.raiden.wal) if not light_client: return ApiErrorBuilder.build_and_log_error( errors="There is no light client associated with the api key provided", status_code=HTTPStatus.FORBIDDEN, log=log) messages = LightClientService.get_light_client_messages(from_message, light_client.address, self.raiden_api.raiden.wal) response = [message.to_dict() for message in messages] return api_response(response) ``` Allí podemos apreciar como se chequean los parámetros del request y se obtienen los mensajes desde la base de datos, luego simplemente se responde con la lista de mensajes obtenida. A continuacion veremos como se obtiene la lista de mensajes. Esto ocurre en el ```LightClientService``` en la función ```get_light_client_messages``` ubicada en el archivo **raiden/lightclient/handlers/light_client_service.py:40**: ```python @classmethod def get_light_client_messages(cls, from_message: int, light_client: ChecksumAddress, wal: WriteAheadLog): messages = wal.storage.get_light_client_messages(from_message, light_client) result: List[HubResponseMessage] = [] for message in messages: signed = message[0] order = message[1] payment_id = message[2] unsigned_msg = message[3] signed_msg = message[4] internal_identifier = message[6] message_type = LightClientProtocolMessageType[message[7]] message = signed_msg if signed_msg is not None else unsigned_msg payment_hub_message = PaymentHubMessage(payment_id=payment_id, message_order=order, message=message, is_signed=signed) hub_message = HubResponseMessage(internal_identifier, message_type, payment_hub_message) result.append(hub_message) return result ``` donde vemos como se consultan los mensajes a la base y se crean las estructuras para utilizar en la respuesta, luego si vemos la llamada a la base veremos que es tan solo una simple consulta de datos ubicada en **raiden/storage/sqlite.py:1425**: ```python def get_light_client_messages(self, from_message, light_client): messages = super().get_light_client_messages(from_message, light_client) result = [] if messages: for message in messages: signed = False if message[3] is not None: signed = True if message[3] is not None: serialized_signed_msg = self.serializer.deserialize(message[3]) else: serialized_signed_msg = None if message[2] is not None: serialized_unsigned_msg = self.serializer.deserialize(message[2]) else: serialized_unsigned_msg = None result.append( (signed, message[1], message[4], serialized_unsigned_msg, serialized_signed_msg, message[0], message[5], message[6])) return result ``` que se forma a partir de la llamada en el mismo archivo bajo la linea 1404: ```python def get_light_client_messages(self, from_message, light_client): cursor = self.conn.cursor() cursor.execute( "SELECT identifier, message_order, unsigned_message, signed_message, light_client_payment_id, internal_msg_identifier, message_type" + " FROM light_client_protocol_message A INNER JOIN light_client_payment B" + " ON A.light_client_payment_id = B.payment_id" + " WHERE A.internal_msg_identifier >= ?" + " AND B.light_client_address = ?" + "ORDER BY light_client_payment_id, message_order ASC", (from_message, light_client), ) return cursor.fetchall() ``` Donde podemos apreciar la consulta a la base que permite obtener los datos necesarios para crear la lista de mensajes que sera utilizada en niveles superiores. ### Action Items 1. **Ver cómo se están guardando los mensajes en la base de datos para luego hacer un polling (e.g. store message event)** * Esto se analizó mas arriba 2. **Hoy en día tenemos distintos tipos de mensaje; habría que ver cómo diseñar/extender la tabla para que acepte información de los mensajes que necesitamos. Esto sería conveniente de investigar.** * Ya existe el tipo ```SettlementRequired``` que no esta siendo usado pero que probablemente fue pensado para este caso. Podemos usar la misma tabla y ver de guardar los datos que sabemos son necesarios para la operación. 3. **Una vez resuelto eso, el LC se entera del mensaje, lo firma, y debe mandar la transacción. Hay un endpoint en la API REST que es `Settle`, habría que tener otro que sea `SettlementLight`. Éste último habría uqe investigarlo en comparación con el `Settle` para adaptarlo.** * No hay ningun endpoint `Settle` o al menos no se encontro ninguno, por lo que veo no seria necesario ya que el settle es directamente ejecutado por el nodo actualmente. 4. **Investigar como análogo de Close/Close Light.** * Pendiente