Artículo de Blog

Intuitivo Protocolo de Consenso de Stellar

Autor

Marta Lokhava

Fecha de publicación

Stellar Conensus Protocol

Prueba de acuerdo

El consenso distribuido es literalmente el núcleo de Stellar, pero puede ser difícil de entender. Es fácil perderse porque hay muchos pasos, muchas partes móviles y muchas reglas a tener en cuenta. En Stellar Development Foundation, abordamos problemas complejos construyendo primero un entendimiento de alto nivel. En este post, discutiremos una versión simplificada del Protocolo de Consenso de Stellar (SCP) y proporcionaremos ejemplos concretos de cómo la Red Stellar logra el acuerdo a través de SCP.

Recapitulación de Quórum

Nuestro post anterior, “Por Qué los Quórum Importan y Cómo los Aborda Stellar”, se centró mucho en las rebanadas de quórum y las implicaciones de las configuraciones de quórum. Recuerda,

Una rebanada de quórum es un subconjunto de nodos en la red que un nodo dado elige confiar y depender.
Un conjunto de nodos es un quórum si es no vacío, y contiene una rebanada para cada miembro.

Estas definiciones son bastante abstractas. En la práctica, al configurar un nodo, los operadores especifican listas de nodos en los que confían. También especifican umbrales, o el número mínimo de nodos en dichas listas que deben estar de acuerdo. Llamamos a estas listas conjuntos de quórum. Por ejemplo, si un operador configura su conjunto de quórum para ser {A, B, C} con un umbral de 2, entonces ya sea {A, B}, {B, C} o {C, A} deben estar de acuerdo antes de que el nodo pueda proceder. {A, B}, {B, C} o {C, A} son todas rebanadas de quórum.

Ahora introducimos otro concepto crucial para SCP: conjuntos bloqueantes.

Un conjunto bloqueante es cualquier conjunto de nodos en un conjunto de quórum, sin el cual es imposible alcanzar el consenso.

Por ejemplo, dado un conjunto de quórum de 4 nodos con umbral 3 (lo que significa que al menos 3 de 4 nodos deben estar de acuerdo), cualquier combinación de 2 nodos forma un conjunto bloqueante. Dado que cualquier nodo de un conjunto de quórum puede estar en un conjunto bloqueante, puede haber muchos conjuntos bloqueantes diferentes.

Todo Comienza Con Declaraciones

Una ronda de consenso es un procedimiento para acordar en transacciones a ejecutar en un libro mayor particular. Las declaraciones son el bloque de construcción más pequeño de una ronda de consenso. En el contexto de Stellar, declaraciones válidas expresan diferentes opiniones de los nodos respecto a transacciones en las que acordar para un libro mayor dado. Por ejemplo, “Voto por un conjunto de transacciones C para ser consideradas para este libro mayor” es una declaración válida. De manera similar, “Apliqué un conjunto de transacciones C a este libro mayor” también es una declaración válida.

La Votación Federada Como Herramienta de Acuerdo

Un sistema distribuido, como la red Stellar, necesita un mecanismo de consenso para acordar en diferentes declaraciones. ¿Cómo aseguramos que un nodo no actúe sobre una declaración (por ejemplo, confirmar una transacción de pago) demasiado pronto? Más generalmente, ¿cuándo sabe un nodo que es seguro confirmar algo? En el Protocolo de Consenso de Stellar (SCP), el acuerdo se logra a través de la votación federada.

Para entender la votación federada, primero debemos entender cómo un nodo razona sobre el estado de la red basado en lo que aprende de su conjunto de quórum. Específicamente, un nodo no sabe lo que los nodos fuera de su conjunto de quórum decidieron, sin embargo, la red debe acordar en un único valor. Antes de que una declaración esté 100% acordada por cada nodo honesto en la red, tiene que pasar por tres pasos de votación federada: voto, aceptación, confirmación. Dada una declaración A, un nodo podría tener cuatro opiniones sobre ella:

  1. No sé nada sobre A, y no tengo opinión.
  2. Yo voto por A, es válida, pero no sé si es seguro actuar sobre ella todavía.
  3. Yo acepto A, porque suficientes nodos apoyaron esta declaración, pero no sé si es seguro actuar sobre ella todavía.
  4. Yo confirmo A, es seguro actuar sobre ella. Incluso si cada nodo en mi quórum no ha confirmado A, no podrán confirmar nada más que A.

Para hacer transiciones entre los estados anteriores (voto, aceptación, confirmación), la votación federada prescribe las siguientes reglas:

Vota por A si es consistente con mis votos anteriores

Acepta A si ya sea:

  • Cada nodo en una rebanada de quórum votó por o aceptó A, O
  • Mi conjunto bloqueante aceptó A (incluso si voté por algo que contradice A en el pasado, olvido ese voto, y procedo con “aceptar A”)

