docs(slice-4): sc-service implementation plan V1 — reference verbatim stable2509-3
Captura el plan completo de Slice 4 (full sc-service) con el service.rs VERBATIM del solochain-template polkadot-stable2509-3 (obtenido vía WebFetch sesión 4+), para ejecución mecánica próxima sesión. Razón: 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" → NO commiteo código sc-service sin poder verificar que compila. En su lugar capturo el reference exacto + prerequisitos descubiertos. Hallazgo clave (prerequisito nuevo): Slice 4 NO es un solo ship — son 4 pasos encadenados: - 4a: runtime exporta WASM_BINARY const (HOY NO lo hace — grep -c WASM_BINARY = 0). Requiere build.rs con substrate_wasm_builder + include!(OUT_DIR/wasm_binary.rs). - 4b: chain_spec → sc_service::GenericChainSpec con WASM_BINARY embebido. - 4c: service.rs + rpc.rs (núcleo sc-service wire) — verbatim capturado §3-4. - 4d: CLI inline → sc_cli::SubstrateCli + comando run. Contenido del plan: - §2 Paso 4a WASM_BINARY export + build.rs - §3 service.rs VERBATIM stable2509-3 (correcciones vs reconstrucción: standalone:: slot_duration_at + from_timestamp_and_slot_duration + warp_sync_config + import_queue 6 type params + spawn_tasks Arc::new(network) + grandpa sync Arc) - §4 rpc.rs pattern estándar (System + TransactionPayment) - §5 Paso 4b chain_spec GenericChainSpec - §6 Paso 4d CLI sc_cli migration - §7 Orden 3 ships (v0.0.47-49) + node Cargo deps exactos - §8 Risk register 7 risks + mitigaciones - §9 Reference checkout command Ejecución: 3 ships próxima sesión con tooling sano. El service.rs verbatim coincide exacto con workspace pins stable2509-3 → bajo riesgo de API drift. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c2aa04fe02
commit
28408cdf73
1 file changed
+609
@@ -0,0 +1,609 @@
|
||||
# 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`):
|
||||
|
||||
```rust
|
||||
// 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:
|
||||
|
||||
```rust
|
||||
// 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):
|
||||
```bash
|
||||
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 que `origin_chain_runtime::opaque::Block` existe (hay `pub mod opaque`). Si `RuntimeApi` no está en root, usar el path que genere `impl_runtime_apis!` (normalmente root crate). El template lo tiene en `apis::` porque su runtime re-exporta así; el mío lo genera en root → usar `origin_chain_runtime::RuntimeApi`.
|
||||
|
||||
```rust
|
||||
//! 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_providers` usa `sc_consensus_aura::standalone::slot_duration_at(&*cidp_client, parent_hash)?` + `sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration` (NO `Slot::from_timestamp`).
|
||||
- `import_queue::<AuraPair, _, _, _, _, _>` — 6 type params.
|
||||
- `BuildNetworkParams.warp_sync_config` (NO `warp_sync_params`).
|
||||
- `FullNetworkConfiguration::new(&config.network, config.prometheus_registry().cloned())`.
|
||||
- `spawn_tasks.network: Arc::new(network.clone())`.
|
||||
- grandpa `sync: Arc::new(sync_service)`.
|
||||
- `Service` other tuple es `(GrandpaBlockImport, LinkHalf, Option<Telemetry>)` con tipos explícitos.
|
||||
|
||||
---
|
||||
|
||||
## §4 — rpc.rs (pattern estándar solochain stable2509)
|
||||
|
||||
```rust
|
||||
//! 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:
|
||||
|
||||
```rust
|
||||
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`:
|
||||
|
||||
```rust
|
||||
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)
|
||||
|
||||
1. **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.)
|
||||
2. **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)]`).
|
||||
3. **v0.0.49 — Paso 4d + COMPLETE**: CLI sc_cli migration + run wire. `./target/release/origin-chain-node --dev --tmp` produce bloques. E2E: Polkadot.js Apps ws://localhost:9944. Monorepo bump + LANDMARK + roadmap.
|
||||
|
||||
### Node Cargo.toml deps (todos pins ya en workspace v0.0.38)
|
||||
|
||||
```toml
|
||||
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)
|
||||
|
||||
```bash
|
||||
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`).
|
||||
|
||||
---
|
||||
|
||||
— Charon ⚓ (Pilar 4, Slice 4 plan V1 con reference verbatim stable2509-3, sesión 4+ 2026-05-27. Ejecución 3 ships próxima sesión tooling sano.)
|
||||
Reference in new issue
Block a user