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

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

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

Actor

Quien escribe el pallet

Define qué se guarda, qué se puede llamar y qué falla; el runtime fija los parámetros.

Evidencia

Eventos y errores con nombre

Cada llamada deja un evento si tuvo éxito o un error tipado si falló.

Estado

Storage de cada pallet

Cada 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

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. 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." 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. 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." El glosario lo compara con un módulo de una aplicación tradicional: un bloque de construcción que se enchufa al runtime. 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:

  • 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". 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 FranjaOcupada o DepositoInsuficiente, 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:

rust
// 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). 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.

Del pallet al runtime: ensamblar y exponer

Un runtime es "a collection of pallets that are amalgamated together". 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.

rust
// 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. 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. La idea es la misma: listar pallets y fijar su configuración.

Figura 1

De pallets a runtime con FRAME

Fuentes:

[1]
De pallets a runtime con FRAME
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.

Visual original de TrautsLab. Un pallet propio tiene Config, storage, llamadas, eventos y errores. construct_runtime lo junta con pallets existentes y fija cada Config; impl_runtime_apis expone las puertas para el nodo; el resultado se compila a un 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.

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). Dos comités con reglas distintas, un solo código auditado.

Decisión de diseño

¿Pallet existente, otra instancia o uno propio?

Recomendado

Usar un pallet existente

La responsabilidad ya está resuelta: saldos, multifirma, gobierno.

Ideal para
Depósitos de la cooperativa: pallet_balances con su Config.
Ojo con
Leer su Config completo: los valores por defecto pueden no servir en producción.

Instanciarlo dos veces

Mismo comportamiento, dos conjuntos de datos y parámetros.

Ideal para
Dos comités o dos registros con reglas distintas.
Ojo con
Cada instancia tiene su propio storage; migrar una no migra la otra.
Precaución

Escribir uno propio

La lógica es específica del negocio y no existe en FRAME.

Ideal para
Las reservas con depósito y penalización de la cooperativa.
Ojo con
Un error aquí afecta a toda la cadena; requiere pruebas, pesos y revisión.
Reutilizar código ya auditado reduce riesgo; escribir uno propio es la última opción, no la primera.

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. 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."

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.
  • 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.
  • 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.

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. 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.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [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. [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. [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).

    source codeID: FRAME-DISPATCHAbrir fuente (abre en otra pestaña)

    Variantes de origen: Root, Signed, None y Authorized.

  4. [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).

    source codeID: KITCHENSINK-RUNTIMEAbrir fuente (abre en otra pestaña)

    Runtime de demostración con todos los pallets de FRAME; usa #[frame_support::runtime] e instancias de pallet_collective.

  5. [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. [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.

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.