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:
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);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user