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
Prerequisitos recomendados
Si alguno de estos conceptos todavía está medio nebuloso, empieza por aquí. Te ahorra tropiezos y un par de cejas fruncidas.
El runtime en Wasm: nodo y runtime hablan por dos puertas
Dónde vive el runtime de Substrate (la clave :code del estado) y cómo conversa con el nodo: host functions de sp_io y runtime APIs de sp_api.
Blockchain15 min de lectura→Extrinsics: transacciones firmadas, sin firmar e inherentes
Qué entra a un bloque Substrate desde fuera del runtime, cómo se codifica un extrinsic versión 5 y qué comprueban las transaction extensions antes de ejecutarlo.
Blockchain16 min de lectura→
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
Actor
El nodo que decide ejecutar el workerAlgunos nodos lo corren y otros no; la cadena funciona igual en ambos casos.
Evidencia
Un extrinsic enviado por el workerEs la única salida que llega al estado, y se valida como cualquier otro.
Estado
Ninguno durante el workerLo 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. [1]
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. [2]
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.
- 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:
// 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. [3]
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. [1]
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. [4] 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.
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.
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. [3]
- 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. [1]
Offchain worker o aplicación externa
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
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.
Bibliografía académica
- [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]
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]
Parity Technologies, "frame/examples/offchain-worker/src/lib.rs," paritytech/polkadot-sdk, GitHub repository, accedido 2026-11-13.
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]
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.
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.
Series relacionadas
HotStuff: de cero a estado del arte
Ruta progresiva para entender consenso BFT moderno: quórums, sincronía parcial, locks, taxonomía one/two/three-chain, QC/highQC, multipipeline, view-change, responsiveness, AggQC, Fast-HotStuff, sync y frontera DAG-BFT.
Ethereum: de historia a labs
Serie progresiva para entender Ethereum desde el whitepaper, forks y The Merge hasta EVM, Solidity, proof-of-stake, rollups, account abstraction, DeFi, observabilidad y laboratorios reproducibles.
Ruta práctica: activos digitales en la operación
Nueve módulos, de menor a mayor dependencia, para decidir cómo usar custodia, wallets, llaves, firmas multifirma y MPC, AML/KYT y stablecoins con Fireblocks, BitGo, Kaleido, Circle y Tether.
Labs para convertir lectura en práctica
Prueba de trabajo y reajuste de dificultad
Laboratorio determinista para medir el costo de la prueba de trabajo y ver cómo el reajuste por altura de bloque estabiliza el tiempo entre bloques.
Consensus benchmark
Banco reproducible para comparar consenso BFT y documentar los trade-offs de HotStuff-2.
Ethereum local: Anvil, wallet y primera transacción
Laboratorio base para levantar una red local, crear cuentas, firmar transacciones y observar receipts sin gastar ETH real.
RPC y Cast: leer Ethereum con lupa
Ejercicios con JSON-RPC y Cast para consultar bloques, nonces, gas, receipts, logs y errores reproducibles.
Sigue leyendo
Ideas conectadas con este artículo
Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.
Qué es Substrate hoy: el Polkadot SDK por dentro
Substrate ya no es un repositorio aparte: es el framework base del Polkadot SDK. Un mapa de nombres, piezas y crates para empezar sin perderse.
La cadena como función de transición de estado
El modelo mental que sostiene toda la ruta: una función determinista toma el estado y un bloque y devuelve el estado siguiente. En Substrate esa función se llama runtime.
FRAME: pallets que se ensamblan en un runtime
Un pallet encapsula una responsabilidad; el runtime es la suma de pallets configurados. Anatomía de un pallet, cómo se ensamblan y por qué un pallet no es un contrato inteligente.
Extrinsics: transacciones firmadas, sin firmar e inherentes
Transacciones firmadas, sin firmar e inherentes: los bytes de un extrinsic versión 5, el payload que se firma y las extensiones que validan génesis, mortalidad, nonce, versión y comisión.