//! Firma extrinsics como los firma un wallet, para el E2E por RPC contra un nodo vivo. //! //! Existe porque un E2E que solo mira bloques no ve lo que ve un usuario: el 2026-10-07 el //! nodo producía y finalizaba bloques mientras rechazaba con «bad signature» toda firma de //! wallet (el runtime se creía `spec 0`, ver `type Version` en `configs.rs`). Esta //! herramienta firma igual que polkadot.js: la versión que anuncia el runtime, el hash del //! génesis, era inmortal, sin metadata hash. //! //! ```text //! cargo build -p origin-chain-runtime --example firmar_como_wallet # con SKIP_WASM_BUILD=1 //! F=target/debug/examples/firmar_como_wallet //! //! firmar [spec tx] extrinsic firmado por //Alice, en hex //! llamadas: remark · finalize_override (propuesta 0x09…09) · uphold0 (peso 0) //! set_code: (sudo de //Alice, ensayo de upgrade sin fork) //! spec y tx: los que da `state_getRuntimeVersion`. Por defecto, los de `VERSION` //! compilado. Tras un `set_code` hay que pasarlos: el wallet firma con la versión //! nueva que anuncia el nodo, no con la que se compiló aquí. //! eventos decodifica los eventos de un bloque //! cuenta "" [//derivación] llave sr25519 y dirección SS58 (42) que verá un wallet //! Substrate (substrate-bip39 → mini-secret → sr25519): vectores para la wallet Origin! //! verificar pasa la firma por el check del runtime nativo //! sobre el preset development (sin nodo) //! ``` //! //! Recorrido por RPC (nodo `run --dev --rpc-port 9955`): //! `chain_getBlockHash 0` → génesis; `system_accountNextIndex` → nonce; //! `author_submitExtrinsic` con el hex; y en el bloque que lo incluye, //! `state_getStorage 0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7` //! (System::Events) → `eventos`. use frame_system::EventRecord; use origin_chain_runtime::{ Runtime, RuntimeCall, RuntimeEvent, TxExtension, UncheckedExtrinsic, VERSION, }; use parity_scale_codec::{Decode, Encode}; use sp_core::{sr25519, Pair, H256}; use sp_runtime::{ generic::{Era, SignedPayload}, MultiAddress, MultiSignature, }; fn hex(arg: &str) -> Vec { sp_core::bytes::from_hex(arg).unwrap_or_else(|e| panic!("hex inválido {arg}: {e:?}")) } fn llamada(nombre: &str) -> RuntimeCall { let id = origin_chain_governance::ProposalId([9u8; 32]); match nombre { "remark" => RuntimeCall::System(frame_system::Call::remark { remark: b"origin".to_vec(), }), "finalize_override" => { RuntimeCall::Governance(origin_chain_governance::Call::finalize_override { proposal_id: id, }) } "uphold0" => RuntimeCall::Governance(origin_chain_governance::Call::uphold_council_veto { proposal_id: id, stake_weight: 0, }), otra => match otra.strip_prefix("set_code:") { Some(ruta) => { let code = std::fs::read(ruta).unwrap_or_else(|e| panic!("{ruta}: {e}")); RuntimeCall::Sudo(pallet_sudo::Call::sudo { call: Box::new(RuntimeCall::System(frame_system::Call::set_code { code })), }) } None => panic!( "llamada desconocida: {otra} (remark · finalize_override · uphold0 · set_code:)" ), }, } } fn firmar(call: RuntimeCall, nonce: u32, genesis: H256, spec: u32, tx: u32) -> UncheckedExtrinsic { let alice = sr25519::Pair::from_string("//Alice", None).expect("//Alice"); let tx_ext: TxExtension = ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(Era::Immortal), frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), frame_metadata_hash_extension::CheckMetadataHash::::new(false), frame_system::WeightReclaim::::new(), ); // Lo que pone el wallet: la versión que anuncia el RPC, no la que crea el storage. let implicito = ((), spec, tx, genesis, genesis, (), (), (), None, ()); let payload = SignedPayload::::from_raw(call, tx_ext, implicito); let firma = payload.using_encoded(|p| alice.sign(p)); let (call, tx_ext, _) = payload.deconstruct(); UncheckedExtrinsic::new_signed( call, MultiAddress::Id(alice.public().into()), MultiSignature::Sr25519(firma), tx_ext, ) } fn main() { let args: Vec = std::env::args().collect(); let modo = args.get(1).map(String::as_str).unwrap_or(""); match modo { "firmar" => { let nonce: u32 = args[3].parse().expect("nonce"); let genesis = H256::from_slice(&hex(&args[4])); let spec = args .get(5) .map_or(VERSION.spec_version, |v| v.parse().expect("spec")); let tx = args .get(6) .map_or(VERSION.transaction_version, |v| v.parse().expect("tx")); let xt = firmar(llamada(&args[2]), nonce, genesis, spec, tx); // Hex completo: `HexDisplay` recorta con «...» pasados 1024 bytes, y un // `set_code` lleva el runtime entero (MB). println!("{}", sp_core::bytes::to_hex(&xt.encode(), false)); } "eventos" => { let bytes = hex(&args[2]); let eventos = Vec::>::decode(&mut &bytes[..]) .expect("System::Events"); for e in eventos { println!("{:?}", e.event); } } "cuenta" => { use sp_core::crypto::Ss58Codec; let uri = format!("{}{}", args[2], args.get(3).map(String::as_str).unwrap_or("")); let par = sr25519::Pair::from_string(&uri, None).expect("frase o derivación inválida"); let publica = par.public(); println!("sr25519 publica 0x{}", sp_core::hexdisplay::HexDisplay::from(&publica.0)); println!("ss58 (42) {}", publica.to_ss58check()); } "verificar" => { use sp_runtime::traits::Checkable; let bytes = hex(&args[2]); let genesis = H256::from_slice(&hex(&args[3])); let xt = UncheckedExtrinsic::decode(&mut &bytes[..]).expect("extrinsic"); let json = origin_chain_runtime::genesis_config_presets::get_preset( &sp_genesis_builder::PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), ) .expect("preset development"); let mut ext = sp_io::TestExternalities::new_empty(); ext.execute_with(|| { frame_support::genesis_builder_helper::build_state::< origin_chain_runtime::RuntimeGenesisConfig, >(json) .expect("estado del génesis"); frame_system::Pallet::::set_block_number(5); frame_system::BlockHash::::insert(0, genesis); let r = xt.check(&frame_system::ChainContext::::default()); println!("check del runtime: {:?}", r.map(|_| "firma aceptada")); }); } _ => panic!("modo: firmar · eventos · verificar · cuenta (ver la cabecera del fichero)"), } }