# Todo lo que necesitas saber sobre los canales IBC (Parte 1)

Los canales son una parte integral del protocolo central de Comunicación Inter Blockchain (IBC, por sus siglas en inglés). Sirven como un conducto para transferir paquetes entre un módulo/aplicación en una cadena y un módulo en otra cadena. Para los desarrolladores de aplicaciones, los canales son la capa más importante de abstracción de IBC con la que deben estar familiarizados.

Este artículo tiene como objetivo explicar los canales y abordar las preguntas/malentendidos comunes que los desarrolladores tienen sobre los canales de IBC. Los temas clave cubiertos incluyen el orden de los canales, los apretones de manos, el cierre, la reapertura, las propiedades de seguridad de los canales y los tiempos de espera de los paquetes.

Hay mucho que cubrir, por lo que esta es la primera parte de una serie de dos. La Parte 1 se centra en los fundamentos de los canales de IBC, mientras que la Parte 2 aborda algunas de las preguntas frecuentes sobre canales de los desarrolladores de aplicaciones.

Tipos de canales Un canal es un conducto para transferir paquetes entre dos módulos/aplicaciones diferentes en dos cadenas diferentes. Aseguran que los paquetes se ejecuten solo una vez y se entreguen solo al módulo correspondiente que posee el otro extremo del canal.

Cada canal está asociado con una conexión específica, y una conexión puede tener cualquier cantidad de canales asociados.

Actualmente, existen dos tipos principales de canales:

1. Ordenados: donde los paquetes se entregan (al destino) exactamente en el orden en que se enviaron (desde la fuente).
2. No ordenados: donde los paquetes pueden entregarse en cualquier orden, independientemente del orden en que se enviaron.

El orden de los canales se hace cumplir mediante el uso de secuencias de paquetes.

Cada paquete tiene una secuencia de paquetes que corresponde al orden de envío y recepción de los paquetes. Un paquete con un número de secuencia 1 será recibido y procesado en el destino antes que un paquete con un número de secuencia 2 o superior.

Un extremo del canal mantiene tres secuencias diferentes: nextSequenceSend, nextSequenceRecv y nextSequenceAck. Cada vez que un canal envía, recibe o reconoce un paquete, se incrementan los contadores de nextSequenceSend, nextSequenceRecv y nextSequenceAck, respectivamente.

En canales ordenados, un paquete se recibe y procesa solo si la secuencia del paquete corresponde a la nextSequenceRecv que el extremo del canal espera. Si la secuencia del paquete es menor que la nextSequenceRecv, lo que significa que el paquete ya se recibió, entonces el IBC central no realiza ninguna operación, como se muestra aquí.

Si la secuencia del paquete es mayor que la nextSequenceRecv, lo que significa que existe un paquete con una secuencia anterior que debe recibirse antes del actual, el paquete es rechazado y un relayer eventualmente envía el paquete correcto.

Cuando se recibe un paquete en un canal no ordenado, el IBC central simplemente se asegura de que el paquete no haya sido recibido previamente (ya que el orden no importa) buscando un recibo del paquete, es decir, un solo bit que indica que se recibió un paquete. Si el recibo ya existe, entonces el canal no realiza ninguna operación. Si no existe, se establece el recibo y se procesa el paquete.

Apertura/cierre del canal mediante un apretón de manos Los canales se establecen a través de un apretón de manos de 4 pasos. En cada paso del proceso de apretón de manos, el IBC central realiza verificaciones y lógica básica, al tiempo que realiza llamadas a la aplicación donde se puede realizar una lógica personalizada de apretón de manos.

La negociación de versión entre los dos módulos tiene lugar durante el apretón de manos. Esto permite que las aplicaciones acuerden la estructura de los datos del paquete que se enviará por ese canal, la lógica con respecto al uso de middleware, etc.

El apretón de manos del canal en el caso de ICS-20¹ es el siguiente:

1. Un relayer llama a la función chanOpenInit en la cadena A. Posteriormente, la devolución de llamada onChanOpenInit permite que la aplicación en la cadena A realice una lógica personalizada. Esto establece el estado del extremo del canal (en A) en INIT. Un relayer pasa esta versión a la cadena B en el paso 2 como la cadena contraparteVersion.
2. Se llama a chanOpenTry en la cadena B, donde la aplicación realiza una lógica personalizada de apretón de manos en onChanOpenTry, estableciendo el estado del extremo del canal en TRY. La aplicación en B asegura que su versión sea la misma que la propuesta por la aplicación contraparte, como se muestra aquí.
3. Se llama a chanOpenAck en A. La negociación de versión de la aplicación se finaliza durante este paso y el estado del extremo del canal en A se establece en OPEN.
4. Se llama a chanOpenConfirm en B, donde el extremo del canal también se establece en OPEN.

Es posible que las devoluciones de llamada devuelvan un error en cualquier etapa del proceso de apretón de manos. En este caso, el apretón de manos del canal falla y es necesario negociar uno nuevo.

Una vez que se ha establecido un canal entre dos módulos, no es posible actualizar dichos módulos o utilizar middleware para envolver los módulos sin abrir un nuevo canal o coordinar una actualización en toda la red. Nuestro trabajo actual sobre la capacidad de actualización del canal aborda este problema para que los módulos puedan actualizarse para aprovechar nuevas características o se pueda agregar middleware en ambos lados manteniendo los mismos canales.

