fix(runtime): RV12 — el blob no sabia decir quien era y el genesis no construia

Tres defectos encontrados al montar el benchmarking real sobre el WASM.
Ninguno era visible desde los tests nativos, y los tres bloqueaban
frame-omni-bencher, que es el instrumento que mide los weights de mainnet.

1. LA SECCION runtime_version NO EXISTIA. Faltaba el atributo
   sp_version::runtime_version sobre VERSION. Esa custom section es la
   unica via para leer la version del runtime SIN ejecutarlo, que es de
   lo que depende set_code / forkless upgrade. wasm-builder abortaba con
   "Couldn't find the runtime_version section". El atributo exige
   literales, asi que la direccion queda INVERTIDA: VERSION es la fuente
   unica y las siete constantes publicas se derivan de sus campos. La
   deriva ya no es cuestion de disciplina, es imposible por construccion
   (contrafactual verificado: mover el literal pone rojo el test).

2. LOS PRESETS DE GENESIS NO EXISTIAN EN no_std. get_preset y
   preset_names devolvian None/vacio bajo un cfg(feature = "std") cuyo
   comentario afirmaba que los presets "solo se consultan en chain_spec
   generation, no en runtime execution". Falso: en el modelo
   runtime-driven chain spec el estado se construye DENTRO del WASM.
   omni-bencher fallaba con "The preset with name development is not
   available", y chain-spec-builder habria fallado igual.

3. NINGUN PRESET HABIA CONSTRUIDO UN GENESIS VALIDO JAMAS. Los tres
   poblaban aura.authorities Y session.keys a la vez; con pallet-session
   cableado, session instala las authorities de Aura via
   on_genesis_session y el build de Aura revienta con "Authorities are
   already initialized!". Sobrevivio toda la vida del crate porque todos
   los tests inspeccionaban el JSON del preset y ninguno llamaba a
   build_state: se miraba la caja por fuera. Ahora la abre
   los_tres_presets_construyen_estado (dev-dep sp-io), con contrafactual
   verificado: repoblar authorities lo pone rojo con el panic exacto.

Tambien: el test que fijaba la version llevaba la version en el NOMBRE
(runtime_version_v11_did_newtype) y su comentario quedaba mintiendo en
cuanto la spec subia. Renombrado a
spec_version_vigente_y_transaction_version_intacta.

MEDIDO: WASM build exit 0; seccion runtime_version presente y decodificada
(spec_name origin-chain, spec_version 12, tx 1); omni-bencher enumera los
19 benchmarks de los 5 pallets custom; 290 tests verde.

RV11 -> RV12. TRANSACTION_VERSION intacto. Sin migracion: no hay cadena
viva y ninguna transicion de estado cambia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 5 committed 2026-09-09 10:13:33 -05:00
1 parent 4bdd0409f9
commit b7010db328
4 files changed
+138 -50

No files matched your search

