Substrate: del runtime al consenso propio

Artículo 11 de 20

55%

completado al llegar aquí

Sistemas distribuidosNota de investigación17 min de lectura

Producción de bloques por turnos: slots y Aura

Cómo Substrate divide el tiempo en slots, por qué Aura asigna cada slot a A[s mod |A|] y qué falta para que esos bloques sean definitivos.

Ruta Substrate: 0/20

Tres empresas comparten una cadena privada construida con Substrate (Polkadot SDK). Cada una opera un nodo validador, y las tres claves de autoridad están en el chain spec desde el génesis. Un lunes, el nodo de la tercera empresa se reinicia por mantenimiento. Nadie toca la configuración, pero el panel de operaciones muestra algo curioso: la cadena sigue creciendo, aunque uno de cada tres turnos queda vacío. Cuando el nodo vuelve, los huecos desaparecen.

Ese comportamiento no es una falla: es exactamente lo que promete Aura, el motor de producción de bloques más simple de Substrate. Este capítulo explica por qué. Al terminar, deberías poder responder quién produce el próximo bloque, cuándo lo hace, qué verifica un nodo cuando recibe un bloque ajeno y por qué una cadena que solo usa Aura todavía puede revertir.

Brújula de lectura

Actor, evidencia y estado

Antes de avanzar, ubica quién actúa, qué prueba queda y qué parte del sistema cambia.

Actor

Autoridad dueña del slot

Una lista fija de autoridades se turna: el slot s corresponde a A[s mod |A|].

Evidencia

Número de slot y sello firmado

La cabecera lleva el slot en un digest previo y la firma del autor en un digest de sello.

Estado

Cadena más larga, todavía revisable

Aura elige la cadena más larga; la finalidad la aporta otro protocolo.

El reloj compartido: qué es un slot

Un slot es un intervalo de tiempo durante el cual ciertos eventos pueden o deben ocurrir. Así lo define el crate genérico de Substrate para motores basados en slots: "Some consensus algorithms have a concept of slots, which are intervals in time during which certain events can and/or must occur." La idea es vieja y sencilla: en lugar de discutir en cada momento quién habla, se reparte el tiempo en turnos de igual duración y se fija de antemano a quién le toca cada turno.

Para que los turnos sirvan, todos tienen que contar el tiempo de forma parecida. Aura lo asume explícitamente: funciona con una lista de autoridades "who are expected to roughly agree on the current time". No hace falta un reloj perfecto, pero sí uno razonable. Si el reloj de un nodo se adelanta o se atrasa demasiado, ese nodo creerá estar en otro slot, y sus bloques o sus juicios sobre bloques ajenos dejarán de coincidir con los del resto.

¿Cómo sabe un nodo en qué slot está? En el solochain template, el nodo toma la hora del sistema como inherente de timestamp y calcula el slot a partir de ese timestamp y de la duración del slot; el código usa sp_timestamp::InherentDataProvider::from_system_time() y luego from_timestamp_and_slot_duration. La cuenta es una división entera: milisegundos desde la época Unix divididos por la duración del slot en milisegundos. Un inherente, como vimos en el capítulo de extrinsics, es un dato que el autor del bloque inserta y que nadie firma; el timestamp es el ejemplo clásico.

La duración del slot no la decide el nodo, sino el runtime. Aura expone una runtime API con dos funciones: slot_duration() y authorities(). El comentario del código aclara un detalle operativo importante: "Currently, only the value provided by this type at genesis will be used." Cambiar la duración del slot en una cadena viva, por tanto, no es tan simple como publicar un runtime nuevo con otro número.

Aura en una línea: A[s mod |A|]

Con un reloj común y una lista de autoridades, la regla de Aura cabe en una línea. El código la enuncia así: "Time is divided up into discrete slots of t seconds each. For each slot s, the author of that slot is A[s % |A|]." Si hay tres autoridades, el slot 0 es de la primera, el 1 de la segunda, el 2 de la tercera, el 3 vuelve a la primera, y así sucesivamente. Es un round robin puro: no hay sorteo, no hay peso por stake, no hay secreto.

La regla tiene una segunda mitad que importa tanto como la primera: "The author is allowed to issue one block but not more during that slot, and it will be built upon the longest valid chain that has been seen." Un bloque por slot, como máximo, y siempre sobre la cadena válida más larga que el autor conoce. La función que implementa la asignación en el SDK es corta:

