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,226 @@
//! # Benchmarks para `pallet-origin-governance` (FRAME v2)
//!
//! Fuente de verdad de la forma del weight de los 13 extrinsics. Verificado por
//! `impl_benchmark_test_suite!` en el mock (`cargo test -p origin-chain-governance
//! --features runtime-benchmarks`). Números canónicos gated por HW/omni-bencher
//! (ver `weights.rs` §Estado).
//!
//! Los preconditions (proposal en un estado, membresía de council, endosos) se
//! siembran por storage directo en el setup — patrón estándar de benchmarking.
//! Los thresholds con council_size=1 son 1 (veto y emergency), así que un solo
//! miembro/endoso alcanza el umbral.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_support::traits::fungible::{Inspect, Mutate};
use frame_system::RawOrigin;
use origin_chain_types::AccountId;
/// Fondea `who` con holgura para bonds + treasury deposits.
fn fund<T: Config>(who: &T::AccountId) {
let _ = T::Currency::mint_into(who, MIN_PROPOSAL_STAKE.saturating_mul(100));
}
/// Siembra una `Proposal` en el estado/end_block dados (bypass del extrinsic).
fn seed_proposal<T: Config>(id: ProposalId, status: ProposalStatus, end_block: u64) {
let proposal = Proposal {
id,
proposer: AccountId::new([9u8; 32]),
title_hash: [1u8; 32],
body_hash: [2u8; 32],
stake: MIN_PROPOSAL_STAKE,
start_block: 0,
end_block,
status,
yes_weight: 0,
nay_weight: 0,
abstain_weight: 0,
};
Proposals::<T>::insert(id, proposal);
}
/// Añade `who` al council (size=1) por storage directo.
fn seed_council<T: Config>(who: &T::AccountId) {
let member = CouncilMember {
account: AccountId::new([0u8; 32]),
joined_at_block: 0,
role: CouncilRole::Member,
};
Council::<T>::insert(who, member);
CouncilSize::<T>::put(1u32);
}
const EM_TITLE: [u8; 32] = [3u8; 32];
const EM_BODY: [u8; 32] = [4u8; 32];
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn submit_proposal() {
let caller: T::AccountId = whitelisted_caller();
fund::<T>(&caller);
#[extrinsic_call]
submit_proposal(RawOrigin::Signed(caller), [1u8; 32], [2u8; 32], 1_000u64, MIN_PROPOSAL_STAKE);
assert_eq!(Proposals::<T>::iter().count(), 1);
}
#[benchmark]
fn cast_vote() {
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 1_000_000u64);
#[extrinsic_call]
cast_vote(RawOrigin::Signed(caller.clone()), id, VoteStance::Aye, 100u128);
assert!(Votes::<T>::contains_key(id, &caller));
}
#[benchmark]
fn finalize_proposal() {
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 0u64);
// current_block debe ser > end_block(0) → avanza a 2.
frame_system::Pallet::<T>::set_block_number(2u32.into());
#[extrinsic_call]
finalize_proposal(RawOrigin::Signed(caller), id);
assert!(!matches!(
Proposals::<T>::get(id).unwrap().status,
ProposalStatus::Voting
));
}
#[benchmark]
fn add_council_member() {
let acct: T::AccountId = account("member", 0, 0);
#[extrinsic_call]
add_council_member(RawOrigin::Root, acct.clone(), CouncilRole::Member);
assert!(Council::<T>::contains_key(&acct));
}
#[benchmark]
fn remove_council_member() {
let acct: T::AccountId = account("member", 0, 0);
seed_council::<T>(&acct);
#[extrinsic_call]
remove_council_member(RawOrigin::Root, acct.clone());
assert!(!Council::<T>::contains_key(&acct));
}
#[benchmark]
fn veto_proposal() {
let caller: T::AccountId = whitelisted_caller();
seed_council::<T>(&caller);
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Voting, 1_000_000u64);
#[extrinsic_call]
veto_proposal(RawOrigin::Signed(caller.clone()), id);
assert!(VetoVotes::<T>::contains_key(id, &caller));
}
#[benchmark]
fn mark_as_advisor() {
let acct: T::AccountId = account("advisor", 0, 0);
#[extrinsic_call]
mark_as_advisor(RawOrigin::Root, acct.clone());
assert!(AdvisorFlags::<T>::contains_key(&acct));
}
#[benchmark]
fn unmark_as_advisor() {
let acct: T::AccountId = account("advisor", 0, 0);
AdvisorFlags::<T>::insert(&acct, ());
#[extrinsic_call]
unmark_as_advisor(RawOrigin::Root, acct.clone());
assert!(!AdvisorFlags::<T>::contains_key(&acct));
}
#[benchmark]
fn override_council_veto() {
let caller: T::AccountId = whitelisted_caller();
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Rejected, 0u64);
VetoRejected::<T>::insert(id, ());
#[extrinsic_call]
override_council_veto(RawOrigin::Signed(caller.clone()), id, 100u128);
assert!(OverrideVotes::<T>::contains_key(id, &caller));
}
#[benchmark]
fn submit_emergency_proposal() {
let caller: T::AccountId = whitelisted_caller();
fund::<T>(&caller);
seed_council::<T>(&caller);
// council_size=1 ⇒ emergency threshold=1; un endoso VIVO del caller basta.
let content_id = crate::emergency_content_id(&EM_TITLE, &EM_BODY);
EmergencyEndorsements::<T>::insert(content_id, &caller, ());
#[extrinsic_call]
submit_emergency_proposal(RawOrigin::Signed(caller), EM_TITLE, EM_BODY, MIN_PROPOSAL_STAKE);
assert!(Proposals::<T>::iter().count() >= 1);
}
#[benchmark]
fn treasury_disbursement() {
let id = ProposalId([7u8; 32]);
seed_proposal::<T>(id, ProposalStatus::Approved, 0u64);
let recipient: T::AccountId = account("recipient", 0, 0);
// Fondea la tesorería para que el transfer real tenga de dónde salir.
let _ = T::Currency::mint_into(
&Pallet::<T>::treasury_account(),
MIN_PROPOSAL_STAKE.saturating_mul(10),
);
#[extrinsic_call]
treasury_disbursement(RawOrigin::Root, id, recipient, MIN_PROPOSAL_STAKE);
assert!(Disbursed::<T>::contains_key(id));
}
#[benchmark]
fn treasury_deposit() {
let caller: T::AccountId = whitelisted_caller();
fund::<T>(&caller);
#[extrinsic_call]
treasury_deposit(RawOrigin::Signed(caller), MIN_PROPOSAL_STAKE);
assert!(T::Currency::balance(&Pallet::<T>::treasury_account()) >= MIN_PROPOSAL_STAKE);
}
#[benchmark]
fn endorse_emergency_proposal() {
let caller: T::AccountId = whitelisted_caller();
seed_council::<T>(&caller);
#[extrinsic_call]
endorse_emergency_proposal(RawOrigin::Signed(caller.clone()), EM_TITLE, EM_BODY);
let content_id = crate::emergency_content_id(&EM_TITLE, &EM_BODY);
assert!(EmergencyEndorsements::<T>::contains_key(content_id, &caller));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}