feat(gobierno): spec 18 (RV17), quorum del voto ordinario y pago del tesoro atado a la propuesta
Gobierno (origin-chain-governance): - El voto ordinario exige un quorum de Aye, el 10 % de PILAR_4 §6.6. Se fija AL CREAR la propuesta (VoteQuorumOf) y se consume al decidirla: un set_code no mueve la meta de un voto abierto. Cuenta solo el Aye: con el Nay, un voto en contra podria aprobar; con la abstencion, el mismo OGC inflaria el quorum de muchas propuestas. - Un override del veto tiene que alcanzar ese quorum y superar el Nay original, las dos condiciones del voto ordinario. Sin ellas, vetar abarataba una aprobacion (dos bloqueantes de las revisiones adversariales). - submit_treasury_proposal (call 16) fija destinatario y monto (TreasuryActions), y treasury_disbursement solo paga exactamente eso, una vez. Rechaza montos bajo el deposito existencial y como destinatario la propia tesoreria o la cuenta-bond. - Sin entrada en VoteQuorumOf no hay quorum: la emergencia (que nunca lleva accion de tesoro) y lo creado antes de RV17, que se abrio y voto sin quorum. Runtime: los dos quorums son el 10 % de la emision VIGENTE, redondeado hacia arriba y nunca por debajo del bond (DiezPorCientoDeLaEmision). Con 100M fijos, en la testnet de la casa (100M emitidos, todos de Alice) nada se habria aprobado nunca; en mainnet sigue siendo ~100M. Spec 18, transaction_version 1: todo lo nuevo es aditivo, sin migracion. Node: pasa a opaque::Block. Decodificaba los extrinsics con tipos nativos y rechazaba en el pool la llamada que anadia el set_code (1040, el mismo error que una llamada inexistente). Los bytes y los hashes no cambian: el binario nuevo sincronizo desde cero los 39 273 bloques de la testnet de la casa con los mismos hashes en 8 alturas. El RPC sirve los extrinsics en hex. Pesos: seis extrinsics cambiaron de codigo y se midieron en la Pi 5 (smoke declarado: 5 procesos limpios, sin CI en ningun segundo, maximo de los 5). Los otros once siguen siendo los certificados por Laurha. Se descarto una medida contaminada: su muestreador no veia los tests de la CI. Pruebas: - 363 tests verdes en el workspace. - 24 contrafactuales, los 24 en rojo. - Ensayo en la rp1 sobre una cadena con el codigo vivo de spec 17: upgrade a 18 y E2E firmando como un wallet (scripts/e2e/rv17_tesoro.py). - Ejemplo firmar_como_wallet con tesoro, pagar, votar, depositar y finalizar; los eventos llevan su fase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
abe680e853
commit
b6d05eaf45
14 files changed
+1368
-155
No files matched your search
@@ -13,10 +13,15 @@
|
||||
//! 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)
|
||||
//! tesoro:<monto> (RV17: propuesta de tesoro de //Alice que paga <monto> plancks
|
||||
//! a //Bob; 14 días y el bond mínimo) · pagar:<id_hex>:<monto> (sudo de
|
||||
//! treasury_disbursement a //Bob) · votar:<id_hex>:<peso> (Aye de //Alice)
|
||||
//! · depositar:<monto> (treasury_deposit de //Alice al vault)
|
||||
//! · finalizar:<id_hex> (finalize_proposal)
|
||||
//! 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
|
||||
//! eventos <hex de System::Events> decodifica los eventos de un bloque, con su fase
|
||||
//! cuenta "<frase BIP39>" [//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 <hex_extrinsic> <genesis_hex> pasa la firma por el check del runtime nativo
|
||||
@@ -44,6 +49,10 @@ fn hex(arg: &str) -> Vec<u8> {
|
||||
sp_core::bytes::from_hex(arg).unwrap_or_else(|e| panic!("hex inválido {arg}: {e:?}"))
|
||||
}
|
||||
|
||||
fn bob() -> sp_runtime::AccountId32 {
|
||||
sr25519::Pair::from_string("//Bob", None).expect("//Bob").public().into()
|
||||
}
|
||||
|
||||
fn llamada(nombre: &str) -> RuntimeCall {
|
||||
let id = origin_chain_governance::ProposalId([9u8; 32]);
|
||||
match nombre {
|
||||
@@ -59,6 +68,53 @@ fn llamada(nombre: &str) -> RuntimeCall {
|
||||
proposal_id: id,
|
||||
stake_weight: 0,
|
||||
}),
|
||||
otra if otra.starts_with("tesoro:") => {
|
||||
let amount: u128 = otra["tesoro:".len()..].parse().expect("tesoro:<monto en plancks>");
|
||||
RuntimeCall::Governance(origin_chain_governance::Call::submit_treasury_proposal {
|
||||
title_hash: sp_core::blake2_256(b"e2e-rv17/titulo"),
|
||||
body_hash: sp_core::blake2_256(b"e2e-rv17/cuerpo"),
|
||||
voting_period_blocks: origin_chain_governance::MIN_VOTING_PERIOD_BLOCKS,
|
||||
stake: origin_chain_governance::MIN_PROPOSAL_STAKE,
|
||||
recipient: bob(),
|
||||
amount,
|
||||
})
|
||||
}
|
||||
otra if otra.starts_with("votar:") => {
|
||||
let (id, peso) =
|
||||
otra["votar:".len()..].split_once(':').expect("votar:<id_hex>:<peso>");
|
||||
let id: [u8; 32] = hex(id).try_into().expect("el id son 32 bytes");
|
||||
RuntimeCall::Governance(origin_chain_governance::Call::cast_vote {
|
||||
proposal_id: origin_chain_governance::ProposalId(id),
|
||||
stance: origin_chain_types::VoteStance::Aye,
|
||||
stake_weight: peso.parse().expect("peso en plancks"),
|
||||
})
|
||||
}
|
||||
otra if otra.starts_with("finalizar:") => {
|
||||
let id: [u8; 32] =
|
||||
hex(&otra["finalizar:".len()..]).try_into().expect("el id son 32 bytes");
|
||||
RuntimeCall::Governance(origin_chain_governance::Call::finalize_proposal {
|
||||
proposal_id: origin_chain_governance::ProposalId(id),
|
||||
})
|
||||
}
|
||||
otra if otra.starts_with("depositar:") => {
|
||||
RuntimeCall::Governance(origin_chain_governance::Call::treasury_deposit {
|
||||
amount: otra["depositar:".len()..].parse().expect("depositar:<monto en plancks>"),
|
||||
})
|
||||
}
|
||||
otra if otra.starts_with("pagar:") => {
|
||||
let (id, amount) =
|
||||
otra["pagar:".len()..].split_once(':').expect("pagar:<id_hex>:<monto>");
|
||||
let id: [u8; 32] = hex(id).try_into().expect("el id son 32 bytes");
|
||||
RuntimeCall::Sudo(pallet_sudo::Call::sudo {
|
||||
call: Box::new(RuntimeCall::Governance(
|
||||
origin_chain_governance::Call::treasury_disbursement {
|
||||
proposal_id: origin_chain_governance::ProposalId(id),
|
||||
recipient: bob(),
|
||||
amount: amount.parse().expect("monto en plancks"),
|
||||
},
|
||||
)),
|
||||
})
|
||||
}
|
||||
otra => match otra.strip_prefix("set_code:") {
|
||||
Some(ruta) => {
|
||||
let code = std::fs::read(ruta).unwrap_or_else(|e| panic!("{ruta}: {e}"));
|
||||
@@ -67,7 +123,9 @@ fn llamada(nombre: &str) -> RuntimeCall {
|
||||
})
|
||||
}
|
||||
None => panic!(
|
||||
"llamada desconocida: {otra} (remark · finalize_override · uphold0 · set_code:<ruta>)"
|
||||
"llamada desconocida: {otra} (remark · finalize_override · uphold0 · \
|
||||
set_code:<ruta> · tesoro:<monto> · pagar:<id_hex>:<monto> · \
|
||||
votar:<id_hex>:<peso> · depositar:<monto> · finalizar:<id_hex>)"
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -122,8 +180,9 @@ fn main() {
|
||||
let bytes = hex(&args[2]);
|
||||
let eventos = Vec::<EventRecord<RuntimeEvent, H256>>::decode(&mut &bytes[..])
|
||||
.expect("System::Events");
|
||||
// La fase va delante: dice a qué extrinsic pertenece cada evento.
|
||||
for e in eventos {
|
||||
println!("{:?}", e.event);
|
||||
println!("{:?} · {:?}", e.phase, e.event);
|
||||
}
|
||||
}
|
||||
"cuenta" => {
|
||||
|
||||
@@ -547,6 +547,29 @@ parameter_types! {
|
||||
frame_support::PalletId(*b"og/gvtry");
|
||||
}
|
||||
|
||||
/// RV17: el «10% quorum» de PILAR_4 §6.6, sobre la emisión VIGENTE, redondeado hacia arriba.
|
||||
/// Nunca baja del bond de una propuesta: con menos, el proponente aprobaría solo su propuesta
|
||||
/// (o revertiría solo un veto) con un voto del tamaño de su fianza.
|
||||
///
|
||||
/// Hasta RV17 eran 100M OGC fijos, el 10 % de 1B. Dos razones para medirlo sobre la emisión:
|
||||
/// - las comisiones se queman al 100 % (`OU = ()`) y la emisión sólo transfiere desde el pool,
|
||||
/// así que el suministro BAJA, y 100M acabaría siendo más que el 10 %;
|
||||
/// - en una cadena con otro génesis el número fijo no significa nada. En la testnet de la casa
|
||||
/// (génesis dev: 100M emitidos, todos de Alice) ninguna propuesta ordinaria podía llegar
|
||||
/// nunca a 100M de Aye (revisión adversarial de RV17).
|
||||
///
|
||||
/// Mainnet (1B) da lo mismo que antes, 100M.
|
||||
pub struct DiezPorCientoDeLaEmision;
|
||||
impl frame_support::traits::Get<u128> for DiezPorCientoDeLaEmision {
|
||||
fn get() -> u128 {
|
||||
use frame_support::traits::fungible::Inspect;
|
||||
let emision: u128 = <crate::Balances as Inspect<ChainAccountId>>::total_issuance();
|
||||
emision
|
||||
.div_ceil(10)
|
||||
.max(origin_chain_governance::MIN_PROPOSAL_STAKE + 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl origin_chain_governance::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = crate::Balances;
|
||||
@@ -555,11 +578,13 @@ impl origin_chain_governance::Config for Runtime {
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
// RV15: votos VIVOS simultáneos por cuenta (los vencidos se descartan solos).
|
||||
type MaxActiveVotes = frame_support::traits::ConstU32<32>;
|
||||
// RV16: participación mínima para revertir un veto del council: 100M OGC, el 10%
|
||||
// del suministro que ratifica PILAR_4 §6.6 como quórum («10% quorum»). Es un
|
||||
// parámetro de la constitución del DAO y lo fija David; mientras, este. Lo pinna
|
||||
// `quorum_de_override_del_runtime` (genesis_config_presets.rs).
|
||||
type MinOverrideQuorum = frame_support::traits::ConstU128<{ 100_000_000 * OGC }>;
|
||||
// RV16 y RV17: los dos quórums son el «10% quorum» de PILAR_4 §6.6, sobre la
|
||||
// emisión VIGENTE (ver `DiezPorCientoDeLaEmision`). El pallet los lee en el veto y al
|
||||
// crear la propuesta, y los guarda. Son parámetro de la constitución del DAO y los
|
||||
// fija David; mientras, éstos. Los pinna `quorums_del_runtime`
|
||||
// (genesis_config_presets.rs).
|
||||
type MinOverrideQuorum = DiezPorCientoDeLaEmision;
|
||||
type VoteQuorum = DiezPorCientoDeLaEmision;
|
||||
type TreasuryPalletId = GovernanceTreasuryPalletId;
|
||||
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
|
||||
type WeightInfo = origin_chain_governance::weights::SubstrateWeight<Runtime>;
|
||||
|
||||
@@ -677,21 +677,59 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
/// RV16: el quórum de override del runtime es el 10% del suministro que
|
||||
/// ratifica PILAR_4 §6.6 (100M OGC). Sin este test, un typo como `{ 100_000_000 }`
|
||||
/// sin `* OGC` (10^-10 OGC) o `ConstU128<0>` pasaba toda la suite (revisión de
|
||||
/// RV16). `integrity_test` exige además que supere el bond de una propuesta.
|
||||
/// RV16 y RV17: los dos quórums del runtime son el 10 % de la emisión VIGENTE, redondeado
|
||||
/// hacia arriba y nunca por debajo del bond. Se miden sobre el estado que construye cada
|
||||
/// preset, no sobre una constante. Sin este test pasaba toda la suite cualquiera de estos
|
||||
/// errores: un typo como `{ 100_000_000 }` sin `* OGC`, `ConstU128<0>` (revisión de
|
||||
/// RV16), dividir entre 100 u olvidar el piso.
|
||||
#[test]
|
||||
fn quorum_de_override_del_runtime() {
|
||||
use frame_support::traits::Get;
|
||||
let quorum: u128 =
|
||||
<<crate::Runtime as origin_chain_governance::Config>::MinOverrideQuorum as Get<u128>>::get();
|
||||
assert_eq!(quorum, 100_000_000 * crate::configs::OGC);
|
||||
assert_eq!(
|
||||
quorum,
|
||||
crate::configs::OGC * 1_000_000_000 / 10,
|
||||
"10% del suministro"
|
||||
);
|
||||
assert!(quorum > origin_chain_governance::MIN_PROPOSAL_STAKE);
|
||||
fn quorums_del_runtime() {
|
||||
use frame_support::traits::{fungible::Inspect, Get};
|
||||
type Gob = crate::Runtime;
|
||||
let voto = || <<Gob as origin_chain_governance::Config>::VoteQuorum as Get<u128>>::get();
|
||||
let veto =
|
||||
|| <<Gob as origin_chain_governance::Config>::MinOverrideQuorum as Get<u128>>::get();
|
||||
let ogc = crate::configs::OGC;
|
||||
// pallet-revive acuña un depósito existencial en su cuenta al construir el génesis
|
||||
// (`mint_into(account_id, minimum_balance())`): la emisión es la suma de las
|
||||
// asignaciones MÁS ese ED.
|
||||
let ed = crate::configs::EXISTENTIAL_DEPOSIT;
|
||||
|
||||
// Sin estado (así corre `integrity_test`): el piso, que supera el bond.
|
||||
sp_io::TestExternalities::new_empty().execute_with(|| {
|
||||
assert_eq!(voto(), origin_chain_governance::MIN_PROPOSAL_STAKE + 1);
|
||||
assert_eq!(veto(), voto());
|
||||
// «El 10 %» se redondea hacia ARRIBA: con 10·k + 1 de emisión, k no llega al
|
||||
// 10 %. Las emisiones de los presets son múltiplos de 10 y no lo verían.
|
||||
let k = 10_000_000 * ogc;
|
||||
pallet_balances::TotalIssuance::<crate::Runtime>::put(10 * k + 1);
|
||||
assert_eq!(voto(), k + 1);
|
||||
assert_eq!(veto(), k + 1);
|
||||
});
|
||||
for (preset, emision, quorum) in [
|
||||
(
|
||||
PRESET_MAINNET_GENESIS,
|
||||
Some(1_000_000_000 * ogc + ed),
|
||||
Some(100_000_000 * ogc + ed / 10),
|
||||
),
|
||||
(PRESET_DEVELOPMENT, Some(100_000_000 * ogc + ed), Some(10_000_000 * ogc + ed / 10)),
|
||||
(PRESET_LOCAL_TESTNET, None, None),
|
||||
] {
|
||||
let json = get_preset(&PresetId::from(preset)).expect("preset");
|
||||
sp_io::TestExternalities::new_empty().execute_with(|| {
|
||||
frame_support::genesis_builder_helper::build_state::<RuntimeGenesisConfig>(json)
|
||||
.expect("el preset construye estado");
|
||||
let total = <crate::Balances as Inspect<ChainAccountId>>::total_issuance();
|
||||
if let Some(e) = emision {
|
||||
assert_eq!(total, e, "{preset}: emisión");
|
||||
}
|
||||
assert_eq!(voto(), total.div_ceil(10), "{preset}: 10 % de la emisión");
|
||||
if let Some(q) = quorum {
|
||||
assert_eq!(voto(), q, "{preset}");
|
||||
}
|
||||
assert_eq!(veto(), voto(), "{preset}: los dos quórums son el mismo 10 %");
|
||||
assert!(voto() > origin_chain_governance::MIN_PROPOSAL_STAKE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,6 +373,19 @@ pub const IMPL_NAME: &str = borrowed_str(&VERSION.impl_name);
|
||||
/// de cada `weights.rs`). Sustituyen a los interinos medidos en el Mac. No cambia ninguna
|
||||
/// lógica ni ningún storage, sólo lo que cuesta cada extrinsic. Es el primer upgrade que se
|
||||
/// aplica en vivo a la testnet de la casa (rp1), por `set_code`.
|
||||
///
|
||||
/// **v18 (2026-10-28), RV17:**
|
||||
/// - el voto ordinario exige un quórum de Aye. Es el 10 % de PILAR_4 §6.6 sobre la emisión
|
||||
/// vigente, se fija al crear la propuesta (`VoteQuorumOf`) y lo tiene que alcanzar también
|
||||
/// un override del veto;
|
||||
/// - el quórum mínimo del override pasa a ser el mismo 10 % (antes, 100M fijos);
|
||||
/// - el tesoro sólo paga la acción que la propuesta fijó al crearse (`submit_treasury_proposal`,
|
||||
/// storage `TreasuryActions`).
|
||||
///
|
||||
/// No cambia la codificación de ninguna llamada existente (transaction_version igual) ni migra
|
||||
/// storage: lo nuevo es aditivo, y una propuesta anterior sin quórum guardado se decide como
|
||||
/// se abrió. La llamada nueva exige un node con `opaque::Block` (ver `opaque.rs`): un node
|
||||
/// que decodifica con tipos nativos la rechaza en el pool.
|
||||
pub const RUNTIME_VERSION: u32 = VERSION.spec_version;
|
||||
|
||||
/// Canonical authoring version per Substrate semver canonical.
|
||||
@@ -1025,7 +1038,7 @@ pub const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion {
|
||||
spec_name: alloc::borrow::Cow::Borrowed("origin-chain"),
|
||||
impl_name: alloc::borrow::Cow::Borrowed("origin-chain-charon"),
|
||||
authoring_version: 1,
|
||||
spec_version: 17,
|
||||
spec_version: 18,
|
||||
impl_version: 1,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
transaction_version: 1,
|
||||
@@ -1186,7 +1199,7 @@ mod tests {
|
||||
/// mismo commit en que la version subio a 12.
|
||||
#[test]
|
||||
fn spec_version_vigente_y_transaction_version_intacta() {
|
||||
assert_eq!(RUNTIME_VERSION, 17);
|
||||
assert_eq!(RUNTIME_VERSION, 18);
|
||||
assert_eq!(TRANSACTION_VERSION, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,24 @@
|
||||
//! (puede correr cualquier runtime WASM compatible) + runtime sovereignty (puede
|
||||
//! evolucionar pallets sin breakar node binaries).
|
||||
|
||||
use sp_runtime::impl_opaque_keys;
|
||||
use sp_runtime::{generic, impl_opaque_keys};
|
||||
|
||||
/// El extrinsic que ve el NODO: bytes sin interpretar. Sólo el WASM los decodifica.
|
||||
///
|
||||
/// Hasta el 2026-10-28 el node trabajaba sobre el `Block` completo del runtime y decodificaba
|
||||
/// cada extrinsic con los tipos NATIVOS con que se compiló. Un `set_code` que añadía una
|
||||
/// llamada no servía: el pool del node rechazaba la llamada nueva («Could not decode
|
||||
/// `RuntimeCall::Governance.0`: variant doesn't exist») antes de que llegara al runtime.
|
||||
/// Además, un node viejo no podía importar un bloque que la llevara. Se midió en el ensayo de
|
||||
/// RV17, firmando `submit_treasury_proposal` contra un node de spec 16 subido a 18.
|
||||
///
|
||||
/// Los bytes son los mismos: el `UncheckedExtrinsic` ya va prefijado por su largo, que es lo
|
||||
/// que lee `OpaqueExtrinsic`. Los bloques y la base de datos no cambian.
|
||||
pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
|
||||
/// La cabecera, idéntica a la del runtime: el hash de un bloque no depende de este cambio.
|
||||
pub type Header = crate::Header;
|
||||
/// El bloque que usa el node.
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
// Slice 2e Layer 5 fix: import Vec for no_std impl_opaque_keys macro expansion.
|
||||
extern crate alloc;
|
||||
@@ -36,3 +53,117 @@ impl_opaque_keys! {
|
||||
pub grandpa: crate::Grandpa,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Runtime, RuntimeCall, TxExtension};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use sp_core::{sr25519, Pair, H256};
|
||||
use sp_runtime::generic::{Era, SignedPayload};
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use sp_runtime::{MultiAddress, MultiSignature};
|
||||
|
||||
/// Un extrinsic firmado por //Alice como lo firma un wallet, con una llamada de RV17.
|
||||
fn firmado() -> (crate::UncheckedExtrinsic, Vec<u8>) {
|
||||
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(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(),
|
||||
);
|
||||
let genesis = H256::repeat_byte(7);
|
||||
let v = crate::VERSION;
|
||||
let implicito = (
|
||||
(),
|
||||
v.spec_version,
|
||||
v.transaction_version,
|
||||
genesis,
|
||||
genesis,
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
None,
|
||||
(),
|
||||
);
|
||||
let call =
|
||||
RuntimeCall::Governance(origin_chain_governance::Call::submit_treasury_proposal {
|
||||
title_hash: [1u8; 32],
|
||||
body_hash: [2u8; 32],
|
||||
voting_period_blocks: origin_chain_governance::MIN_VOTING_PERIOD_BLOCKS,
|
||||
stake: origin_chain_governance::MIN_PROPOSAL_STAKE,
|
||||
recipient: sp_runtime::AccountId32::new([3u8; 32]),
|
||||
amount: 5,
|
||||
});
|
||||
let llamada = call.encode();
|
||||
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 = crate::UncheckedExtrinsic::new_signed(
|
||||
call,
|
||||
MultiAddress::Id(alice.public().into()),
|
||||
MultiSignature::Sr25519(firma),
|
||||
tx_ext,
|
||||
);
|
||||
(xt, llamada)
|
||||
}
|
||||
|
||||
/// El node lee como `opaque::Block` lo que el runtime escribió como `crate::Block`, con los
|
||||
/// mismos bytes y el mismo hash. Es lo que permite cambiar el binario de un validador
|
||||
/// sobre su base de datos vieja (ensayado en la rp1 el 2026-10-28: 39 273 bloques
|
||||
/// sincronizados desde cero y los mismos hashes en 8 alturas).
|
||||
#[test]
|
||||
fn el_bloque_opaco_es_el_mismo_bloque() {
|
||||
let (xt, _) = firmado();
|
||||
let header = crate::Header::new(
|
||||
7,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
H256::repeat_byte(9),
|
||||
Default::default(),
|
||||
);
|
||||
let completo = crate::Block::new(header, alloc::vec![xt.clone()]);
|
||||
let bytes = completo.encode();
|
||||
let opaco = Block::decode(&mut &bytes[..]).expect("el node lo lee");
|
||||
assert_eq!(opaco.encode(), bytes, "mismos bytes");
|
||||
assert_eq!(
|
||||
opaco.header().hash(),
|
||||
completo.header().hash(),
|
||||
"mismo hash"
|
||||
);
|
||||
assert_eq!(opaco.extrinsics()[0].encode(), xt.encode());
|
||||
}
|
||||
|
||||
/// Y por qué hace falta: con una llamada que el runtime nativo no conoce (aquí,
|
||||
/// Governance 99), los tipos nativos no decodifican el extrinsic y el opaco sí. El node
|
||||
/// viejo rechazaba así, en su pool, la llamada 16 que añadía `set_code`.
|
||||
#[test]
|
||||
fn el_node_opaco_no_necesita_conocer_la_llamada() {
|
||||
let (xt, llamada) = firmado();
|
||||
let mut bytes = xt.encode();
|
||||
let pos = bytes
|
||||
.windows(llamada.len())
|
||||
.position(|w| w == llamada)
|
||||
.expect("la llamada");
|
||||
assert_eq!(
|
||||
&bytes[pos..pos + 2],
|
||||
&[10, 16],
|
||||
"Governance (10), llamada 16"
|
||||
);
|
||||
assert!(crate::UncheckedExtrinsic::decode(&mut &bytes[..]).is_ok());
|
||||
bytes[pos + 1] = 99;
|
||||
assert!(
|
||||
crate::UncheckedExtrinsic::decode(&mut &bytes[..]).is_err(),
|
||||
"los tipos nativos no conocen la llamada 99"
|
||||
);
|
||||
let opaco = UncheckedExtrinsic::decode(&mut &bytes[..]).expect("el opaco no la mira");
|
||||
assert_eq!(opaco.encode(), bytes);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user