feat(bench): maquinaria FRAME v2 de benchmarking para los 5 pallets custom (RV 8→9)
Los 19 extrinsics de provenance/fees/agent-identity/emission/governance pasan de weights placeholder `from_parts(10_000, 0)` (proof_size=0, sin contar reads/writes de DB — vector de DoS real) a weights INTERIM hand-derived DB-aware vía el trait `WeightInfo`. Cierra el mispricing de DoS en TODOS los extrinsics del chain. Cada pallet gana: - weights.rs: trait WeightInfo + SubstrateWeight<T> (DB-aware vía T::DbWeight) + impl () - benchmarking.rs: benchmarks FRAME v2, VERIFICADOS por impl_benchmark_test_suite! (19 benchmarks ejecutan+pasan en los mocks — cargo test --features runtime-benchmarks) - Config: `type WeightInfo` + swap de los `#[pallet::weight]` Runtime: - define_benchmarks! con los 5 pallets + runtime API Benchmark (inerte sin la feature) - WeightInfo cableado a SubstrateWeight<Runtime> en cada Config - RUNTIME_VERSION 8→9 (weights = consenso-relevante: fee + llenado de bloque; las firmas de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto) 3 bugs latentes cerrados (territorio virgen — el runtime NUNCA se compiló con runtime-benchmarks; las dimensiones nuevas cazan lo que no se había visto): - pallet-grandpa/try-runtime mal ubicado en la feature runtime-benchmarks (copy-paste erróneo: features ortogonales; NUNCA activaba los benchmarks de grandpa) - feature runtime-benchmarks incompleta → E0046 en OriginFeeAdapter (métodos benchmark-gated de OnChargeTransaction) + pallet-staking (StakingUnchecked::migrate_to_direct_staker); ahora lista TODOS los pallets como un runtime de producción Substrate - pallet-revive/runtime-benchmarks omitido a propósito (fixtures RISC-V/PolkaVM indisponibles; ReviveApi deferred, no benchmarkeado) emission: mock extraído del `mod tests` inline a `pub mod mock` (reuso del test-suite, sin duplicar el scaffold; los 15 tests existentes + el benchmark verdes). GATED (documentado en el §Estado de cada weights.rs): la generación de números CANÓNICOS por CLI requiere el node sc-service o omni-bencher+WASM (ambos bloqueados hace sesiones). Los weights interinos son DB-aware/DoS-safe y regenerables de forma mecánica cuando aterrice el HW de referencia (Pi5/VPS) — benchmarking.rs es la fuente de verdad de la FORMA. Verificado: 5 test-suites de pallet verdes (con y sin runtime-benchmarks) + runtime compila en ambos modos + workspace sin regresión + 40 tests de runtime (incl. v9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
0d56e0bcbf
commit
c7fc6a88fc
29 files changed
+1206
-121
No files matched your search
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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::<method>()`.
|
||||
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<Event<Self>>
|
||||
+ 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):
|
||||
// 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<T>,
|
||||
signing_payload: BoundedVec<u8, MaxPayloadLen>,
|
||||
@@ -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<T>,
|
||||
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<T>,
|
||||
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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user