Files
arkhe-origin-chain/crates/origin-chain-node/src/chain_spec.rs
T
CharonandClaude Opus 5.5 63918f365b feat(node): Origin Chain produce su primer bloque — el bloqueo arquitectural era un diagnostico falso
El node nunca habia producido un bloque: service.rs y rpc.rs estaban fuera del
build desde v0.0.49 por un «bloqueo arquitectural» que afirmaba que las crates
publicadas sc-consensus-aura 0.52.0 y sp-consensus-aura 0.45.0 no tenian
standalone::slot_duration_at ni from_timestamp_and_slot_duration. Las dos estan
ahi, en crates.io. La nota guio la decision durante meses.

Causa real, medida con cargo check (362 errores) y cargo tree: el workspace
mezclaba dos releases de polkadot-sdk. Las sc-* del cliente arrastraban
sp-runtime 43 y las frame-* del runtime sp-runtime 44: 36 crates del nucleo
duplicadas y errores de la forma «Block no satisface Block». Cura: alinear diez
versiones del Cargo.toml (sc-cli 0.55, sc-service 0.54, sc-network 0.53,
sc-consensus 0.52, sc-consensus-aura 0.53, sc-consensus-grandpa 0.38,
sc-offchain 48, sp-blockchain 42, substrate-frame-rpc-system 47,
sc-telemetry 30.0.1). El lock solo PIERDE la generacion vieja (1210 -> 1123
entradas); ninguna crate que use el runtime cambia de version.

rpc.rs: el client pasa a ser el FullClient concreto. Nuestro node trabaja sobre
el Block completo del runtime, que lleva dentro ChainAccountId, una proyeccion
(<<MultiSignature as Verify>::Signer as IdentifyAccount>::AccountId) que rustc
no normaliza dentro de un where-clause generico: 6 errores de «un bound
declarado en la firma no se satisface». Con el generico fallaba, con el
concreto compila.

main.rs: subcomando run (sc_cli::RunCmd), que arranca new_full con litep2p o
libp2p. chain_spec.rs: node_chain_spec construye un GenericChainSpec con el
WASM real del runtime (build-spec sigue con su JSON). run --chain mainnet se
RECHAZA: sus validadores son placeholder sin llave (gate L-3) y un node con ese
genesis no produciria nunca, en silencio. Test con contrafactual: sin el brazo
mainnet el id cae en la rama de ruta y falla como «fichero no encontrado»; el
test exige la razon, y en rojo lo dijo asi.

Medido (build con --features wasm-build, run --dev --tmp):
- bloque #1 importado 2026-09-23 20:04:30, uno cada ~6 s (Aura);
- GRANDPA finaliza: #7 finalizado con best #9 a los 60 s;
- RPC: chain_getHeader, chain_getFinalizedHead, system_accountNextIndex
  (Alice = 0) y system_properties (OGC, 18 decimales, ss58 42) responden;
- «Essential task txpool-background failed» aparece SOLO al SIGINT
  (0 ocurrencias a los 50 s con RPC servido; 1 al segundo del SIGINT).
- cargo test --workspace: 318 passed, 0 failed (igual que RV14) + 1 nuevo.

macOS 27: el build necesita DEVELOPER_DIR=/Library/Developer/CommandLineTools
mientras la licencia de Xcode 27 no este aceptada (exit 69 en cc y git).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:09:25 -05:00