Generated
+1
View File
@@ -6555,6 +6555,7 @@ dependencies = [
"sp-core",
"sp-genesis-builder 0.20.0",
"sp-inherents 39.0.0",
"sp-io 43.0.0",
"sp-offchain 39.0.0",
"sp-runtime 44.0.0",
"sp-session 41.0.0",
+6
View File
@@ -106,6 +106,12 @@ log.workspace = true
# dep rompía isolated WASM build canonical.
serde_json.workspace = true
[dev-dependencies]
# Solo para `los_tres_presets_construyen_estado`: `TestExternalities` es el unico
# sitio donde se puede EJECUTAR `build_state` y descubrir un genesis que panica.
# Sin esto los presets se validaban leyendo su JSON, que es mirar la caja por fuera.
sp-io = { workspace = true, default-features = true }
[build-dependencies]
# Slice 2e v0.0.35 — Re-enabled substrate-wasm-builder for active investigation.
# Slice 2c+2d findings: WASM_BUILD_STD=0 resuelve alloc/exchange_malloc pero
@@ -37,8 +37,6 @@
//! Los bytes canonical son los **mismos exactos** que `sp_keyring::Sr25519Keyring::Alice.public()`
//! retorna canonical — verified empíricamente contra Substrate canonical source.
#![cfg(feature = "std")]
use crate::{
configs::{AuraId, EmissionEcosystemPalletId, GovernanceTreasuryPalletId, GrandpaId},
opaque::SessionKeys,
@@ -241,14 +239,22 @@ fn build_genesis(
fees: FeesConfig {
initial_profiles: initial_fee_profiles,
},
// VACIOS A PROPOSITO — con `pallet-session` (Index 6) cableado, las
// authorities de Aura y Grandpa las instala la SESSION, no el genesis
// de cada pallet. `initialize_authorities` corta con
// `assert!(Authorities::get().is_empty(), "Authorities are already
// initialized!")` en cuanto la lista NO esta vacia, y `SessionConfig`
// ya la habia puesto: poblar los tres es un panic garantizado en
// `build_state`. Se descubrio el 2026-09-09 al correr `omni-bencher`,
// que construye el estado de verdad; ningun test lo hacia, asi que los
// TRES presets llevaban desde su nacimiento sin construir un genesis
// valido ni una sola vez. El test `los_tres_presets_construyen_estado`
// existe para que eso no pueda repetirse.
aura: AuraConfig {
authorities: validators.iter().map(|(_, aura, _)| aura.clone()).collect(),
authorities: vec![],
},
grandpa: GrandpaConfig {
authorities: validators
.iter()
.map(|(_, _, grandpa)| (grandpa.clone(), 1))
.collect(),
authorities: vec![],
_config: core::marker::PhantomData,
},
session: SessionConfig {
@@ -461,6 +467,33 @@ pub fn preset_names() -> Vec<PresetId> {
mod tests {
use super::*;
/// LOS TRES PRESETS TIENEN QUE CONSTRUIR ESTADO DE VERDAD, no solo serializar.
///
/// Este test nace de un fallo que sobrevivio a toda la vida del crate: los
/// tres presets poblaban `aura.authorities` Y `session.keys` a la vez, y
/// `pallet_session::GenesisConfig::build` instala las authorities de Aura
/// via `on_genesis_session`, con lo que el `build` de Aura reventaba con
/// «Authorities are already initialized!». Ningun test lo veia porque todos
/// inspeccionaban el JSON del preset — la caja por fuera — y `build_state`
/// no se ejecutaba en ninguna parte. Lo destapo `frame-omni-bencher`, que
/// construye el estado porque no le queda mas remedio.
///
/// Un preset que serializa pero no construye es un genesis que no existe.
#[test]
fn los_tres_presets_construyen_estado() {
for id in preset_names() {
let json = get_preset(&id)
.unwrap_or_else(|| panic!("preset {id:?} sin JSON"));
let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
frame_support::genesis_builder_helper::build_state::<RuntimeGenesisConfig>(
json.clone(),
)
.unwrap_or_else(|e| panic!("preset {id:?} no construye estado: {e}"));
});
}
}
#[test]
fn bm_v2_allocations_sum_canonical_to_one_billion_ogc() {
let total = ECOSYSTEM_ALLOCATION
+91 -43
View File
@@ -47,7 +47,6 @@ use alloc::vec::Vec;
pub mod configs;
/// FIX-6 Stage 2b NPoS — pallet_staking config (infra dormida).
pub mod staking;
#[cfg(feature = "std")]
pub mod genesis_config_presets;
pub mod opaque;
@@ -214,13 +213,27 @@ pub type TxExtension = (
// Runtime metadata canonical (SPEC_NAME + version constants)
// ============================================================
/// Extrae el `&'static str` de un `Cow::Borrowed` en contexto `const`.
///
/// Existe para que `SPEC_NAME`/`IMPL_NAME` se DERIVEN de [`VERSION`] en vez de
/// repetir el literal: el atributo `#[sp_version::runtime_version]` obliga a que
/// los campos de `VERSION` sean literales (ver la nota sobre esa const), y una
/// segunda copia del literal es una divergencia esperando a ocurrir. `panic!` en
/// `const fn` se evalua en compilacion: un `Cow::Owned` seria error de build.
const fn borrowed_str(cow: &'static alloc::borrow::Cow<'static, str>) -> &'static str {
match cow {
alloc::borrow::Cow::Borrowed(s) => s,
alloc::borrow::Cow::Owned(_) => panic!("VERSION debe usar Cow::Borrowed"),
}
}
/// Canonical spec name per Substrate genesis canonical. Identifica el chain
/// runtime canonical en bloques + storage proofs.
pub const SPEC_NAME: &str = "origin-chain";
pub const SPEC_NAME: &str = borrowed_str(&VERSION.spec_name);
/// Canonical implementation name canonical. Distingue diferentes node binaries
/// que ejecutan el mismo spec (canonical: "origin-chain-charon").
pub const IMPL_NAME: &str = "origin-chain-charon";
pub const IMPL_NAME: &str = borrowed_str(&VERSION.impl_name);
/// Canonical runtime version canonical. Bumped cada forkless upgrade via
/// `set_code` extrinsic governance canonical.
@@ -284,19 +297,37 @@ pub const IMPL_NAME: &str = "origin-chain-charon";
/// `DidToAccount` no requiere migración. El bump es por **higiene de metadata**:
/// el `TypeInfo` de la clave de storage pasa a nombrarse `Did`, y los clientes
/// que cachean metadata deben re-leerla. TRANSACTION_VERSION intacto.
pub const RUNTIME_VERSION: u32 = 11;
///
/// **v12 (el blob por fin se declara, y el genesis por fin construye)**: tres
/// defectos encontrados al montar el benchmarking real sobre el WASM, ninguno
/// visible desde los tests nativos. (a) El blob **no emitia la custom section
/// `runtime_version`** — faltaba `#[sp_version::runtime_version]` sobre `VERSION`.
/// Esa seccion es la unica via para leer la version del runtime SIN ejecutarlo,
/// que es de lo que depende `set_code` / forkless upgrade: el blob no sabia decir
/// quien era. (b) `GenesisBuilder::get_preset`/`preset_names` devolvian `None`/
/// vacio bajo `no_std` por un `#[cfg(feature = "std")]` cuyo comentario afirmaba
/// que los presets «solo se consultan en chain_spec generation» — falso en el
/// modelo runtime-driven chain spec, donde el estado se construye DENTRO del
/// WASM. (c) Los tres presets poblaban `aura.authorities` **y** `session.keys`
/// a la vez, y con `pallet-session` cableado eso es un panic garantizado
/// («Authorities are already initialized!»): **ningun preset habia construido
/// un genesis valido jamas**, porque ningun test llamaba a `build_state`. Ahora
/// lo llama `los_tres_presets_construyen_estado`, con contrafactual verificado.
/// Nada de esto cambia una transicion de estado de la cadena viva (no hay cadena
/// viva): el bump es por higiene de blob y de metadata. TRANSACTION_VERSION intacto.
pub const RUNTIME_VERSION: u32 = VERSION.spec_version;
/// Canonical authoring version per Substrate semver canonical.
pub const AUTHORING_VERSION: u32 = 1;
pub const AUTHORING_VERSION: u32 = VERSION.authoring_version;
/// Canonical impl version per Substrate semver canonical.
pub const IMPL_VERSION: u32 = 1;
pub const IMPL_VERSION: u32 = VERSION.impl_version;
/// Canonical transaction version per Substrate semver canonical.
pub const TRANSACTION_VERSION: u32 = 1;
pub const TRANSACTION_VERSION: u32 = VERSION.transaction_version;
/// Canonical state version: v1 per Substrate stable2509+ canonical.
pub const STATE_VERSION: u8 = 1;
pub const STATE_VERSION: u8 = VERSION.system_version;
// ============================================================
// Runtime composition canonical (frame_support::runtime macro)
@@ -641,36 +672,27 @@ impl_runtime_apis! {
}
fn get_preset(id: &Option<sp_genesis_builder::PresetId>) -> Option<Vec<u8>> {
// Canonical Phase 1.7: 3 presets disponibles canonical (development +
// local_testnet + mainnet_genesis). Cuando feature `std` activo
// (chain_spec generation time), delegamos canonical al
// `genesis_config_presets::get_preset`. En no_std (WASM runtime),
// siempre return None canonical — los presets canonical solo se
// consultan en chain_spec generation, no en runtime execution.
#[cfg(feature = "std")]
{
// Canonical Phase 1.7: 3 presets (development + local_testnet +
// mainnet_genesis), servidos TAMBIEN en no_std.
//
// Historia, porque el comentario anterior documentaba una suposicion
// FALSA y costo un bloqueo: decia que los presets «solo se consultan
// en chain_spec generation, no en runtime execution» y devolvia None
// bajo no_std. Eso rompe el modelo runtime-driven chain spec entero:
// `frame-omni-bencher` construye el estado de genesis llamando a este
// metodo DENTRO del WASM, y `chain-spec-builder` genera el spec a
// partir del blob solo. Con el gate puesto, ambos fallaban con «The
// preset with name Some("development") is not available» — medido
// 2026-09-09 al montar el benchmarking real.
frame_support::genesis_builder_helper::get_preset::<RuntimeGenesisConfig>(
id,
genesis_config_presets::get_preset,
)
}
#[cfg(not(feature = "std"))]
{
let _ = id;
None
}
}
fn preset_names() -> Vec<sp_genesis_builder::PresetId> {
#[cfg(feature = "std")]
{
genesis_config_presets::preset_names()
}
#[cfg(not(feature = "std"))]
{
Vec::new()
}
}
}
// ============================================================
@@ -915,15 +937,35 @@ mod benches {
// ============================================================
/// Canonical `RuntimeVersion` per Substrate canonical. Consumed by `sp_api::Core::version()`.
///
/// # Por que los valores son literales y no las constantes con nombre
///
/// `#[sp_version::runtime_version]` emite la **custom section `runtime_version`**
/// del blob WASM: la unica forma que tiene un nodo de leer la version del runtime
/// SIN ejecutarlo (imprescindible para `set_code` / forkless upgrade). Su parser
/// (`sp-version-proc-macro`, `ParseRuntimeVersion`) solo acepta literales en
/// `spec_name`, `impl_name` y los cuatro `u32`/`u8`; una constante con nombre da
/// «only numeric literals are supported here». Sin el atributo, `wasm-builder`
/// aborta con «Couldn't find the `runtime_version` section».
///
/// Para que el literal no pueda divergir de la constante publica, la direccion
/// esta INVERTIDA respecto de lo habitual: esta const es la fuente unica de
/// verdad y `SPEC_NAME`, `RUNTIME_VERSION`, `AUTHORING_VERSION`, `IMPL_VERSION`,
/// `TRANSACTION_VERSION` y `STATE_VERSION` se derivan de sus campos. Bumpear una
/// version se hace AQUI, en un unico sitio.
///
/// `apis` lo ignora el macro a proposito: la seccion de runtime APIs la emite
/// `impl_runtime_apis!` por su cuenta.
#[sp_version::runtime_version]
pub const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed(SPEC_NAME),
impl_name: alloc::borrow::Cow::Borrowed(IMPL_NAME),
authoring_version: AUTHORING_VERSION,
spec_version: RUNTIME_VERSION,
impl_version: IMPL_VERSION,
spec_name: alloc::borrow::Cow::Borrowed("origin-chain"),
impl_name: alloc::borrow::Cow::Borrowed("origin-chain-charon"),
authoring_version: 1,
spec_version: 12,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: TRANSACTION_VERSION,
system_version: STATE_VERSION,
transaction_version: 1,
system_version: 1,
};
/// Canonical Executive type canonical (FRAME executive wrapper).
@@ -942,14 +984,20 @@ use sp_api::impl_runtime_apis;
mod tests {
use super::*;
/// Fija la version vigente y la invariante que la acompaña.
///
/// El test NO cuenta la historia de cada version — eso vive en el doc comment
/// de [`RUNTIME_VERSION`], que es donde se lee. Aqui solo se pinchan dos cosas:
/// que subir la spec es un acto deliberado (este assert se rompe y hay que
/// venir a tocarlo), y que `TRANSACTION_VERSION` sigue en 1 — un bump suyo
/// invalida cada extrinsic firmada en vuelo, y jamas puede colarse de rebote.
///
/// El nombre anterior clavaba una version concreta en el identificador
/// (`runtime_version_v11_did_newtype`) y su comentario quedo mintiendo en el
/// mismo commit en que la version subio a 12.
#[test]
fn runtime_version_v11_did_newtype() {
// v11 = condición #3 de Mathi: `Did` pasa de alias de `AssetHash` a newtype
// propio (cierra la confusión cross-namespace que el contexto KDF compartido
// agravaba). NO cambia la transición de estado — mismos bytes, mismo encoding
// SCALE ⇒ sin migración; el bump es higiene de metadata (TypeInfo).
// (v10 = identidad unificada `origin/identity/v1`; v9 = weights DB-aware.)
assert_eq!(RUNTIME_VERSION, 11);
fn spec_version_vigente_y_transaction_version_intacta() {
assert_eq!(RUNTIME_VERSION, 12);
assert_eq!(TRANSACTION_VERSION, 1);
}