rust
// substrate/client/consensus/aura/src/standalone.rs (fragmento ilustrativo)
pub fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
    if authorities.is_empty() {
        return None;
    }
    let idx = *slot % (authorities.len() as u64);
    authorities.get(idx as usize)
}

Volvamos al escenario inicial. Con tres autoridades y la tercera apagada, los slots 2, 5, 8 y siguientes no tienen bloque. El autor del slot 3 no espera: construye sobre el bloque del slot 1, que es la punta de la cadena más larga que conoce. La cadena pierde un tercio de su capacidad, pero no se detiene. La figura 1 muestra ese patrón.

Figura 1

Aura: turnos fijos y slots vacíos

Fuentes:

[2]
Aura: turnos fijos y slots vacíos
Cómo leer esta figura

Una fila de ocho slots numerados del 0 al 7, cada uno con su autoridad: A0, A1, A2, A0, A1, A2, A0, A1. Debajo, los bloques B0, B1, B3, B4, B6 y B7 en verde y dos casillas vacías en rojo discontinuo en los slots 2 y 5, marcadas como A2 apagado. Flechas curvas unen B1 con B3 y B4 con B6, saltando los huecos. Abajo, dos recuadros: a la izquierda la regla, autor del slot s igual a A[s mod |A|], un bloque como máximo sobre la cadena más larga; a la derecha la advertencia de que Aura produce pero no finaliza y que la cadena más larga puede cambiar hasta que GRANDPA vote.

Visual original de TrautsLab. Con tres autoridades, el slot s pertenece a A[s mod 3]. Si A2 está apagada, sus slots quedan vacíos y el siguiente autor construye sobre la cadena más larga que conoce. Aura produce bloques, pero no los finaliza.

¿Qué pasa si dos autoridades creen ser dueñas del mismo slot? Con la misma lista y relojes razonables, no debería ocurrir: la regla es determinista. Lo que sí puede pasar es que una autoridad deshonesta o mal configurada firme dos bloques distintos en su propio slot. Eso se llama equivocación, y lo retomamos al hablar de verificación.

Dos mitades: el nodo propone, el runtime informa

Hasta aquí, Aura parece una fórmula. En el código es una colaboración entre el nodo y el runtime. La documentación del SDK lo resume así: "the majority of the implementation of any consensus engine is in the node side, but the runtime also typically needs to expose a custom runtime-api to enable the particular consensus engine to work". En Aura, esa runtime API es AuraApi, la que entrega la duración del slot y la lista de autoridades.

En el lado del nodo trabaja un SlotWorker. El crate de slots ofrece un esqueleto genérico, SimpleSlotWorker, que "tries to claim a slot at its beginning and tries to produce a block if successfully claimed, timing out if block production takes too long". Aura implementa sus piezas: claim_slot comprueba si la clave local es la del autor del slot, y pre_digest_data agrega a la cabecera el número de slot.

Cuando el slot es propio, el worker pide un bloque al Proposer, la pieza que arma el bloque con extrinsics del pool y con inherentes. El Proposer es genérico; como dice sp-consensus, "Proposers are generic over bits of "consensus data" which are engine-specific." Aura le pasa su digest de slot y, al final, sella la cabecera con la firma del autor.

Flujo visual

Un slot propio, paso a paso

El worker solo propone si la regla A[s mod |A|] le asigna el slot, y solo durante la proporción del slot reservada para proponer.
  1. Tic del slotel reloj local entra en el slot s
  2. Reclamar¿mi clave es A[s mod |A|]?
  3. Proponerinherentes, extrinsics y digest de slot
  4. Sellarfirma del autor en la cabecera
  5. Importar y anunciarblock import local y difusión

Hay un parámetro que se ajusta en el servicio del nodo y que conviene entender: block_proposal_slot_portion, "The proportion of the slot dedicated to proposing." Proponer no puede ocupar todo el slot, porque el bloque todavía tiene que viajar por la red y ser importado por los demás antes de que empiece el siguiente turno. Hay además un máximo, max_block_proposal_slot_portion, que se aplica cuando el nodo concede tiempo extra porque hubo slots sin bloques.

Cómo verifica un nodo un bloque Aura ajeno

