From c7fc6a88fc55ce4d6efb3ff7a2a8c981a2c2b2af Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Tue, 7 Jul 2026 22:48:26 -0500 Subject: [PATCH] =?UTF-8?q?feat(bench):=20maquinaria=20FRAME=20v2=20de=20b?= =?UTF-8?q?enchmarking=20para=20los=205=20pallets=20custom=20(RV=208?= =?UTF-8?q?=E2=86=929)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (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 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 --- Cargo.lock | 7 + crates/origin-chain-agent-identity/Cargo.toml | 6 + .../src/benchmarking.rs | 96 ++++++++ crates/origin-chain-agent-identity/src/lib.rs | 15 +- .../origin-chain-agent-identity/src/mock.rs | 1 + .../src/weights.rs | 44 ++++ crates/origin-chain-emission/Cargo.toml | 6 + .../origin-chain-emission/src/benchmarking.rs | 30 +++ crates/origin-chain-emission/src/lib.rs | 119 ++------- crates/origin-chain-emission/src/mock.rs | 104 ++++++++ crates/origin-chain-emission/src/weights.rs | 40 ++++ crates/origin-chain-fees/Cargo.toml | 6 + crates/origin-chain-fees/src/benchmarking.rs | 36 +++ crates/origin-chain-fees/src/lib.rs | 15 +- crates/origin-chain-fees/src/mock.rs | 1 + crates/origin-chain-fees/src/weights.rs | 40 ++++ crates/origin-chain-governance/Cargo.toml | 6 + .../src/benchmarking.rs | 226 ++++++++++++++++++ crates/origin-chain-governance/src/lib.rs | 39 ++- crates/origin-chain-governance/src/mock.rs | 1 + crates/origin-chain-governance/src/weights.rs | 103 ++++++++ crates/origin-chain-provenance/Cargo.toml | 6 + .../src/benchmarking.rs | 131 ++++++++++ crates/origin-chain-provenance/src/lib.rs | 22 +- crates/origin-chain-provenance/src/mock.rs | 1 + crates/origin-chain-provenance/src/weights.rs | 87 +++++++ crates/origin-chain-runtime/Cargo.toml | 38 ++- crates/origin-chain-runtime/src/configs.rs | 11 + crates/origin-chain-runtime/src/lib.rs | 90 ++++++- 29 files changed, 1206 insertions(+), 121 deletions(-) create mode 100644 crates/origin-chain-agent-identity/src/benchmarking.rs create mode 100644 crates/origin-chain-agent-identity/src/weights.rs create mode 100644 crates/origin-chain-emission/src/benchmarking.rs create mode 100644 crates/origin-chain-emission/src/mock.rs create mode 100644 crates/origin-chain-emission/src/weights.rs create mode 100644 crates/origin-chain-fees/src/benchmarking.rs create mode 100644 crates/origin-chain-fees/src/weights.rs create mode 100644 crates/origin-chain-governance/src/benchmarking.rs create mode 100644 crates/origin-chain-governance/src/weights.rs create mode 100644 crates/origin-chain-provenance/src/benchmarking.rs create mode 100644 crates/origin-chain-provenance/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 5aa1bc1..680fa34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6383,6 +6383,7 @@ name = "origin-chain-agent-identity" version = "0.0.1" dependencies = [ "blake3", + "frame-benchmarking", "frame-support", "frame-system", "origin-chain-types", @@ -6413,6 +6414,7 @@ dependencies = [ name = "origin-chain-emission" version = "0.0.1" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", @@ -6427,6 +6429,7 @@ dependencies = [ name = "origin-chain-fees" version = "0.0.1" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "origin-chain-types", @@ -6444,6 +6447,7 @@ dependencies = [ name = "origin-chain-governance" version = "0.0.1" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "origin-chain-types", @@ -6499,6 +6503,7 @@ version = "0.0.1" dependencies = [ "blake3", "ed25519-dalek", + "frame-benchmarking", "frame-support", "frame-system", "origin-chain-types", @@ -6514,6 +6519,7 @@ dependencies = [ name = "origin-chain-runtime" version = "0.0.1" dependencies = [ + "frame-benchmarking", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", @@ -6789,6 +6795,7 @@ dependencies = [ "pallet-authorship", "pallet-session", "parity-scale-codec", + "rand_chacha 0.3.1", "scale-info", "serde", "sp-application-crypto 43.0.0", diff --git a/crates/origin-chain-agent-identity/Cargo.toml b/crates/origin-chain-agent-identity/Cargo.toml index 485254e..692a34f 100644 --- a/crates/origin-chain-agent-identity/Cargo.toml +++ b/crates/origin-chain-agent-identity/Cargo.toml @@ -34,6 +34,9 @@ sp-io = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] # Mock runtime canonical para integration tests v0.0.26 — Pattern v0.0.18/v0.0.22 mirror. sp-core = { workspace = true, default-features = true } @@ -52,8 +55,11 @@ std = [ "sp-runtime/std", "parity-scale-codec/std", "scale-info/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/crates/origin-chain-agent-identity/src/benchmarking.rs b/crates/origin-chain-agent-identity/src/benchmarking.rs new file mode 100644 index 0000000..5a226e5 --- /dev/null +++ b/crates/origin-chain-agent-identity/src/benchmarking.rs @@ -0,0 +1,96 @@ +//! # Benchmarks para `pallet-agent-identity` (FRAME v2) +//! +//! Fuente de verdad de la forma del weight de `register_agent_did`. Verificado por +//! `impl_benchmark_test_suite!` en el mock (`cargo test -p origin-chain-agent-identity +//! --features runtime-benchmarks`). Números canónicos gated por HW/omni-bencher +//! (ver `weights.rs` §Estado). +//! +//! ## Sin firmar en no_std (mismo patrón que provenance) +//! +//! `register_agent_did` verifica una firma Ed25519 de binding sobre +//! `AGENT_DID_REGISTER_DOMAIN || who.encode()`. El benchmark no puede firmar en +//! no_std, así que usa un **golden vector determinista**: caller fijo `[1u8;32]` +//! (= `alice_account` del mock), keypair seed `[0x11;32]` (= `alice_pair`), y la +//! firma precomputada de ese par sobre el mensaje de ESE caller. Como el mensaje +//! depende de `who`, el caller DEBE ser exactamente `[1u8;32]`. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_support::pallet_prelude::Decode; +use frame_system::RawOrigin; +// `AgentKind` llega vía `super::*` (re-exportado en el crate root). + +use alloc::vec::Vec; + +/// Caller golden: `alice_account` del mock = `AccountId32::new([1u8;32])`. +const CALLER: [u8; 32] = [1u8; 32]; +/// Pubkey golden: `alice_pair` (seed `[0x11;32]`). +const BENCH_PUBKEY: &str = "d04ab232742bb4ab3a1368bd4615e4e6d0224ab71a016baf8520a332c9778737"; +/// Firma golden: `alice_pair` sobre `AGENT_DID_REGISTER_DOMAIN || [1u8;32].encode()`. +const BENCH_SIG: &str = "9f593fc1941f09b36b41f9001a9da9ecde506edd08627a35e571c0c9994c2dccbd9e44b371751fdbed85f7bc0ce0c011653b2e5304ac96da5a948c63557c0b04"; + +fn hexval(c: u8) -> u8 { + match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + b'A'..=b'F' => c - b'A' + 10, + _ => 0, + } +} + +fn unhex(s: &str) -> Vec { + let bytes = s.as_bytes(); + let mut out = Vec::with_capacity(bytes.len() / 2); + let mut i = 0; + while i + 1 < bytes.len() { + out.push((hexval(bytes[i]) << 4) | hexval(bytes[i + 1])); + i += 2; + } + out +} + +fn to_32(s: &str) -> [u8; 32] { + let v = unhex(s); + let mut out = [0u8; 32]; + out.copy_from_slice(&v[..32]); + out +} + +fn to_64(s: &str) -> [u8; 64] { + let v = unhex(s); + let mut out = [0u8; 64]; + out.copy_from_slice(&v[..64]); + out +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn register_agent_did() { + // Caller EXACTO `[1u8;32]` para que el mensaje firmado coincida. + // Asume `T::AccountId = AccountId32` (mock + runtime real). + let who: T::AccountId = + Decode::decode(&mut &CALLER[..]).expect("AccountId32 decodes from 32 bytes"); + let pubkey = to_32(BENCH_PUBKEY); + let signature = to_64(BENCH_SIG); + let co_authors: Vec<[u8; 32]> = Vec::new(); + + #[extrinsic_call] + register_agent_did( + RawOrigin::Signed(who.clone()), + pubkey, + AgentKind::Human, + co_authors, + signature, + ); + + // Post-estado: la identidad quedó registrada para el caller. + assert!(Identities::::contains_key(&who)); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/crates/origin-chain-agent-identity/src/lib.rs b/crates/origin-chain-agent-identity/src/lib.rs index cb39dce..403e38a 100644 --- a/crates/origin-chain-agent-identity/src/lib.rs +++ b/crates/origin-chain-agent-identity/src/lib.rs @@ -52,6 +52,14 @@ 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; + /// Re-export canonical types desde origin-chain-types para consumers. pub use origin_chain_types::{AgentIdentity, AgentKind, Did}; @@ -127,6 +135,8 @@ pub mod pallet { use origin_chain_types::{AccountId, AgentChain, AgentIdentity, AgentKind, Did}; use sp_core::ed25519; use sp_runtime::traits::SaturatedConversion; + // Trait de weights en scope para resolver `T::WeightInfo::()`. + use crate::weights::WeightInfo; /// Pallet configuration trait canonical. /// @@ -142,6 +152,9 @@ pub mod pallet { /// Runtime event type canonical wired into `RuntimeEvent` aggregate. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information canonical para los extrinsics de este pallet. + type WeightInfo: crate::weights::WeightInfo; } /// Pallet definition canonical. @@ -248,7 +261,7 @@ pub mod pallet { /// 7. Insert storage canonical: `Identities` + `DidToAccount` /// 8. Emit `AgentRegistered` + `AgentBound` (if cross-species) canonical #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::register_agent_did())] pub fn register_agent_did( origin: OriginFor, public_key: [u8; 32], diff --git a/crates/origin-chain-agent-identity/src/mock.rs b/crates/origin-chain-agent-identity/src/mock.rs index 5b04bd3..d7c158e 100644 --- a/crates/origin-chain-agent-identity/src/mock.rs +++ b/crates/origin-chain-agent-identity/src/mock.rs @@ -29,6 +29,7 @@ impl frame_system::Config for Test { impl origin_chain_agent_identity::Config for Test { type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); } /// Test externalities builder canonical (pattern v0.0.18/v0.0.22 mirror). diff --git a/crates/origin-chain-agent-identity/src/weights.rs b/crates/origin-chain-agent-identity/src/weights.rs new file mode 100644 index 0000000..3c65f2a --- /dev/null +++ b/crates/origin-chain-agent-identity/src/weights.rs @@ -0,0 +1,44 @@ +//! # Weights para `pallet-agent-identity` +//! +//! ## Estado: INTERIM hand-derived (DB-aware, DoS-safe) — pendiente regeneración HW +//! +//! Ver `origin-chain-provenance/src/weights.rs` §Estado para el racional completo. +//! Números conservadores DB-aware vía `T::DbWeight` (cierran el DoS de proof_size=0 +//! del placeholder); `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-agent-identity`. +pub trait WeightInfo { + /// Peso de `register_agent_did` — 2-3 reads + verify Ed25519 + derive_did + 2 writes. + fn register_agent_did() -> Weight; +} + +/// Weights parametrizados por el `DbWeight` del runtime. +pub struct SubstrateWeight(PhantomData); + +impl WeightInfo for SubstrateWeight { + fn register_agent_did() -> Weight { + // Compute: Ed25519 verify (dominante) + derive_did (BLAKE3) + decode + + // build BoundedVec agent_chain. Storage: contains_key(Identities) + + // contains_key(DidToAccount) + block_number = 3 reads; insert(Identities) + // + insert(DidToAccount) = 2 writes. + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } +} + +/// Impl no-op para mocks / tests. +impl WeightInfo for () { + fn register_agent_did() -> Weight { + Weight::from_parts(50_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } +} diff --git a/crates/origin-chain-emission/Cargo.toml b/crates/origin-chain-emission/Cargo.toml index 3a30de7..a1ef750 100644 --- a/crates/origin-chain-emission/Cargo.toml +++ b/crates/origin-chain-emission/Cargo.toml @@ -21,6 +21,9 @@ sp-runtime = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] # Mock runtime canonical — pallet_balances para testear el minteo real. pallet-balances = { workspace = true, default-features = true } @@ -36,8 +39,11 @@ std = [ "sp-runtime/std", "parity-scale-codec/std", "scale-info/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/crates/origin-chain-emission/src/benchmarking.rs b/crates/origin-chain-emission/src/benchmarking.rs new file mode 100644 index 0000000..c17c593 --- /dev/null +++ b/crates/origin-chain-emission/src/benchmarking.rs @@ -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::::get()); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/crates/origin-chain-emission/src/lib.rs b/crates/origin-chain-emission/src/lib.rs index 286a192..881deff 100644 --- a/crates/origin-chain-emission/src/lib.rs +++ b/crates/origin-chain-emission/src/lib.rs @@ -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::()`. + 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; + + /// 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, 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; - - 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; - type AccountData = pallet_balances::AccountData; - } - - #[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 for MockFindAuthor { - fn find_author<'a, I>(_digests: I) -> Option - where - I: 'a + IntoIterator, - { - 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::::default() - .build_storage() - .expect("genesis builds"); - pallet_balances::GenesisConfig:: { - 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() { diff --git a/crates/origin-chain-emission/src/mock.rs b/crates/origin-chain-emission/src/mock.rs new file mode 100644 index 0000000..fd3c51a --- /dev/null +++ b/crates/origin-chain-emission/src/mock.rs @@ -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; + +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; + type AccountData = pallet_balances::AccountData; +} + +#[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 for MockFindAuthor { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + 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::::default() + .build_storage() + .expect("genesis builds"); + pallet_balances::GenesisConfig:: { + 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]) +} diff --git a/crates/origin-chain-emission/src/weights.rs b/crates/origin-chain-emission/src/weights.rs new file mode 100644 index 0000000..2c7bd04 --- /dev/null +++ b/crates/origin-chain-emission/src/weights.rs @@ -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(PhantomData); + +impl WeightInfo for SubstrateWeight { + 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) + } +} diff --git a/crates/origin-chain-fees/Cargo.toml b/crates/origin-chain-fees/Cargo.toml index b96729b..80ba57f 100644 --- a/crates/origin-chain-fees/Cargo.toml +++ b/crates/origin-chain-fees/Cargo.toml @@ -29,6 +29,9 @@ scale-info = { workspace = true } pallet-transaction-payment = { workspace = true } sp-runtime = { workspace = true } +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] # Mock runtime canonical para integration tests v0.0.22 — Pattern v0.0.18 mirror. # FIX-6 Stage 2a: + pallet-balances para los tests del OriginFeeAdapter @@ -48,8 +51,11 @@ std = [ "scale-info/std", "pallet-transaction-payment/std", "sp-runtime/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", diff --git a/crates/origin-chain-fees/src/benchmarking.rs b/crates/origin-chain-fees/src/benchmarking.rs new file mode 100644 index 0000000..362b33c --- /dev/null +++ b/crates/origin-chain-fees/src/benchmarking.rs @@ -0,0 +1,36 @@ +//! # Benchmarks para `pallet-origin-fees` (FRAME v2) +//! +//! Fuente de verdad de la forma del weight de `set_tier_profile`. Verificado por +//! `impl_benchmark_test_suite!` en el mock (`cargo test -p origin-chain-fees +//! --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_tier_profile() { + // Root asigna el TierProfile de una cuenta (insumo del fee unificado #16). + let who: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + set_tier_profile( + RawOrigin::Root, + who.clone(), + HardwareTier::Desktop, + SubscriptionTier::Free, + ); + + // Post-estado: el perfil quedó explícitamente asentado para la cuenta. + assert!(TierProfiles::::contains_key(&who)); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/crates/origin-chain-fees/src/lib.rs b/crates/origin-chain-fees/src/lib.rs index 76ab1e3..f08e2ab 100644 --- a/crates/origin-chain-fees/src/lib.rs +++ b/crates/origin-chain-fees/src/lib.rs @@ -47,6 +47,14 @@ 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; + /// Vendor copy temporal Mathi `OitFeeSchedule` (cross-workspace boundary). pub mod fee_schedule; @@ -121,6 +129,8 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use origin_chain_types::{ChainAction, HardwareTier, SubscriptionTier}; + // Trait de weights en scope para resolver `T::WeightInfo::()`. + use crate::weights::WeightInfo; /// Pallet configuration trait canonical. /// @@ -140,6 +150,9 @@ pub mod pallet { /// misma cuenta determinística. #[pallet::constant] type SubsidyPalletId: Get; + + /// Weight information canonical para los extrinsics de este pallet. + type WeightInfo: crate::weights::WeightInfo; } /// Pallet definition canonical. @@ -329,7 +342,7 @@ pub mod pallet { /// FIX-3/FIX-4), este dispatchable migra a verificación de attestation /// firmada en vez de Root fiat. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::set_tier_profile())] pub fn set_tier_profile( origin: OriginFor, who: T::AccountId, diff --git a/crates/origin-chain-fees/src/mock.rs b/crates/origin-chain-fees/src/mock.rs index 8cbde5d..9340dc3 100644 --- a/crates/origin-chain-fees/src/mock.rs +++ b/crates/origin-chain-fees/src/mock.rs @@ -87,6 +87,7 @@ parameter_types! { impl origin_chain_fees::Config for Test { type RuntimeEvent = RuntimeEvent; type SubsidyPalletId = MockSubsidyPalletId; + type WeightInfo = (); } /// Test externalities builder canonical (pattern v0.0.18 mirror). diff --git a/crates/origin-chain-fees/src/weights.rs b/crates/origin-chain-fees/src/weights.rs new file mode 100644 index 0000000..432e89a --- /dev/null +++ b/crates/origin-chain-fees/src/weights.rs @@ -0,0 +1,40 @@ +//! # Weights para `pallet-origin-fees` +//! +//! ## Estado: INTERIM hand-derived (DB-aware, DoS-safe) — pendiente regeneración HW +//! +//! Ver `origin-chain-provenance/src/weights.rs` §Estado para el racional completo. +//! Resumen: números conservadores derivados a mano (no autogenerados por CLI — +//! gated por sc-service/omni-bencher+WASM), pero **DB-aware** vía `T::DbWeight` +//! → pueblan ref_time + proof_size por read/write real, cerrando el DoS de los +//! placeholders `Weight::from_parts(10_000, 0)` (proof_size = 0, sin contar DB). +//! `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-fees`. +pub trait WeightInfo { + /// Peso de `set_tier_profile` (Root) — 1 write (TierProfiles). + fn set_tier_profile() -> Weight; +} + +/// Weights parametrizados por el `DbWeight` del runtime. +pub struct SubstrateWeight(PhantomData); + +impl WeightInfo for SubstrateWeight { + fn set_tier_profile() -> Weight { + // ensure_root (in-memory) + insert(TierProfiles) = 1 write. + Weight::from_parts(8_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +/// Impl no-op para mocks / tests. +impl WeightInfo for () { + fn set_tier_profile() -> Weight { + Weight::from_parts(8_000_000, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/crates/origin-chain-governance/Cargo.toml b/crates/origin-chain-governance/Cargo.toml index 240346d..42a8ef0 100644 --- a/crates/origin-chain-governance/Cargo.toml +++ b/crates/origin-chain-governance/Cargo.toml @@ -28,6 +28,9 @@ sp-runtime = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] # Mock runtime canonical para integration tests v0.0.30 — Pattern v0.0.26 mirror. sp-core = { workspace = true, default-features = true } @@ -47,8 +50,11 @@ std = [ "sp-runtime/std", "parity-scale-codec/std", "scale-info/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/crates/origin-chain-governance/src/benchmarking.rs b/crates/origin-chain-governance/src/benchmarking.rs new file mode 100644 index 0000000..0f8f638 --- /dev/null +++ b/crates/origin-chain-governance/src/benchmarking.rs @@ -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(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(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::::insert(id, proposal); +} + +/// Añade `who` al council (size=1) por storage directo. +fn seed_council(who: &T::AccountId) { + let member = CouncilMember { + account: AccountId::new([0u8; 32]), + joined_at_block: 0, + role: CouncilRole::Member, + }; + Council::::insert(who, member); + CouncilSize::::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::(&caller); + + #[extrinsic_call] + submit_proposal(RawOrigin::Signed(caller), [1u8; 32], [2u8; 32], 1_000u64, MIN_PROPOSAL_STAKE); + + assert_eq!(Proposals::::iter().count(), 1); + } + + #[benchmark] + fn cast_vote() { + let caller: T::AccountId = whitelisted_caller(); + let id = ProposalId([7u8; 32]); + seed_proposal::(id, ProposalStatus::Voting, 1_000_000u64); + + #[extrinsic_call] + cast_vote(RawOrigin::Signed(caller.clone()), id, VoteStance::Aye, 100u128); + + assert!(Votes::::contains_key(id, &caller)); + } + + #[benchmark] + fn finalize_proposal() { + let caller: T::AccountId = whitelisted_caller(); + let id = ProposalId([7u8; 32]); + seed_proposal::(id, ProposalStatus::Voting, 0u64); + // current_block debe ser > end_block(0) → avanza a 2. + frame_system::Pallet::::set_block_number(2u32.into()); + + #[extrinsic_call] + finalize_proposal(RawOrigin::Signed(caller), id); + + assert!(!matches!( + Proposals::::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::::contains_key(&acct)); + } + + #[benchmark] + fn remove_council_member() { + let acct: T::AccountId = account("member", 0, 0); + seed_council::(&acct); + + #[extrinsic_call] + remove_council_member(RawOrigin::Root, acct.clone()); + + assert!(!Council::::contains_key(&acct)); + } + + #[benchmark] + fn veto_proposal() { + let caller: T::AccountId = whitelisted_caller(); + seed_council::(&caller); + let id = ProposalId([7u8; 32]); + seed_proposal::(id, ProposalStatus::Voting, 1_000_000u64); + + #[extrinsic_call] + veto_proposal(RawOrigin::Signed(caller.clone()), id); + + assert!(VetoVotes::::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::::contains_key(&acct)); + } + + #[benchmark] + fn unmark_as_advisor() { + let acct: T::AccountId = account("advisor", 0, 0); + AdvisorFlags::::insert(&acct, ()); + + #[extrinsic_call] + unmark_as_advisor(RawOrigin::Root, acct.clone()); + + assert!(!AdvisorFlags::::contains_key(&acct)); + } + + #[benchmark] + fn override_council_veto() { + let caller: T::AccountId = whitelisted_caller(); + let id = ProposalId([7u8; 32]); + seed_proposal::(id, ProposalStatus::Rejected, 0u64); + VetoRejected::::insert(id, ()); + + #[extrinsic_call] + override_council_veto(RawOrigin::Signed(caller.clone()), id, 100u128); + + assert!(OverrideVotes::::contains_key(id, &caller)); + } + + #[benchmark] + fn submit_emergency_proposal() { + let caller: T::AccountId = whitelisted_caller(); + fund::(&caller); + seed_council::(&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::::insert(content_id, &caller, ()); + + #[extrinsic_call] + submit_emergency_proposal(RawOrigin::Signed(caller), EM_TITLE, EM_BODY, MIN_PROPOSAL_STAKE); + + assert!(Proposals::::iter().count() >= 1); + } + + #[benchmark] + fn treasury_disbursement() { + let id = ProposalId([7u8; 32]); + seed_proposal::(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::::treasury_account(), + MIN_PROPOSAL_STAKE.saturating_mul(10), + ); + + #[extrinsic_call] + treasury_disbursement(RawOrigin::Root, id, recipient, MIN_PROPOSAL_STAKE); + + assert!(Disbursed::::contains_key(id)); + } + + #[benchmark] + fn treasury_deposit() { + let caller: T::AccountId = whitelisted_caller(); + fund::(&caller); + + #[extrinsic_call] + treasury_deposit(RawOrigin::Signed(caller), MIN_PROPOSAL_STAKE); + + assert!(T::Currency::balance(&Pallet::::treasury_account()) >= MIN_PROPOSAL_STAKE); + } + + #[benchmark] + fn endorse_emergency_proposal() { + let caller: T::AccountId = whitelisted_caller(); + seed_council::(&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::::contains_key(content_id, &caller)); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/crates/origin-chain-governance/src/lib.rs b/crates/origin-chain-governance/src/lib.rs index 97b882e..55f5bfb 100644 --- a/crates/origin-chain-governance/src/lib.rs +++ b/crates/origin-chain-governance/src/lib.rs @@ -40,6 +40,14 @@ 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; + /// Re-export canonical types desde origin-chain-types para consumers. pub use origin_chain_types::{ProposalId, Vote, VoteStance}; @@ -124,6 +132,8 @@ pub mod pallet { use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::traits::{AccountIdConversion, SaturatedConversion}; + // Trait de weights en scope para resolver `T::WeightInfo::()`. + use crate::weights::WeightInfo; /// ProposalStatus canonical — proposal lifecycle stages canonical. #[derive( @@ -246,6 +256,9 @@ pub mod pallet { /// tesorería no depende de una llave humana custodiable. #[pallet::constant] type TreasuryPalletId: Get; + + /// Weight information canonical para los extrinsics de este pallet. + type WeightInfo: crate::weights::WeightInfo; } /// Pallet definition canonical. @@ -623,7 +636,7 @@ pub mod pallet { /// 7-step canonical flow: ensure_signed + validate_stake + derive_id + /// collision_check + build + insert + emit. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::submit_proposal())] pub fn submit_proposal( origin: OriginFor, title_hash: [u8; 32], @@ -683,7 +696,7 @@ pub mod pallet { /// CHN-4 5.4 canonical: `cast_vote` real dispatchable canonical. #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::cast_vote())] pub fn cast_vote( origin: OriginFor, proposal_id: ProposalId, @@ -746,7 +759,7 @@ pub mod pallet { /// CHN-4 canonical: `finalize_proposal` real dispatchable canonical. #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::finalize_proposal())] pub fn finalize_proposal( origin: OriginFor, proposal_id: ProposalId, @@ -799,7 +812,7 @@ pub mod pallet { /// 6-step canonical flow: ensure_root + size_check + no_duplicate + build + /// insert + emit. #[pallet::call_index(3)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::add_council_member())] pub fn add_council_member( origin: OriginFor, account: T::AccountId, @@ -843,7 +856,7 @@ pub mod pallet { /// /// 5-step canonical flow: ensure_root + validate_exists + remove + decrement + emit. #[pallet::call_index(4)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::remove_council_member())] pub fn remove_council_member( origin: OriginFor, account: T::AccountId, @@ -880,7 +893,7 @@ pub mod pallet { /// 7. Check threshold canonical: if count >= required → flip status to Rejected /// + emit ProposalVetoed canonical; else emit CouncilVetoVoteCast canonical #[pallet::call_index(5)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::veto_proposal())] pub fn veto_proposal( origin: OriginFor, proposal_id: ProposalId, @@ -964,7 +977,7 @@ pub mod pallet { /// Sentinel). Advisor canonical NO puede votar — humans-only vote canonical /// non-negotiable per CHN-4 + Inmutable #41. #[pallet::call_index(6)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::mark_as_advisor())] pub fn mark_as_advisor( origin: OriginFor, account: T::AccountId, @@ -981,7 +994,7 @@ pub mod pallet { /// CHN-4 5.6 v0.0.34 canonical: `unmark_as_advisor` Phase 5.6 dispatchable. #[pallet::call_index(7)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::unmark_as_advisor())] pub fn unmark_as_advisor( origin: OriginFor, account: T::AccountId, @@ -1012,7 +1025,7 @@ pub mod pallet { /// 6. Insert vote + increment total stake /// 7. Check threshold canonical 4/5: if >= → flip to Approved + emit CouncilVetoOverridden #[pallet::call_index(8)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::override_council_veto())] pub fn override_council_veto( origin: OriginFor, proposal_id: ProposalId, @@ -1084,7 +1097,7 @@ pub mod pallet { /// canonical 5/7 council pre-approval flow (gather signatures via separate /// dispatchable y luego promote canonical). #[pallet::call_index(9)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::submit_emergency_proposal())] pub fn submit_emergency_proposal( origin: OriginFor, title_hash: [u8; 32], @@ -1179,7 +1192,7 @@ pub mod pallet { /// 5. (Phase 5.9+ refinement canonical: transfer via pallet_balances integration) /// 6. Emit TreasuryDisbursed canonical #[pallet::call_index(10)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::treasury_disbursement())] pub fn treasury_disbursement( origin: OriginFor, proposal_id: ProposalId, @@ -1233,7 +1246,7 @@ pub mod pallet { /// canonical (BM V2 250M OGC Foundation allocation). Real Phase 5.9+ refinement /// canonical: replace canonical via Genesis storage canonical config. #[pallet::call_index(11)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::treasury_deposit())] pub fn treasury_deposit(origin: OriginFor, amount: u128) -> DispatchResult { // FIX-6: donación REAL a la tesorería — el caller transfiere `amount` // de su propio balance a la cuenta-tesorería. Cualquiera puede fondear @@ -1251,7 +1264,7 @@ pub mod pallet { /// reemplaza el gate 5/7 TAUTOLÓGICO anterior (que dejaba a UN solo miembro /// fast-trackear a 48h). #[pallet::call_index(12)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::endorse_emergency_proposal())] pub fn endorse_emergency_proposal( origin: OriginFor, title_hash: [u8; 32], diff --git a/crates/origin-chain-governance/src/mock.rs b/crates/origin-chain-governance/src/mock.rs index 1ffcd77..de5a4fd 100644 --- a/crates/origin-chain-governance/src/mock.rs +++ b/crates/origin-chain-governance/src/mock.rs @@ -43,6 +43,7 @@ impl origin_chain_governance::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type TreasuryPalletId = TestTreasuryPalletId; + type WeightInfo = (); } /// Balance inicial de cada cuenta de test. Audit 2026-07-04: debe ser diff --git a/crates/origin-chain-governance/src/weights.rs b/crates/origin-chain-governance/src/weights.rs new file mode 100644 index 0000000..fb43b04 --- /dev/null +++ b/crates/origin-chain-governance/src/weights.rs @@ -0,0 +1,103 @@ +//! # Weights para `pallet-origin-governance` +//! +//! ## Estado: INTERIM hand-derived (DB-aware, DoS-safe) — pendiente regeneración HW +//! +//! Ver `origin-chain-provenance/src/weights.rs` §Estado para el racional completo. +//! Los 13 extrinsics tenían `Weight::from_parts(10_000, 0)` (proof_size=0, sin +//! contar DB) → DoS. Aquí cada uno se cuesta con `T::DbWeight` sobre los reads/writes +//! REALES contados del código (incluyendo los `iter_prefix`/`clear_prefix` acotados +//! por el council ≤ 7). `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}; + +/// Compute floor conservador para la lógica de cada extrinsic (hashing blake2, +/// aritmética de umbrales, build de structs). Los que hashean content/proposal id +/// (submit_*, endorse) llevan un poco más vía el propio conteo de reads. +const COMPUTE: u64 = 12_000_000; + +/// Trait de weights para los 13 extrinsics de `pallet-origin-governance`. +pub trait WeightInfo { + /// `submit_proposal` — contains_key + bond transfer + 2 inserts. + fn submit_proposal() -> Weight; + /// `cast_vote` — advisor/proposal/vote reads + 2 inserts. + fn cast_vote() -> Weight; + /// `finalize_proposal` — proposal read/write + release bond. + fn finalize_proposal() -> Weight; + /// `add_council_member` — size/dup reads + 2 writes. + fn add_council_member() -> Weight; + /// `remove_council_member` — exists read + remove + size write. + fn remove_council_member() -> Weight; + /// `veto_proposal` — council iter (≤7) + threshold flip path. + fn veto_proposal() -> Weight; + /// `mark_as_advisor` — 1 read + 1 write. + fn mark_as_advisor() -> Weight; + /// `unmark_as_advisor` — 1 read + 1 write. + fn unmark_as_advisor() -> Weight; + /// `override_council_veto` — proposal/veto/advisor reads + vote inserts. + fn override_council_veto() -> Weight; + /// `submit_emergency_proposal` — endorsement iter (≤7) + bond + clear_prefix. + fn submit_emergency_proposal() -> Weight; + /// `treasury_disbursement` — proposal read + single-use + transfer. + fn treasury_disbursement() -> Weight; + /// `treasury_deposit` — transfer only. + fn treasury_deposit() -> Weight; + /// `endorse_emergency_proposal` — council iter (≤7) + insert. + fn endorse_emergency_proposal() -> Weight; +} + +macro_rules! w { + ($r:expr, $w:expr) => { + Weight::from_parts(COMPUTE, 0) + .saturating_add(T::DbWeight::get().reads($r as u64)) + .saturating_add(T::DbWeight::get().writes($w as u64)) + }; +} + +/// Weights parametrizados por el `DbWeight` del runtime. +pub struct SubstrateWeight(PhantomData); + +impl WeightInfo for SubstrateWeight { + fn submit_proposal() -> Weight { w!(3, 4) } + fn cast_vote() -> Weight { w!(4, 2) } + fn finalize_proposal() -> Weight { w!(3, 4) } + fn add_council_member() -> Weight { w!(3, 2) } + fn remove_council_member() -> Weight { w!(2, 2) } + fn veto_proposal() -> Weight { w!(10, 4) } + fn mark_as_advisor() -> Weight { w!(1, 1) } + fn unmark_as_advisor() -> Weight { w!(1, 1) } + fn override_council_veto() -> Weight { w!(5, 3) } + fn submit_emergency_proposal() -> Weight { w!(12, 11) } + fn treasury_disbursement() -> Weight { w!(4, 3) } + fn treasury_deposit() -> Weight { w!(2, 2) } + fn endorse_emergency_proposal() -> Weight { w!(10, 1) } +} + +macro_rules! wnop { + ($r:expr, $w:expr) => { + Weight::from_parts(COMPUTE, 0) + .saturating_add(RocksDbWeight::get().reads($r as u64)) + .saturating_add(RocksDbWeight::get().writes($w as u64)) + }; +} + +/// Impl no-op para mocks / tests. +impl WeightInfo for () { + fn submit_proposal() -> Weight { wnop!(3, 4) } + fn cast_vote() -> Weight { wnop!(4, 2) } + fn finalize_proposal() -> Weight { wnop!(3, 4) } + fn add_council_member() -> Weight { wnop!(3, 2) } + fn remove_council_member() -> Weight { wnop!(2, 2) } + fn veto_proposal() -> Weight { wnop!(10, 4) } + fn mark_as_advisor() -> Weight { wnop!(1, 1) } + fn unmark_as_advisor() -> Weight { wnop!(1, 1) } + fn override_council_veto() -> Weight { wnop!(5, 3) } + fn submit_emergency_proposal() -> Weight { wnop!(12, 11) } + fn treasury_disbursement() -> Weight { wnop!(4, 3) } + fn treasury_deposit() -> Weight { wnop!(2, 2) } + fn endorse_emergency_proposal() -> Weight { wnop!(10, 1) } +} diff --git a/crates/origin-chain-provenance/Cargo.toml b/crates/origin-chain-provenance/Cargo.toml index 3a81539..84b4392 100644 --- a/crates/origin-chain-provenance/Cargo.toml +++ b/crates/origin-chain-provenance/Cargo.toml @@ -33,6 +33,9 @@ sp-io = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +frame-benchmarking = { workspace = true, optional = true } + [dev-dependencies] # Mock runtime canonical para integration tests — PROV-3 E2E flow + golden # vectors ASP-3. ed25519-dalek solo aqui (fixtures); el runtime usa sp_io. @@ -52,8 +55,11 @@ std = [ "sp-io/std", "parity-scale-codec/std", "scale-info/std", + "frame-benchmarking?/std", ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/crates/origin-chain-provenance/src/benchmarking.rs b/crates/origin-chain-provenance/src/benchmarking.rs new file mode 100644 index 0000000..4eff73d --- /dev/null +++ b/crates/origin-chain-provenance/src/benchmarking.rs @@ -0,0 +1,131 @@ +//! # Benchmarks para `pallet-origin-provenance` (FRAME v2) +//! +//! Fuente de verdad de la **forma** del weight de cada extrinsic. Los números +//! canónicos se generan con `benchmark pallet` sobre HW de referencia (gated por +//! el node sc-service / omni-bencher+WASM, ver `weights.rs` §Estado); mientras +//! tanto, `SubstrateWeight` lleva números conservadores DB-aware. +//! +//! **Verificación sin HW**: `impl_benchmark_test_suite!` ejecuta cada benchmark +//! una vez dentro del mock runtime (`cargo test -p origin-chain-provenance +//! --features runtime-benchmarks`), probando que el harness corre y que el +//! `verify` post-estado se cumple — el mismo camino que el CLI recorrerá luego. +//! +//! ## `attest_asset` sin firmar en no_std +//! +//! El benchmark no puede firmar Ed25519 (corre en no_std/wasm). Reusa los **golden +//! vectors ASP-3** (payload postcard + firma constantes, keypair seed `[7u8;32]`, +//! genesis `0xAB×32`) que ya prueban el fee-path en `tests_integration.rs`. El +//! setup siembra `block_hash(0) = 0xAB×32` para que el dominio de la firma +//! coincida, exactamente como `mock::new_test_ext_with_genesis`. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_support::pallet_prelude::Decode; +use frame_support::BoundedVec; +// `sp-runtime` es dev-dep (no dep normal) → se accede vía el re-export de frame_support. +use frame_support::sp_runtime::traits::Zero; +use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::RawOrigin; + +use alloc::vec::Vec; + +/// Genesis de los golden vectors ASP-3 (`0xAB×32`). El pallet lee `block_hash(0)` +/// para el dominio de la firma; el setup lo siembra con este valor. +const GOLDEN_GENESIS: [u8; 32] = [0xABu8; 32]; + +/// Pubkey del validador golden (seed `[7u8;32]`). +const GOLDEN_PUBKEY: [u8; 32] = [ + 0xea, 0x4a, 0x6c, 0x63, 0xe2, 0x9c, 0x52, 0x0a, 0xbe, 0xf5, 0x50, 0x7b, 0x13, 0x2e, 0xc5, 0xf9, + 0x95, 0x47, 0x76, 0xae, 0xbe, 0xbe, 0x7b, 0x92, 0x42, 0x1e, 0xea, 0x69, 0x14, 0x46, 0xd2, 0x2c, +]; + +/// PAYLOAD1: asset `[1×32]`, atelier level 3, Passed/Info → ANCLA (golden ASP-3). +const PAYLOAD1: &str = "05312e302e300101010101010101010101010101010101010101010101010101010101010101146174656c6965725f63616e6f6e6963616c5f763103ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22cd285d8cc0400002a"; +/// SIG1: firma Ed25519 de PAYLOAD1 (dominio-separada con genesis `0xAB×32`). +const SIG1: &str = "e155043ad90df787d73e8ba4c3ee961427ecb228637ade77e19bd9b57bb727dcdf3c247462aa8e6facf5697887f3e08f96af1c5f2fdcab10fcfbd2daabe73204"; +/// Asset id anclado por PAYLOAD1 (para el `verify` post-estado). +const PAYLOAD1_ASSET_ID: [u8; 32] = [1u8; 32]; + +fn hexval(c: u8) -> u8 { + match c { + b'0'..=b'9' => c - b'0', + b'a'..=b'f' => c - b'a' + 10, + b'A'..=b'F' => c - b'A' + 10, + _ => 0, + } +} + +fn unhex(s: &str) -> Vec { + let bytes = s.as_bytes(); + let mut out = Vec::with_capacity(bytes.len() / 2); + let mut i = 0; + while i + 1 < bytes.len() { + out.push((hexval(bytes[i]) << 4) | hexval(bytes[i + 1])); + i += 2; + } + out +} + +fn to_sig(s: &str) -> [u8; 64] { + let v = unhex(s); + let mut out = [0u8; 64]; + out.copy_from_slice(&v[..64]); + out +} + +/// Siembra `block_hash(0) = 0xAB×32` para que la firma golden verifique. +/// Asume `T::Hash = H256` (mock + runtime real), que es lo que exigen los vectores. +fn seed_golden_genesis() { + let genesis: T::Hash = + Decode::decode(&mut &GOLDEN_GENESIS[..]).expect("H256 decodes from 32 bytes"); + frame_system::BlockHash::::insert(BlockNumberFor::::zero(), genesis); +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn attest_asset() { + let submitter: T::AccountId = whitelisted_caller(); + seed_golden_genesis::(); + // El validador golden debe estar registrado a nivel >= 3 (FIX-4 gate). + Pallet::::register_validator(RawOrigin::Root.into(), GOLDEN_PUBKEY, 3) + .expect("Root registra el validador golden"); + + let payload: BoundedVec = + BoundedVec::try_from(unhex(PAYLOAD1)).expect("payload dentro de la cota"); + let signature = to_sig(SIG1); + + #[extrinsic_call] + attest_asset(RawOrigin::Signed(submitter), payload, signature); + + // Post-estado: la attestation quedó anclada bajo su asset_id BLAKE3. + assert!(Attestations::::contains_key(AssetHash::new(PAYLOAD1_ASSET_ID))); + } + + #[benchmark] + fn register_validator() { + let pubkey = GOLDEN_PUBKEY; + + #[extrinsic_call] + register_validator(RawOrigin::Root, pubkey, 3u8); + + assert_eq!(AuthorizedValidators::::get(pubkey), Some(3u8)); + } + + #[benchmark] + fn revoke_validator() { + let pubkey = GOLDEN_PUBKEY; + AuthorizedValidators::::insert(pubkey, 3u8); + + #[extrinsic_call] + revoke_validator(RawOrigin::Root, pubkey); + + assert!(AuthorizedValidators::::get(pubkey).is_none()); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/crates/origin-chain-provenance/src/lib.rs b/crates/origin-chain-provenance/src/lib.rs index 37d9e26..b33ca79 100644 --- a/crates/origin-chain-provenance/src/lib.rs +++ b/crates/origin-chain-provenance/src/lib.rs @@ -63,6 +63,10 @@ pub use pallet::*; +/// Weights canonical (INTERIM hand-derived DB-aware — ver `weights.rs` §Estado). +pub mod weights; +pub use weights::WeightInfo; + /// Re-export canonical types desde origin-chain-types para consumers. pub use origin_chain_types::{ AssetAttestation, AssetHash, OitIdentity, SeverityTier, ValidationAttestation, @@ -107,6 +111,8 @@ pub mod pallet { use origin_chain_types::{ attestation_signed_message, decode_signing_payload, AssetAttestation, AssetHash, }; + // Trait de weights en scope para resolver `T::WeightInfo::()`. + use crate::weights::WeightInfo; /// Cota DoS del `signing_payload` postcard recibido (el real ~115 bytes; /// 512 es holgado para schema/identifier futuros sin permitir flooding). @@ -121,6 +127,11 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Weight information canonical para los extrinsics de este pallet. + /// El runtime lo cablea a `weights::SubstrateWeight`; los mocks + /// usan `()`. Ver `weights.rs` + `benchmarking.rs`. + type WeightInfo: crate::weights::WeightInfo; + // Future canonical (post OCAFB ratify): // type OitFeeSchedule: arkhe_amis_server::OitFeeSchedule; } @@ -281,7 +292,7 @@ pub mod pallet { /// con el `genesis_hash` de ESTA cadena (cierra replay cross-chain). /// 7. Insert `AssetAttestation` (creator = validador, block real) + evento. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(20_000, 0))] + #[pallet::weight(T::WeightInfo::attest_asset())] pub fn attest_asset( origin: OriginFor, signing_payload: BoundedVec, @@ -353,7 +364,7 @@ pub mod pallet { /// validadores que governance habilita pueden anclar. Sunset a /// attestation-driven cuando el registro OIT de Mathi aterrice. #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::register_validator())] pub fn register_validator( origin: OriginFor, public_key: [u8; 32], @@ -367,7 +378,7 @@ pub mod pallet { /// FIX-4: revoca un validador — ya no puede anclar. **Root-only.** #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(10_000, 0))] + #[pallet::weight(T::WeightInfo::revoke_validator())] pub fn revoke_validator( origin: OriginFor, public_key: [u8; 32], @@ -382,6 +393,11 @@ pub mod pallet { extern crate alloc; +/// Benchmarks canonical (FRAME v2) — solo bajo la feature `runtime-benchmarks`. +/// Verificados por `impl_benchmark_test_suite!` en el mock (ver `benchmarking.rs`). +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + // v0.0.18 — Mock runtime canonical + integration tests E2E PROV-3 flow. // Per audit canonical sesión 3+ (`AUDIT_CHARON_SESION_3_STATE_V1.md`), // own fixtures basados en vendor copy ValidationAttestation real existente diff --git a/crates/origin-chain-provenance/src/mock.rs b/crates/origin-chain-provenance/src/mock.rs index 2021610..9188fb5 100644 --- a/crates/origin-chain-provenance/src/mock.rs +++ b/crates/origin-chain-provenance/src/mock.rs @@ -33,6 +33,7 @@ impl frame_system::Config for Test { impl origin_chain_provenance::Config for Test { type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); } /// Genesis hash de los golden vectors ASP-3 (`/tmp/golden-asp3`, GENESIS=0xAB×32). diff --git a/crates/origin-chain-provenance/src/weights.rs b/crates/origin-chain-provenance/src/weights.rs new file mode 100644 index 0000000..2884d90 --- /dev/null +++ b/crates/origin-chain-provenance/src/weights.rs @@ -0,0 +1,87 @@ +//! # Weights para `pallet-origin-provenance` +//! +//! ## Estado: INTERIM hand-derived (DB-aware, DoS-safe) — pendiente regeneración HW +//! +//! Estos weights son **derivados a mano de forma conservadora**, NO autogenerados +//! por `frame-benchmarking-cli` sobre hardware de referencia todavía. La razón es +//! infraestructural, no de diseño: generar los números canónicos requiere ejecutar +//! el loop `benchmark pallet`, que necesita **o** el node `sc-service` real +//! (bloqueado por el mismatch de API sc-*/sp-consensus del tag stable2509-3, ver +//! `main.rs` §BLOQUEO) **o** `frame-omni-bencher` sobre el `WASM_BINARY` (gated tras +//! la feature `wasm-build`). Cuando cualquiera de los dos aterrice (VPS / HW de +//! referencia Pi5), regenerar es mecánico: los benchmarks de `benchmarking.rs` son +//! la **fuente de verdad de la forma** y sobrescribirán estos números. +//! +//! ## Por qué esto ya es una mejora de seguridad REAL +//! +//! Los placeholders anteriores eran `Weight::from_parts(10_000, 0)`: **proof_size = 0 +//! y sin contabilizar reads/writes de DB**. Eso es un vector de DoS — un atacante +//! llama extrinsics que tocan varias entradas de storage pagando casi nada, y el +//! `proof_size = 0` miente al block-builder sobre el peso PoV. Estos weights usan +//! `T::DbWeight` (RocksDbWeight en el runtime), que puebla **tanto ref_time como +//! proof_size** por cada read/write real contado del código — cerrando el hueco. +//! +//! El componente `from_parts(N, 0)` fijo cubre el compute (decode postcard, verify +//! Ed25519, hashing) con holgura conservadora; el `DbWeight` cubre el storage. + +#![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-provenance`. +pub trait WeightInfo { + /// Peso de `attest_asset` — decode postcard + 3-4 reads + verify Ed25519 + 1 write. + fn attest_asset() -> Weight; + /// Peso de `register_validator` (Root) — 1 write. + fn register_validator() -> Weight; + /// Peso de `revoke_validator` (Root) — 1 write (remove). + fn revoke_validator() -> Weight; +} + +/// Weights para `pallet-origin-provenance` parametrizados por el `DbWeight` del runtime. +/// +/// Se usa en el runtime como `SubstrateWeight` → los reads/writes se cuestan +/// con el `RocksDbWeight` real de la cadena, no con constantes hardcodeadas. +pub struct SubstrateWeight(PhantomData); + +impl WeightInfo for SubstrateWeight { + fn attest_asset() -> Weight { + // Compute conservador: decode postcard (~115 bytes) + Ed25519 verify + // (dominante, ~50µs en HW de referencia) + BLAKE-hashing del dominio. + // Storage: contains_key(Attestations) + get(AuthorizedValidators) + + // block_hash(0) + block_number = 4 reads; insert(Attestations) = 1 write. + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn register_validator() -> Weight { + // ensure_root (in-memory) + insert(AuthorizedValidators) = 1 write. + Weight::from_parts(8_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn revoke_validator() -> Weight { + // ensure_root (in-memory) + remove(AuthorizedValidators) = 1 write. + Weight::from_parts(8_000_000, 0).saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +/// Impl no-op para mocks / tests — usa `RocksDbWeight` directo (no requiere `T`). +impl WeightInfo for () { + fn attest_asset() -> Weight { + Weight::from_parts(50_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn register_validator() -> Weight { + Weight::from_parts(8_000_000, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn revoke_validator() -> Weight { + Weight::from_parts(8_000_000, 0).saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/crates/origin-chain-runtime/Cargo.toml b/crates/origin-chain-runtime/Cargo.toml index c5552b9..be24c79 100644 --- a/crates/origin-chain-runtime/Cargo.toml +++ b/crates/origin-chain-runtime/Cargo.toml @@ -32,6 +32,10 @@ frame-support.workspace = true frame-executive.workspace = true frame-metadata-hash-extension.workspace = true +# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks. +# Provee el runtime API `Benchmark` + los macros define/list/add_benchmarks. +frame-benchmarking = { workspace = true, optional = true } + # FRAME pallets Phase 1.3-1.5 (per SBS-4 ratified) pallet-balances.workspace = true pallet-timestamp.workspace = true @@ -154,13 +158,45 @@ std = [ "pallet-transaction-payment-rpc-runtime-api/std", "parity-scale-codec/std", "scale-info/std", + "frame-benchmarking?/std", # Slice 2c canonical deferred — substrate-wasm-builder bloqueado WASM build ] runtime-benchmarks = [ + "dep:frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", - "pallet-grandpa/try-runtime", + # Pallets upstream wired restantes. Necesarios para ALINEAR sus impls de traits + # benchmark-gated (p.ej. StakingUnchecked::migrate_to_direct_staker de + # pallet-staking, gated en sp-staking/runtime-benchmarks) con los traits upstream. + # La feature del runtime nunca fue comprehensiva — producción Substrate lista TODO. + "pallet-staking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-session/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", + # NOTA: `pallet-revive/runtime-benchmarks` OMITIDO a propósito — arrastra + # `pallet-revive-fixtures`, cuyo build script requiere el toolchain PolkaVM + # RISC-V (rv32/rv64) que no está disponible aquí y NO es benchmarkeado (ReviveApi + # deferred, ver impl_runtime_apis §SCAFFOLD). Su gating queda OFF de forma + # consistente → sin misalignment de traits. Re-activar cuando el toolchain PolkaVM + # esté en el reference HW y se quieran weights de contratos. + # Pallets custom Charon. TODOS deben propagar runtime-benchmarks para que sus + # impls de traits benchmark-gated (p.ej. OnChargeTransaction del OriginFeeAdapter + # en fees) alineen con los traits upstream. Provenance ya tiene maquinaria FRAME + # v2 (ver `mod benches`); los demás la reciben en el fan-out. + "origin-chain-provenance/runtime-benchmarks", + "origin-chain-fees/runtime-benchmarks", + "origin-chain-emission/runtime-benchmarks", + "origin-chain-agent-identity/runtime-benchmarks", + "origin-chain-governance/runtime-benchmarks", + # BUGFIX: antes decía `pallet-grandpa/try-runtime` — activar try-runtime desde + # la feature de benchmarks era un copy-paste erróneo (features ortogonales; + # metía código try-runtime en builds de benchmark y NUNCA activaba los + # benchmarks de grandpa). Grandpa no está en `define_benchmarks!`, así que la + # corrección es removerlo, no re-apuntarlo. ] diff --git a/crates/origin-chain-runtime/src/configs.rs b/crates/origin-chain-runtime/src/configs.rs index e3d2cb3..c25b623 100644 --- a/crates/origin-chain-runtime/src/configs.rs +++ b/crates/origin-chain-runtime/src/configs.rs @@ -427,6 +427,9 @@ impl pallet_session::Config for Runtime { impl origin_chain_provenance::Config for Runtime { type RuntimeEvent = RuntimeEvent; + // Weights INTERIM hand-derived DB-aware (cierra el DoS de proof_size=0 de los + // placeholders). Regenerable vía `benchmark pallet` cuando aterrice HW/omni-bencher. + type WeightInfo = origin_chain_provenance::weights::SubstrateWeight; } // ============================================================ @@ -449,6 +452,8 @@ impl origin_chain_fees::Config for Runtime { /// — pool-alimentación y pool-consumo apuntan a la misma cuenta og/subsy /// (jamás re-declarar el literal: un typo divergiría las cuentas). type SubsidyPalletId = EmissionSubsidyPalletId; + // Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW. + type WeightInfo = origin_chain_fees::weights::SubstrateWeight; } // ============================================================ @@ -466,6 +471,8 @@ impl origin_chain_fees::Config for Runtime { impl origin_chain_agent_identity::Config for Runtime { type RuntimeEvent = RuntimeEvent; + // Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW. + type WeightInfo = origin_chain_agent_identity::weights::SubstrateWeight; } // ============================================================ @@ -495,6 +502,8 @@ impl origin_chain_governance::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = crate::Balances; type TreasuryPalletId = GovernanceTreasuryPalletId; + // Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW. + type WeightInfo = origin_chain_governance::weights::SubstrateWeight; } // ============================================================ @@ -544,6 +553,8 @@ impl origin_chain_emission::Config for Runtime { // Reusa el MISMO vault de governance (og/gvtry) para el treasury. type TreasuryPalletId = GovernanceTreasuryPalletId; type SubsidyPalletId = EmissionSubsidyPalletId; + // Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW. + type WeightInfo = origin_chain_emission::weights::SubstrateWeight; } // ============================================================ diff --git a/crates/origin-chain-runtime/src/lib.rs b/crates/origin-chain-runtime/src/lib.rs index def3261..dec3e7c 100644 --- a/crates/origin-chain-runtime/src/lib.rs +++ b/crates/origin-chain-runtime/src/lib.rs @@ -258,7 +258,15 @@ pub const IMPL_NAME: &str = "origin-chain-charon"; /// 5/7 de emergencia real: nueva dispatchable `endorse_emergency_proposal` /// (call_index 12) + gate real (antes tautológico). Nuevas storage/errors/evento /// + 1 call aditiva ⇒ spec bump; TRANSACTION_VERSION intacto (call_index nuevo). -pub const RUNTIME_VERSION: u32 = 8; +/// +/// **v9 (benchmarking real machinery)**: los 19 extrinsics de los 5 pallets custom +/// pasan de weights placeholder `from_parts(10_000, 0)` (proof_size=0, sin contar +/// DB — DoS) a weights INTERIM hand-derived DB-aware vía `WeightInfo`. Los weights +/// son consenso-relevantes (afectan fee y llenado de bloque) ⇒ spec bump obligatorio. +/// Las FIRMAS de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto. La feature +/// `runtime-benchmarks` gana `define_benchmarks!` + runtime API `Benchmark` (inerte +/// sin la feature) + fix del bug `pallet-grandpa/try-runtime` en la feature. +pub const RUNTIME_VERSION: u32 = 9; /// Canonical authoring version per Substrate semver canonical. pub const AUTHORING_VERSION: u32 = 1; @@ -763,6 +771,52 @@ impl_runtime_apis! { } } + // ============================================================ + // frame_benchmarking::Benchmark — runtime API para `benchmark pallet` + // ============================================================ + // + // Solo bajo la feature `runtime-benchmarks`. Expone la maquinaria FRAME v2 de + // los pallets custom (ver `benches` mod + `define_benchmarks!`). La GENERACIÓN + // de números canónicos por CLI está gated por el node sc-service o + // omni-bencher+WASM (ambos bloqueados hace sesiones, ver `main.rs` §BLOQUEO y + // el §Estado de cada `weights.rs`); mientras tanto los `SubstrateWeight` llevan + // números conservadores DB-aware. Este impl compila y queda listo para que el + // loop de medición sea mecánico cuando la infra aterrice. + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata( + extra: bool, + ) -> ( + alloc::vec::Vec, + alloc::vec::Vec, + ) { + use frame_support::traits::StorageInfoTrait; + + let mut list = alloc::vec::Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig, + ) -> Result, alloc::string::String> + { + use frame_support::traits::TrackedStorageKey; + + // Whitelist vacía: cada benchmark siembra su propio estado (validador + // golden, etc.) y no depende de claves pre-existentes. + let whitelist: alloc::vec::Vec = alloc::vec::Vec::new(); + + let mut batches = alloc::vec::Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + // ============================================================ // Phase 3 Slice 3.1 SCAFFOLD — ReviveApi exposure DEFERRED // ============================================================ @@ -818,6 +872,26 @@ impl_runtime_apis! { */ } +// ============================================================ +// Registro de benchmarks canonical (frame_benchmarking::define_benchmarks!) +// ============================================================ +// +// Lista los pallets custom cuya maquinaria FRAME v2 se expone vía el runtime API +// `Benchmark`. Genera los macros `list_benchmarks!` + `add_benchmarks!` (ambos +// `#[macro_export]` → crate-global). Cada `[crate_path, PalletAlias]` mapea al +// alias del `#[runtime]` de arriba. Los pallets upstream (balances/timestamp/…) +// se benchmarkean con sus propios weights ya generados; aquí solo van los míos. +#[cfg(feature = "runtime-benchmarks")] +mod benches { + frame_benchmarking::define_benchmarks!( + [origin_chain_provenance, Provenance] + [origin_chain_fees, Fees] + [origin_chain_agent_identity, AgentIdentity] + [origin_chain_emission, Emission] + [origin_chain_governance, Governance] + ); +} + // ============================================================ // Runtime metadata canonical (RuntimeVersion + Executive) // ============================================================ @@ -851,13 +925,13 @@ mod tests { use super::*; #[test] - fn runtime_version_v8_governance_hardening() { - // v8 = hardening de governance (audit ultra 2026-07-04 + decisiones A/B): - // correctitud (override/treasury/veto) + bond de propuesta (A) + flujo 5/7 - // de emergencia real con endorse_emergency_proposal (B, call_index 12). - // Call aditiva + storage/metadata nuevos ⇒ spec bump; los extrinsics - // EXISTENTES no cambian formato ⇒ TRANSACTION_VERSION intacto. - assert_eq!(RUNTIME_VERSION, 8); + fn runtime_version_v9_benchmarking_weights() { + // v9 = benchmarking real machinery: los 19 extrinsics de los 5 pallets + // custom pasan de weights placeholder (proof_size=0, DoS) a weights INTERIM + // hand-derived DB-aware vía WeightInfo. Los weights son consenso-relevantes + // (fee + llenado de bloque) ⇒ spec bump. Las FIRMAS de los extrinsics NO + // cambian ⇒ TRANSACTION_VERSION intacto. + assert_eq!(RUNTIME_VERSION, 9); assert_eq!(TRANSACTION_VERSION, 1); }