Files
arkhe-origin-chain/crates/origin-chain-node/src/service.rs
T
CharonandClaude Opus 5.5 b6d05eaf45 feat(gobierno): spec 18 (RV17), quorum del voto ordinario y pago del tesoro atado a la propuesta
Gobierno (origin-chain-governance):
- El voto ordinario exige un quorum de Aye, el 10 % de PILAR_4 §6.6. Se fija AL CREAR la
  propuesta (VoteQuorumOf) y se consume al decidirla: un set_code no mueve la meta de un
  voto abierto. Cuenta solo el Aye: con el Nay, un voto en contra podria aprobar; con la
  abstencion, el mismo OGC inflaria el quorum de muchas propuestas.
- Un override del veto tiene que alcanzar ese quorum y superar el Nay original, las dos
  condiciones del voto ordinario. Sin ellas, vetar abarataba una aprobacion (dos bloqueantes
  de las revisiones adversariales).
- submit_treasury_proposal (call 16) fija destinatario y monto (TreasuryActions), y
  treasury_disbursement solo paga exactamente eso, una vez. Rechaza montos bajo el deposito
  existencial y como destinatario la propia tesoreria o la cuenta-bond.
- Sin entrada en VoteQuorumOf no hay quorum: la emergencia (que nunca lleva accion de
  tesoro) y lo creado antes de RV17, que se abrio y voto sin quorum.

Runtime: los dos quorums son el 10 % de la emision VIGENTE, redondeado hacia arriba y nunca
por debajo del bond (DiezPorCientoDeLaEmision). Con 100M fijos, en la testnet de la casa
(100M emitidos, todos de Alice) nada se habria aprobado nunca; en mainnet sigue siendo ~100M.
Spec 18, transaction_version 1: todo lo nuevo es aditivo, sin migracion.

Node: pasa a opaque::Block. Decodificaba los extrinsics con tipos nativos y rechazaba en el
pool la llamada que anadia el set_code (1040, el mismo error que una llamada inexistente).
Los bytes y los hashes no cambian: el binario nuevo sincronizo desde cero los 39 273 bloques
de la testnet de la casa con los mismos hashes en 8 alturas. El RPC sirve los extrinsics en hex.

Pesos: seis extrinsics cambiaron de codigo y se midieron en la Pi 5 (smoke declarado: 5
procesos limpios, sin CI en ningun segundo, maximo de los 5). Los otros once siguen siendo los
certificados por Laurha. Se descarto una medida contaminada: su muestreador no veia los tests
de la CI.

Pruebas:
- 363 tests verdes en el workspace.
- 24 contrafactuales, los 24 en rojo.
- Ensayo en la rp1 sobre una cadena con el codigo vivo de spec 17: upgrade a 18 y E2E
  firmando como un wallet (scripts/e2e/rv17_tesoro.py).
- Ejemplo firmar_como_wallet con tesoro, pagar, votar, depositar y finalizar; los eventos
  llevan su fase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:55:24 -05:00

349 lines
13 KiB
Rust

//! Origin Chain node service (Slice 4 Paso 4c).
//!
//! Verbatim del solochain-template `polkadot-stable2509-3`
//! (`templates/solochain/node/src/service.rs`), con un único cambio: el import
//! del runtime apunta a `origin_chain_runtime`. Aura (block production) + Grandpa
//! (finality) + JSON-RPC + libp2p networking + telemetry.
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;
// El node trabaja sobre `opaque::Block`: los extrinsics son bytes y sólo el WASM los
// decodifica. Con el `Block` completo del runtime (hasta el 2026-10-28) el node decodificaba
// con sus tipos nativos y rechazaba toda llamada que un `set_code` hubiera añadido (ver
// `origin_chain_runtime::opaque`). Las runtime APIs que genera `impl_runtime_apis!` son
// genéricas en el bloque y cruzan la frontera en SCALE, así que valen igual sobre éste.
use origin_chain_runtime::{opaque::Block, RuntimeApi};
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>;
/// Periodo mínimo de bloques sobre los que se importan/generan justifications.
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),
})
}
/// Construye un nuevo service para un full client.
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(),
},
)?;
// La tarea de authoring de AURA es esencial: si falla, baja el service.
task_manager
.spawn_essential_handle()
.spawn_blocking("aura", Some("block-authoring"), aura);
}
if enable_grandpa {
// Si el node no participa activamente en consensus, no necesita keystore.
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),
};
// La tarea del voter de GRANDPA es infalible: si falla, baja el service.
task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter",
None,
sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?,
);
}
Ok(task_manager)
}