Producir es la mitad del trabajo. La otra mitad ocurre en cada nodo que recibe el bloque: la cola de importación (import queue) lo verifica antes de pasarlo al block import. El verificador de Aura comprueba que la cabecera esté firmada por la clave correcta para su slot; según su documentación, "If the slot is too far in the future, an error will be returned."

¿Cuánto es "demasiado lejos"? En el código, el verificador compara contra el slot actual más uno; el comentario dice "we add one to allow for some small drift". Un bloque que declara un slot más adelantado se rechaza con el mensaje "too far in the future". Esto convierte la sincronización de relojes en un requisito operativo: un nodo con el reloj atrasado rechazará bloques legítimos, y un autor con el reloj adelantado verá sus bloques rechazados.

Si el bloque trae cuerpo, el verificador también pide al runtime que revise los inherentes, para comprobar "that the internally-set timestamp in the inherents actually matches the slot set in the seal". Así, el autor no puede declarar un slot en la cabecera y un timestamp incompatible dentro del bloque.

El verificador también puede buscar equivocaciones: dos cabeceras distintas firmadas por el mismo autor para el mismo slot. La función compartida del crate de slots devuelve la prueba si la encuentra, aunque solo dentro de una ventana acotada de slots recientes. Qué se hace con esa prueba (reportarla, castigar) ya no es tarea de Aura, sino del runtime; el capítulo sobre vectores de ataque BFT explica por qué importa.

Por último, el verificador marca el bloque con la regla de elección de cadena: ForkChoiceStrategy::LongestChain. Cuando hay dos ramas, el nodo considera mejor la más larga. Si los relojes se desordenan o la red se parte, cada lado puede seguir creciendo por su cuenta; el capítulo sobre GST y estabilización global explica por qué los protocolos que suponen sincronía parcial no prometen nada útil hasta que la red vuelve a comportarse.

Lo que Aura no hace: finalidad

Aura responde a la pregunta de quién produce y cuándo. No responde a otra igual de importante: cuándo un bloque ya no puede revertirse. Con la regla de la cadena más larga, un bloque está "probablemente" asentado si tiene muchos bloques encima, pero nada impide que aparezca una rama más larga que lo deje fuera. El capítulo sobre el doble gasto muestra cómo se explota esa ventana.

Por eso el solochain template no usa Aura sola. Su servicio crea primero el block import de GRANDPA con sc_consensus_grandpa::block_import y se lo entrega a la cola de importación de Aura, de modo que todo bloque verificado por Aura pasa también por la capa de finalidad. Aura produce; GRANDPA, que estudiaremos en el capítulo 13, finaliza. La separación es deliberada: si la finalidad se detiene, la producción puede seguir.

Matriz de decisión

Decisiones al configurar Aura

Reglas de decisión rápida

  • ¿Las autoridades son pocas, conocidas y estables?

    Consorcio, red de pruebas, parachain con collators fijos.

    → Aura más una finalidad es razonable

  • ¿Preocupa que se sepa de antemano quién produce?

    Ataques de denegación de servicio dirigidos al próximo autor.

    → Evaluar BABE o Sassafras

  • ¿Slots cortos con bloques pesados?

    La propuesta no termina dentro de la proporción del slot.

    → Alargar el slot o reducir el peso máximo

  • ¿Relojes sin NTP ni monitoreo?

    Bloques rechazados por 'too far in the future'.

    → Sincronizar relojes antes de culpar al consenso

Aura es simple de operar, pero sus supuestos (lista conocida de autoridades, relojes razonables) tienen que ser ciertos en tu red.

Qué cambia respecto de Ethereum

Quien viene de la ruta de Ethereum ya conoce los slots. En la Beacon Chain, el tiempo se divide en slots de 12 segundos y en cada slot se elige un validador que propone; el capítulo sobre la Beacon Chain lo detalla. La diferencia está en cómo se elige. En Ethereum, la selección depende del saldo efectivo de cada validador y de aleatoriedad del protocolo. En Aura, el turno es una cuenta modular sobre una lista fija, sin peso ni azar.

