feat(e2e): simulacro de set_code 16→17 — la cadena ya se actualiza sin fork, y sin 11e532e no podía
Ensayo en vivo el 2026-10-12, nodo --dev con WASM real, todo firmado por RPC como un wallet:
- A, control negativo: set_code con la MISMA spec 16 → Sudid Err(System, 1) =
SpecVersionNeedsToIncrease.
- B, el upgrade: sudo(set_code) con spec 17 → CodeUpdated + Sudid Ok.
- El RPC anuncia 17 y LastRuntimeUpgrade pasa de {16,"origin-chain"} a {17,"origin-chain"}.
- La migración del simulacro dejó su marca (0x11000000): on_runtime_upgrade corrió.
- C, un wallet tras el upgrade: firmado con spec 16 → «bad signature»; con spec 17 →
ExtrinsicSuccess.
- La finalidad siguió: el bloque del upgrade (#5) quedó finalizado.
Contrafactual, nodo sin type Version (el runtime de antes de 11e532e):
- LastRuntimeUpgrade = {0, ""}.
- Firmando como wallet → «bad signature».
- Firmando con spec 0 para poder llegar → Sudid Err(System, 0) = InvalidSpecName.
- Ningún upgrade y ninguna marca.
O sea: hasta el 7-oct la cadena no podía cambiar su runtime sin un fork.
Herramientas, para repetirlo antes de la testnet:
- scripts/e2e/simulacro_set_code.py: los pasos A, B y C contra un nodo vivo. Lo corrí tal cual
y reproduce lo de arriba.
- scripts/e2e/parche_simulacro.py: aplica de forma temporal la spec N+1 con la migración
marcada, y al restaurar comprueba por hash que lib.rs queda idéntico.
- firmar_como_wallet:
- modo set_code:<ruta> (sudo de //Alice);
- spec y tx opcionales: tras un upgrade, el wallet firma con la versión que anuncia el RPC;
- hex completo: HexDisplay recortaba con «...» pasados 1024 bytes.
Las builds son reproducibles: los dos WASM salieron con el mismo sha en dos builds separados
(9b933ff1 spec 16, 51663085 spec 17).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ce91aa4572
commit
3cd1068893
3 files changed
+194
-18
No files matched your search
@@ -10,8 +10,12 @@
|
||||
//! 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
|
||||
//! firmar <llamada> <nonce> <genesis_hex> [spec tx] extrinsic firmado por //Alice, en hex
|
||||
//! llamadas: remark · finalize_override (propuesta 0x09…09) · uphold0 (peso 0)
|
||||
//! set_code:<ruta.wasm> (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 <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)
|
||||
@@ -53,11 +57,21 @@ fn llamada(nombre: &str) -> RuntimeCall {
|
||||
proposal_id: id,
|
||||
stake_weight: 0,
|
||||
}),
|
||||
otra => panic!("llamada desconocida: {otra} (remark · finalize_override · uphold0)"),
|
||||
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:<ruta>)"
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn firmar(call: RuntimeCall, nonce: u32, genesis: H256) -> UncheckedExtrinsic {
|
||||
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::<Runtime>::new(),
|
||||
@@ -71,19 +85,8 @@ fn firmar(call: RuntimeCall, nonce: u32, genesis: H256) -> UncheckedExtrinsic {
|
||||
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,
|
||||
(),
|
||||
);
|
||||
// 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::<RuntimeCall, TxExtension>::from_raw(call, tx_ext, implicito);
|
||||
let firma = payload.using_encoded(|p| alice.sign(p));
|
||||
let (call, tx_ext, _) = payload.deconstruct();
|
||||
@@ -102,8 +105,16 @@ fn main() {
|
||||
"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()));
|
||||
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]);
|
||||
|
||||
Reference in new issue
Block a user