Un canal se cierra solo en circunstancias excepcionales. Hablaremos de todas las razones por las que un canal podría cerrarse en la parte 2 de esta serie. El cierre de un canal se realiza mediante un apretón de manos de cierre de canal de 2 pasos. Un relayer envía ChannelCloseInit, lo que permite que la aplicación en esa cadena ejecute una lógica personalizada en onChanCloseInit. Se envía ChannelCloseConfirm en la contraparte donde se llama a la devolución de llamada onChanCloseConfirm.

Especificar el tiempo de espera basado en el reloj local del receptor garantiza que nunca se produzca una situación en la que el remitente crea que un paquete ha agotado su tiempo y tome una acción, como liberar tokens en una transferencia de tokens en garantía, mientras que el paquete en realidad se recibió a tiempo por la contraparte, quien luego procede a acuñar tokens, lo que resultaría en un doble gasto.

Existen dos escenarios en los que un paquete puede agotar el tiempo: 1) el caso estándar en el que ha pasado la marca de tiempo o la altura de tiempo de espera en la cadena receptora, o 2) un canal se cierra y, por lo tanto, todos los paquetes que no se recibieron agotarán el tiempo.

Nos sumergiremos en ambos de estos casos por separado. Primero, echemos un vistazo al escenario más común.

Tiempos de espera estándar para paquetes Cuando se envía un paquete a la cadena de destino donde ha pasado la marca de tiempo/altura de tiempo de espera, la función RecvPacket devuelve un error. Un relayer escanea tales errores (ErrPacketTimeout) y envía un TimeoutPacket a la cadena de origen. El TimeoutPacket consta de una prueba y una ProofHeight. La prueba se utiliza para demostrar a la cadena de origen que en esta altura en particular, el paquete no fue recibido. Si está familiarizado con cómo funcionan los clientes ligeros, entonces sabrá que cada ProofHeight está asociada con un ConsensusState particular. Y cada ConsensusState tiene una marca de tiempo particular. Por lo tanto, obtenemos la marca de tiempo asociada con una ProofHeight particular utilizando la función GetTimestampAtHeight. Por lo tanto, la ProofHeight demuestra que la marca de tiempo de la contraparte (asociada con la ProofHeight) es mayor que la marca de tiempo de espera que se especificó en el paquete. Si se especifica una altura de tiempo de espera en lugar de una marca de tiempo, entonces demostramos que la ProofHeight es mayor o igual a la altura de tiempo de espera. Una vez que hemos demostrado los pasos 5. o 6., pasamos a demostrar que en esta altura, el paquete no fue recibido. Este paso difiere según el orden del canal: 7.a. En el caso simple de canales desordenados, demostramos que en esta ProofHeight, no existe un recibo del paquete. En otras palabras, esta es una prueba de que una cierta clave no existe en el árbol de estado.²

7.b. Para canales ordenados, verificamos que el nextSequenceReceive es menor o igual a la secuencia del paquete. Esto asegura que la cadena de destino no ha recibido dicho paquete. Por ejemplo, si el nextSequenceReceive es 3 y la secuencia del paquete es 4, entonces el canal espera recibir el paquete con secuencia 3 antes de recibir el 4. A diferencia de 7.a., esto es una prueba de membresía del nextSequenceReceive. ³

Una vez que hemos demostrado 5. (o 6.) y 7., el módulo emisor llama a OnTimeoutPacket para revertir cualquier cambio de estado o realizar lógica personalizada. En el caso de las transferencias de tokens de ICS-20, los tokens en garantía se liberan y se reembolsan al usuario.

Tiempo de espera cuando se cierra un canal La función TimeoutOnClose es llamada por el módulo emisor con el fin de demostrar que el canal por el cual se envió un paquete ha sido cerrado.

Realizar la lógica de tiempo de espera cuando un canal se cierra es sencillo. A diferencia de los tiempos de espera mencionados anteriormente, donde el núcleo de IBC demuestra que el tiempo en la contraparte ha superado el tiempo de espera del paquete, en este caso, solo necesitamos demostrar que el estado del canal de la contraparte está cerrado (como se muestra aquí).

Fungibilidad de tokens En IBC, el mismo token enviado a través de dos canales diferentes no es fungible. Esto no es un error, sino un aspecto fundamental del modelo de seguridad de IBC.

Incluso en el escenario en el que existan dos canales diferentes 1 y 2, entre las mismas dos cadenas A y B, el mismo token $FOO enviado a través de estos canales no será fungible. Esto se debe a que cada canal tiene sus propiedades de seguridad. Por ejemplo, dos canales diferentes pueden tener versiones diferentes. O los canales pueden no estar asociados con la misma conexión, sino con dos conexiones diferentes (que podrían estar conectando diferentes clientes ligeros). Pero lo más importante es que los módulos no tienen forma de saber a qué cadena pertenece el extremo del canal de la contraparte. En otras palabras, los módulos solo tienen conocimiento del canal por el que están enviando datos y no de la cadena con la que están comunicándose.

Como resultado, es imperativo que cada canal esté aislado en cuanto a sus límites de seguridad y que no se hagan suposiciones ingenuas con respecto a los canales y sus clientes ligeros asociados.

Conclusión Como se ha explicado anteriormente, los canales desempeñan un papel importante en el transporte y la ordenación de paquetes dentro de IBC. Dado que los módulos y los canales están estrechamente acoplados, es útil que los desarrolladores de aplicaciones estén familiarizados con los detalles de los canales de IBC.

En la segunda parte de esta serie, aclararemos algunas de las preguntas frecuentes de los desarrolladores de aplicaciones con respecto a los canales. Mientras tanto, no dudes en consultar nuestro portal para desarrolladores para obtener más información sobre los canales de IBC.