Confirma A si cada nodo en una rebanada de quórum aceptó A[@portabletext/react] Unknown block type "span", specify a component for it in the `components.types` prop

Pero, ¿por qué realmente la votación federada es un proceso de tres pasos? ¿No podría alcanzarse el acuerdo simplemente aceptando declaraciones? La respuesta yace en una de las reglas anteriores: “Mi conjunto bloqueante aceptó A”. Sin el paso de confirmación, el conjunto bloqueante de un nodo puede convencer al nodo de aceptar cualquier valor arbitrario. Aceptar declaraciones solo funciona si todos los conjuntos bloqueantes son honestos, pero no hay garantía de que lo serán. Con el paso de confirmación, un nodo solo procederá a estar de acuerdo en una declaración si cada nodo en su quórum también aceptó esa declaración, lo que proporciona seguridad óptima.

Votación Federada en SCP

Cada ronda de consenso puede ser separada en dos etapas:

  • Seleccionando conjuntos de transacciones candidatas que pueden ser incluidas en un libro mayor (protocolo de nominación)
  • Asegurando que la red puede confirmar y aplicar unánimemente conjuntos de transacciones nominadas (protocolo de balota)

Intuitivamente, SCP actúa como un embudo:

  • Un nodo comienza con la posibilidad de acordar en cualquier valor (en el contexto de Stellar, un valor es un conjunto de transacciones)
  • Nominar las declaraciones buscan seleccionar un número (idealmente) pequeño N de valores válidos candidatos.
  • Preparar las declaraciones intentan asegurar un subconjunto M (mucho menor que N) de ese conjunto finito (es decir, cualquier valor que haya sido confirmado nominado) puede ser comprometido.
  • Comprometer las declaraciones proceden a comprometer un valor preparado confirmado, o comprometen lo que el conjunto de bloqueo de un nodo ha comprometido.

Nominación

El protocolo de nominación ayuda a delimitar el conjunto de valores sobre los que se acuerda, realizando votaciones federadas en declaraciones como “Nominar conjunto de transacciones C”. Si la votación tiene éxito, el conjunto de transacciones se añade a la lista de candidatos para ser utilizado más tarde en el protocolo de balota. Hay un invariante importante: tan pronto como un nodo confirma su primer candidato, deja de votar para nominar nuevos conjuntos de transacciones. Aún puede aceptar y confirmar declaraciones de nominación que se introdujeron antes, si, por ejemplo, ve que el conjunto de bloqueo aceptó algún valor nuevo. Este invariante garantiza que en algún momento todos los nodos convergerán en un conjunto de candidatos. Intuitivamente, si cada nodo en la red deja de introducir nuevos valores, pero continúa confirmando lo que otros nodos confirmaron, eventualmente todos terminarán con la misma lista de candidatos.

Debido a que no puede saber si otros nodos han convergido (no se están votando nuevos valores) o algunas votaciones aún están en tránsito (no se han recibido), un nodo puede iniciar el protocolo de balota tan pronto como confirma un candidato. La entrada al protocolo de balota se llama valor compuesto, y es el conjunto de transacciones más grande en la lista de candidatos confirmados a través de la nominación.

Incluso después de que un nodo confirma su primer candidato y comienza el protocolo de balota, la nominación continúa ejecutándose en segundo plano, por lo que el nodo puede confirmar más candidatos a medida que se entera de nominaciones aceptadas y confirmadas de otros nodos en la red.

Votación

El protocolo de balota intenta comprometer de manera segura el valor compuesto, incluso si algunos nodos en la red se vuelven inaccesibles. Consiste en dos pasos:

  • “preparar”, que verifica que la porción de quórum de un nodo tiene el valor correcto y está dispuesto a comprometerlo
  • “comprometer”, que asegura que la porción de quórum de un nodo realmente comprometa el valor.

El protocolo de balota incorpora votaciones federadas en declaraciones “Preparar conjunto de transacciones C” y “Comprometer conjunto de transacciones X”. Se comporta de manera optimista e intenta comprometer el valor compuesto mientras la nominación aún puede estar ejecutándose. En el mejor de los casos, el resto de la red está de acuerdo con el valor propuesto. En el peor de los casos, un nodo puede quedarse atascado intentando comprometer su valor compuesto; entonces haría un tiempo de espera y lo intentaría de nuevo con un nuevo valor compuesto (actualizado por nominación) o cambiaría su voto a lo que el conjunto de bloqueo aceptó (abandonando su voto anterior).

Recorrido por una Ronda de Consenso

Para el propósito de este ejemplo, asumimos que la red puede retrasar o descartar arbitrariamente mensajes que los nodos envían.

Considere la siguiente configuración de quórum, donde ambos lados están bien conectados; sin embargo, la dependencia entre los dos lados es unidireccional. En otras palabras, el lado izquierdo tiene nodos del lado derecho en sus porciones de quórum, pero no al revés. De hecho, el lado derecho es un quórum en sí mismo dentro de un quórum más grande, como se muestra por las líneas punteadas. Esto implica que A no puede proceder sin el acuerdo de los nodos del lado derecho. Además, A es un conjunto de bloqueo para el lado izquierdo.

