feat(runtime): firmar_como_wallet — el E2E por RPC firma como un wallet, no solo mira bloques
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
11e532e251
commit
ce91aa4572
1 file changed
+139
@@ -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 <llamada> <nonce> <genesis_hex> extrinsic firmado por //Alice, en hex
|
||||
//! llamadas: remark · finalize_override (propuesta 0x09…09) · uphold0 (peso 0)
|
||||
//! eventos <hex de System::Events> decodifica los eventos de un bloque
|
||||
//! verificar <hex_extrinsic> <genesis_hex> 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<u8> {
|
||||
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::<Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<Runtime>::new(),
|
||||
frame_system::CheckGenesis::<Runtime>::new(),
|
||||
frame_system::CheckEra::<Runtime>::from(Era::Immortal),
|
||||
frame_system::CheckNonce::<Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
|
||||
frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false),
|
||||
frame_system::WeightReclaim::<Runtime>::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::<RuntimeCall, TxExtension>::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<String> = 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::<EventRecord<RuntimeEvent, H256>>::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::<Runtime>::set_block_number(5);
|
||||
frame_system::BlockHash::<Runtime>::insert(0, genesis);
|
||||
let r = xt.check(&frame_system::ChainContext::<Runtime>::default());
|
||||
println!("check del runtime: {:?}", r.map(|_| "firma aceptada"));
|
||||
});
|
||||
}
|
||||
_ => panic!("modo: firmar · eventos · verificar (ver la cabecera del fichero)"),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user