diff --git a/Cargo.lock b/Cargo.lock index 680fa34..789a294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/origin-chain-runtime/Cargo.toml b/crates/origin-chain-runtime/Cargo.toml index be24c79..43abda4 100644 --- a/crates/origin-chain-runtime/Cargo.toml +++ b/crates/origin-chain-runtime/Cargo.toml @@ -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 diff --git a/crates/origin-chain-runtime/src/genesis_config_presets.rs b/crates/origin-chain-runtime/src/genesis_config_presets.rs index 85b1156..57caeeb 100644 --- a/crates/origin-chain-runtime/src/genesis_config_presets.rs +++ b/crates/origin-chain-runtime/src/genesis_config_presets.rs @@ -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 { 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::( + 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 diff --git a/crates/origin-chain-runtime/src/lib.rs b/crates/origin-chain-runtime/src/lib.rs index f9d6bfd..d497862 100644 --- a/crates/origin-chain-runtime/src/lib.rs +++ b/crates/origin-chain-runtime/src/lib.rs @@ -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,35 +672,26 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - // 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")] - { - frame_support::genesis_builder_helper::get_preset::( - id, - genesis_config_presets::get_preset, - ) - } - #[cfg(not(feature = "std"))] - { - let _ = id; - None - } + // 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::( + id, + genesis_config_presets::get_preset, + ) } fn preset_names() -> Vec { - #[cfg(feature = "std")] - { - genesis_config_presets::preset_names() - } - #[cfg(not(feature = "std"))] - { - Vec::new() - } + genesis_config_presets::preset_names() } } @@ -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); }