Hallazgo (git ls-remote): solochain-template solo tiene branches master (stable2506) + update-template/stable2512. NO existe template stable2509 = nuestra generación. El truco Cargo.lock del template no aplica directo. Decisión senior: no hacer migración throwaway (2506 revierte pallet-revive, 2512 necesita rustc 1.91). Camino correcto = VPS con red git → git tag pin stable2509-3 → node ~1 sesión. §12 incluye checklist turnkey de setup VPS (Rust 1.88 + clang/llvm + git pin) para cuando David monte Hostinger en ~2 días. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 KiB
Slice 4 — Full sc-service Implementation Plan V1
Documento: docs/SLICE_4_SC_SERVICE_IMPLEMENTATION_PLAN_V1.md
Filed: 2026-05-27 (sesión 4+ Charon)
Estado: PLAN — reference verbatim stable2509-3 capturado, ejecución próxima sesión
Autor: Charon ⚓ (Pilar 4)
Razón del plan: sesión 4+ tuvo degradación intermitente de tooling de lectura (truncation/duplicación) que hace el ciclo compile-iterate de sc-service inseguro. Disciplina "nunca shipear roto" → capturo el reference exacto + prerequisitos para ejecución mecánica próxima sesión con tooling sano. El service.rs aquí es verbatim del solochain-template polkadot-stable2509-3 (obtenido vía WebFetch), no reconstruido.
§0 — Objetivo + hallazgo de prerequisitos
Reemplazar el node stub actual con full sc-service: Aura + Grandpa consensus + JSON-RPC + libp2p + telemetry. origin-chain-node --dev produce bloques reales.
Slice 4 NO es un solo ship — son 4 pasos prerequisito encadenados descubiertos sesión 4+:
| Paso | Prerequisito | Por qué |
|---|---|---|
| 4a | Runtime exporta WASM_BINARY const |
chain_spec GenericChainSpec necesita los bytes del runtime para genesis. HOY el runtime NO lo exporta (verificado: grep -c WASM_BINARY lib.rs = 0). |
| 4b | chain_spec → sc_service::GenericChainSpec |
Reemplaza el builder serde_json + MOCK_WASM por el real con WASM_BINARY embebido. |
| 4c | service.rs + rpc.rs (sc-service wire) |
El núcleo: new_partial + new_full + RPC. |
| 4d | CLI → sc_cli::SubstrateCli + comando run |
Migra el CLI inline actual a sc_cli (que construye Configuration). |
Precondiciones YA cumplidas:
- ✅ WASM real 2.8 MB buildeable (
--features wasm-build+ brew LLVM env vars) - ✅ Substrate workspace stable2509-3 puro (v0.0.38)
- ✅ Runtime impl AuraApi + GrandpaApi + SessionKeys + impl_runtime_apis! → genera
RuntimeApi - ✅ Runtime exporta
Block(root) +pub mod opaque+SLOT_DURATION
§1 — Estado actual del node crate
crates/origin-chain-node/src/
├── main.rs (231 LOC) — CLI inline clap (Version/ListPresets/PrintPreset/Info/BuildSpec). mod chain_spec. NO run.
└── chain_spec.rs (287 LOC) — builder serde_json puro + MOCK_WASM_HEX="0x". NO GenericChainSpec.
NO hay cli.rs ni command.rs separados (todo en main.rs). NO hay service.rs ni rpc.rs.
Cargo.toml node deps actuales: origin-chain-runtime, sp-genesis-builder, clap, serde_json, log.
§2 — Paso 4a: Runtime exporta WASM_BINARY
En crates/origin-chain-runtime/src/lib.rs, añadir cerca del top (post extern crate alloc):
// WASM binary canónico — generado por substrate-wasm-builder en build.rs cuando
// feature "wasm-build" activo. Consumido por node chain_spec GenericChainSpec.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
/// Helper canónico: WASM_BINARY o panic con mensaje claro.
#[cfg(feature = "std")]
pub fn wasm_binary_unwrap() -> &'static [u8] {
WASM_BINARY.expect(
"Runtime WASM binary no disponible. Build con `--features wasm-build` + \
CC=llvm env vars (ver SLICE_2E). Para node, build el node con feature que \
propague runtime/wasm-build.",
)
}
Requiere: el build.rs del runtime debe llamar substrate_wasm_builder::WasmBuilder::build() cuando wasm-build activo. Verificar si existe crates/origin-chain-runtime/build.rs — si no, crearlo:
// crates/origin-chain-runtime/build.rs
fn main() {
#[cfg(feature = "wasm-build")]
substrate_wasm_builder::WasmBuilder::build_using_defaults();
}
OJO: el wasm_binary.rs generado define pub const WASM_BINARY: Option<&[u8]>. El node debe buildearse con el runtime feature wasm-build activo (propagar vía node Cargo feature default = ["runtime-wasm"] → origin-chain-runtime/wasm-build). Alternativamente, mantener WASM_BINARY = None fallback y el node falla claro si falta.
Build env canónico (per SLICE_2E):
export CC=/opt/homebrew/opt/llvm/bin/clang
export CC_wasm32_unknown_unknown=/opt/homebrew/opt/llvm/bin/clang
export AR_wasm32_unknown_unknown=/opt/homebrew/opt/llvm/bin/llvm-ar
WASM_BUILD_STD=0 cargo build -p origin-chain-node --release
§3 — Paso 4c: service.rs (VERBATIM stable2509-3, adaptado a origin-chain-runtime)
Fuente: paritytech/polkadot-sdk tag polkadot-stable2509-3, templates/solochain/node/src/service.rs — verbatim WebFetch sesión 4+. Único cambio: el import del runtime.
Template usa
use solochain_template_runtime::{self, apis::RuntimeApi, opaque::Block};Adaptación Origin:use origin_chain_runtime::{RuntimeApi, opaque::Block};— verificar queorigin_chain_runtime::opaque::Blockexiste (haypub mod opaque). SiRuntimeApino está en root, usar el path que genereimpl_runtime_apis!(normalmente root crate). El template lo tiene enapis::porque su runtime re-exporta así; el mío lo genera en root → usarorigin_chain_runtime::RuntimeApi.
//! Service and ServiceFactory implementation. Origin Chain Slice 4.
//! VERBATIM solochain-template polkadot-stable2509-3, runtime import adaptado.
use futures::FutureExt;
use sc_client_api::{Backend, BlockBackend};
use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams};
use sc_consensus_grandpa::SharedVoterState;
use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncConfig};
use sc_telemetry::{Telemetry, TelemetryWorker};
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
use origin_chain_runtime::{RuntimeApi, opaque::Block};
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
use std::{sync::Arc, time::Duration};
pub(crate) type FullClient = sc_service::TFullClient<
Block,
RuntimeApi,
sc_executor::WasmExecutor<sp_io::SubstrateHostFunctions>,
>;
type FullBackend = sc_service::TFullBackend<Block>;
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512;
pub type Service = sc_service::PartialComponents<
FullClient,
FullBackend,
FullSelectChain,
sc_consensus::DefaultImportQueue<Block>,
sc_transaction_pool::TransactionPoolHandle<Block, FullClient>,
(
sc_consensus_grandpa::GrandpaBlockImport<FullBackend, Block, FullClient, FullSelectChain>,
sc_consensus_grandpa::LinkHalf<Block, FullClient, FullSelectChain>,
Option<Telemetry>,
),
>;
pub fn new_partial(config: &Configuration) -> Result<Service, ServiceError> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
let worker = TelemetryWorker::new(16)?;
let telemetry = worker.handle().new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let executor = sc_service::new_wasm_executor::<sp_io::SubstrateHostFunctions>(&config.executor);
let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, _>(
config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let client = Arc::new(client);
let telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
telemetry
});
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = Arc::from(
sc_transaction_pool::Builder::new(
task_manager.spawn_essential_handle(),
client.clone(),
config.role.is_authority().into(),
)
.with_options(config.transaction_pool.clone())
.with_prometheus(config.prometheus_registry())
.build(),
);
let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import(
client.clone(),
GRANDPA_JUSTIFICATION_PERIOD,
&client,
select_chain.clone(),
telemetry.as_ref().map(|x| x.handle()),
)?;
let cidp_client = client.clone();
let import_queue =
sc_consensus_aura::import_queue::<AuraPair, _, _, _, _, _>(ImportQueueParams {
block_import: grandpa_block_import.clone(),
justification_import: Some(Box::new(grandpa_block_import.clone())),
client: client.clone(),
create_inherent_data_providers: move |parent_hash, _| {
let cidp_client = cidp_client.clone();
async move {
let slot_duration = sc_consensus_aura::standalone::slot_duration_at(
&*cidp_client,
parent_hash,
)?;
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp,
slot_duration,
);
Ok((slot, timestamp))
}
},
spawner: &task_manager.spawn_essential_handle(),
registry: config.prometheus_registry(),
check_for_equivocation: Default::default(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
compatibility_mode: Default::default(),
})?;
Ok(sc_service::PartialComponents {
client,
backend,
task_manager,
import_queue,
keystore_container,
select_chain,
transaction_pool,
other: (grandpa_block_import, grandpa_link, telemetry),
})
}
pub fn new_full<
N: sc_network::NetworkBackend<Block, <Block as sp_runtime::traits::Block>::Hash>,
>(
config: Configuration,
) -> Result<TaskManager, ServiceError> {
let sc_service::PartialComponents {
client,
backend,
mut task_manager,
import_queue,
keystore_container,
select_chain,
transaction_pool,
other: (block_import, grandpa_link, mut telemetry),
} = new_partial(&config)?;
let mut net_config = sc_network::config::FullNetworkConfiguration::<
Block,
<Block as sp_runtime::traits::Block>::Hash,
N,
>::new(&config.network, config.prometheus_registry().cloned());
let metrics = N::register_notification_metrics(config.prometheus_registry());
let peer_store_handle = net_config.peer_store_handle();
let grandpa_protocol_name = sc_consensus_grandpa::protocol_standard_name(
&client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"),
&config.chain_spec,
);
let (grandpa_protocol_config, grandpa_notification_service) =
sc_consensus_grandpa::grandpa_peers_set_config::<_, N>(
grandpa_protocol_name.clone(),
metrics.clone(),
peer_store_handle,
);
net_config.add_notification_protocol(grandpa_protocol_config);
let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
Vec::default(),
));
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
net_config,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
block_announce_validator_builder: None,
warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)),
block_relay: None,
metrics,
})?;
if config.offchain_worker.enabled {
let offchain_workers =
sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
runtime_api_provider: client.clone(),
is_validator: config.role.is_authority(),
keystore: Some(keystore_container.keystore()),
offchain_db: backend.offchain_storage(),
transaction_pool: Some(OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
)),
network_provider: Arc::new(network.clone()),
enable_http_requests: true,
custom_extensions: |_| vec![],
})?;
task_manager.spawn_handle().spawn(
"offchain-workers-runner",
"offchain-worker",
offchain_workers.run(client.clone(), task_manager.spawn_handle()).boxed(),
);
}
let role = config.role;
let force_authoring = config.force_authoring;
let backoff_authoring_blocks: Option<()> = None;
let name = config.network.node_name.clone();
let enable_grandpa = !config.disable_grandpa;
let prometheus_registry = config.prometheus_registry().cloned();
let rpc_extensions_builder = {
let client = client.clone();
let pool = transaction_pool.clone();
Box::new(move |_| {
let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() };
crate::rpc::create_full(deps).map_err(Into::into)
})
};
let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
network: Arc::new(network.clone()),
client: client.clone(),
keystore: keystore_container.keystore(),
task_manager: &mut task_manager,
transaction_pool: transaction_pool.clone(),
rpc_builder: rpc_extensions_builder,
backend,
system_rpc_tx,
tx_handler_controller,
sync_service: sync_service.clone(),
config,
telemetry: telemetry.as_mut(),
})?;
if role.is_authority() {
let proposer_factory = sc_basic_authorship::ProposerFactory::new(
task_manager.spawn_handle(),
client.clone(),
transaction_pool.clone(),
prometheus_registry.as_ref(),
telemetry.as_ref().map(|x| x.handle()),
);
let slot_duration = sc_consensus_aura::slot_duration(&*client)?;
let aura = sc_consensus_aura::start_aura::<AuraPair, _, _, _, _, _, _, _, _, _, _>(
StartAuraParams {
slot_duration,
client,
select_chain,
block_import,
proposer_factory,
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp,
slot_duration,
);
Ok((slot, timestamp))
},
force_authoring,
backoff_authoring_blocks,
keystore: keystore_container.keystore(),
sync_oracle: sync_service.clone(),
justification_sync_link: sync_service.clone(),
block_proposal_slot_portion: SlotProportion::new(2f32 / 3f32),
max_block_proposal_slot_portion: None,
telemetry: telemetry.as_ref().map(|x| x.handle()),
compatibility_mode: Default::default(),
},
)?;
task_manager
.spawn_essential_handle()
.spawn_blocking("aura", Some("block-authoring"), aura);
}
if enable_grandpa {
let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None };
let grandpa_config = sc_consensus_grandpa::Config {
gossip_duration: Duration::from_millis(333),
justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD,
name: Some(name),
observer_enabled: false,
keystore,
local_role: role,
telemetry: telemetry.as_ref().map(|x| x.handle()),
protocol_name: grandpa_protocol_name,
};
let grandpa_config = sc_consensus_grandpa::GrandpaParams {
config: grandpa_config,
link: grandpa_link,
network,
sync: Arc::new(sync_service),
notification_service: grandpa_notification_service,
voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(),
prometheus_registry,
shared_voter_state: SharedVoterState::empty(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool),
};
task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter",
None,
sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?,
);
}
Ok(task_manager)
}
Diferencias clave vs mi reconstrucción anterior (corregidas aquí):
create_inherent_data_providersusasc_consensus_aura::standalone::slot_duration_at(&*cidp_client, parent_hash)?+sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration(NOSlot::from_timestamp).import_queue::<AuraPair, _, _, _, _, _>— 6 type params.BuildNetworkParams.warp_sync_config(NOwarp_sync_params).FullNetworkConfiguration::new(&config.network, config.prometheus_registry().cloned()).spawn_tasks.network: Arc::new(network.clone()).- grandpa
sync: Arc::new(sync_service). Serviceother tuple es(GrandpaBlockImport, LinkHalf, Option<Telemetry>)con tipos explícitos.
§4 — rpc.rs (pattern estándar solochain stable2509)
//! Origin Chain node RPC (Slice 4). Pattern solochain-template stable2509-3.
use std::sync::Arc;
use jsonrpsee::RpcModule;
use origin_chain_runtime::{opaque::Block, AccountId, Balance, Nonce};
use sc_transaction_pool_api::TransactionPool;
use sp_api::ProvideRuntimeApi;
use sp_block_builder::BlockBuilder;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
pub struct FullDeps<C, P> {
pub client: Arc<C>,
pub pool: Arc<P>,
}
pub fn create_full<C, P>(
deps: FullDeps<C, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
C: ProvideRuntimeApi<Block>
+ HeaderBackend<Block>
+ HeaderMetadata<Block, Error = BlockChainError>
+ Send + Sync + 'static,
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Nonce>
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>
+ BlockBuilder<Block>,
P: TransactionPool + 'static,
{
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
use substrate_frame_rpc_system::{System, SystemApiServer};
let mut module = RpcModule::new(());
let FullDeps { client, pool } = deps;
module.merge(System::new(client.clone(), pool).into_rpc())?;
module.merge(TransactionPayment::new(client).into_rpc())?;
Ok(module)
}
OJO: verificar origin_chain_runtime exporta AccountId, Balance, Nonce (root). AccountId re-exported de origin-chain-types ✓. Balance + Nonce son pub type root ✓. opaque::Block ✓.
§5 — Paso 4b: chain_spec.rs → GenericChainSpec
Reemplazar el builder serde_json + MOCK_WASM por:
use origin_chain_runtime::WASM_BINARY;
pub type ChainSpec = sc_service::GenericChainSpec;
pub fn development_config() -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(
WASM_BINARY.ok_or("Development wasm not available")?,
None,
)
.with_name("Origin Chain Development")
.with_id("origin_chain_dev")
.with_chain_type(sc_service::ChainType::Development)
.with_genesis_config_preset_name("development") // usa genesis_config_presets::get_preset
.with_protocol_id("ogc")
.with_properties(chain_properties())
.build())
}
// análogo local_testnet_config (Local) + mainnet_config (Live)
with_genesis_config_preset_name invoca el sp_genesis_builder::GenesisBuilder runtime API que ya está impl. Los presets development/local_testnet/mainnet_genesis ya existen en genesis_config_presets. chain_properties (OGC/18/ss58=42) reusar del builder actual.
§6 — Paso 4d: CLI → sc_cli::SubstrateCli + run
Migrar main.rs (CLI inline) a sc_cli. Patrón solochain command.rs:
impl SubstrateCli for Cli {
fn impl_name() -> String { "Origin Chain Node".into() }
fn impl_version() -> String { env!("CARGO_PKG_VERSION").into() }
fn description() -> String { "Origin Metaverse settlement layer — Pilar 4".into() }
fn author() -> String { "Charon + David Herran".into() }
fn support_url() -> String { "https://git.originmetaverse.com".into() }
fn copyright_start_year() -> i32 { 2026 }
fn load_spec(&self, id: &str) -> Result<Box<dyn sc_service::ChainSpec>, String> {
Ok(match id {
"dev" | "development" => Box::new(chain_spec::development_config()?),
"local" | "local_testnet" => Box::new(chain_spec::local_testnet_config()?),
"mainnet" | "" => Box::new(chain_spec::mainnet_config()?),
path => Box::new(chain_spec::ChainSpec::from_json_file(path.into())?),
})
}
}
pub fn run() -> sc_cli::Result<()> {
let cli = <Cli as clap::Parser>::parse();
match &cli.subcommand {
Some(Subcommand::BuildSpec(cmd)) => {
let runner = cli.create_runner(cmd)?;
runner.sync_run(|config| cmd.run(config.chain_spec, config.network))
}
None => {
let runner = cli.create_runner(&cli.run)?;
runner.run_node_until_exit(|config| async move {
service::new_full::<sc_network::NetworkWorker<_, _>>(config)
.map_err(sc_cli::Error::Service)
})
}
}
}
Cli struct: { #[command(subcommand)] subcommand: Option<Subcommand>, #[clap(flatten)] run: sc_cli::RunCmd }. Los comandos custom actuales (info/list-presets/print-preset) reañadir como Subcommand variants OR conservar en binario aparte. Decisión: conservar BuildSpec (sc_cli::BuildSpecCmd) + run (default). info/presets → opcional re-add.
§7 — Orden de ejecución (3 ships)
- v0.0.47 — Paso 4a + 4b: runtime WASM_BINARY export + build.rs + chain_spec GenericChainSpec. Build runtime con wasm-build. Verificar
WASM_BINARY.is_some(). (Bajo riesgo, aislado.) - v0.0.48 — Paso 4c: service.rs + rpc.rs + node Cargo deps (§ABAJO).
cargo build -p origin-chain-node 2>&1 | tee /tmp/b.log. Iterar leyendo /tmp/b.log chunked. NO tocar CLI aún (módulos no-usados con#[allow(dead_code)]). - v0.0.49 — Paso 4d + COMPLETE: CLI sc_cli migration + run wire.
./target/release/origin-chain-node --dev --tmpproduce bloques. E2E: Polkadot.js Apps ws://localhost:9944. Monorepo bump + LANDMARK + roadmap.
Node Cargo.toml deps (todos pins ya en workspace v0.0.38)
sc-cli.workspace = true
sc-service.workspace = true
sc-executor.workspace = true
sc-network.workspace = true
sc-consensus.workspace = true
sc-consensus-aura.workspace = true
sc-consensus-grandpa.workspace = true
sc-transaction-pool.workspace = true
sc-transaction-pool-api.workspace = true
sc-client-api.workspace = true
sc-basic-authorship.workspace = true
sc-telemetry.workspace = true
sc-offchain.workspace = true
sp-core.workspace = true
sp-runtime.workspace = true
sp-io.workspace = true
sp-timestamp.workspace = true
sp-consensus-aura.workspace = true
sp-api.workspace = true
sp-block-builder.workspace = true
sp-blockchain.workspace = true
jsonrpsee.workspace = true
substrate-frame-rpc-system.workspace = true
pallet-transaction-payment-rpc.workspace = true
futures.workspace = true
Verificar pins faltantes en workspace: sp-blockchain, sp-timestamp, futures, sc-client-api, sc-transaction-pool-api — si faltan, añadir al workspace (stable2509-3 versions: sp-blockchain ~41, sp-timestamp 39, futures 0.3.31). NO añadir sp-keyring (conflicto sp-core documentado) — dev accounts vía genesis presets existentes.
§8 — Risk register
| Risk | Mitigación |
|---|---|
OUT_DIR/wasm_binary.rs no generado (build.rs falta) |
Crear build.rs runtime + verificar WASM_BINARY.is_some() Paso 4a aislado antes de service |
origin_chain_runtime::RuntimeApi path (root vs apis::) |
impl_runtime_apis! genera en root → origin_chain_runtime::RuntimeApi. Si falla, buscar módulo generado |
opaque::Block vs root Block |
Runtime tiene pub mod opaque; verificar exporta Block. Si no, usar root origin_chain_runtime::Block |
| sp-blockchain / sp-timestamp / futures faltan en workspace | Añadir pins stable2509-3 al workspace antes de Ship 2 |
| CLI migration rompe build-spec actual | Ship 3 aislado; Ships 1-2 mantienen CLI actual intacto |
| Tooling truncation (sesión 4+) | Ejecutar tooling sano; tee /tmp/b.log + reads chunked + grep -c error para counts |
| build_network/spawn_tasks field drift | service.rs es VERBATIM stable2509-3 — coincide con workspace pins exactos |
§9 — Reference checkout (si se necesita verificar)
cd /tmp && rm -rf sc-ref && mkdir sc-ref && cd sc-ref && git init -q
git remote add origin https://github.com/paritytech/polkadot-sdk
git config core.sparseCheckout true
echo "templates/solochain/node/src/*" > .git/info/sparse-checkout
git fetch --depth 1 origin polkadot-stable2509-3 && git checkout FETCH_HEAD
# (NOTA sesión 4+: `timeout` no disponible en este shell; usar git fetch directo)
El service.rs en §3 es verbatim de esa fuente. rpc.rs §4 es el pattern estándar (verificar templates/solochain/node/src/rpc.rs).
§10 — Hallazgos de ejecución (v0.0.48 + v0.0.49, 2026-05-27)
Ship 1 (v0.0.48 WASM_BINARY export) shipped limpio. Ship 2 reveló dos cosas — una resuelta, una que necesita decisión de David.
§10.1 — RESUELTO: core2 yanked (el muro de 3 sesiones)
sc-network-types 0.16.0 → cid ^0.9 (única 0.9.x = 0.9.0, yanked) → core2 ^0.4 (única 0.4.x = 0.4.0, yanked; las no-yanked son 0.3.x que no satisfacen ^0.4). Cargo no auto-selecciona versiones yanked → la resolución del node fallaba. Este era exactamente el blocker documentado en chain_spec.rs que difirió sc-service desde Slice 3.
Fix aplicado (v0.0.49): vendorizado el .crate publicado de core2 0.4.0 (yanked pero descargable de static.crates.io) en vendor/core2-0.4.0/ + [patch.crates-io] core2 = { path = "vendor/core2-0.4.0" }. Una path source es seleccionable por cargo independiente del estado yanked. El git source (technocreatives/core2 master) NO sirvió — resultó ser workspace ≤0.3.x. Resuelto: core2 0.4.0 + cid 0.9.0 ya en Cargo.lock, deps del node resuelven.
§10.2 — DECISIÓN DE DAVID: crates.io consensus crates ≠ git tag API
Las crates sc-/sp- publicadas en crates.io tienen API distinta al git tag polkadot-stable2509-3 bajo el mismo número de versión:
| Símbolo | git tag stable2509-3 | crates.io publicado |
|---|---|---|
| slot_duration_at | sc_consensus_aura::standalone::slot_duration_at |
sc_consensus_aura::slot_duration_at (root) |
| slot inherent provider | sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration |
NO EXISTE (Slot no implementa el trait; Slot::from_timestamp toma 1 arg) |
polkadot-sdk reusa números de versión con cambios git no publicados (el mismo patrón que pallet-revive: crates.io 0.12.2 ≠ git tag 0.10.1). El sp-consensus-aura 0.45.0 publicado no tiene un slot InherentDataProvider en absoluto → el patrón de node service Aura no es construible contra las crates publicadas.
Dos opciones (necesito tu decisión, David):
-
Opción A — Migrar sc-/sp- consensus a git sources del tag stable2509-3. Permite un node service real ahora. Riesgo: la misma cirugía duplicate-crate que golpeó pallet-revive (cargo trata git source + crates.io como crates distintos → conflictos de tipos en frame-system/sp-runtime). Mitigación: mover TODO el stack consensus (no solo aura) a git para coherencia. Scope: 1-2 sesiones cuidadosas, alto riesgo de cascada.
-
Opción B — Mantener crates.io publicadas; diferir el node service real. El node sigue siendo CLI + chain_spec builder (sin
--devproduciendo bloques) hasta una futura generación Substrate donde las publicadas alcancen al tag. Bajo riesgo. Phase 4 testnet necesitaría node real → empuja testnet. -
Opción C — Migrar TODO el workspace a git sources
polkadot-stable2509-3(no solo consensus). Coherencia total tag-vs-publicado, resuelve pallet-revive vendor + este gap de una vez. Mayor scope pero elimina la clase entera de bug "publicado ≠ tag". Probable mejor inversión a largo plazo pre-mainnet.
Recomendación Charon: Opción C como inversión pre-Phase-4, pero es decisión tuya por el scope. Mientras tanto el core2 unblock + WASM_BINARY export quedan firmes; service.rs/rpc.rs conservados como WIP (verbatim tag + 2 fixes intentados contra publicado).
§10.3 — Estado de los 4 pasos
- 4a WASM_BINARY export — ✅ DONE (v0.0.48)
- 4b chain_spec GenericChainSpec — ⏸ bloqueado por §10.2 (GenericChainSpec vive en sc-service stack)
- 4c service.rs + rpc.rs — ⏸ WIP escrito, bloqueado por §10.2 (API gap consensus)
- 4d CLI sc_cli — ⏸ pendiente 4b/4c
§11 — CORRECCIÓN del diagnóstico §10.2 + estado v0.0.50 (Opción C, David)
David eligió Opción C (stack consistente del tag). Al ejecutarla descubrí que mi diagnóstico §10.2 era parcialmente erróneo — corrección honesta:
§11.1 — El problema NO era "API gap" — era duplicate-crate por generación sc-* incorrecta
Las APIs SÍ existen en las crates publicadas (lo verifiqué en el source local):
sc-consensus-aura 0.52 standalone::slot_duration_at→ existe (standalone.rs:58)sp-consensus-aura 0.45 inherents::InherentDataProvider::from_timestamp_and_slot_duration→ existe (inherents.rs:61)
El service.rs verbatim del template era correcto; mis "fixes" de v0.0.49 (cambiar a slot_duration_at root + Slot::from_timestamp) lo rompieron. Revertidos a verbatim en v0.0.50.
El problema REAL: el lock tenía dos generaciones simultáneas (sp-consensus-aura 0.44.0 + 0.45.0, sp-timestamp 38 + 39). Mis pins sc-* (0.52/0.53) eran de una generación distinta a mis sp-*/frame-* (0.45/44) → duplicate-crate → los ~50 trait-bound errors (sp_io::*::HostFunctions not satisfied, P: TransactionPool not satisfied, etc. son todos síntomas de tipos de dos copias del mismo crate).
§11.2 — Opción C choca con el publishing inconsistente de polkadot-sdk
Intenté lograr Opción C vía versiones publicadas en crates.io (la red git falla en este entorno — core2 fetch dio Eof, así que clonar el monorepo no es viable). El intento reveló el muro real, verificado con el sparse index de crates.io:
sc-consensus-aura → sp-consensus-aura requerido, por versión publicada:
0.52.0→^0.44.00.53.0→^0.45.0✓ (mi generación)0.53.1→^0.45.0✓0.54.0→^0.44.0(¡vuelve a 0.44!)
El número de versión oscila entre generaciones de sp-* en patches consecutivos. Y peor: sc-service 0.54.1 (la versión del tag stable2509-3) NO está publicada en crates.io (cargo info sc-service@0.54.1 → "could not find"). polkadot-sdk publica sus ~15 sub-crates sc-* en cadencias distintas, de modo que no existe una isla de versiones publicadas internamente consistente que reproduzca el tag. El tag solo es consistente vía su Cargo.lock commiteado en el monorepo git.
Conclusión dura (corrige §10.2): el problema NO es "API gap" (las APIs existen) ni "elegir bien las versiones publicadas" (no hay set consistente). El problema es estructural del ecosistema: un node service real requiere el Cargo.lock del monorepo git, que necesita un entorno con red git funcional.
§11.3 — Lo que SÍ quedó firme + lo correcto preservado
- WASM_BINARY export (Paso 4a) — ✅ shipped v0.0.48, verde.
- core2 yanked unblock — ✅ shipped v0.0.49 (vendor path patch), verde.
- service.rs + rpc.rs — corregidos a la forma correcta y preservados como archivos (mods off):
Blockroot (noopaque::Block) — el runtime exponeimpl_runtime_apis!sobre el Block root.- CIDP verbatim del template (
standalone::slot_duration_at+inherents::InherentDataProvider::from_timestamp_and_slot_duration) — ambos SÍ existen en sp-consensus-aura 0.45.0 publicado (verificado en source: inherents.rs:61). Mi "fix" de v0.0.49 (Slot::from_timestamp) era incorrecto; revertido a verbatim.
- Workspace Cargo.toml — en el estado verde de v0.0.49 (sc-* sin tocar; node compila con mods off).
§11.4 — Decisión real para David (recomendación senior)
El node service real es construible, pero requiere un entorno con red git para usar los git sources del tag (con su Cargo.lock consistente). Eso es exactamente el entorno que Phase 4 testnet necesita de todos modos (CI/CD + build de validadores Pi 5). Por lo tanto:
Recomendación: diferir el node service real al setup de infraestructura de Phase 4 testnet, donde montamos un build env con red git + cargo build desde el monorepo pinned. NO seguir adivinando versiones publicadas una por una (no convergen). Mientras tanto el node sigue siendo CLI + chain_spec builder funcional, y todo lo demás de Slice 4 (WASM export + core2 unblock + service.rs/rpc.rs correctos) queda firme para wire-in inmediato cuando el env esté listo.
Alternativa si quieres node YA: dame un entorno con git fetch habilitado (o un Cargo.lock del monorepo stable2509-3 que yo consuma), y wire-in es ~1 sesión.
— Charon ⚓ (Pilar 4, Slice 4 plan V1 + §10 + §11 corrección honesta Opción C, sesión 4+ 2026-05-27. WASM export + core2 unblock firmes; node real diferido a env con red git por publishing inconsistente del ecosistema.)
§12 — Opción 2 investigada + decisión VPS (David 2026-05-27)
David autorizó Opción 2 (Cargo.lock del solochain-template) como puente, y anunció VPS Hostinger en ~2 días que servirá origincodex.io + originmetaverse.com + originethos.io + seeds del Command Center + build env (esperando a que Ergon termine su parte).
Hallazgo Opción 2 (verificado vía git ls-remote): el standalone polkadot-sdk-solochain-template solo tiene 2 generaciones de branch:
master→ stable2506 (sp-core 36, frame 40, sc-service 0.50.0, pallet-revive 0.7.0)update-template/stable2512→ stable2512 (frame 47+, pallet-revive 0.12.2, probable rustc 1.91)
NO existe template stable2509 — que es nuestra generación (frame 43, pallet-revive 0.10.1). Por tanto el truco "tomar prestado el Cargo.lock del template" NO aplica directo a nuestro workspace stable2509. La razón es la misma que §11: el set publicado consistente de stable2509 solo vive en el Cargo.lock del monorepo git.
Decisión senior (no hacer el puente riesgoso): forzar node ahora requeriría migrar TODO el workspace a stable2506 (revertir frame 43→40 + pallet-revive 0.10→0.7) o a stable2512 (subir a frame 47 + rustc 1.91, que no tengo local). Ambos son trabajo throwaway que desharíamos cuando llegue la VPS. Contra "completeness over speed". NO se hace.
Camino correcto con la VPS (Opción 1/3 real): la VPS tendrá red git → pineo sc-*/sp-*/frame-* a git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2509-3" → cargo usa el Cargo.lock canónico → node compila. Wire-in ~1 sesión. El service.rs/rpc.rs ya están escritos correctos (Block root + CIDP verbatim) — solo esperan el env.
Checklist de setup VPS (turnkey cuando llegue, ~2 días)
# 1. OS: Ubuntu 24.04 LTS (Hostinger VPS)
# 2. Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.88.0
rustup target add wasm32-unknown-unknown
# 3. Build deps (LLVM para WASM, ver SLICE_2E)
sudo apt install -y clang llvm protobuf-compiler git build-essential
export CC=clang # (en Linux clang del sistema sirve; en macOS era brew LLVM)
# 4. Clonar + build
git clone https://git.originmetaverse.com/Origin_Metaverse/arkhe-origin-chain
cd arkhe-origin-chain
# 5. Charon pinea sc-*/sp-*/frame- a git tag stable2509-3 (1 sesión)
# 6. cargo build --release -p origin-chain-node (node real)
# 7. WASM_BUILD_STD=0 + CC vars → WASM_BINARY real
Cuando Ergon termine su parte y montes la VPS, en 1 sesión tenemos node real + genesis multi-validador + faucet + explorer deployables. Todo lo node-independiente (validator guide, faucet design, security review, este plan) ya está firme.
— Charon ⚓ (§12 Opción 2 investigada + VPS turnkey checklist, sesión 4+ 2026-05-27)