Substrate: del runtime al consenso propio

Artículo 10 de 20

50%

completado al llegar aquí

Sistemas distribuidosNota de investigación12 min de lectura

Offchain workers: lo que el runtime hace fuera de la cadena

Qué es un offchain worker en Substrate, por qué puede llamar a una API HTTP sin romper el determinismo y por qué su única vía para cambiar el estado es enviar un extrinsic.

Ruta Substrate: 0/20

Un equipo quiere guardar en su cadena el precio BTC/USD. La primera idea de alguien nuevo en Substrate (Polkadot SDK) es poner una llamada HTTP en el hook on_initialize de un pallet: al empezar cada bloque, se consulta una API y se guarda el precio. No funciona, y entender por qué es entender la cadena entera. Cada validador que importe el bloque re-ejecutaría ese código, haría su propia llamada HTTP en otro momento, recibiría otro número y calcularía otra raíz de estado. El bloque sería válido para unos e inválido para otros.

Substrate ofrece otra herramienta para este caso: el offchain worker. Este capítulo explica qué es, qué puede hacer y, sobre todo, qué no puede.

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

El nodo que decide ejecutar el worker

Algunos nodos lo corren y otros no; la cadena funciona igual en ambos casos.

Evidencia

Un extrinsic enviado por el worker

Es la única salida que llega al estado, y se valida como cualquier otro.

Estado

Ninguno durante el worker

Lo que el worker escriba en el estado se descarta; solo persiste su almacenamiento local, fuera de la cadena.

El problema: una cadena no puede llamar a una API

Una cadena es una máquina de estados replicada. Todos los nodos aplican la misma función a los mismos bloques y deben obtener el mismo estado; si no, no hay acuerdo. El capítulo sobre la función de transición fijó esa idea: el runtime tiene que ser determinista.

Una llamada HTTP no lo es. La respuesta depende del momento, del servidor, de la red y de quién pregunta. La documentación del SDK lo dice con todas las letras: una llamada HTTP de un offchain worker es no determinista por diseño, y las cadenas no pueden ni podrán hacer operaciones no deterministas como llamar a un servidor externo.

El mismo texto advierte contra un malentendido frecuente: ver la API HTTP del worker e imaginar que Substrate resolvió el determinismo y que ahora una cadena puede hacer HTTP. No es así. El worker puede hacer HTTP precisamente porque no forma parte de la función de transición.

Qué es un offchain worker

Si recuerdas el runtime en Wasm, el nodo y el runtime se hablan por dos puertas: host functions (runtime hacia nodo) y runtime APIs (nodo hacia runtime). Un offchain worker no es una tercera puerta. Es una runtime API más, OffchainWorkerApi, que el nodo invoca cuando le parece oportuno.

Lo que la distingue son dos propiedades:

  1. No tiene impacto en el estado. Si durante su ejecución algo modifica el estado, ese cambio se descarta. Está literalmente fuera de la cadena.
  2. Tiene acceso a host functions adicionales que el resto del runtime no puede usar, como hacer peticiones HTTP.

De la primera sale una tercera, que suele sorprender: ejecutarlo es opcional. Algunos nodos lo llamarán y otros no, y eso no afecta a la cadena, porque ninguna ejecución altera el estado. La CLI del nodo permite a cada operador decidir cuándo correrlo.

En FRAME, un pallet declara su worker en el hook offchain_worker, junto a on_initialize y on_finalize:

rust
// Fragmento de docs/sdk/src/reference_docs/frame_offchain_workers.rs (polkadot-sdk)
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
    fn offchain_worker(block_number: BlockNumberFor<T>) {
        // ...
    }
}

El pallet de ejemplo del SDK precisa cuándo se llama: cuando el nodo está sincronizado y acaba de importar un nuevo mejor bloque. También avisa que no hay garantía de que corra en todos los bloques; puede saltarse algunos o correr dos veces sobre la misma altura si hay una reorganización, y el código debe tolerarlo.