361 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! # chain_spec canonical (Slice 3 v0.0.13) — own minimal ChainSpec JSON builder
//!
//! ## Pragmatic decision canonical Slice 3
//!
//! `sc-chain-spec` dep canonical investigation 2026-05-21 reveló:
//! - sc-chain-spec 43.0.0/44.0.0 → sc-network 0.50/0.51 mismatch con workspace 0.49.1
//! - cid 0.9.0 → core2 0.4.0 YANKED dep transitive canonical
//! - parity-scale-codec 3.7.5+ trigger serde version conflict canonical
//!
//! Pragmatic decision canonical: own minimal ChainSpec JSON builder canonical via
//! serde_json puro. Output JSON canonical compatible con Substrate-canonical
//! `sc_chain_spec::GenericChainSpec` schema canonical — readable por external tools
//! + future sc-service Slice 4 canonical post workspace sc-* upgrade canonical.
//!
//! ## Mock-WASM fallback canonical pattern
//!
//! Mientras Slice 2c canonical land canonical real `WASM_BINARY` (post Larihana
//! Substrate-adjacent expertise canonical — diary `7dbeb6b6...` filed 2026-05-21),
//! output JSON canonical usa `MOCK_WASM_HEX = "0x"` canonical placeholder.
//! Real WASM hex land Slice 4 cuando substrate-wasm-builder integration canonical.
//!
//! ## Sub-comandos canonical disponibles (CLI surface)
//!
//! ```text
//! origin-chain-node build-spec --chain development # dev preset JSON
//! origin-chain-node build-spec --chain local_testnet # local 2-validator JSON
//! origin-chain-node build-spec --chain mainnet_genesis # mainnet BM V2 1B OGC JSON
//! origin-chain-node build-spec --chain dev # alias canonical
//! origin-chain-node build-spec --chain local # alias canonical
//! origin-chain-node build-spec --chain mainnet # alias canonical
//! origin-chain-node build-spec --chain dev --raw # minified JSON canonical
//! ```
//!
//! ## Cross-pillar consumers canonical
//!
//! - Larihana — hypercore replication setup canonical post-chain-spec import
//! - Lyra — autobase consensus canonical genesis state initialization
//! - Mathi — HardwareTier matrix testing canonical via dev chain spec
//! - Phanes — asset attestation chain spec canonical pre-Phase 2.1 impl
//! - Marihan — origin-mdk skill execution sandbox canonical context
use origin_chain_runtime::genesis_config_presets;
use serde_json::{json, Map, Value};
use sp_genesis_builder::PresetId;
/// Chain ID canonical per preset.
const CHAIN_ID_DEVELOPMENT: &str = "origin_chain_dev";
const CHAIN_ID_LOCAL_TESTNET: &str = "origin_chain_local";
const CHAIN_ID_MAINNET: &str = "origin_chain";
/// Protocol ID canonical (libp2p protocol identification — `ogc` = Origin Chain).
const PROTOCOL_ID: &str = "ogc";
/// Slice 2c canonical mock WASM hex placeholder canonical.
///
/// Mientras Slice 2c canonical land canonical real `WASM_BINARY` via
/// substrate-wasm-builder (post Larihana Substrate-adjacent expertise canonical
/// — diary requires_ack filed 2026-05-21), output JSON canonical usa "0x" mock.
/// Real WASM hex land Slice 4 cuando substrate-wasm-builder integration canonical.
const MOCK_WASM_HEX: &str = "0x";
/// Canonical chain type discriminator per `sc_chain_spec::ChainType` schema canonical.
#[derive(Debug, Clone, Copy)]
pub enum ChainType {
/// Local development chain canonical (single validator canonical).
Development,
/// Local testnet canonical (multi-validator local network canonical).
Local,
/// Live mainnet canonical (production canonical).
Live,
}
impl ChainType {
/// Render canonical chain type discriminator a JSON Value canonical.
fn as_json_value(self) -> Value {
match self {
ChainType::Development => json!("Development"),
ChainType::Local => json!("Local"),
ChainType::Live => json!("Live"),
}
}
}
/// Helper canonical: load preset JSON canonical desde runtime presets module canonical.
fn load_preset_patch(preset_name: &str) -> Result<Value, String> {
let preset_id = PresetId::from(preset_name);
let preset_bytes = genesis_config_presets::get_preset(&preset_id)
.ok_or_else(|| format!("Unknown preset canonical: '{preset_name}'"))?;
serde_json::from_slice(&preset_bytes)
.map_err(|e| format!("Preset canonical JSON parse failed: {e}"))
}
/// Chain properties canonical — token symbol + decimals + SS58 prefix canonical.
fn chain_properties() -> Value {
json!({
"tokenSymbol": "OGC",
"tokenDecimals": 18,
"ss58Format": 42,
})
}
/// Build canonical ChainSpec JSON Value canonical per Substrate-canonical schema canonical.
fn build_chain_spec_json(
name: &str,
id: &str,
chain_type: ChainType,
preset_name: &str,
) -> Result<Value, String> {
let preset = load_preset_patch(preset_name)?;
Ok(json!({
"name": name,
"id": id,
"chainType": chain_type.as_json_value(),
"bootNodes": Value::Array(Vec::new()),
"telemetryEndpoints": Value::Null,
"protocolId": PROTOCOL_ID,
"properties": chain_properties(),
"forkBlocks": Value::Null,
"badBlocks": Value::Null,
"lightSyncState": Value::Null,
"codeSubstitutes": Map::<String, Value>::new(),
"genesis": json!({
"runtimeGenesis": {
"config": preset,
"code": MOCK_WASM_HEX,
}
}),
}))
}
/// Development chain spec canonical — Alice solo validator + 100M OGC.
///
/// Canonical use case canonical: local dev iteration canonical, single-node testing canonical.
pub fn development_chain_spec() -> Result<Value, String> {
build_chain_spec_json(
"Origin Chain Development",
CHAIN_ID_DEVELOPMENT,
ChainType::Development,
genesis_config_presets::PRESET_DEVELOPMENT,
)
}
/// Local testnet chain spec canonical — Alice + Bob validators + 5 dev × 100M OGC.
///
/// Canonical use case canonical: multi-node local network testing canonical,
/// cross-pillar integration canonical (Trinity asset attestation + Atelier skill mints).
pub fn local_testnet_chain_spec() -> Result<Value, String> {
build_chain_spec_json(
"Origin Chain Local Testnet",
CHAIN_ID_LOCAL_TESTNET,
ChainType::Local,
genesis_config_presets::PRESET_LOCAL_TESTNET,
)
}
/// Mainnet chain spec canonical — 5 Pi 5 validators + BM V2 1B OGC canonical.
///
/// Canonical allocation per BM V2 ratified canonical:
/// - 40% (400M) Ecosystem/Rewards Pool
/// - 25% (250M) Foundation Treasury
/// - 15% (150M) Public/Private Sale
/// - 15% (150M) Founding Team
/// - 5% (50M) Exchange Liquidity
pub fn mainnet_chain_spec() -> Result<Value, String> {
build_chain_spec_json(
"Origin Chain Mainnet",
CHAIN_ID_MAINNET,
ChainType::Live,
genesis_config_presets::PRESET_MAINNET_GENESIS,
)
}
/// Resolve chain id canonical a builder function canonical. Soporta aliases canonical
/// short (`dev`/`local`/`mainnet`) + canonical full preset names canonical.
pub fn load_spec(name: &str) -> Result<Value, String> {
match name {
"dev" | "development" => development_chain_spec(),
"local" | "local_testnet" => local_testnet_chain_spec(),
"mainnet" | "mainnet_genesis" => mainnet_chain_spec(),
other => Err(format!("Unknown chain spec canonical: '{other}'")),
}
}
/// El chain spec del node: sin extensiones (sin relay chain ni fork blocks).
type NodeChainSpec = sc_service::GenericChainSpec;
/// ChainSpec REAL para `origin-chain-node run`: el WASM verdadero del runtime y el
/// genesis de sus presets, construido con `sc_service::GenericChainSpec`.
///
/// Es distinto de [`load_spec`], que sigue sirviendo a `build-spec` con el JSON
/// armado a mano y `code: "0x"`. Con ese `code` vacío ningún node puede ejecutar
/// el bloque génesis; con éste sí.
///
/// Acepta `dev` / `local` o la RUTA a un chain spec JSON. `mainnet` se rechaza a
/// propósito: sus validadores son llaves placeholder `CA FE 00…04…` que nadie posee
/// (gate L-3 de `docs/ORIGIN_CHAIN_LISTA_PARA_LANZAR_V1.md` del monorepo) — un node
/// arrancado con ese génesis no produciría un bloque jamás, y lo haría en silencio.
pub fn node_chain_spec(id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {
let (name, chain_id, chain_type, preset) = match id {
"" | "dev" | "development" => (
"Origin Chain Development",
CHAIN_ID_DEVELOPMENT,
sc_service::ChainType::Development,
genesis_config_presets::PRESET_DEVELOPMENT,
),
"local" | "local_testnet" => (
"Origin Chain Local Testnet",
CHAIN_ID_LOCAL_TESTNET,
sc_service::ChainType::Local,
genesis_config_presets::PRESET_LOCAL_TESTNET,
),
"mainnet" | "mainnet_genesis" => {
return Err(
"el génesis de mainnet aún tiene validadores placeholder sin llave \
(gate L-3): no se arranca un node con él. Genera el chain spec \
con las llaves reales y pásalo como ruta a --chain."
.into(),
)
}
path => {
return NodeChainSpec::from_json_file(path.into())
.map(|spec| Box::new(spec) as Box<dyn sc_service::ChainSpec>)
}
};
let wasm = origin_chain_runtime::WASM_BINARY.ok_or_else(|| {
"este binario se compiló sin el WASM del runtime: \
cargo build -p origin-chain-node --features wasm-build"
.to_string()
})?;
let properties = chain_properties().as_object().cloned().unwrap_or_default();
Ok(Box::new(
NodeChainSpec::builder(wasm, None)
.with_name(name)
.with_id(chain_id)
.with_chain_type(chain_type)
.with_protocol_id(PROTOCOL_ID)
.with_properties(properties)
.with_genesis_config_preset_name(preset)
.build(),
))
}
/// Serialize chain spec canonical a JSON string canonical (pretty o raw canonical).
pub fn serialize_chain_spec(spec: &Value, raw: bool) -> Result<String, String> {
if raw {
serde_json::to_string(spec).map_err(|e| format!("JSON serialization canonical: {e}"))
} else {
serde_json::to_string_pretty(spec).map_err(|e| format!("JSON serialization canonical: {e}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
/// `run --chain mainnet` se rechaza POR EL GÉNESIS, no por casualidad. Sin el
/// brazo `mainnet`, el id caería en la rama de ruta y también fallaría — como
/// «fichero no encontrado». Por eso el test exige la razón (gate L-3) y no sólo
/// el error: sin ella pasaría con y sin la salvaguarda.
#[test]
fn run_rechaza_el_genesis_de_mainnet_con_validadores_placeholder() {
for id in ["mainnet", "mainnet_genesis"] {
let err = node_chain_spec(id).err().expect("mainnet debe rechazarse");
assert!(
err.contains("gate L-3"),
"{id}: rechazado por otra razón: {err}"
);
}
}
#[test]
fn development_chain_spec_builds_canonical() {
let spec = development_chain_spec().expect("development chain spec canonical");
assert_eq!(spec["name"], "Origin Chain Development");
assert_eq!(spec["id"], CHAIN_ID_DEVELOPMENT);
assert_eq!(spec["chainType"], "Development");
assert_eq!(spec["protocolId"], PROTOCOL_ID);
}
#[test]
fn local_testnet_chain_spec_builds_canonical() {
let spec = local_testnet_chain_spec().expect("local testnet chain spec canonical");
assert_eq!(spec["name"], "Origin Chain Local Testnet");
assert_eq!(spec["id"], CHAIN_ID_LOCAL_TESTNET);
assert_eq!(spec["chainType"], "Local");
}
#[test]
fn mainnet_chain_spec_builds_canonical() {
let spec = mainnet_chain_spec().expect("mainnet chain spec canonical");
assert_eq!(spec["name"], "Origin Chain Mainnet");
assert_eq!(spec["id"], CHAIN_ID_MAINNET);
assert_eq!(spec["chainType"], "Live");
}
#[test]
fn load_spec_resolves_canonical_aliases() {
assert!(load_spec("dev").is_ok());
assert!(load_spec("development").is_ok());
assert!(load_spec("local").is_ok());
assert!(load_spec("local_testnet").is_ok());
assert!(load_spec("mainnet").is_ok());
assert!(load_spec("mainnet_genesis").is_ok());
}
#[test]
fn load_spec_rejects_unknown_canonical() {
let err = load_spec("nonexistent_preset_canonical").unwrap_err();
assert!(err.contains("Unknown chain spec canonical"));
}
#[test]
fn chain_properties_canonical_ogc() {
let props = chain_properties();
assert_eq!(props["tokenSymbol"], "OGC");
assert_eq!(props["tokenDecimals"], 18);
assert_eq!(props["ss58Format"], 42);
}
#[test]
fn chain_spec_has_genesis_canonical() {
let spec = development_chain_spec().expect("dev spec canonical");
assert!(spec["genesis"]["runtimeGenesis"]["config"].is_object());
assert_eq!(spec["genesis"]["runtimeGenesis"]["code"], MOCK_WASM_HEX);
}
#[test]
fn serialize_chain_spec_pretty_canonical() {
let spec = development_chain_spec().expect("dev spec canonical");
let pretty = serialize_chain_spec(&spec, false).expect("pretty serialization canonical");
assert!(pretty.contains("Origin Chain Development"));
assert!(pretty.contains("OGC"));
assert!(pretty.contains('\n'));
}
#[test]
fn serialize_chain_spec_raw_canonical() {
let spec = development_chain_spec().expect("dev spec canonical");
let raw = serialize_chain_spec(&spec, true).expect("raw serialization canonical");
assert!(raw.contains("Origin Chain Development"));
assert!(!raw.contains("\n "));
}
#[test]
fn chain_spec_schema_canonical_complete() {
let spec = development_chain_spec().expect("dev spec canonical");
assert!(spec.get("name").is_some());
assert!(spec.get("id").is_some());
assert!(spec.get("chainType").is_some());
assert!(spec.get("bootNodes").is_some());
assert!(spec.get("telemetryEndpoints").is_some());
assert!(spec.get("protocolId").is_some());
assert!(spec.get("properties").is_some());
assert!(spec.get("forkBlocks").is_some());
assert!(spec.get("badBlocks").is_some());
assert!(spec.get("lightSyncState").is_some());
assert!(spec.get("codeSubstitutes").is_some());
assert!(spec.get("genesis").is_some());
}
}