Protocolo entre nodos Lightning: introducción

El protocolo original que escribí para que dos nodos Lightning se comunicaran fue simple: para hacer un cambio, tú lo propusiste y enviaste la firma para su transacción de compromiso, ellos respondieron con el secreto de revocación del anterior y una firma para ti, luego lo terminó proporcionándoles una firma. Si ambos lados propusieron cambios al mismo tiempo, uno retrocedió.

Desafortunadamente, era demasiado simple: J o seph Poon quería algo mucho más eficiente. Hacer eso bien ha sido un viaje más largo de lo que quería, pero vale la pena escribir dónde llegamos, ya que seguramente causará mucha confusión.

Hay dos formas de hacerlo más eficiente: procesamiento por lotes significa que podemos enviar más de una actualización a la vez, y desincronización significa que ambos lados son independientes. Las reglas para hacer esto no son muy complicadas, pero el sistema en general puede ser muy difícil de seguir; tanto es así que terminé escribiendo un simulador de protocolo para asegurarme de que funciona.

Cada nodo realiza un seguimiento de dos transacciones de compromiso; su local (que dejará caer en la cadena de bloques si algo sale mal), y la remota (que podría ver en la cadena de bloques si el otro lado la libera). Tanto para los compromisos locales como para los remotos, realiza un seguimiento de dos tipos de cambios: cambios no reconocidos y cambios reconocidos .

El nodo A envía un lote de actualizaciones al compromiso remoto, como “ofrecer este nuevo HTLC”, “Canjeo su HTLC existente” o “su HTLC existente no pudo enrutar”. En algún momento, A los bloquea enviando un mensaje de confirmación con su firma para la nueva transacción de compromiso remota con esos cambios. El nodo B comprueba la firma y devuelve la preimagen de revocación de su transacción de compromiso anterior, prometiendo no utilizarla nunca.

Entonces, el nodo B tiene una transacción de compromiso firmada con los cambios, pero el nodo A no. Podríamos hacer que el nodo B envíe exactamente las mismas actualizaciones a A, seguido de una confirmación, pero el mensaje de revocación tiene el mismo propósito: A sabe que B ha visto todas esas actualizaciones, por lo que ahora puede poner en cola esos cambios en su propia transacción de compromiso. . Pero aún necesita la firma de B.

Entonces B ahora envía su propio mensaje de actualización, firmando la transacción de compromiso de A con esos cambios aplicados. A comprueba la firma y envía su preimagen de revocación anterior para dejar obsoleta la antigua transacción de compromiso. Ambos lados están sincronizados.

Aquí hay un diagrama generado automáticamente de A que agrega HTLC # 1:

El caso general

Para tratar el caso en el que B hace propuestas simultáneas, necesitamos ser un poco más formales. Recuerde que cada nodo hace propuestas para el lado remoto; estos cambios solo se aplican localmente una vez que se aprueban.

Las dos listas garantizan que cada cambio se aplique primero en el nodo receptor y luego en el nodo proponente. Hay varias formas en las que una implementación puede optimizar esto, por supuesto.

Aquí hay un caso, en el que ambos ofrecen un HTLC al mismo tiempo, luego los commits vuelan hacia adelante y hacia atrás hasta que ambos estén sincronizados nuevamente:

Tarifas

La negociación de tarifas es un poco extraña: no tengo ningún interés real en las tarifas que paga el compromiso del otro nodo (dentro de lo razonable), pero necesito mantener competitivas las tarifas de mi compromiso, en caso de que necesite dejar la transacción en la cadena de bloques . Entonces, a diferencia de los HTLC, no son simétricos.

Encajamos los mensajes de “cambio de tarifa” en el mismo modelo de tuyo-luego-mío al ignorar sus cambios de tarifa cuando aplicamos cambios a nuestra propia transacción de compromiso (técnicamente, solo aplicamos cambios de tarifa desde el reconocido conjunto). Entonces enviamos un cambio de tarifa como cualquier otro cambio, enviamos la confirmación y luego, cuando la otra parte se compromete, es cuando finalmente ajustamos la tarifa de nuestra propia transacción de confirmación. Afortunadamente, los cambios de tarifas no deberían ser muy comunes:

¡Ay, mi cerebro!

Ha habido varios giros incorrectos en este diseño; pasó por varias iteraciones usando números de ack explícitos antes de convencerme de que eran innecesarios. Mi implementación tenía un error cuando se cruzaban los mensajes de confirmación, lo cual era tan confuso que tuve que volver a los primeros principios; también ha sido una fuente de confusión en la lista de distribución.

Sería fácil culpar al recién nacido y la falta de sueño, pero la verdad es que los protocolos tienen que ser lo suficientemente simples para implementar mal: ¡lo serán de todos modos!

Implementar esto en mi prototipo es el problema final para la versión 0.3, que ya tiene un nombre en clave gracias a Braydon Fuller…