La única salida: enviar un extrinsic

Si el worker no puede escribir en el estado, ¿para qué sirve? Para preparar datos y proponerlos. La documentación lo marca como regla: la vía principal por la que un offchain worker interactúa con el estado es enviar un extrinsic a la cadena, y es la única forma de alterar el estado desde un worker.

La especificación del protocolo contempla esa ruta: además de llegar por la red, una transacción puede entrar al nodo desde un offchain worker a través del host API. A partir de ahí, el extrinsic sigue el mismo camino que cualquier otro: el runtime lo valida, entra al pool, se difunde, un autor lo incluye en un bloque y todos los nodos lo ejecutan de forma determinista.

Figura 1

Dos carriles: lo que corre fuera de la cadena y lo que entra en ella

Fuentes:

[1][3]
Dos carriles: lo que corre fuera de la cadena y lo que entra en ella
Cómo leer esta figura

Dos carriles horizontales. Arriba, el carril de la cadena: el bloque N se importa; más a la derecha, el pool valida un extrinsic con validate_transaction; luego un bloque N más k lo incluye y todos los nodos lo ejecutan. Abajo, el carril fuera de la cadena, solo en los nodos que deciden ejecutar el worker: tras importar N, el nodo llama a offchain_worker(N); el worker lee el estado de N, hace una petición HTTP con deadline y consulta o bloquea una entrada de su almacenamiento local; arma un extrinsic firmado o general. Una flecha azul, rotulada única salida, sube desde el extrinsic del worker al pool del carril superior. Una línea discontinua roja lleva desde el worker a un recuadro rojo que dice que las escrituras al estado durante el worker se descartan.

Visual original de TrautsLab. El worker corre en un carril aparte, después de importar un bloque: puede leer el estado, hacer HTTP y usar su almacenamiento local, pero lo que escribe en el estado se descarta. Su única salida es un extrinsic, que vuelve al carril de la cadena y se valida como cualquier otro.

Ese camino es la defensa. Lo que el worker reporta no se cree por venir de un worker: se valida en la cadena. Si el extrinsic es firmado, paga comisión y queda atado a una cuenta. Si no lleva firma, el pallet tiene que escribir su propia autorización, como viste en extrinsics: el ejemplo del SDK usa transacciones general con #[pallet::authorize] y acepta como máximo una cada AuthorizedTxInterval bloques.

Las herramientas del worker

El pallet de ejemplo del SDK, un oráculo de precio que el propio código declara no apto para producción, muestra las piezas en uso.

  • HTTP con plazo. La petición se arma con http::Request::get, se le pone un deadline (dos segundos en el ejemplo) y el host la ejecuta. El worker puede lanzar varias a la vez y esperar la respuesta cuando quiera.
  • Almacenamiento local. Un espacio clave-valor persistente, propio del nodo, que no forma parte del estado de la cadena. Sobrevive entre ejecuciones del worker y es común a todos los workers del nodo, así que conviene prefijar las claves con el nombre del pallet.
  • Exclusión mutua. Como varios workers pueden correr a la vez, el ejemplo escribe en el almacenamiento local con mutate, que usa compare-and-set por debajo: solo uno consigue el "candado" y envía la transacción.
  • Lectura del estado. El worker ve el estado del bloque que acaba de importarse con las mismas herramientas de storage que el resto del pallet.

El ejemplo también muestra el patrón de agregación en la cadena: guarda los últimos 64 precios recibidos y calcula su promedio. Es didáctico, no seguro. Si cualquiera puede reportar, un solo reporte malicioso mueve el promedio.

Cuándo no usar un offchain worker

La documentación del SDK pide pensarlo dos veces. Un worker escrito con esta API no es, en principio, distinto de una aplicación externa escrita con una biblioteca cliente como Polkadot-JS: ambos leen el estado y ambos solo pueden cambiarlo enviando un extrinsic.