Las configuraciones exactas de porción de quórum se omiten por simplicidad.

1. Una ronda de consenso comienza con el nodo A introduciendo el conjunto de transacciones C votando para “Nominar C”. En SCP, los nodos transmiten sus decisiones sobre declaraciones, como votar, aceptar o comprometer, por lo que el nodo A también transmite su voto de nominación.

2. Ambos lados reciben el mensaje de transmisión del nodo A, y ya que es válido, también votan para nominar C.

3. Dado que cada nodo votó para nominar C, es seguro proceder a aceptar la nominación de C. Nuevamente, los nodos transmiten su aceptación.

4. La calidad de la conexión puede variar entre nodos. En este ejemplo, imagine que la red es bastante confiable en el lado izquierdo, pero está experimentando retrasos/interrupciones en el lado derecho. Debido a estos retrasos, el lado derecho no ve la aceptación de la nominación de C. Por otro lado, ya que el lado izquierdo recibió los mensajes, y cada nodo vio que una porción de quórum aceptó Nominar C (incluyendo al nodo A, su conjunto de bloqueo), procede a confirmar la declaración.

5. Recuerde, cuando una declaración de “nominar” se confirma, su conjunto de transacciones se añade al conjunto de candidatos que SCP lleva la cuenta. Denotamos tales conjuntos con llaves. El nodo entonces selecciona el valor compuesto, que es el conjunto de transacciones más grande en el conjunto de candidatos. En este ejemplo, el lado izquierdo añade C a su conjunto de candidatos (actualmente vacío), creando {C}. Luego selecciona C como el valor compuesto (bastante sencillo ya que C es el único valor en el conjunto de candidatos), y comienza el protocolo de balota votando en una declaración para preparar C.

6. Recuerde, el lado derecho y el nodo A están experimentando interrupciones, por lo que los mensajes “Aceptar Nominar C” aún están en tránsito. Sin ellos, el lado derecho no puede proceder a confirmar la nominación de C. Mientras tanto, hay un nuevo conjunto de transacciones D introducido por algunos nodos en el lado derecho, y la votación para “Nominar D” se transmite.

7. El nodo A recibe un voto para nominar D, y ya que es válido, y A aún no ha confirmado nada (recuerde que la nominación de C se atascó debido a la falta de fiabilidad de la red), vota para nominar D también. Debido a que el lado izquierdo ya ha confirmado la nominación de C, no puede votar por nuevos valores (solo acepta lo que su conjunto de bloqueo acepta), por lo que no vota por D.

8. Dado que el lado derecho votó para nominar D, pueden aceptar D y transmitir sus mensajes. Para este momento, los problemas de conectividad que plagaban al lado derecho se han solucionado, por lo que esta vez la red entrega correctamente esos mensajes.

9. Dado que cada nodo en el lado derecho ve una porción de quórum aceptar la nominación de D, todos proceden a confirmar D. Cuando se introducen nuevos valores, los nodos incluyen valores previos por los que votaron o aceptaron en sus mensajes. El nodo A previamente aceptó la nominación de C, por lo que cuando los nodos en el lado derecho reciben mensajes sobre D, también se enteran de la aceptación de la nominación de C. El lado derecho y A confirman C también, y lo añaden al conjunto de candidatos. Al final de este paso, el conjunto de candidatos para el lado derecho es {C, D}.

10. El lado derecho (incluyendo a A) ahora puede calcular su valor compuesto y comenzar el protocolo de balota. En este ejemplo, supongamos que el conjunto de transacciones D es más grande que el conjunto de transacciones C. Dado que ese es el caso, el conjunto de candidatos {C, D} produce el valor compuesto D, por el cual el lado derecho vota para preparar.

11. Los votos para preparar D se transmiten con éxito, y como el lado derecho votó por él, aceptan “Preparar D” y lo transmiten.

12. Recuerde que A es un conjunto de bloqueo para el lado izquierdo — es decir, el lado izquierdo no puede proceder sin el acuerdo de A. El lado izquierdo recibe el mensaje de que A aceptó “Preparar D”, y acepta “Preparar D”, aunque votó por “Preparar C” anteriormente.

13. En este punto, todos los nodos pueden confirmar “Prepare D”, como lo prescriben las reglas de votación federadas.

14. A través de otra ronda de votación federada sobre la declaración “Commit D”, los nodos de ambos lados acuerdan que es seguro actuar sobre D.

Tan pronto como un nodo confirma “Commit D”, es seguro actuar sobre D. Los nodos escriben las transacciones que componen D en la base de datos, cierran el libro mayor y comienzan uno nuevo. El proceso se repite una y otra vez cada 5 segundos, ad infinitum.