La segunda diferencia es la regla de elección de cadena. Ethereum combina LMD-GHOST, que cuenta votos de testigos, con Casper FFG para la finalidad; lo vimos en Gasper. Aura usa la cadena más larga y deja toda la finalidad a otro protocolo. El patrón de fondo es el mismo en ambos mundos (un mecanismo produce, otro finaliza), pero en Substrate cada mitad es una pieza intercambiable del nodo, y por eso el mismo SDK ofrece Aura, BABE y otros motores.

La tercera diferencia es de escala y de propósito. Aura está pensada para conjuntos de autoridades conocidos y moderados: redes privadas, entornos de prueba y, como veremos en el capítulo 15, parachains cuyos collators se turnan. No intenta resolver la selección abierta de cientos de miles de validadores.

Para seguir: el capítulo anterior, offchain workers, cerró la parte del runtime. El siguiente, BABE, reemplaza el turno fijo por una lotería secreta.

Laboratorio propuesto

Estado: propuesta. Este laboratorio todavía no está publicado en lab.trautslab.dev; describe lo que se construirá, en local, sin red pública ni fondos reales.

Objetivo. Ver con los propios ojos la regla A[s mod |A|], los slots vacíos y el efecto del reloj sobre la verificación.

Pasos propuestos.

  1. Levantar el solochain template con tres autoridades en un chain spec local, cada una con su propia clave Aura.
  2. Apagar una autoridad y observar en los logs los slots que quedan vacíos.
  3. Desfasar el reloj de un nodo (en un contenedor o una máquina virtual) y registrar qué bloques se rechazan o se difieren.

Evidencia esperada. Con tres autoridades activas, un bloque por slot. Con una apagada, huecos regulares cada tres slots y bloques que saltan sobre el hueco. Con un reloj adelantado más de un slot, rechazos del tipo "too far in the future" en los demás nodos. Los valores exactos de duración de slot y los mensajes de log dependen de la versión del template y quedan a validar al construir el laboratorio.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    Parity Technologies, "sc-consensus-slots: slot-based consensus engines," polkadot-sdk, substrate/client/consensus/slots/src/lib.rs, commit 544e829, 2026.

    Definición de slot, SlotWorker y SimpleSlotWorker, proporción del slot dedicada a proponer.

  2. [2]

    Parity Technologies, "sc-consensus-aura: Aura (Authority-round) consensus in Substrate," polkadot-sdk, substrate/client/consensus/aura/src/lib.rs, commit 544e829, 2026.

    Asignación A[s % |A|], un bloque por slot sobre la cadena más larga y supuesto de relojes aproximadamente de acuerdo.

  3. [3]

    Parity Technologies, "Solochain template: node service," polkadot-sdk, templates/solochain/node/src/service.rs, 2026.

    source codeID: SDK-SOLOCHAIN-SERVICEAbrir fuente (abre en otra pestaña)

    Cómo el template conecta Aura, el inherente de timestamp y el block import de GRANDPA.

  4. [4]

    Parity Technologies, "sp-consensus-aura: primitives for Aura," polkadot-sdk, substrate/primitives/consensus/aura/src/lib.rs, commit 544e829, 2026.

    source codeID: SDK-AURA-APIAbrir fuente (abre en otra pestaña)

    Runtime API AuraApi: slot_duration() y authorities().

  5. [5]

    Parity Technologies, "polkadot_sdk_docs: (Omni) Node," polkadot-sdk, docs/sdk/src/reference_docs/omni_node.rs, commit 544e829, 2026.

    El consenso vive sobre todo en el nodo, pero el runtime expone una runtime API propia del motor.

  6. [6]

    Parity Technologies, "sp-consensus: Environment and Proposer," polkadot-sdk, substrate/primitives/consensus/common/src/lib.rs, commit 544e829, 2026.

    source codeID: SDK-SP-CONSENSUSAbrir fuente (abre en otra pestaña)

    Proposer genérico sobre datos de consenso propios de cada motor.

  7. [7]

    Parity Technologies, "sc-consensus-aura: import queue," polkadot-sdk, substrate/client/consensus/aura/src/import_queue.rs, commit 544e829, 2026.

    source codeID: SDK-AURA-IMPORTAbrir fuente (abre en otra pestaña)

    Verificación de cabeceras Aura: firma del autor del slot, margen de un slot, chequeo de inherentes y fork choice LongestChain.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

Rutas vecinas

Si este tema te abrió hambre, sigue por aquí.

Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.