Matriz de decisión

Offchain worker o aplicación externa

Recomendado

Offchain worker

Código dentro del Wasm del runtime, ejecutado por los nodos que lo activen.

Ideal para
Tareas que leen mucho estado y deben seguir el formato del storage tras cada actualización.
Ojo con
Engorda el Wasm, depende de que los operadores lo activen y comparte recursos con el nodo.

Aplicación externa

Un servicio aparte que lee por RPC o light client y envía transacciones.

Ideal para
Integraciones con sistemas propios, claves en un HSM o lógica que cambia más rápido que el runtime.
Ojo con
Hay que actualizarla a mano cada vez que el runtime cambia el formato del storage.
Las dos tienen el mismo poder sobre el estado: enviar un extrinsic. Cambian el despliegue y el mantenimiento.

La ventaja que más pesa a favor del worker es la sincronía con el runtime. Si una actualización sin fork cambia el tipo de un item de storage, el worker ya viene actualizado en el mismo Wasm, o el runtime no compila. Una aplicación externa tiene que enterarse y desplegarse aparte.

Qué cambia respecto de Ethereum

En Ethereum no hay nada equivalente dentro del protocolo. Los datos externos llegan por oráculos: redes externas que observan fuentes, firman un reporte y lo envían en una transacción a un contrato, que verifica firmas y umbrales antes de aceptar el valor.

Un offchain worker no cambia ese modelo de confianza; solo cambia dónde corre el observador. La pregunta de fondo sigue siendo la misma que en Ethereum: ¿quién puede reportar, cuántos reportes hacen falta y qué pasa si mienten? Un worker que corre en cada validador y firma con una clave de sesión acota quién reporta. La agregación robusta, por ejemplo una mediana con un mínimo de reportes, tiene que escribirla el pallet en la cadena.

Decisiones para quien diseña el pallet

Lista de verificación

Antes de confiar en lo que reporta un worker

0 de 5 revisados

Con este capítulo termina el bloque intermedio: ya sabes qué entra a un bloque, dónde escribe, cuánto cuesta, cómo cambia la lógica y cómo entran datos de fuera. El siguiente, producción de bloques por turnos: slots y Aura, pasa del runtime al nodo y responde quién produce el próximo bloque y cuándo.

Referencias

Bibliografía académica

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

    Parity Technologies, "Offchain Workers," polkadot_sdk_docs, paritytech/polkadot-sdk, GitHub repository, accedido 2026-11-13.

    Runtime API OffchainWorkerApi; sin impacto en el estado; host functions extendidas como HTTP; ejecución opcional; enviar un extrinsic es la única vía para cambiar el estado; cuándo preferir una aplicación externa.

  2. [2]

    Parity Technologies, "WASM Meta Protocol," polkadot_sdk_docs, paritytech/polkadot-sdk, GitHub repository, accedido 2026-11-13.

    Host functions (runtime a nodo) y runtime APIs (nodo a runtime); sp_api::Core ejecuta bloques.

  3. [3]

    Parity Technologies, "frame/examples/offchain-worker/src/lib.rs," paritytech/polkadot-sdk, GitHub repository, accedido 2026-11-13.

    source codeID: OCW-EXAMPLEAbrir fuente (abre en otra pestaña)

    Oráculo BTC/USD de ejemplo, no apto para producción: cuándo corre el worker, HTTP con deadline de 2 s, almacenamiento local persistente con mutate atómico, transacciones general con #[pallet::authorize] y AuthorizedTxInterval.

  4. [4]

    Web3 Foundation, "States and Transitions," Polkadot Protocol Specification, w3f/polkadot-spec, GitHub repository, accedido 2026-11-13.

    Las transacciones pueden llegar al host desde un offchain worker por el host API; toda transacción nueva se valida con el runtime antes de guardarla y difundirla.

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.