feat(bench): maquinaria FRAME v2 de benchmarking para los 5 pallets custom (RV 8→9)

Los 19 extrinsics de provenance/fees/agent-identity/emission/governance pasan de
weights placeholder `from_parts(10_000, 0)` (proof_size=0, sin contar reads/writes
de DB — vector de DoS real) a weights INTERIM hand-derived DB-aware vía el trait
`WeightInfo`. Cierra el mispricing de DoS en TODOS los extrinsics del chain.

Cada pallet gana:
- weights.rs: trait WeightInfo + SubstrateWeight<T> (DB-aware vía T::DbWeight) + impl ()
- benchmarking.rs: benchmarks FRAME v2, VERIFICADOS por impl_benchmark_test_suite!
  (19 benchmarks ejecutan+pasan en los mocks — cargo test --features runtime-benchmarks)
- Config: `type WeightInfo` + swap de los `#[pallet::weight]`

Runtime:
- define_benchmarks! con los 5 pallets + runtime API Benchmark (inerte sin la feature)
- WeightInfo cableado a SubstrateWeight<Runtime> en cada Config
- RUNTIME_VERSION 8→9 (weights = consenso-relevante: fee + llenado de bloque;
  las firmas de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto)

3 bugs latentes cerrados (territorio virgen — el runtime NUNCA se compiló con
runtime-benchmarks; las dimensiones nuevas cazan lo que no se había visto):
- pallet-grandpa/try-runtime mal ubicado en la feature runtime-benchmarks (copy-paste
  erróneo: features ortogonales; NUNCA activaba los benchmarks de grandpa)
- feature runtime-benchmarks incompleta → E0046 en OriginFeeAdapter (métodos
  benchmark-gated de OnChargeTransaction) + pallet-staking
  (StakingUnchecked::migrate_to_direct_staker); ahora lista TODOS los pallets como
  un runtime de producción Substrate
- pallet-revive/runtime-benchmarks omitido a propósito (fixtures RISC-V/PolkaVM
  indisponibles; ReviveApi deferred, no benchmarkeado)

emission: mock extraído del `mod tests` inline a `pub mod mock` (reuso del test-suite,
sin duplicar el scaffold; los 15 tests existentes + el benchmark verdes).

GATED (documentado en el §Estado de cada weights.rs): la generación de números
CANÓNICOS por CLI requiere el node sc-service o omni-bencher+WASM (ambos bloqueados
hace sesiones). Los weights interinos son DB-aware/DoS-safe y regenerables de forma
mecánica cuando aterrice el HW de referencia (Pi5/VPS) — benchmarking.rs es la fuente
de verdad de la FORMA.

Verificado: 5 test-suites de pallet verdes (con y sin runtime-benchmarks) + runtime
compila en ambos modos + workspace sin regresión + 40 tests de runtime (incl. v9).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.8 committed 2026-07-07 22:48:26 -05:00
1 parent 0d56e0bcbf
commit c7fc6a88fc
29 files changed
+1206 -121

No files matched your search

