From ce91aa457219b32f3b3f4a9ecc48605340112f4c Mon Sep 17 00:00:00 2001 From: Charon Date: Wed, 7 Oct 2026 10:38:21 -0500 Subject: [PATCH] =?UTF-8?q?feat(runtime):=20firmar=5Fcomo=5Fwallet=20?= =?UTF-8?q?=E2=80=94=20el=20E2E=20por=20RPC=20firma=20como=20un=20wallet,?= =?UTF-8?q?=20no=20solo=20mira=20bloques?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Herramienta (example del runtime) con la que se midió el defecto de 11e532e. Firma por //Alice como polkadot.js (versión que anuncia el runtime, hash del génesis, era inmortal) para mandarlo con author_submitExtrinsic a un nodo vivo, decodifica System::Events del bloque que lo incluye y verifica la firma contra el runtime nativo sobre el preset development, sin nodo. Uso y recorrido por RPC en la cabecera del fichero. Antes de esta herramienta, los E2E de la cadena solo comprobaban que se producían y finalizaban bloques, y así 16 specs no vieron que ningún wallet podía firmar. Co-Authored-By: Claude Opus 5.5 --- .../examples/firmar_como_wallet.rs | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 crates/origin-chain-runtime/examples/firmar_como_wallet.rs diff --git a/crates/origin-chain-runtime/examples/firmar_como_wallet.rs b/crates/origin-chain-runtime/examples/firmar_como_wallet.rs new file mode 100644 index 0000000..97ef773 --- /dev/null +++ b/crates/origin-chain-runtime/examples/firmar_como_wallet.rs @@ -0,0 +1,139 @@ +//! 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 extrinsic firmado por //Alice, en hex +//! llamadas: remark · finalize_override (propuesta 0x09…09) · uphold0 (peso 0) +//! eventos decodifica los eventos de un bloque +//! 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 => panic!("llamada desconocida: {otra} (remark · finalize_override · uphold0)"), + } +} + +fn firmar(call: RuntimeCall, nonce: u32, genesis: H256) -> 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 del RPC (= VERSION), no la que crea el storage. + let implicito = ( + (), + VERSION.spec_version, + VERSION.transaction_version, + 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 xt = firmar(llamada(&args[2]), nonce, genesis); + println!("0x{}", sp_core::hexdisplay::HexDisplay::from(&xt.encode())); + } + "eventos" => { + let bytes = hex(&args[2]); + let eventos = Vec::>::decode(&mut &bytes[..]) + .expect("System::Events"); + for e in eventos { + println!("{:?}", e.event); + } + } + "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 (ver la cabecera del fichero)"), + } +}