# 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:

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