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

Generated
+7
View File
@@ -6383,6 +6383,7 @@ name = "origin-chain-agent-identity"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"blake3", "blake3",
"frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"origin-chain-types", "origin-chain-types",
@@ -6413,6 +6414,7 @@ dependencies = [
name = "origin-chain-emission" name = "origin-chain-emission"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"pallet-balances", "pallet-balances",
@@ -6427,6 +6429,7 @@ dependencies = [
name = "origin-chain-fees" name = "origin-chain-fees"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"origin-chain-types", "origin-chain-types",
@@ -6444,6 +6447,7 @@ dependencies = [
name = "origin-chain-governance" name = "origin-chain-governance"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"origin-chain-types", "origin-chain-types",
@@ -6499,6 +6503,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"blake3", "blake3",
"ed25519-dalek", "ed25519-dalek",
"frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"origin-chain-types", "origin-chain-types",
@@ -6514,6 +6519,7 @@ dependencies = [
name = "origin-chain-runtime" name = "origin-chain-runtime"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-executive", "frame-executive",
"frame-metadata-hash-extension", "frame-metadata-hash-extension",
@@ -6789,6 +6795,7 @@ dependencies = [
"pallet-authorship", "pallet-authorship",
"pallet-session", "pallet-session",
"parity-scale-codec", "parity-scale-codec",
"rand_chacha 0.3.1",
"scale-info", "scale-info",
"serde", "serde",
"sp-application-crypto 43.0.0", "sp-application-crypto 43.0.0",
@@ -34,6 +34,9 @@ sp-io = { workspace = true }
parity-scale-codec = { workspace = true } parity-scale-codec = { workspace = true }
scale-info = { workspace = true } scale-info = { workspace = true }
# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks.
frame-benchmarking = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
# Mock runtime canonical para integration tests v0.0.26 — Pattern v0.0.18/v0.0.22 mirror. # Mock runtime canonical para integration tests v0.0.26 — Pattern v0.0.18/v0.0.22 mirror.
sp-core = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true }
@@ -52,8 +55,11 @@ std = [
"sp-runtime/std", "sp-runtime/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"frame-benchmarking?/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
] ]
@@ -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<u8> {
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::<T>::contains_key(&who));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+14 -1
View File
@@ -52,6 +52,14 @@
pub use pallet::*; 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. /// Re-export canonical types desde origin-chain-types para consumers.
pub use origin_chain_types::{AgentIdentity, AgentKind, Did}; 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 origin_chain_types::{AccountId, AgentChain, AgentIdentity, AgentKind, Did};
use sp_core::ed25519; use sp_core::ed25519;
use sp_runtime::traits::SaturatedConversion; use sp_runtime::traits::SaturatedConversion;
// Trait de weights en scope para resolver `T::WeightInfo::<method>()`.
use crate::weights::WeightInfo;
/// Pallet configuration trait canonical. /// Pallet configuration trait canonical.
/// ///
@@ -142,6 +152,9 @@ pub mod pallet {
/// Runtime event type canonical wired into `RuntimeEvent` aggregate. /// Runtime event type canonical wired into `RuntimeEvent` aggregate.
type RuntimeEvent: From<Event<Self>> type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>; + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Weight information canonical para los extrinsics de este pallet.
type WeightInfo: crate::weights::WeightInfo;
} }
/// Pallet definition canonical. /// Pallet definition canonical.
@@ -248,7 +261,7 @@ pub mod pallet {
/// 7. Insert storage canonical: `Identities` + `DidToAccount` /// 7. Insert storage canonical: `Identities` + `DidToAccount`
/// 8. Emit `AgentRegistered` + `AgentBound` (if cross-species) canonical /// 8. Emit `AgentRegistered` + `AgentBound` (if cross-species) canonical
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::register_agent_did())]
pub fn register_agent_did( pub fn register_agent_did(
origin: OriginFor<T>, origin: OriginFor<T>,
public_key: [u8; 32], public_key: [u8; 32],
@@ -29,6 +29,7 @@ impl frame_system::Config for Test {
impl origin_chain_agent_identity::Config for Test { impl origin_chain_agent_identity::Config for Test {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
} }
/// Test externalities builder canonical (pattern v0.0.18/v0.0.22 mirror). /// Test externalities builder canonical (pattern v0.0.18/v0.0.22 mirror).
@@ -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<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
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))
}
}
+6
View File
@@ -21,6 +21,9 @@ sp-runtime = { workspace = true }
parity-scale-codec = { workspace = true } parity-scale-codec = { workspace = true }
scale-info = { workspace = true } scale-info = { workspace = true }
# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks.
frame-benchmarking = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
# Mock runtime canonical — pallet_balances para testear el minteo real. # Mock runtime canonical — pallet_balances para testear el minteo real.
pallet-balances = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true }
@@ -36,8 +39,11 @@ std = [
"sp-runtime/std", "sp-runtime/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"frame-benchmarking?/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
] ]
@@ -0,0 +1,30 @@
//! # Benchmarks para `pallet-origin-emission` (FRAME v2)
//!
//! Fuente de verdad de la forma del weight de `set_emission_paused` (kill-switch
//! de governance). Verificado por `impl_benchmark_test_suite!` en el mock
//! (`cargo test -p origin-chain-emission --features runtime-benchmarks`). Números
//! canónicos gated por HW/omni-bencher (ver `weights.rs` §Estado).
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn set_emission_paused() {
// `EmissionPaused` arranca en `false` (default); pausar (`true`) es un
// cambio real → pasa el guard anti-no-op del extrinsic.
#[extrinsic_call]
set_emission_paused(RawOrigin::Root, true);
// Post-estado: la emisión quedó pausada.
assert!(EmissionPaused::<T>::get());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+25 -94
View File
@@ -51,6 +51,18 @@
pub use pallet::*; 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; extern crate alloc;
#[frame_support::pallet] #[frame_support::pallet]
@@ -62,6 +74,8 @@ pub mod pallet {
use frame_support::PalletId; use frame_support::PalletId;
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use sp_runtime::traits::AccountIdConversion; 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). /// StorageVersion en código (FIX-2 lifecycle): arranca en 0 (genesis).
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); 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). /// PalletId del subsidy pool (proof-of-equity, Inmutable #16 anti-clasista).
#[pallet::constant] #[pallet::constant]
type SubsidyPalletId: Get<PalletId>; type SubsidyPalletId: Get<PalletId>;
/// Weight information canonical para los extrinsics de este pallet.
type WeightInfo: crate::weights::WeightInfo;
} }
/// Pallet definition. /// Pallet definition.
@@ -291,7 +308,7 @@ pub mod pallet {
/// todo cambio de governance quede como evento observable on-chain. /// todo cambio de governance quede como evento observable on-chain.
/// `TotalEmitted` se preserva — pausar NO reescribe el histórico. /// `TotalEmitted` se preserva — pausar NO reescribe el histórico.
#[pallet::call_index(0)] #[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 { pub fn set_emission_paused(origin: OriginFor<T>, paused: bool) -> DispatchResult {
frame_system::ensure_root(origin)?; frame_system::ensure_root(origin)?;
ensure!( ensure!(
@@ -326,100 +343,14 @@ pub mod pallet {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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::fungible::Inspect;
use frame_support::traits::{FindAuthor, Hooks}; use frame_support::traits::Hooks;
use frame_support::{ use frame_support::{assert_noop, assert_ok};
assert_noop, assert_ok, construct_runtime, derive_impl, parameter_types, PalletId, use sp_runtime::{AccountId32, DispatchError};
};
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])
}
#[test] #[test]
fn block_reward_released_and_split_correctly() { fn block_reward_released_and_split_correctly() {
+104
View File
@@ -0,0 +1,104 @@
//! # Mock runtime para tests + benchmark test-suite de `pallet-origin-emission`
//!
//! Extraído del inline `mod tests` (v0.0.x) a `pub mod mock` para que
//! `impl_benchmark_test_suite!` (benchmarking.rs) pueda reusar `Test` +
//! `new_test_ext` — mismo patrón que provenance/fees/agent-identity. Los tests de
//! `lib.rs` importan de aquí (`use crate::mock::*`).
#![cfg(test)]
use crate as origin_chain_emission;
use frame_support::traits::FindAuthor;
use frame_support::{construct_runtime, derive_impl, parameter_types, PalletId};
use sp_runtime::traits::AccountIdConversion;
use sp_runtime::{AccountId32, BuildStorage};
/// Mock block type.
pub type Block = frame_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test {
System: frame_system,
Balances: pallet_balances,
Emission: origin_chain_emission,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
type AccountId = AccountId32;
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
type AccountData = pallet_balances::AccountData<u128>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = u128;
}
/// FindAuthor de prueba: devuelve siempre ALICE como autor del bloque.
pub struct MockFindAuthor;
impl FindAuthor<AccountId32> for MockFindAuthor {
fn find_author<'a, I>(_digests: I) -> Option<AccountId32>
where
I: 'a + IntoIterator<Item = (sp_runtime::ConsensusEngineId, &'a [u8])>,
{
Some(AccountId32::new([1u8; 32]))
}
}
parameter_types! {
pub const BlockRewardCfg: u128 = 1_000;
pub const AuthorShareBpsCfg: u16 = 7_000; // 70%
pub const TreasuryShareBpsCfg: u16 = 2_000; // 20% (subsidy = 10% residual)
pub const EcosystemPid: PalletId = PalletId(*b"og/ecosy");
pub const TreasuryPid: PalletId = PalletId(*b"og/gvtry");
pub const SubsidyPid: PalletId = PalletId(*b"og/subsy");
}
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type FindAuthor = MockFindAuthor;
type BlockReward = BlockRewardCfg;
type AuthorShareBps = AuthorShareBpsCfg;
type TreasuryShareBps = TreasuryShareBpsCfg;
type EcosystemPalletId = EcosystemPid;
type TreasuryPalletId = TreasuryPid;
type SubsidyPalletId = SubsidyPid;
type WeightInfo = ();
}
/// Cuenta del pool Ecosystem (fuente finita de la emisión).
pub fn ecosystem_account() -> AccountId32 {
EcosystemPid::get().into_account_truncating()
}
/// Fondea el pool Ecosystem con `pool_balance` en génesis (la fuente finita
/// de la que se libera; sin esto, transfer no mueve nada — techo duro).
pub fn new_test_ext_with_pool(pool_balance: u128) -> sp_io::TestExternalities {
let mut storage = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.expect("genesis builds");
pallet_balances::GenesisConfig::<Test> {
balances: vec![(ecosystem_account(), pool_balance)],
..Default::default()
}
.assimilate_storage(&mut storage)
.expect("balances genesis");
let mut ext: sp_io::TestExternalities = storage.into();
ext.execute_with(|| System::set_block_number(1));
ext
}
/// Default: pool amplio (1M) — cubre miles de bloques sin agotarse.
pub fn new_test_ext() -> sp_io::TestExternalities {
new_test_ext_with_pool(1_000_000)
}
/// Autor del bloque (ALICE) usado por los tests de split.
pub fn author() -> AccountId32 {
AccountId32::new([1u8; 32])
}
@@ -0,0 +1,40 @@
//! # Weights para `pallet-origin-emission`
//!
//! ## Estado: INTERIM hand-derived (DB-aware) — pendiente regeneración HW
//!
//! Ver `origin-chain-provenance/src/weights.rs` §Estado. Nota: `set_emission_paused`
//! ya era DB-aware antes de esta migración (usaba `T::DbWeight::get().reads_writes(1,1)`
//! directo — el ÚNICO extrinsic del chain que nunca fue DoS-prone). Aquí se mueve
//! ese mismo cálculo detrás del trait `WeightInfo` por consistencia con los demás
//! pallets + para exponerlo a `benchmark pallet`. `benchmarking.rs` es la fuente
//! de verdad de la forma.
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(clippy::unnecessary_cast)]
use core::marker::PhantomData;
use frame_support::{traits::Get, weights::constants::RocksDbWeight, weights::Weight};
/// Trait de weights para los extrinsics de `pallet-origin-emission`.
pub trait WeightInfo {
/// Peso de `set_emission_paused` (Root) — 1 read (guard no-op) + 1 write.
fn set_emission_paused() -> Weight;
}
/// Weights parametrizados por el `DbWeight` del runtime.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
fn set_emission_paused() -> Weight {
// ensure_root + get(EmissionPaused) [guard no-op] + put(EmissionPaused).
T::DbWeight::get().reads_writes(1, 1)
}
}
/// Impl no-op para mocks / tests.
impl WeightInfo for () {
fn set_emission_paused() -> Weight {
RocksDbWeight::get().reads_writes(1, 1)
}
}
+6
View File
@@ -29,6 +29,9 @@ scale-info = { workspace = true }
pallet-transaction-payment = { workspace = true } pallet-transaction-payment = { workspace = true }
sp-runtime = { workspace = true } sp-runtime = { workspace = true }
# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks.
frame-benchmarking = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
# Mock runtime canonical para integration tests v0.0.22 — Pattern v0.0.18 mirror. # 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 # FIX-6 Stage 2a: + pallet-balances para los tests del OriginFeeAdapter
@@ -48,8 +51,11 @@ std = [
"scale-info/std", "scale-info/std",
"pallet-transaction-payment/std", "pallet-transaction-payment/std",
"sp-runtime/std", "sp-runtime/std",
"frame-benchmarking?/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks",
@@ -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::<T>::contains_key(&who));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+14 -1
View File
@@ -47,6 +47,14 @@
pub use pallet::*; 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). /// Vendor copy temporal Mathi `OitFeeSchedule` (cross-workspace boundary).
pub mod fee_schedule; pub mod fee_schedule;
@@ -121,6 +129,8 @@ pub mod pallet {
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use origin_chain_types::{ChainAction, HardwareTier, SubscriptionTier}; use origin_chain_types::{ChainAction, HardwareTier, SubscriptionTier};
// Trait de weights en scope para resolver `T::WeightInfo::<method>()`.
use crate::weights::WeightInfo;
/// Pallet configuration trait canonical. /// Pallet configuration trait canonical.
/// ///
@@ -140,6 +150,9 @@ pub mod pallet {
/// misma cuenta determinística. /// misma cuenta determinística.
#[pallet::constant] #[pallet::constant]
type SubsidyPalletId: Get<frame_support::PalletId>; type SubsidyPalletId: Get<frame_support::PalletId>;
/// Weight information canonical para los extrinsics de este pallet.
type WeightInfo: crate::weights::WeightInfo;
} }
/// Pallet definition canonical. /// Pallet definition canonical.
@@ -329,7 +342,7 @@ pub mod pallet {
/// FIX-3/FIX-4), este dispatchable migra a verificación de attestation /// FIX-3/FIX-4), este dispatchable migra a verificación de attestation
/// firmada en vez de Root fiat. /// firmada en vez de Root fiat.
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::set_tier_profile())]
pub fn set_tier_profile( pub fn set_tier_profile(
origin: OriginFor<T>, origin: OriginFor<T>,
who: T::AccountId, who: T::AccountId,
+1
View File
@@ -87,6 +87,7 @@ parameter_types! {
impl origin_chain_fees::Config for Test { impl origin_chain_fees::Config for Test {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type SubsidyPalletId = MockSubsidyPalletId; type SubsidyPalletId = MockSubsidyPalletId;
type WeightInfo = ();
} }
/// Test externalities builder canonical (pattern v0.0.18 mirror). /// Test externalities builder canonical (pattern v0.0.18 mirror).
+40
View File
@@ -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<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
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))
}
}
@@ -28,6 +28,9 @@ sp-runtime = { workspace = true }
parity-scale-codec = { workspace = true } parity-scale-codec = { workspace = true }
scale-info = { workspace = true } scale-info = { workspace = true }
# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks.
frame-benchmarking = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
# Mock runtime canonical para integration tests v0.0.30 — Pattern v0.0.26 mirror. # Mock runtime canonical para integration tests v0.0.30 — Pattern v0.0.26 mirror.
sp-core = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true }
@@ -47,8 +50,11 @@ std = [
"sp-runtime/std", "sp-runtime/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"frame-benchmarking?/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
] ]
@@ -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);
}
+26 -13
View File
@@ -40,6 +40,14 @@
pub use pallet::*; 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. /// Re-export canonical types desde origin-chain-types para consumers.
pub use origin_chain_types::{ProposalId, Vote, VoteStance}; pub use origin_chain_types::{ProposalId, Vote, VoteStance};
@@ -124,6 +132,8 @@ pub mod pallet {
use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_runtime::traits::{AccountIdConversion, SaturatedConversion}; use sp_runtime::traits::{AccountIdConversion, SaturatedConversion};
// Trait de weights en scope para resolver `T::WeightInfo::<method>()`.
use crate::weights::WeightInfo;
/// ProposalStatus canonical — proposal lifecycle stages canonical. /// ProposalStatus canonical — proposal lifecycle stages canonical.
#[derive( #[derive(
@@ -246,6 +256,9 @@ pub mod pallet {
/// tesorería no depende de una llave humana custodiable. /// tesorería no depende de una llave humana custodiable.
#[pallet::constant] #[pallet::constant]
type TreasuryPalletId: Get<PalletId>; type TreasuryPalletId: Get<PalletId>;
/// Weight information canonical para los extrinsics de este pallet.
type WeightInfo: crate::weights::WeightInfo;
} }
/// Pallet definition canonical. /// Pallet definition canonical.
@@ -623,7 +636,7 @@ pub mod pallet {
/// 7-step canonical flow: ensure_signed + validate_stake + derive_id + /// 7-step canonical flow: ensure_signed + validate_stake + derive_id +
/// collision_check + build + insert + emit. /// collision_check + build + insert + emit.
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::submit_proposal())]
pub fn submit_proposal( pub fn submit_proposal(
origin: OriginFor<T>, origin: OriginFor<T>,
title_hash: [u8; 32], title_hash: [u8; 32],
@@ -683,7 +696,7 @@ pub mod pallet {
/// CHN-4 5.4 canonical: `cast_vote` real dispatchable canonical. /// CHN-4 5.4 canonical: `cast_vote` real dispatchable canonical.
#[pallet::call_index(1)] #[pallet::call_index(1)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::cast_vote())]
pub fn cast_vote( pub fn cast_vote(
origin: OriginFor<T>, origin: OriginFor<T>,
proposal_id: ProposalId, proposal_id: ProposalId,
@@ -746,7 +759,7 @@ pub mod pallet {
/// CHN-4 canonical: `finalize_proposal` real dispatchable canonical. /// CHN-4 canonical: `finalize_proposal` real dispatchable canonical.
#[pallet::call_index(2)] #[pallet::call_index(2)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::finalize_proposal())]
pub fn finalize_proposal( pub fn finalize_proposal(
origin: OriginFor<T>, origin: OriginFor<T>,
proposal_id: ProposalId, proposal_id: ProposalId,
@@ -799,7 +812,7 @@ pub mod pallet {
/// 6-step canonical flow: ensure_root + size_check + no_duplicate + build + /// 6-step canonical flow: ensure_root + size_check + no_duplicate + build +
/// insert + emit. /// insert + emit.
#[pallet::call_index(3)] #[pallet::call_index(3)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::add_council_member())]
pub fn add_council_member( pub fn add_council_member(
origin: OriginFor<T>, origin: OriginFor<T>,
account: T::AccountId, account: T::AccountId,
@@ -843,7 +856,7 @@ pub mod pallet {
/// ///
/// 5-step canonical flow: ensure_root + validate_exists + remove + decrement + emit. /// 5-step canonical flow: ensure_root + validate_exists + remove + decrement + emit.
#[pallet::call_index(4)] #[pallet::call_index(4)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::remove_council_member())]
pub fn remove_council_member( pub fn remove_council_member(
origin: OriginFor<T>, origin: OriginFor<T>,
account: T::AccountId, account: T::AccountId,
@@ -880,7 +893,7 @@ pub mod pallet {
/// 7. Check threshold canonical: if count >= required → flip status to Rejected /// 7. Check threshold canonical: if count >= required → flip status to Rejected
/// + emit ProposalVetoed canonical; else emit CouncilVetoVoteCast canonical /// + emit ProposalVetoed canonical; else emit CouncilVetoVoteCast canonical
#[pallet::call_index(5)] #[pallet::call_index(5)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::veto_proposal())]
pub fn veto_proposal( pub fn veto_proposal(
origin: OriginFor<T>, origin: OriginFor<T>,
proposal_id: ProposalId, proposal_id: ProposalId,
@@ -964,7 +977,7 @@ pub mod pallet {
/// Sentinel). Advisor canonical NO puede votar — humans-only vote canonical /// Sentinel). Advisor canonical NO puede votar — humans-only vote canonical
/// non-negotiable per CHN-4 + Inmutable #41. /// non-negotiable per CHN-4 + Inmutable #41.
#[pallet::call_index(6)] #[pallet::call_index(6)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::mark_as_advisor())]
pub fn mark_as_advisor( pub fn mark_as_advisor(
origin: OriginFor<T>, origin: OriginFor<T>,
account: T::AccountId, 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. /// CHN-4 5.6 v0.0.34 canonical: `unmark_as_advisor` Phase 5.6 dispatchable.
#[pallet::call_index(7)] #[pallet::call_index(7)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::unmark_as_advisor())]
pub fn unmark_as_advisor( pub fn unmark_as_advisor(
origin: OriginFor<T>, origin: OriginFor<T>,
account: T::AccountId, account: T::AccountId,
@@ -1012,7 +1025,7 @@ pub mod pallet {
/// 6. Insert vote + increment total stake /// 6. Insert vote + increment total stake
/// 7. Check threshold canonical 4/5: if >= → flip to Approved + emit CouncilVetoOverridden /// 7. Check threshold canonical 4/5: if >= → flip to Approved + emit CouncilVetoOverridden
#[pallet::call_index(8)] #[pallet::call_index(8)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::override_council_veto())]
pub fn override_council_veto( pub fn override_council_veto(
origin: OriginFor<T>, origin: OriginFor<T>,
proposal_id: ProposalId, proposal_id: ProposalId,
@@ -1084,7 +1097,7 @@ pub mod pallet {
/// canonical 5/7 council pre-approval flow (gather signatures via separate /// canonical 5/7 council pre-approval flow (gather signatures via separate
/// dispatchable y luego promote canonical). /// dispatchable y luego promote canonical).
#[pallet::call_index(9)] #[pallet::call_index(9)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::submit_emergency_proposal())]
pub fn submit_emergency_proposal( pub fn submit_emergency_proposal(
origin: OriginFor<T>, origin: OriginFor<T>,
title_hash: [u8; 32], title_hash: [u8; 32],
@@ -1179,7 +1192,7 @@ pub mod pallet {
/// 5. (Phase 5.9+ refinement canonical: transfer via pallet_balances integration) /// 5. (Phase 5.9+ refinement canonical: transfer via pallet_balances integration)
/// 6. Emit TreasuryDisbursed canonical /// 6. Emit TreasuryDisbursed canonical
#[pallet::call_index(10)] #[pallet::call_index(10)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::treasury_disbursement())]
pub fn treasury_disbursement( pub fn treasury_disbursement(
origin: OriginFor<T>, origin: OriginFor<T>,
proposal_id: ProposalId, proposal_id: ProposalId,
@@ -1233,7 +1246,7 @@ pub mod pallet {
/// canonical (BM V2 250M OGC Foundation allocation). Real Phase 5.9+ refinement /// canonical (BM V2 250M OGC Foundation allocation). Real Phase 5.9+ refinement
/// canonical: replace canonical via Genesis storage canonical config. /// canonical: replace canonical via Genesis storage canonical config.
#[pallet::call_index(11)] #[pallet::call_index(11)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::treasury_deposit())]
pub fn treasury_deposit(origin: OriginFor<T>, amount: u128) -> DispatchResult { pub fn treasury_deposit(origin: OriginFor<T>, amount: u128) -> DispatchResult {
// FIX-6: donación REAL a la tesorería — el caller transfiere `amount` // 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 // 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 /// reemplaza el gate 5/7 TAUTOLÓGICO anterior (que dejaba a UN solo miembro
/// fast-trackear a 48h). /// fast-trackear a 48h).
#[pallet::call_index(12)] #[pallet::call_index(12)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::endorse_emergency_proposal())]
pub fn endorse_emergency_proposal( pub fn endorse_emergency_proposal(
origin: OriginFor<T>, origin: OriginFor<T>,
title_hash: [u8; 32], title_hash: [u8; 32],
@@ -43,6 +43,7 @@ impl origin_chain_governance::Config for Test {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type Currency = Balances; type Currency = Balances;
type TreasuryPalletId = TestTreasuryPalletId; type TreasuryPalletId = TestTreasuryPalletId;
type WeightInfo = ();
} }
/// Balance inicial de cada cuenta de test. Audit 2026-07-04: debe ser /// Balance inicial de cada cuenta de test. Audit 2026-07-04: debe ser
@@ -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<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
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) }
}
@@ -33,6 +33,9 @@ sp-io = { workspace = true }
parity-scale-codec = { workspace = true } parity-scale-codec = { workspace = true }
scale-info = { workspace = true } scale-info = { workspace = true }
# Benchmarking canonical (FRAME v2) — optional, solo activo bajo runtime-benchmarks.
frame-benchmarking = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
# Mock runtime canonical para integration tests — PROV-3 E2E flow + golden # Mock runtime canonical para integration tests — PROV-3 E2E flow + golden
# vectors ASP-3. ed25519-dalek solo aqui (fixtures); el runtime usa sp_io. # vectors ASP-3. ed25519-dalek solo aqui (fixtures); el runtime usa sp_io.
@@ -52,8 +55,11 @@ std = [
"sp-io/std", "sp-io/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"frame-benchmarking?/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
] ]
@@ -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<u8> {
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<T: Config>() {
let genesis: T::Hash =
Decode::decode(&mut &GOLDEN_GENESIS[..]).expect("H256 decodes from 32 bytes");
frame_system::BlockHash::<T>::insert(BlockNumberFor::<T>::zero(), genesis);
}
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn attest_asset() {
let submitter: T::AccountId = whitelisted_caller();
seed_golden_genesis::<T>();
// El validador golden debe estar registrado a nivel >= 3 (FIX-4 gate).
Pallet::<T>::register_validator(RawOrigin::Root.into(), GOLDEN_PUBKEY, 3)
.expect("Root registra el validador golden");
let payload: BoundedVec<u8, MaxPayloadLen> =
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::<T>::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::<T>::get(pubkey), Some(3u8));
}
#[benchmark]
fn revoke_validator() {
let pubkey = GOLDEN_PUBKEY;
AuthorizedValidators::<T>::insert(pubkey, 3u8);
#[extrinsic_call]
revoke_validator(RawOrigin::Root, pubkey);
assert!(AuthorizedValidators::<T>::get(pubkey).is_none());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+19 -3
View File
@@ -63,6 +63,10 @@
pub use pallet::*; 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. /// Re-export canonical types desde origin-chain-types para consumers.
pub use origin_chain_types::{ pub use origin_chain_types::{
AssetAttestation, AssetHash, OitIdentity, SeverityTier, ValidationAttestation, AssetAttestation, AssetHash, OitIdentity, SeverityTier, ValidationAttestation,
@@ -107,6 +111,8 @@ pub mod pallet {
use origin_chain_types::{ use origin_chain_types::{
attestation_signed_message, decode_signing_payload, AssetAttestation, AssetHash, attestation_signed_message, decode_signing_payload, AssetAttestation, AssetHash,
}; };
// Trait de weights en scope para resolver `T::WeightInfo::<method>()`.
use crate::weights::WeightInfo;
/// Cota DoS del `signing_payload` postcard recibido (el real ~115 bytes; /// Cota DoS del `signing_payload` postcard recibido (el real ~115 bytes;
/// 512 es holgado para schema/identifier futuros sin permitir flooding). /// 512 es holgado para schema/identifier futuros sin permitir flooding).
@@ -121,6 +127,11 @@ pub mod pallet {
type RuntimeEvent: From<Event<Self>> type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>; + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Weight information canonical para los extrinsics de este pallet.
/// El runtime lo cablea a `weights::SubstrateWeight<Runtime>`; los mocks
/// usan `()`. Ver `weights.rs` + `benchmarking.rs`.
type WeightInfo: crate::weights::WeightInfo;
// Future canonical (post OCAFB ratify): // Future canonical (post OCAFB ratify):
// type OitFeeSchedule: arkhe_amis_server::OitFeeSchedule; // type OitFeeSchedule: arkhe_amis_server::OitFeeSchedule;
} }
@@ -281,7 +292,7 @@ pub mod pallet {
/// con el `genesis_hash` de ESTA cadena (cierra replay cross-chain). /// con el `genesis_hash` de ESTA cadena (cierra replay cross-chain).
/// 7. Insert `AssetAttestation` (creator = validador, block real) + evento. /// 7. Insert `AssetAttestation` (creator = validador, block real) + evento.
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(20_000, 0))] #[pallet::weight(T::WeightInfo::attest_asset())]
pub fn attest_asset( pub fn attest_asset(
origin: OriginFor<T>, origin: OriginFor<T>,
signing_payload: BoundedVec<u8, MaxPayloadLen>, signing_payload: BoundedVec<u8, MaxPayloadLen>,
@@ -353,7 +364,7 @@ pub mod pallet {
/// validadores que governance habilita pueden anclar. Sunset a /// validadores que governance habilita pueden anclar. Sunset a
/// attestation-driven cuando el registro OIT de Mathi aterrice. /// attestation-driven cuando el registro OIT de Mathi aterrice.
#[pallet::call_index(1)] #[pallet::call_index(1)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::register_validator())]
pub fn register_validator( pub fn register_validator(
origin: OriginFor<T>, origin: OriginFor<T>,
public_key: [u8; 32], public_key: [u8; 32],
@@ -367,7 +378,7 @@ pub mod pallet {
/// FIX-4: revoca un validador — ya no puede anclar. **Root-only.** /// FIX-4: revoca un validador — ya no puede anclar. **Root-only.**
#[pallet::call_index(2)] #[pallet::call_index(2)]
#[pallet::weight(Weight::from_parts(10_000, 0))] #[pallet::weight(T::WeightInfo::revoke_validator())]
pub fn revoke_validator( pub fn revoke_validator(
origin: OriginFor<T>, origin: OriginFor<T>,
public_key: [u8; 32], public_key: [u8; 32],
@@ -382,6 +393,11 @@ pub mod pallet {
extern crate alloc; 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. // 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`), // Per audit canonical sesión 3+ (`AUDIT_CHARON_SESION_3_STATE_V1.md`),
// own fixtures basados en vendor copy ValidationAttestation real existente // own fixtures basados en vendor copy ValidationAttestation real existente
@@ -33,6 +33,7 @@ impl frame_system::Config for Test {
impl origin_chain_provenance::Config for Test { impl origin_chain_provenance::Config for Test {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
} }
/// Genesis hash de los golden vectors ASP-3 (`/tmp/golden-asp3`, GENESIS=0xAB×32). /// Genesis hash de los golden vectors ASP-3 (`/tmp/golden-asp3`, GENESIS=0xAB×32).
@@ -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<Runtime>` → los reads/writes se cuestan
/// con el `RocksDbWeight` real de la cadena, no con constantes hardcodeadas.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
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))
}
}
+37 -1
View File
@@ -32,6 +32,10 @@ frame-support.workspace = true
frame-executive.workspace = true frame-executive.workspace = true
frame-metadata-hash-extension.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) # FRAME pallets Phase 1.3-1.5 (per SBS-4 ratified)
pallet-balances.workspace = true pallet-balances.workspace = true
pallet-timestamp.workspace = true pallet-timestamp.workspace = true
@@ -154,13 +158,45 @@ std = [
"pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment-rpc-runtime-api/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"frame-benchmarking?/std",
# Slice 2c canonical deferred — substrate-wasm-builder bloqueado WASM build # Slice 2c canonical deferred — substrate-wasm-builder bloqueado WASM build
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"dep:frame-benchmarking",
"frame-benchmarking/runtime-benchmarks",
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"pallet-balances/runtime-benchmarks", "pallet-balances/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks",
"pallet-transaction-payment/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.
] ]
@@ -427,6 +427,9 @@ impl pallet_session::Config for Runtime {
impl origin_chain_provenance::Config for Runtime { impl origin_chain_provenance::Config for Runtime {
type RuntimeEvent = RuntimeEvent; 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<Runtime>;
} }
// ============================================================ // ============================================================
@@ -449,6 +452,8 @@ impl origin_chain_fees::Config for Runtime {
/// — pool-alimentación y pool-consumo apuntan a la misma cuenta og/subsy /// — 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). /// (jamás re-declarar el literal: un typo divergiría las cuentas).
type SubsidyPalletId = EmissionSubsidyPalletId; type SubsidyPalletId = EmissionSubsidyPalletId;
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
type WeightInfo = origin_chain_fees::weights::SubstrateWeight<Runtime>;
} }
// ============================================================ // ============================================================
@@ -466,6 +471,8 @@ impl origin_chain_fees::Config for Runtime {
impl origin_chain_agent_identity::Config for Runtime { impl origin_chain_agent_identity::Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
type WeightInfo = origin_chain_agent_identity::weights::SubstrateWeight<Runtime>;
} }
// ============================================================ // ============================================================
@@ -495,6 +502,8 @@ impl origin_chain_governance::Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type Currency = crate::Balances; type Currency = crate::Balances;
type TreasuryPalletId = GovernanceTreasuryPalletId; type TreasuryPalletId = GovernanceTreasuryPalletId;
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
type WeightInfo = origin_chain_governance::weights::SubstrateWeight<Runtime>;
} }
// ============================================================ // ============================================================
@@ -544,6 +553,8 @@ impl origin_chain_emission::Config for Runtime {
// Reusa el MISMO vault de governance (og/gvtry) para el treasury. // Reusa el MISMO vault de governance (og/gvtry) para el treasury.
type TreasuryPalletId = GovernanceTreasuryPalletId; type TreasuryPalletId = GovernanceTreasuryPalletId;
type SubsidyPalletId = EmissionSubsidyPalletId; type SubsidyPalletId = EmissionSubsidyPalletId;
// Weights INTERIM hand-derived DB-aware (ver weights.rs). Regenerable en HW.
type WeightInfo = origin_chain_emission::weights::SubstrateWeight<Runtime>;
} }
// ============================================================ // ============================================================
+82 -8
View File
@@ -258,7 +258,15 @@ pub const IMPL_NAME: &str = "origin-chain-charon";
/// 5/7 de emergencia real: nueva dispatchable `endorse_emergency_proposal` /// 5/7 de emergencia real: nueva dispatchable `endorse_emergency_proposal`
/// (call_index 12) + gate real (antes tautológico). Nuevas storage/errors/evento /// (call_index 12) + gate real (antes tautológico). Nuevas storage/errors/evento
/// + 1 call aditiva ⇒ spec bump; TRANSACTION_VERSION intacto (call_index nuevo). /// + 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. /// Canonical authoring version per Substrate semver canonical.
pub const AUTHORING_VERSION: u32 = 1; 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<Block> for Runtime {
fn benchmark_metadata(
extra: bool,
) -> (
alloc::vec::Vec<frame_benchmarking::BenchmarkList>,
alloc::vec::Vec<frame_support::traits::StorageInfo>,
) {
use frame_support::traits::StorageInfoTrait;
let mut list = alloc::vec::Vec::<frame_benchmarking::BenchmarkList>::new();
list_benchmarks!(list, extra);
let storage_info = AllPalletsWithSystem::storage_info();
(list, storage_info)
}
fn dispatch_benchmark(
config: frame_benchmarking::BenchmarkConfig,
) -> Result<alloc::vec::Vec<frame_benchmarking::BenchmarkBatch>, 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<TrackedStorageKey> = alloc::vec::Vec::new();
let mut batches = alloc::vec::Vec::<frame_benchmarking::BenchmarkBatch>::new();
let params = (&config, &whitelist);
add_benchmarks!(params, batches);
Ok(batches)
}
}
// ============================================================ // ============================================================
// Phase 3 Slice 3.1 SCAFFOLD — ReviveApi exposure DEFERRED // 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) // Runtime metadata canonical (RuntimeVersion + Executive)
// ============================================================ // ============================================================
@@ -851,13 +925,13 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn runtime_version_v8_governance_hardening() { fn runtime_version_v9_benchmarking_weights() {
// v8 = hardening de governance (audit ultra 2026-07-04 + decisiones A/B): // v9 = benchmarking real machinery: los 19 extrinsics de los 5 pallets
// correctitud (override/treasury/veto) + bond de propuesta (A) + flujo 5/7 // custom pasan de weights placeholder (proof_size=0, DoS) a weights INTERIM
// de emergencia real con endorse_emergency_proposal (B, call_index 12). // hand-derived DB-aware vía WeightInfo. Los weights son consenso-relevantes
// Call aditiva + storage/metadata nuevos ⇒ spec bump; los extrinsics // (fee + llenado de bloque) ⇒ spec bump. Las FIRMAS de los extrinsics NO
// EXISTENTES no cambian formato ⇒ TRANSACTION_VERSION intacto. // cambian ⇒ TRANSACTION_VERSION intacto.
assert_eq!(RUNTIME_VERSION, 8); assert_eq!(RUNTIME_VERSION, 9);
assert_eq!(TRANSACTION_VERSION, 1); assert_eq!(TRANSACTION_VERSION, 1);
} }