fix(runtime): frame_system no sabía su propia versión — ningún wallet podía firmar
Medido en el E2E de RV16 por el canal de un wallet: Alice firmando como polkadot.js (versión de state_getRuntimeVersion, hash del génesis, era inmortal) recibía del nodo vivo «Transaction has a bad signature». Causa: SolochainDefaultConfig trae type Version = (), es decir RuntimeVersion::default() (nombre vacío, spec 0, tx 0), y el impl de frame_system lo heredaba sin declararlo — el mismo agujero que DbWeight en RV14. El RPC anunciaba spec 16 / tx 1; CheckSpecVersion y CheckTxVersion reconstruían el payload con 0 / 0. Rompía tres cosas: 1. Ningún wallet podía firmar un extrinsic (BadProof). 2. set_code habría fallado con InvalidSpecName («origin-chain» contra «»): sin upgrades. 3. Las migraciones no corrían nunca: Executive::runtime_upgraded compara 0 contra 0. Nadie lo vio en 16 specs porque ningún test firmaba por el canal del consumidor: los del pallet llaman a las funciones directamente, y los E2E anteriores solo miraban bloques. Arreglo: type Version = Version, con Version = crate::VERSION. Tests (contrafactual: sin la línea, los dos reprueban en la ASERCIÓN — spec_name "" y BadProof): - frame_system_conoce_la_version_que_anuncia_el_rpc - una_firma_de_wallet_estandar_pasa_el_check_del_runtime: firma como un wallet sobre el estado del preset development y pasa por el check real de la firma. E2E después del arreglo, nodo --dev con WASM real: dos extrinsics firmados por //Alice entran por author_submitExtrinsic, se incluyen en el bloque #3 (finalizado) y fallan con los errores que tocaban: finalize_override → ProposalNotFound, uphold_council_veto de peso 0 → ZeroWeightWindowVote. La metadata viva trae las llamadas, el storage, MinOverrideQuorum = 1e26 (100M OGC) y los errores nuevos. Tests: runtime 46, workspace 350. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3771a15165
commit
11e532e251
2 files changed
+112
No files matched your search
@@ -87,6 +87,8 @@ parameter_types! {
|
|||||||
pub const SS58Prefix: u16 = SS58_PREFIX;
|
pub const SS58Prefix: u16 = SS58_PREFIX;
|
||||||
/// Block hash count canonical wrapped.
|
/// Block hash count canonical wrapped.
|
||||||
pub const BlockHashCount: BlockNumber = BLOCK_HASH_COUNT;
|
pub const BlockHashCount: BlockNumber = BLOCK_HASH_COUNT;
|
||||||
|
/// La versión que el runtime cree tener. Ver `type Version` abajo.
|
||||||
|
pub const Version: sp_version::RuntimeVersion = crate::VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)]
|
#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)]
|
||||||
@@ -112,6 +114,23 @@ impl frame_system::Config for Runtime {
|
|||||||
/// ningún efecto en el runtime. Lo que RV13 sí arregló de verdad es el `proof_size`
|
/// ningún efecto en el runtime. Lo que RV13 sí arregló de verdad es el `proof_size`
|
||||||
/// y el `ref_time` de ejecución, que no pasan por `DbWeight`.
|
/// y el `ref_time` de ejecución, que no pasan por `DbWeight`.
|
||||||
type DbWeight = frame_support::weights::constants::RocksDbWeight;
|
type DbWeight = frame_support::weights::constants::RocksDbWeight;
|
||||||
|
/// **La versión que el runtime cree tener.** Faltaba, y por eso era `spec 0`.
|
||||||
|
///
|
||||||
|
/// Mismo agujero que `DbWeight`: `SolochainDefaultConfig` trae `type Version = ();`,
|
||||||
|
/// es decir `RuntimeVersion::default()` (nombre vacío, `spec 0`, `tx 0`), mientras el
|
||||||
|
/// RPC anunciaba `VERSION`. Rompía tres cosas, medidas el 2026-10-07 en el E2E de RV16:
|
||||||
|
///
|
||||||
|
/// 1. **Ningún wallet podía firmar.** Un wallet firma con la versión que lee del RPC
|
||||||
|
/// y `CheckSpecVersion`/`CheckTxVersion` reconstruyen el payload con esta: `0 ≠ 16`
|
||||||
|
/// y el nodo respondía `bad signature` a Alice firmando como polkadot.js.
|
||||||
|
/// 2. **`set_code` fallaba:** `can_set_code` compara el nombre del WASM nuevo con
|
||||||
|
/// este (`"origin-chain"` contra `""`) → `InvalidSpecName`.
|
||||||
|
/// 3. **Las migraciones no corrían nunca:** `Executive::runtime_upgraded` compara
|
||||||
|
/// `LastRuntimeUpgrade` con esta versión, y `0` contra `0` nunca es una subida.
|
||||||
|
///
|
||||||
|
/// Nadie lo vio en 16 specs porque ningún test firmaba un extrinsic por el canal de
|
||||||
|
/// un wallet: los del pallet llaman a las funciones directamente.
|
||||||
|
type Version = Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -1074,6 +1074,99 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `frame_system` tiene que conocer la versión que el RPC anuncia.
|
||||||
|
///
|
||||||
|
/// Mismo defecto que `DbWeight`: heredaba `Version = ()`, y con eso `spec 0` y nombre
|
||||||
|
/// vacío para `CheckSpecVersion`, `set_code` y la detección de migraciones (ver
|
||||||
|
/// `type Version` en `configs.rs`). Tipo anotado por la misma razón que arriba.
|
||||||
|
#[test]
|
||||||
|
fn frame_system_conoce_la_version_que_anuncia_el_rpc() {
|
||||||
|
use frame_support::traits::Get;
|
||||||
|
let v: sp_version::RuntimeVersion = <Runtime as frame_system::Config>::Version::get();
|
||||||
|
assert_eq!(
|
||||||
|
v.spec_name, VERSION.spec_name,
|
||||||
|
"set_code daría InvalidSpecName"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
v.spec_version, VERSION.spec_version,
|
||||||
|
"los wallets firmarían otra spec"
|
||||||
|
);
|
||||||
|
assert_eq!(v.transaction_version, VERSION.transaction_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// El canal del consumidor: Alice firma como firma un wallet, y el runtime acepta.
|
||||||
|
///
|
||||||
|
/// Un wallet (polkadot.js, Talisman, Ledger) lee `spec_version`/`transaction_version`
|
||||||
|
/// de `state_getRuntimeVersion` —que es `VERSION`—, toma el hash del génesis y firma
|
||||||
|
/// `(call, extensiones, implícito)`. Aquí se hace lo mismo sobre el estado del preset
|
||||||
|
/// `development` y se pasa por el `check` real de la firma. Antes del arreglo el nodo
|
||||||
|
/// vivo respondía `bad signature` a exactamente esto (E2E de RV16, 2026-10-07).
|
||||||
|
#[test]
|
||||||
|
fn una_firma_de_wallet_estandar_pasa_el_check_del_runtime() {
|
||||||
|
use parity_scale_codec::Encode;
|
||||||
|
use sp_core::{sr25519, Pair};
|
||||||
|
use sp_runtime::generic::{Era, SignedPayload};
|
||||||
|
use sp_runtime::traits::Checkable;
|
||||||
|
|
||||||
|
let json = 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::<RuntimeGenesisConfig>(json)
|
||||||
|
.expect("estado del génesis");
|
||||||
|
// El nodo guarda el hash real del génesis al iniciar el bloque 1.
|
||||||
|
let genesis = H256::repeat_byte(0x13);
|
||||||
|
frame_system::Pallet::<Runtime>::set_block_number(5);
|
||||||
|
frame_system::BlockHash::<Runtime>::insert(0, genesis);
|
||||||
|
|
||||||
|
let alice = sr25519::Pair::from_string("//Alice", None).expect("//Alice");
|
||||||
|
let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![1, 6] });
|
||||||
|
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(0),
|
||||||
|
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 el wallet pone de su lado: la versión del RPC, no la del 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();
|
||||||
|
let xt = UncheckedExtrinsic::new_signed(
|
||||||
|
call,
|
||||||
|
MultiAddress::Id(alice.public().into()),
|
||||||
|
sp_runtime::MultiSignature::Sr25519(firma),
|
||||||
|
tx_ext,
|
||||||
|
);
|
||||||
|
let r = xt.check(&frame_system::ChainContext::<Runtime>::default());
|
||||||
|
assert!(
|
||||||
|
r.is_ok(),
|
||||||
|
"el runtime rechaza la firma de un wallet: {:?}",
|
||||||
|
r.err()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Fija la version vigente y la invariante que la acompaña.
|
/// Fija la version vigente y la invariante que la acompaña.
|
||||||
///
|
///
|
||||||
/// El test NO cuenta la historia de cada version — eso vive en el doc comment
|
/// El test NO cuenta la historia de cada version — eso vive en el doc comment
|
||||||
|
|||||||
Reference in new issue
Block a user