@@ -0,0 +1,30 @@
//! # Benchmarks para `pallet-origin-emission` (FRAME v2)
//!
//! Fuente de verdad de la forma del weight de `set_emission_paused` (kill-switch
//! de governance). Verificado por `impl_benchmark_test_suite!` en el mock
//! (`cargo test -p origin-chain-emission --features runtime-benchmarks`). Números
//! canónicos gated por HW/omni-bencher (ver `weights.rs` §Estado).
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn set_emission_paused() {
// `EmissionPaused` arranca en `false` (default); pausar (`true`) es un
// cambio real → pasa el guard anti-no-op del extrinsic.
#[extrinsic_call]
set_emission_paused(RawOrigin::Root, true);
// Post-estado: la emisión quedó pausada.
assert!(EmissionPaused::<T>::get());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+25 -94
View File
@@ -51,6 +51,18 @@
pub use pallet::*;
/// Weights canonical (INTERIM hand-derived DB-aware — ver `weights.rs` §Estado).
pub mod weights;
pub use weights::WeightInfo;
/// Benchmarks canonical (FRAME v2) — solo bajo `runtime-benchmarks`.
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
/// Mock runtime canonical (cfg test) — extraído para reuso del benchmark test-suite.
#[cfg(test)]
pub mod mock;
extern crate alloc;
#[frame_support::pallet]
@@ -62,6 +74,8 @@ pub mod pallet {
use frame_support::PalletId;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::AccountIdConversion;
// Trait de weights en scope para resolver `T::WeightInfo::<method>()`.
use crate::weights::WeightInfo;
/// StorageVersion en código (FIX-2 lifecycle): arranca en 0 (genesis).
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
@@ -109,6 +123,9 @@ pub mod pallet {
/// PalletId del subsidy pool (proof-of-equity, Inmutable #16 anti-clasista).
#[pallet::constant]
type SubsidyPalletId: Get<PalletId>;
/// Weight information canonical para los extrinsics de este pallet.
type WeightInfo: crate::weights::WeightInfo;
}
/// Pallet definition.
@@ -291,7 +308,7 @@ pub mod pallet {
/// todo cambio de governance quede como evento observable on-chain.
/// `TotalEmitted` se preserva — pausar NO reescribe el histórico.
#[pallet::call_index(0)]
#[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
#[pallet::weight(T::WeightInfo::set_emission_paused())]
pub fn set_emission_paused(origin: OriginFor<T>, paused: bool) -> DispatchResult {
frame_system::ensure_root(origin)?;
ensure!(
@@ -326,100 +343,14 @@ pub mod pallet {
#[cfg(test)]
mod tests {
use super::*;
use crate as origin_chain_emission;
// Mock extraído a `crate::mock` (ver mock.rs) — reusado por el benchmark
// test-suite. El glob trae Test/System/Balances/Emission/RuntimeOrigin +
// helpers (new_test_ext, author, ecosystem_account, …).
use crate::mock::*;
use frame_support::traits::fungible::Inspect;
use frame_support::traits::{FindAuthor, Hooks};
use frame_support::{
assert_noop, assert_ok, construct_runtime, derive_impl, parameter_types, PalletId,
};
use sp_runtime::traits::AccountIdConversion;
use sp_runtime::{AccountId32, BuildStorage, DispatchError};
type Block = frame_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test {
System: frame_system,
Balances: pallet_balances,
Emission: origin_chain_emission,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountId = AccountId32;
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
type AccountData = pallet_balances::AccountData<u128>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = u128;
}
/// FindAuthor de prueba: devuelve siempre ALICE como autor del bloque.
pub struct MockFindAuthor;
impl FindAuthor<AccountId32> for MockFindAuthor {
fn find_author<'a, I>(_digests: I) -> Option<AccountId32>
where
I: 'a + IntoIterator<Item = (sp_runtime::ConsensusEngineId, &'a [u8])>,
{
Some(AccountId32::new([1u8; 32]))
}
}
parameter_types! {
pub const BlockRewardCfg: u128 = 1_000;
pub const AuthorShareBpsCfg: u16 = 7_000; // 70%
pub const TreasuryShareBpsCfg: u16 = 2_000; // 20% (subsidy = 10% residual)
pub const EcosystemPid: PalletId = PalletId(*b"og/ecosy");
pub const TreasuryPid: PalletId = PalletId(*b"og/gvtry");
pub const SubsidyPid: PalletId = PalletId(*b"og/subsy");
}
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type FindAuthor = MockFindAuthor;
type BlockReward = BlockRewardCfg;
type AuthorShareBps = AuthorShareBpsCfg;
type TreasuryShareBps = TreasuryShareBpsCfg;
type EcosystemPalletId = EcosystemPid;
type TreasuryPalletId = TreasuryPid;
type SubsidyPalletId = SubsidyPid;
}
fn ecosystem_account() -> AccountId32 {
EcosystemPid::get().into_account_truncating()
}
/// Fondea el pool Ecosystem con `pool_balance` en génesis (la fuente finita
/// de la que se libera; sin esto, transfer no mueve nada — techo duro).
fn new_test_ext_with_pool(pool_balance: u128) -> sp_io::TestExternalities {
let mut storage = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.expect("genesis builds");
pallet_balances::GenesisConfig::<Test> {
balances: vec![(ecosystem_account(), pool_balance)],
..Default::default()
}
.assimilate_storage(&mut storage)
.expect("balances genesis");
let mut ext: sp_io::TestExternalities = storage.into();
ext.execute_with(|| System::set_block_number(1));
ext
}
/// Default: pool amplio (1M) — cubre miles de bloques sin agotarse.
fn new_test_ext() -> sp_io::TestExternalities {
new_test_ext_with_pool(1_000_000)
}
fn author() -> AccountId32 {
AccountId32::new([1u8; 32])
}
use frame_support::traits::Hooks;
use frame_support::{assert_noop, assert_ok};
use sp_runtime::{AccountId32, DispatchError};
#[test]
fn block_reward_released_and_split_correctly() {
+104
View File
@@ -0,0 +1,104 @@
//! # Mock runtime para tests + benchmark test-suite de `pallet-origin-emission`
//!
//! Extraído del inline `mod tests` (v0.0.x) a `pub mod mock` para que
//! `impl_benchmark_test_suite!` (benchmarking.rs) pueda reusar `Test` +
//! `new_test_ext` — mismo patrón que provenance/fees/agent-identity. Los tests de
//! `lib.rs` importan de aquí (`use crate::mock::*`).
#![cfg(test)]
use crate as origin_chain_emission;
use frame_support::traits::FindAuthor;
use frame_support::{construct_runtime, derive_impl, parameter_types, PalletId};
use sp_runtime::traits::AccountIdConversion;
use sp_runtime::{AccountId32, BuildStorage};
/// Mock block type.
pub type Block = frame_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test {
System: frame_system,
Balances: pallet_balances,
Emission: origin_chain_emission,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountId = AccountId32;
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
type AccountData = pallet_balances::AccountData<u128>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = u128;
}
/// FindAuthor de prueba: devuelve siempre ALICE como autor del bloque.
pub struct MockFindAuthor;
impl FindAuthor<AccountId32> for MockFindAuthor {
fn find_author<'a, I>(_digests: I) -> Option<AccountId32>
where
I: 'a + IntoIterator<Item = (sp_runtime::ConsensusEngineId, &'a [u8])>,
{
Some(AccountId32::new([1u8; 32]))
}
}
parameter_types! {
pub const BlockRewardCfg: u128 = 1_000;
pub const AuthorShareBpsCfg: u16 = 7_000; // 70%
pub const TreasuryShareBpsCfg: u16 = 2_000; // 20% (subsidy = 10% residual)
pub const EcosystemPid: PalletId = PalletId(*b"og/ecosy");
pub const TreasuryPid: PalletId = PalletId(*b"og/gvtry");
pub const SubsidyPid: PalletId = PalletId(*b"og/subsy");
}
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type FindAuthor = MockFindAuthor;
type BlockReward = BlockRewardCfg;
type AuthorShareBps = AuthorShareBpsCfg;
type TreasuryShareBps = TreasuryShareBpsCfg;
type EcosystemPalletId = EcosystemPid;
type TreasuryPalletId = TreasuryPid;
type SubsidyPalletId = SubsidyPid;
type WeightInfo = ();
}
/// Cuenta del pool Ecosystem (fuente finita de la emisión).
pub fn ecosystem_account() -> AccountId32 {
EcosystemPid::get().into_account_truncating()
}
/// Fondea el pool Ecosystem con `pool_balance` en génesis (la fuente finita
/// de la que se libera; sin esto, transfer no mueve nada — techo duro).
pub fn new_test_ext_with_pool(pool_balance: u128) -> sp_io::TestExternalities {
let mut storage = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.expect("genesis builds");
pallet_balances::GenesisConfig::<Test> {
balances: vec![(ecosystem_account(), pool_balance)],
..Default::default()
}
.assimilate_storage(&mut storage)
.expect("balances genesis");
let mut ext: sp_io::TestExternalities = storage.into();
ext.execute_with(|| System::set_block_number(1));
ext
}
/// Default: pool amplio (1M) — cubre miles de bloques sin agotarse.
pub fn new_test_ext() -> sp_io::TestExternalities {
new_test_ext_with_pool(1_000_000)
}
/// Autor del bloque (ALICE) usado por los tests de split.
pub fn author() -> AccountId32 {
AccountId32::new([1u8; 32])
}
@@ -0,0 +1,40 @@
//! # Weights para `pallet-origin-emission`
//!
//! ## Estado: INTERIM hand-derived (DB-aware) — pendiente regeneración HW
//!
//! Ver `origin-chain-provenance/src/weights.rs` §Estado. Nota: `set_emission_paused`
//! ya era DB-aware antes de esta migración (usaba `T::DbWeight::get().reads_writes(1,1)`
//! directo — el ÚNICO extrinsic del chain que nunca fue DoS-prone). Aquí se mueve
//! ese mismo cálculo detrás del trait `WeightInfo` por consistencia con los demás
//! pallets + para exponerlo a `benchmark pallet`. `benchmarking.rs` es la fuente
//! de verdad de la forma.
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(clippy::unnecessary_cast)]
use core::marker::PhantomData;
use frame_support::{traits::Get, weights::constants::RocksDbWeight, weights::Weight};
/// Trait de weights para los extrinsics de `pallet-origin-emission`.
pub trait WeightInfo {
/// Peso de `set_emission_paused` (Root) — 1 read (guard no-op) + 1 write.
fn set_emission_paused() -> Weight;
}
/// Weights parametrizados por el `DbWeight` del runtime.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
fn set_emission_paused() -> Weight {
// ensure_root + get(EmissionPaused) [guard no-op] + put(EmissionPaused).
T::DbWeight::get().reads_writes(1, 1)
}
}
/// Impl no-op para mocks / tests.
impl WeightInfo for () {
fn set_emission_paused() -> Weight {
RocksDbWeight::get().reads_writes(1, 1)
}
}