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>
361 lines
14 KiB
Rust
361 lines
14 KiB
Rust
//! # 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());
|
||
}
|
||
}
|