Substrate: del runtime al consenso propio
Artículo 4 de 20
20%
completado al llegar aquí
Sistemas distribuidosNota de investigación14 min de lectura
FRAME: pallets que se ensamblan en un runtime
Cómo FRAME arma un runtime de Substrate con pallets: Config, storage, llamadas, eventos y errores unidos con construct_runtime, y en qué difiere de un contrato.
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.
Una cooperativa quiere una cadena propia para reservar salas compartidas. Cada socio puede reservar una franja horaria dejando un depósito, que se le devuelve si usa la sala y se pierde si no se presenta. El equipo ya sabe, por el capítulo anterior, que esa lógica debe vivir en el runtime y que el runtime es un blob Wasm. Lo que no sabe es cómo escribirlo sin programar desde cero cuentas, saldos, firmas y codificación de bloques. Para eso existe FRAME.
La respuesta de este capítulo: no escribes un runtime, lo ensamblas. Tomas piezas existentes (cuentas, saldos), escribes solo la pieza que falta (las reservas) y las unes con una macro. Cada pieza se llama pallet.
Brújula de lectura
Actor, evidencia y estado
Actor
Quien escribe el palletDefine qué se guarda, qué se puede llamar y qué falla; el runtime fija los parámetros.
Evidencia
Eventos y errores con nombreCada llamada deja un evento si tuvo éxito o un error tipado si falló.
Estado
Storage de cada palletCada pallet declara sus propios ítems de storage dentro del estado de la cadena.
Preguntas guía
- ¿Qué es FRAME y qué problema resuelve?
- ¿Qué partes tiene un pallet y para qué sirve cada una?
- ¿Cómo se convierten varios pallets en un runtime?
- ¿Cuándo conviene usar un pallet existente, instanciarlo dos veces o escribir uno propio?
- ¿En qué se parece y en qué se diferencia un pallet de un contrato inteligente?
Conexiones dentro de la serie
- Antes: El runtime en Wasm: nodo y runtime hablan por dos puertas explica qué es el runtime y cómo lo llama el nodo.
- Contraste: Solidity sin magia: contratos como máquinas de estado da el modelo mental de un contrato, útil para fijar las diferencias con un pallet.
- Después: Tu primer nodo local: templates, omni-node y chain spec ejecuta un runtime armado con FRAME.
FRAME: un framework para escribir la STF
FRAME significa "Framework for Runtime Aggregation of Modularized Entities", y la documentación lo presenta como el framework de la STF (el runtime) de Substrate. [1] Traducido: una forma de agregar módulos para formar un runtime. No es obligatorio; es el camino por defecto.
Está compuesto por dos piezas principales: "FRAME is composed of two major components, pallets and a runtime." [1] Los pallets contienen la lógica. El runtime los junta, les da sus parámetros y expone las runtime APIs que el nodo necesita.
Conviene saber que FRAME no es la única opción. La documentación lo aclara: nada en el código del nodo obliga a usar FRAME, y "any WASM blob that exposes the right set of runtime APIs is a valid Runtime" desde el punto de vista del cliente. [1] En la práctica, casi todas las cadenas Substrate usan FRAME, y esta ruta también.
Anatomía de un pallet
Un pallet es, según la documentación, "a unit of encapsulated logic. It has a clearly defined responsibility and can be linked to other pallets." [1] El glosario lo compara con un módulo de una aplicación tradicional: un bloque de construcción que se enchufa al runtime. [2] Los pallets que trae FRAME intentan ocuparse solo de su responsabilidad y suponer lo mínimo sobre el resto del runtime, para poder reutilizarse.
En código, un pallet es un módulo de Rust envuelto por la macro #[frame::pallet]. Dentro se declaran sus partes. Las cinco más importantes son estas: [1]
- Config. Un trait de Rust con lo que el pallet necesita pero no decide: tipos y valores que fijará el runtime que lo use. En las reservas, por ejemplo, el monto del depósito o la duración máxima de una franja.
- Storage. Los datos que el pallet guarda en el estado de la cadena. Por ejemplo, un mapa de franja horaria a socio que la reservó.
- Dispatchables (call). Las funciones que se pueden invocar desde fuera, normalmente con una transacción. El glosario las define como "the entry points in FRAME pallets". [2] En nuestro caso:
reservar,cancelar,registrar_uso. - Eventos. Avisos que el pallet emite cuando algo pasó, para que clientes e indexadores se enteren.
- Errores. Fallas con nombre, como
FranjaOcupadaoDepositoInsuficiente, en lugar de mensajes de texto libres.
El ejemplo mínimo de la documentación muestra la forma. El fragmento es ilustrativo y está recortado:
// Fuente: docs/sdk/src/polkadot_sdk/frame_runtime.rs (paritytech/polkadot-sdk), fragmento ilustrativo
#[frame::pallet(dev_mode)]
pub mod pallet {
use frame::prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type ValueParameter: Get<u32>; // lo fija el runtime
}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::storage]
pub type Value<T> = StorageValue<Value = u32>; // vive en el estado
#[pallet::call]
impl<T: Config> Pallet<T> {
pub fn some_dispatchable(_origin: OriginFor<T>, _param: u32) -> DispatchResult {
Ok(())
}
}
}Fíjate en el primer parámetro de la llamada: origin. Es quién origina la llamada: una cuenta que firmó (Signed), el propio sistema con el máximo privilegio (Root), nadie (None) o una autorización que define el runtime (Authorized). [3] El pallet decide qué orígenes acepta. Y fíjate en que Config hereda de frame_system::Config: el pallet frame_system es obligatorio en todo runtime y provee lo básico, como cuentas, números de bloque y eventos. [1]
Del pallet al runtime: ensamblar y exponer
Un runtime es "a collection of pallets that are amalgamated together". [1] Para unirlos se usa la macro construct_runtime, que genera un tipo Runtime con todos los pallets declarados. Después, por cada pallet, el runtime implementa su Config: ahí se fija, por ejemplo, que el depósito de una reserva es 10 unidades.
// Fuente: docs/sdk/src/polkadot_sdk/frame_runtime.rs (paritytech/polkadot-sdk), fragmento ilustrativo
construct_runtime!(
pub enum Runtime {
System: frame_system,
Example: pallet_example,
}
);
impl pallet_example::Config for Runtime {
type ValueParameter = ConstU32<42>; // el runtime decide el valor
}Ese ejemplo es de prueba. Un runtime real que quiera compilar a Wasm necesita además implementar sus runtime APIs con la macro impl_runtime_apis. [1] Son las puertas del capítulo anterior: sin Core::execute_block, el nodo no tendría cómo aplicar bloques.
Un detalle de lectura: en runtimes actuales del SDK verás también una sintaxis alternativa, el atributo #[frame_support::runtime], donde cada pallet se declara como un tipo con un índice. El runtime de demostración del repositorio, por ejemplo, declara pub type Balances = pallet_balances::Pallet<Runtime>; dentro de ese bloque. [4] La idea es la misma: listar pallets y fijar su configuración.
De pallets a runtime con FRAME
Fuentes:
[1]Cómo leer esta figura
A la izquierda, una caja verde alta pallet_reservas por dentro con cinco cajas apiladas: Config, lo que fija el runtime; storage, datos en el estado; call, entradas despachables; event, avisos hacia afuera; error, fallas con nombre. Una flecha sale de esa caja hacia una columna central de tres cajas: frame_system, obligatorio; pallet_balances, ya existe; y pallet_reservas, el nuestro. Las tres apuntan a una caja azul construct_runtime, junta los pallets y fija cada Config. De ella baja una flecha a impl_runtime_apis, puertas para el nodo, y de ahí otra a una caja violeta runtime convertido en blob Wasm.
Usar, instanciar o escribir un pallet
Con el modelo claro, la primera decisión de diseño de la cooperativa es cuántos pallets escribir. Casi siempre, menos de los que uno cree. FRAME trae pallets para cuentas, saldos, gobierno, multifirma y muchos más; el runtime de demostración del repositorio, conocido como kitchensink, los reúne a casi todos. [5]
Algunos pallets son instanciables: el mismo pallet aparece dos veces en un runtime, con nombres y configuraciones distintas. El runtime de demostración usa pallet_collective dos veces, como Council (Instance1) y como TechnicalCommittee (Instance2). [4] Dos comités con reglas distintas, un solo código auditado.
¿Pallet existente, otra instancia o uno propio?
La segunda decisión es qué parámetros quedan en el Config del pallet propio. La regla práctica: todo lo que otra cadena podría querer distinto (montos, plazos, quién puede penalizar) va al Config y lo fija el runtime. Lo que es la esencia del pallet (qué significa reservar) queda en su código.
Qué cambia respecto de Ethereum
Un pallet se parece a un contrato de Solidity: tiene storage, funciones que se llaman desde fuera, eventos y errores. La propia documentación de FRAME reconoce el parecido y advierte que hay diferencias cruciales. [1] Una guía del SDK compara ambos modelos y resume la elección así: "If you need to create a Blockchain, then write a runtime. If you need to create a DApp, then write a Smart Contract." [6]
Las diferencias que más importan a quien diseña:
- Alcance de un error. En un runtime, las fallas de seguridad pueden afectar a toda la cadena; en un contrato, el riesgo suele quedar en ese contrato. [6]
- Quién lo despliega. Un contrato lo despliega cualquiera en una cadena existente. Un pallet entra al runtime, y el runtime lo controla quien gobierna la cadena.
- Costo de ejecución. Un contrato se mide instrucción por instrucción (gas). Las operaciones de un runtime se pesan por adelantado con benchmarks. [6]
- Actualizaciones. Un runtime se actualiza con lógica de migración y gobierno en cadena; un contrato es menos flexible para migrar datos, aunque más simple de desplegar e iterar. [6]
Hay un caso que mezcla ambos mundos: un runtime puede incluir un pallet que ejecuta contratos. La guía menciona pallets para contratos Wasm y para contratos compatibles con la EVM. [6] Contratos inteligentes sobre Polkadot quedan fuera de esta ruta.
Qué llevarte de este capítulo
FRAME es la forma por defecto de escribir un runtime: pallets que encapsulan una responsabilidad y un runtime que los ensambla. Un pallet tiene Config, storage, llamadas despachables, eventos y errores. construct_runtime (o #[frame_support::runtime]) junta los pallets y fija cada Config; impl_runtime_apis abre las puertas para el nodo. Antes de escribir un pallet propio, revisa si uno existente o una segunda instancia resuelven el problema. Y recuerda que un pallet no es un contrato: un error en él es un error de toda la cadena. El próximo paso es ejecutar ese runtime en un nodo local.
Bibliografía académica
- [1]
Parity Technologies, "FRAME," polkadot_sdk_docs, paritytech/polkadot-sdk, docs/sdk/src/polkadot_sdk/frame_runtime.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/sdk/src/polkadot_sdk/frame_runtime.rs (consultado 2026-11-13).
Pallets, partes de un pallet, construct_runtime e impl_runtime_apis, con ejemplo mínimo.
- [2]
Parity Technologies, "Glossary," polkadot_sdk_docs, paritytech/polkadot-sdk, docs/sdk/src/reference_docs/glossary.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/sdk/src/reference_docs/glossary.rs (consultado 2026-11-13).
Definiciones de estado, STF, host, nodo, dispatchable, extrinsic y pallet.
- [3]
Parity Technologies, "frame-support: dispatch (RawOrigin)," paritytech/polkadot-sdk, substrate/frame/support/src/dispatch.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/substrate/frame/support/src/dispatch.rs (consultado 2026-11-13).
Variantes de origen: Root, Signed, None y Authorized.
- [4]
Parity Technologies, "kitchensink runtime," paritytech/polkadot-sdk, substrate/bin/node/runtime/src/lib.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/substrate/bin/node/runtime/src/lib.rs (consultado 2026-11-13).
Runtime de demostración con todos los pallets de FRAME; usa #[frame_support::runtime] e instancias de pallet_collective.
- [5]
Parity Technologies, "Polkadot SDK," polkadot_sdk_docs, paritytech/polkadot-sdk, docs/sdk/src/polkadot_sdk/mod.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/sdk/src/polkadot_sdk/mod.rs (consultado 2026-11-13).
Componentes del Polkadot SDK: Substrate, FRAME, Cumulus, XCM y binarios.
- [6]
Parity Technologies, "Runtime vs. Smart Contracts," polkadot_sdk_docs, paritytech/polkadot-sdk, docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs. [Online]. Available: https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs (consultado 2026-11-13).
Comparación entre runtime y contrato inteligente: alcance, upgrades, seguridad y medició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.
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.
El runtime en Wasm: nodo y runtime hablan por dos puertas
El runtime es un blob Wasm guardado en el estado bajo la clave :code. El nodo lo llama por runtime APIs y el runtime pide ayuda al nodo por host functions. Por qué la segunda puerta casi nunca se ensancha.
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.