feat: 🏁 pallet grandpa benchmarking (#442)

## Changes

### Add `pallet-grandpa-benchmarking` crate

Upstream `pallet-grandpa` does not benchmark `report_equivocation`, only
the raw `check_equivocation_proof` crypto proxy.

This PR adds a new `pallet-grandpa-benchmarking` wrapper crate (modelled
after `pallet-session-benchmarking`) that benchmarks both
`report_equivocation` and `note_stalled` against the DataHaven runtimes,
using upstream `check_equivocation_proof()`.

### Add node's benchmark pallet subcommand to benchmarks script

Running the bench for `report_equivocation` requires a real ed25519
verifier. We typically use `frame-omni-bencher`, but this helper
executes the runtime as a WASM blob. In that environment the ed25519
host function does not work as a real verifier, and since the
equivocation proof contains real signatures created outside the WASM
sandbox, the verification always fails and the extrinsic returns
`InvalidEquivocationProof`. So, benchmarks for `pallet_grandpa` must run
via the node's `benchmark pallet` subcommand.

The `run-benchmarks.sh` script was updated to support this case running
directly via node. Any palet included in the new `NODE_PALLETS` list,
routes selected pallets through the node binary, while all other pallets
continue to use `frame-omni-bencher`.

### Calculate proper weights for production

Weight files for all three runtimes (stagenet, testnet, mainnet) are
updated with the
new `report_equivocation(v, n)` linear weight function derived from real
measurements.

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
This commit is contained in:
Gonza Montiel 2026-03-03 09:20:04 +01:00 committed by GitHub
parent 7097767021
commit be4bd2a56c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 626 additions and 74 deletions

View file

@ -60,6 +60,8 @@ jobs:
run: bun ./scripts/check-generated-state.ts
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: v1.4.3
- name: Pull Kurtosis images
run: |
docker pull ${{ env.KURTOSIS_CORE_IMAGE }}:${{ env.KURTOSIS_VERSION }}

22
operator/Cargo.lock generated
View file

@ -2669,6 +2669,7 @@ dependencies = [
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
"pallet-grandpa-benchmarking",
"pallet-identity",
"pallet-im-online",
"pallet-message-queue",
@ -2972,6 +2973,7 @@ dependencies = [
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
"pallet-grandpa-benchmarking",
"pallet-identity",
"pallet-im-online",
"pallet-message-queue",
@ -3128,6 +3130,7 @@ dependencies = [
"pallet-file-system",
"pallet-file-system-runtime-api",
"pallet-grandpa",
"pallet-grandpa-benchmarking",
"pallet-identity",
"pallet-im-online",
"pallet-message-queue",
@ -9282,6 +9285,25 @@ dependencies = [
"sp-staking",
]
[[package]]
name = "pallet-grandpa-benchmarking"
version = "0.25.0"
dependencies = [
"finality-grandpa",
"frame-benchmarking",
"frame-support",
"frame-system",
"pallet-grandpa",
"pallet-session",
"parity-scale-codec",
"sp-application-crypto",
"sp-consensus-grandpa",
"sp-core",
"sp-runtime",
"sp-session",
"sp-std",
]
[[package]]
name = "pallet-identity"
version = "39.1.0"

View file

@ -44,6 +44,7 @@ pallet-external-validators = { path = "./pallets/external-validators", default-f
pallet-external-validators-rewards = { path = "./pallets/external-validators-rewards", default-features = false }
pallet-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false }
pallet-proxy-genesis-companion = { path = "./pallets/proxy-genesis-companion", default-features = false }
pallet-grandpa-benchmarking = { path = "./pallets/grandpa-benchmarking", default-features = false }
pallet-session-benchmarking = { path = "./pallets/session-benchmarking", default-features = false }
# Crates.io (wasm)

View file

@ -0,0 +1,56 @@
[package]
authors = { workspace = true }
description = "Benchmarking helpers for pallet-grandpa in DataHaven runtimes."
edition = { workspace = true }
license = { workspace = true }
name = "pallet-grandpa-benchmarking"
version = { workspace = true }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lints]
workspace = true
[dependencies]
codec = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
finality-grandpa = { version = "0.16.3", default-features = false }
pallet-grandpa = { workspace = true }
pallet-session = { workspace = true, features = ["historical"] }
sp-consensus-grandpa = { workspace = true }
sp-application-crypto = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-session = { workspace = true }
sp-std = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"finality-grandpa/std",
"pallet-grandpa/std",
"pallet-session/std",
"sp-consensus-grandpa/std",
"sp-application-crypto/std",
"sp-core/std",
"sp-runtime/std",
"sp-session/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-grandpa/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
[dev-dependencies]
sp-core = { workspace = true, features = ["full_crypto"] }

View file

@ -0,0 +1,190 @@
//! Benchmarks for `pallet_grandpa` covering both extrinsics in `pallet_grandpa::WeightInfo`:
//! `report_equivocation` and `note_stalled`.
//!
//! Upstream `pallet-grandpa` benchmarks `check_equivocation_proof` (a raw crypto cost proxy) and
//! `note_stalled`, but does not benchmark the actual `report_equivocation` extrinsic dispatch.
//! This crate fills that gap so the node's benchmark command can generate a complete `WeightInfo`
//! impl from real measurements against the DataHaven runtime.
//!
//! The equivocation proof is pre-encoded (see `PREENCODED_EQUIVOCATION_PROOF`) and was generated
//! with the same ed25519 seed used in `setup_equivocation`, so the authority ID embedded in the
//! proof matches the key registered in the session.
//! Regenerate with: `cargo test -p pallet-grandpa-benchmarking --features std -- test_generate_equivocation_blob --nocapture`
//!
//! `frame-omni-bencher` will fail with `InvalidEquivocationProof` because its WASM host does not
//! provide a real ed25519 verifier. Use the node's `benchmark pallet` subcommand instead.
use alloc::{boxed::Box, vec};
use codec::Decode;
use frame_benchmarking::v2::*;
use frame_support::traits::{KeyOwnerProofSystem, OnInitialize};
use frame_system::RawOrigin;
use sp_application_crypto::{RuntimeAppPublic, UncheckedFrom};
use sp_runtime::traits::Convert;
use crate::{Config, Pallet};
type GrandpaId = sp_consensus_grandpa::AuthorityId;
type GrandpaEquivocationProof<T> = sp_consensus_grandpa::EquivocationProof<
<T as frame_system::Config>::Hash,
frame_system::pallet_prelude::BlockNumberFor<T>,
>;
/// Pre-encoded equivocation proof (set_id=0, round=1) signed with the test vector key.
/// Generated by `test_generate_equivocation_blob` in `tests` module of `lib.rs`.
const PREENCODED_EQUIVOCATION_PROOF: [u8; 249] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 215, 90, 152, 1, 130, 177, 10, 183, 213, 75,
254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81,
26, 225, 44, 34, 212, 241, 98, 217, 160, 18, 201, 49, 146, 51, 218, 93, 62, 146, 60, 197, 225,
2, 155, 143, 144, 228, 114, 73, 201, 171, 37, 107, 53, 1, 0, 0, 0, 94, 182, 72, 53, 215, 108,
169, 70, 216, 243, 227, 8, 163, 172, 0, 93, 157, 90, 110, 18, 72, 38, 48, 16, 57, 74, 178, 17,
106, 150, 24, 107, 195, 175, 45, 40, 156, 45, 67, 202, 120, 13, 87, 252, 21, 17, 62, 155, 246,
219, 28, 34, 255, 230, 191, 85, 75, 147, 164, 14, 131, 146, 99, 2, 123, 10, 161, 115, 94, 91,
165, 141, 50, 54, 49, 108, 103, 31, 228, 240, 14, 211, 102, 238, 114, 65, 124, 158, 208, 42,
83, 168, 1, 158, 133, 184, 2, 0, 0, 0, 151, 111, 43, 192, 22, 148, 165, 193, 112, 145, 172, 94,
236, 197, 151, 102, 5, 97, 64, 30, 160, 179, 79, 79, 150, 102, 200, 105, 32, 233, 249, 185,
118, 73, 110, 32, 193, 87, 150, 41, 254, 155, 104, 77, 236, 36, 48, 202, 161, 26, 247, 61, 181,
109, 221, 114, 165, 70, 43, 146, 198, 158, 253, 1,
];
/// Constructs a dummy, unique, deterministic grandpa id
fn grandpa_id_for_validator(i: u32) -> GrandpaId {
let mut raw = [0u8; 32];
raw[..4].copy_from_slice(&i.to_le_bytes());
raw[4] = 0xff;
GrandpaId::unchecked_from(raw)
}
fn setup_equivocation<T: Config>(
extra_validators: u32,
) -> Result<
(
Box<GrandpaEquivocationProof<T>>,
<T as pallet_grandpa::Config>::KeyOwnerProof,
<T as frame_system::Config>::AccountId,
),
BenchmarkError,
> {
use frame_system::pallet_prelude::BlockNumberFor;
use frame_system::Pallet as System;
let reporter: T::AccountId = whitelisted_caller();
frame_system::Pallet::<T>::inc_providers(&reporter);
// Ensure we are at a sane block number and that session is initialized.
System::<T>::set_block_number(1u32.into());
<pallet_session::Pallet<T> as OnInitialize<BlockNumberFor<T>>>::on_initialize(1u32.into());
// Use the same seed as in test_generate_equivocation_blob so the key matches the
// pre-encoded proof and the host keystore can store it for KeyOwnerProof.
let seed = b"0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".to_vec();
let grandpa_id: GrandpaId = GrandpaId::generate_pair(Some(seed));
// Install session keys for the reporter account. The runtime provides the concrete
// `T::Keys` type construction via `Config::benchmark_session_keys`.
let keys = T::benchmark_session_keys(grandpa_id.clone());
pallet_session::Pallet::<T>::set_keys(
RawOrigin::Signed(reporter.clone()).into(),
keys.clone(),
vec![0, 1, 2, 3],
)
.map_err(|_| BenchmarkError::Stop("set_keys failed"))?;
// ValidatorId = AccountId in all DataHaven runtimes; the reporter went through set_keys
// successfully so the conversion is guaranteed to succeed.
let reporter_validator_id: T::ValidatorId = T::ValidatorIdOf::convert(reporter.clone())
.ok_or_else(|| BenchmarkError::Stop("could not convert reporter to ValidatorId"))?;
// Build the full validator + queued-keys lists: the real offender first, then
// `extra_validators` background validators. Each gets a unique GRANDPA key so the session
// trie has `extra_validators + 1` distinct leaves. The resulting trie depth (and therefore
// `key_owner_proof.validator_count()`) scales with `v`, giving the linear regression a
// real signal to measure.
let mut validators = vec![reporter_validator_id.clone()];
let mut queued_keys = vec![(reporter_validator_id, keys)];
for i in 0..extra_validators {
let validator_id: T::ValidatorId = account("validator", i, i);
let validator_keys = T::benchmark_session_keys(grandpa_id_for_validator(i));
pallet_session::NextKeys::<T>::insert(&validator_id, validator_keys.clone());
validators.push(validator_id.clone());
queued_keys.push((validator_id, validator_keys));
}
// Overwrite session storage so the full set (offender + background validators) is active.
pallet_session::Validators::<T>::put(validators);
pallet_session::QueuedKeys::<T>::put(queued_keys);
// Initialize session again after overwriting validator state.
<pallet_session::Pallet<T> as OnInitialize<BlockNumberFor<T>>>::on_initialize(1u32.into());
// Generate a fresh KeyOwnerProof via Historical for the GRANDPA key registered above.
// The authority ID in this proof must match the one embedded in PREENCODED_EQUIVOCATION_PROOF,
// which is guaranteed because both use the same seed. The proof's `validator_count` field
// will now equal `extra_validators + 1`, matching what the weight function receives at
// dispatch time in production.
let key_owner_proof = pallet_session::historical::Pallet::<T>::prove((
sp_consensus_grandpa::KEY_TYPE,
grandpa_id.clone(),
))
.ok_or_else(|| BenchmarkError::Stop("Historical::prove returned None".into()))?;
// Decode the pre-encoded equivocation proof (set_id=0, round=1). The pallet will only
// accept this if the on-chain current_set_id is also 0, which holds in a fresh benchmark
// environment since no set rotation has occurred.
let proof: GrandpaEquivocationProof<T> =
Decode::decode(&mut &PREENCODED_EQUIVOCATION_PROOF[..])
.map_err(|_| BenchmarkError::Stop("failed to decode pre-encoded equivocation proof"))?;
Ok((Box::new(proof), key_owner_proof, reporter))
}
#[benchmarks]
mod benchmarks {
use super::*;
use pallet_grandpa::Call;
#[benchmark]
fn note_stalled() -> Result<(), BenchmarkError> {
let delay = 1000u32.into();
let best_finalized_block_number = 1u32.into();
#[extrinsic_call]
_(RawOrigin::Root, delay, best_finalized_block_number);
Ok(())
}
/// Benchmarks the full `report_equivocation` dispatch cost.
///
/// The upstream `WeightInfo::report_equivocation` signature takes
/// `(validator_count: u32, max_nominators_per_validator: u32)` as linear components.
/// We expose the same parameters here so the generated weight function matches the
/// trait exactly and the linear terms are populated from real measurements.
/// `v` = validator_count, `n` = max_nominators_per_validator (matches upstream component names).
///
/// `v` background validators are registered in the session alongside the real offender, so
/// the session trie has `v + 1` leaves and `key_owner_proof.validator_count()` equals `v + 1`.
/// This means the trie verification path actually grows with `v`, giving the linear regression
/// a real signal and producing a meaningful slope coefficient in the generated weight function.
///
/// # Disclaimer: setup over-counts `v` cost
///
/// The `v` slope in the generated weights is dominated by `Session::NextKeys` reads, because
/// `Historical::prove` (called during setup) reads all `v` validators to build the full trie.
/// In production, `Historical::check_proof` (called during dispatch) only traverses the O(log v)
/// Merkle path nodes — it does not read all validators. The generated weights therefore
/// over-count the per-validator cost and are conservative/safe (they overcharge rather than
/// undercharge), but this is a known tension inherent to benchmarking session historical
/// proofs: the setup must call `prove`, which is more expensive than `check_proof`.
#[benchmark]
fn report_equivocation(v: Linear<0, 1000>, n: Linear<0, 1>) -> Result<(), BenchmarkError> {
let (proof, key_owner_proof, reporter) = setup_equivocation::<T>(v)?;
#[extrinsic_call]
_(RawOrigin::Signed(reporter), proof, key_owner_proof);
Ok(())
}
}

View file

@ -0,0 +1,83 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub struct Pallet<T: Config>(pallet_grandpa::Pallet<T>);
/// Benchmarking configuration for `pallet-grandpa` in DataHaven.
///
/// This is a small wrapper crate (similar to `pallet-session-benchmarking`) that provides
/// benchmarks for GRANDPA extrinsics that upstream `pallet-grandpa` does not expose in its
/// own benchmarking suite. Run via the node's `benchmark pallet` subcommand, not
/// `frame-omni-bencher`, as the latter lacks a real ed25519 verifier.
pub trait Config:
pallet_grandpa::Config<
KeyOwnerProof = <pallet_session::historical::Pallet<Self> as frame_support::traits::KeyOwnerProofSystem<(
sp_core::crypto::KeyTypeId,
sp_consensus_grandpa::AuthorityId,
)>>::Proof,
> + pallet_session::Config
+ pallet_session::historical::Config
{
/// Construct a full `Self::Keys` value for benchmarking, filling all slots except GRANDPA
/// with dummy values and placing `grandpa` in the GRANDPA slot.
fn benchmark_session_keys(grandpa: sp_consensus_grandpa::AuthorityId) -> Self::Keys;
}
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(all(test, feature = "std"))]
mod tests {
use codec::Encode;
use sp_consensus_grandpa;
use sp_core::{ed25519, Pair as PairTrait};
use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
/// Run with: cargo test -p pallet-grandpa-benchmarking --features std -- test_generate_equivocation_blob --nocapture
/// Then paste the printed bytes into PREENCODED_EQUIVOCATION_PROOF in benchmarking.rs.
#[test]
fn test_generate_equivocation_blob() {
let seed = "0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
let pair = ed25519::Pair::from_string(seed, None).expect("valid seed");
let set_id: u64 = 0;
let round: u64 = 1;
let h1 = BlakeTwo256::hash_of(&1u32);
let h2 = BlakeTwo256::hash_of(&2u32);
let prevote1 = finality_grandpa::Prevote {
target_hash: h1,
target_number: 1u32,
};
let prevote2 = finality_grandpa::Prevote {
target_hash: h2,
target_number: 2u32,
};
let msg1 = finality_grandpa::Message::Prevote(prevote1.clone());
let msg2 = finality_grandpa::Message::Prevote(prevote2.clone());
let payload1 = sp_consensus_grandpa::localized_payload(round, set_id, &msg1);
let payload2 = sp_consensus_grandpa::localized_payload(round, set_id, &msg2);
let sig1 = pair.sign(&payload1);
let sig2 = pair.sign(&payload2);
let equivocation = finality_grandpa::Equivocation {
round_number: round,
identity: sp_consensus_grandpa::AuthorityId::from(pair.public()),
first: (prevote1, sig1.into()),
second: (prevote2, sig2.into()),
};
let proof = sp_consensus_grandpa::EquivocationProof::<sp_core::H256, u32>::new(
set_id,
equivocation.into(),
);
let encoded = proof.encode();
println!(
"PREENCODED_EQUIVOCATION_PROOF (len={}): {:?}",
encoded.len(),
encoded
);
}
}

View file

@ -72,6 +72,7 @@ pallet-referenda = { workspace = true }
pallet-safe-mode = { workspace = true }
pallet-scheduler = { workspace = true }
pallet-session = { workspace = true }
pallet-grandpa-benchmarking = { workspace = true, optional = true }
pallet-session-benchmarking = { workspace = true, optional = true }
pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
@ -196,6 +197,7 @@ std = [
"frame-metadata-hash-extension/std",
"frame-support/std",
"frame-system-benchmarking?/std",
"pallet-grandpa-benchmarking?/std",
"pallet-session-benchmarking?/std",
"frame-system-rpc-runtime-api/std",
"frame-system/std",
@ -342,6 +344,7 @@ runtime-benchmarks = [
"pallet-referenda/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-grandpa-benchmarking/runtime-benchmarks",
"pallet-session-benchmarking/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",

View file

@ -22,7 +22,7 @@ frame_benchmarking::define_benchmarks!(
[pallet_mmr, Mmr]
[pallet_beefy_mmr, BeefyMmrLeaf]
[pallet_babe, Babe]
[pallet_grandpa, Grandpa]
[pallet_grandpa, pallet_grandpa_benchmarking::Pallet::<Runtime>]
[pallet_randomness, Randomness]
// Substrate pallets

View file

@ -973,6 +973,18 @@ impl_runtime_apis! {
impl frame_system_benchmarking::Config for Runtime {}
impl pallet_session_benchmarking::Config for Runtime {}
impl pallet_grandpa_benchmarking::Config for Runtime {
fn benchmark_session_keys(grandpa: GrandpaId) -> Self::Keys {
use sp_core::crypto::UncheckedFrom;
SessionKeys {
babe: sp_consensus_babe::AuthorityId::unchecked_from([1u8; 32]),
grandpa,
im_online: pallet_im_online::sr25519::AuthorityId::unchecked_from([1u8; 32]),
beefy: sp_consensus_beefy::ecdsa_crypto::AuthorityId::unchecked_from([1u8; 33]),
}
}
}
impl baseline::Config for Runtime {}
use frame_support::traits::WhitelistedStorageKeys;

View file

@ -17,33 +17,34 @@
//! Autogenerated weights for `pallet_grandpa`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 51.0.0
//! DATE: 2026-01-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 46.2.0
//! DATE: 2026-03-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ip-10-0-0-176`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz`
//! WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
// frame-omni-bencher
// v1
// ./target/production/datahaven-node
// benchmark
// pallet
// --runtime
// target/production/wbuild/datahaven-mainnet-runtime/datahaven_mainnet_runtime.compact.compressed.wasm
// --genesis-builder
// runtime
// --pallet
// pallet_grandpa
// --extrinsic
//
// *
// --steps
// 50
// --repeat
// 20
// --header
// ../file_header.txt
// --template
// benchmarking/frame-weight-template.hbs
// --output
// runtime/mainnet/src/weights/pallet_grandpa.rs
// --steps
// 50
// --repeat
// 20
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
@ -55,24 +56,52 @@ use sp_std::marker::PhantomData;
/// Weights for `pallet_grandpa`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> pallet_grandpa::WeightInfo for WeightInfo<T> {
/// The range of component `x` is `[0, 1]`.
fn report_equivocation(prev: u32, _equivocations: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 78_547_000 picoseconds.
Weight::from_parts(78_982_114, 0)
// Standard Error: 28_584
.saturating_add(Weight::from_parts(129_485, 0).saturating_mul(prev.into()))
}
/// Storage: `Grandpa::Stalled` (r:0 w:1)
/// Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`)
fn note_stalled() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 3_815_000 picoseconds.
Weight::from_parts(3_947_000, 0)
// Minimum execution time: 2_506_000 picoseconds.
Weight::from_parts(2_591_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Session::CurrentIndex` (r:1 w:0)
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::Validators` (r:1 w:0)
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::NextKeys` (r:1001 w:0)
/// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Grandpa::SetIdSession` (r:1 w:0)
/// Proof: `Grandpa::SetIdSession` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`)
/// Storage: `Offences::ConcurrentReportsIndex` (r:1 w:1)
/// Proof: `Offences::ConcurrentReportsIndex` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Offences::Reports` (r:1 w:1)
/// Proof: `Offences::Reports` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `ExternalValidatorsSlashes::SlashingMode` (r:1 w:0)
/// Proof: `ExternalValidatorsSlashes::SlashingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ActiveEra` (r:1 w:0)
/// Proof: `ExternalValidators::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ErasStartSessionIndex` (r:1 w:0)
/// Proof: `ExternalValidators::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:0)
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(2002), added: 2497, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidatorsSlashes::NextSlashId` (r:1 w:1)
/// Proof: `ExternalValidatorsSlashes::NextSlashId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// The range of component `v` is `[0, 1000]`.
/// The range of component `n` is `[0, 1]`.
fn report_equivocation(v: u32, n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1202 + v * (184 ±0)`
// Estimated: `4604 + n * (84 ±3) + v * (2660 ±0)`
// Minimum execution time: 159_361_000 picoseconds.
Weight::from_parts(161_997_000, 4604)
// Standard Error: 6_387
.saturating_add(Weight::from_parts(12_204_491, 0).saturating_mul(v.into()))
.saturating_add(T::DbWeight::get().reads(11_u64))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into())))
.saturating_add(T::DbWeight::get().writes(3_u64))
.saturating_add(Weight::from_parts(0, 84).saturating_mul(n.into()))
.saturating_add(Weight::from_parts(0, 2660).saturating_mul(v.into()))
}
}

View file

@ -72,6 +72,7 @@ pallet-referenda = { workspace = true }
pallet-safe-mode = { workspace = true }
pallet-scheduler = { workspace = true }
pallet-session = { workspace = true }
pallet-grandpa-benchmarking = { workspace = true, optional = true }
pallet-session-benchmarking = { workspace = true, optional = true }
pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
@ -197,6 +198,7 @@ std = [
"frame-metadata-hash-extension/std",
"frame-support/std",
"frame-system-benchmarking?/std",
"pallet-grandpa-benchmarking?/std",
"pallet-session-benchmarking?/std",
"frame-system-rpc-runtime-api/std",
"frame-system/std",
@ -344,6 +346,7 @@ runtime-benchmarks = [
"pallet-referenda/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-grandpa-benchmarking/runtime-benchmarks",
"pallet-session-benchmarking/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",

View file

@ -22,7 +22,7 @@ frame_benchmarking::define_benchmarks!(
[pallet_mmr, Mmr]
[pallet_beefy_mmr, BeefyMmrLeaf]
[pallet_babe, Babe]
[pallet_grandpa, Grandpa]
[pallet_grandpa, pallet_grandpa_benchmarking::Pallet::<Runtime>]
[pallet_randomness, Randomness]
// Substrate pallets

View file

@ -975,6 +975,18 @@ impl_runtime_apis! {
impl frame_system_benchmarking::Config for Runtime {}
impl pallet_session_benchmarking::Config for Runtime {}
impl pallet_grandpa_benchmarking::Config for Runtime {
fn benchmark_session_keys(grandpa: GrandpaId) -> Self::Keys {
use sp_core::crypto::UncheckedFrom;
SessionKeys {
babe: sp_consensus_babe::AuthorityId::unchecked_from([1u8; 32]),
grandpa,
im_online: pallet_im_online::sr25519::AuthorityId::unchecked_from([1u8; 32]),
beefy: sp_consensus_beefy::ecdsa_crypto::AuthorityId::unchecked_from([1u8; 33]),
}
}
}
impl baseline::Config for Runtime {}
use frame_support::traits::WhitelistedStorageKeys;

View file

@ -17,33 +17,34 @@
//! Autogenerated weights for `pallet_grandpa`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 51.0.0
//! DATE: 2025-12-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 46.2.0
//! DATE: 2026-03-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ip-10-0-0-176`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz`
//! WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
// frame-omni-bencher
// v1
// ./target/production/datahaven-node
// benchmark
// pallet
// --runtime
// target/production/wbuild/datahaven-stagenet-runtime/datahaven_stagenet_runtime.compact.compressed.wasm
// --genesis-builder
// runtime
// --pallet
// pallet_grandpa
// --extrinsic
//
// *
// --steps
// 50
// --repeat
// 20
// --header
// ../file_header.txt
// --template
// benchmarking/frame-weight-template.hbs
// --output
// runtime/stagenet/src/weights/pallet_grandpa.rs
// --steps
// 50
// --repeat
// 20
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
@ -55,24 +56,52 @@ use sp_std::marker::PhantomData;
/// Weights for `pallet_grandpa`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> pallet_grandpa::WeightInfo for WeightInfo<T> {
/// The range of component `x` is `[0, 1]`.
fn report_equivocation(prev: u32, _equivocations: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 78_547_000 picoseconds.
Weight::from_parts(78_982_114, 0)
// Standard Error: 28_584
.saturating_add(Weight::from_parts(129_485, 0).saturating_mul(prev.into()))
}
/// Storage: `Grandpa::Stalled` (r:0 w:1)
/// Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`)
fn note_stalled() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 3_815_000 picoseconds.
Weight::from_parts(3_947_000, 0)
// Minimum execution time: 2_453_000 picoseconds.
Weight::from_parts(2_554_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Session::CurrentIndex` (r:1 w:0)
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::Validators` (r:1 w:0)
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::NextKeys` (r:1001 w:0)
/// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Grandpa::SetIdSession` (r:1 w:0)
/// Proof: `Grandpa::SetIdSession` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`)
/// Storage: `Offences::ConcurrentReportsIndex` (r:1 w:1)
/// Proof: `Offences::ConcurrentReportsIndex` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Offences::Reports` (r:1 w:1)
/// Proof: `Offences::Reports` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `ExternalValidatorsSlashes::SlashingMode` (r:1 w:0)
/// Proof: `ExternalValidatorsSlashes::SlashingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ActiveEra` (r:1 w:0)
/// Proof: `ExternalValidators::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ErasStartSessionIndex` (r:1 w:0)
/// Proof: `ExternalValidators::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:0)
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(2002), added: 2497, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidatorsSlashes::NextSlashId` (r:1 w:1)
/// Proof: `ExternalValidatorsSlashes::NextSlashId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// The range of component `v` is `[0, 1000]`.
/// The range of component `n` is `[0, 1]`.
fn report_equivocation(v: u32, n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1202 + v * (184 ±0)`
// Estimated: `4604 + n * (84 ±3) + v * (2660 ±0)`
// Minimum execution time: 183_867_000 picoseconds.
Weight::from_parts(186_282_000, 4604)
// Standard Error: 5_669
.saturating_add(Weight::from_parts(12_384_539, 0).saturating_mul(v.into()))
.saturating_add(T::DbWeight::get().reads(11_u64))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into())))
.saturating_add(T::DbWeight::get().writes(3_u64))
.saturating_add(Weight::from_parts(0, 84).saturating_mul(n.into()))
.saturating_add(Weight::from_parts(0, 2660).saturating_mul(v.into()))
}
}

View file

@ -73,6 +73,7 @@ pallet-referenda = { workspace = true }
pallet-safe-mode = { workspace = true }
pallet-scheduler = { workspace = true }
pallet-session = { workspace = true }
pallet-grandpa-benchmarking = { workspace = true, optional = true }
pallet-session-benchmarking = { workspace = true, optional = true }
pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
@ -197,6 +198,7 @@ std = [
"frame-metadata-hash-extension/std",
"frame-support/std",
"frame-system-benchmarking?/std",
"pallet-grandpa-benchmarking?/std",
"pallet-session-benchmarking?/std",
"frame-system-rpc-runtime-api/std",
"frame-system/std",
@ -340,6 +342,7 @@ runtime-benchmarks = [
"pallet-referenda/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-grandpa-benchmarking/runtime-benchmarks",
"pallet-session-benchmarking/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",

View file

@ -22,7 +22,7 @@ frame_benchmarking::define_benchmarks!(
[pallet_mmr, Mmr]
[pallet_beefy_mmr, BeefyMmrLeaf]
[pallet_babe, Babe]
[pallet_grandpa, Grandpa]
[pallet_grandpa, pallet_grandpa_benchmarking::Pallet::<Runtime>]
[pallet_randomness, Randomness]
// Substrate pallets

View file

@ -973,6 +973,18 @@ impl_runtime_apis! {
impl frame_system_benchmarking::Config for Runtime {}
impl pallet_session_benchmarking::Config for Runtime {}
impl pallet_grandpa_benchmarking::Config for Runtime {
fn benchmark_session_keys(grandpa: GrandpaId) -> Self::Keys {
use sp_core::crypto::UncheckedFrom;
SessionKeys {
babe: sp_consensus_babe::AuthorityId::unchecked_from([1u8; 32]),
grandpa,
im_online: pallet_im_online::sr25519::AuthorityId::unchecked_from([1u8; 32]),
beefy: sp_consensus_beefy::ecdsa_crypto::AuthorityId::unchecked_from([1u8; 33]),
}
}
}
impl baseline::Config for Runtime {}
use frame_support::traits::WhitelistedStorageKeys;

View file

@ -17,33 +17,34 @@
//! Autogenerated weights for `pallet_grandpa`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 51.0.0
//! DATE: 2026-01-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 46.2.0
//! DATE: 2026-03-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `ip-10-0-0-176`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz`
//! WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
// frame-omni-bencher
// v1
// ./target/production/datahaven-node
// benchmark
// pallet
// --runtime
// target/production/wbuild/datahaven-testnet-runtime/datahaven_testnet_runtime.compact.compressed.wasm
// --genesis-builder
// runtime
// --pallet
// pallet_grandpa
// --extrinsic
//
// *
// --steps
// 50
// --repeat
// 20
// --header
// ../file_header.txt
// --template
// benchmarking/frame-weight-template.hbs
// --output
// runtime/testnet/src/weights/pallet_grandpa.rs
// --steps
// 50
// --repeat
// 20
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
@ -55,24 +56,52 @@ use sp_std::marker::PhantomData;
/// Weights for `pallet_grandpa`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> pallet_grandpa::WeightInfo for WeightInfo<T> {
/// The range of component `x` is `[0, 1]`.
fn report_equivocation(prev: u32, _equivocations: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 78_547_000 picoseconds.
Weight::from_parts(78_982_114, 0)
// Standard Error: 28_584
.saturating_add(Weight::from_parts(129_485, 0).saturating_mul(prev.into()))
}
/// Storage: `Grandpa::Stalled` (r:0 w:1)
/// Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`)
fn note_stalled() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 3_815_000 picoseconds.
Weight::from_parts(3_947_000, 0)
// Minimum execution time: 2_418_000 picoseconds.
Weight::from_parts(2_662_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Session::CurrentIndex` (r:1 w:0)
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::Validators` (r:1 w:0)
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `Session::NextKeys` (r:1001 w:0)
/// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Grandpa::SetIdSession` (r:1 w:0)
/// Proof: `Grandpa::SetIdSession` (`max_values`: None, `max_size`: Some(20), added: 2495, mode: `MaxEncodedLen`)
/// Storage: `Offences::ConcurrentReportsIndex` (r:1 w:1)
/// Proof: `Offences::ConcurrentReportsIndex` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Offences::Reports` (r:1 w:1)
/// Proof: `Offences::Reports` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `ExternalValidatorsSlashes::SlashingMode` (r:1 w:0)
/// Proof: `ExternalValidatorsSlashes::SlashingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ActiveEra` (r:1 w:0)
/// Proof: `ExternalValidators::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::ErasStartSessionIndex` (r:1 w:0)
/// Proof: `ExternalValidators::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidators::WhitelistedValidators` (r:1 w:0)
/// Proof: `ExternalValidators::WhitelistedValidators` (`max_values`: Some(1), `max_size`: Some(2002), added: 2497, mode: `MaxEncodedLen`)
/// Storage: `ExternalValidatorsSlashes::NextSlashId` (r:1 w:1)
/// Proof: `ExternalValidatorsSlashes::NextSlashId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// The range of component `v` is `[0, 1000]`.
/// The range of component `n` is `[0, 1]`.
fn report_equivocation(v: u32, n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1202 + v * (184 ±0)`
// Estimated: `4604 + n * (84 ±3) + v * (2660 ±0)`
// Minimum execution time: 186_789_000 picoseconds.
Weight::from_parts(190_096_000, 4604)
// Standard Error: 5_639
.saturating_add(Weight::from_parts(12_403_947, 0).saturating_mul(v.into()))
.saturating_add(T::DbWeight::get().reads(11_u64))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into())))
.saturating_add(T::DbWeight::get().writes(3_u64))
.saturating_add(Weight::from_parts(0, 84).saturating_mul(n.into()))
.saturating_add(Weight::from_parts(0, 2660).saturating_mul(v.into()))
}
}

View file

@ -1,6 +1,9 @@
#!/bin/bash
# DataHaven Benchmarking Script using frame-omni-bencher
# Automatically discovers and benchmarks all pallets in the runtime
# DataHaven Benchmarking Script
# Uses frame-omni-bencher for most pallets.
# Pallets listed in NODE_PALLETS are benchmarked via the native node binary instead,
# because frame-omni-bencher's WASM host lacks the crypto primitives they require
# (e.g. pallet_grandpa needs a real ed25519 verifier for report_equivocation).
set -e
@ -10,6 +13,11 @@ STEPS=${2:-50}
REPEAT=${3:-20}
FEATURES="runtime-benchmarks"
# Pallets that must be benchmarked via the native node binary instead of frame-omni-bencher.
# Add pallet names here (space-separated) when their benchmarks require crypto or host
# functions that the WASM execution environment cannot provide.
NODE_PALLETS=("pallet_grandpa")
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
@ -52,9 +60,17 @@ if [ ! -f "$TEMPLATE_PATH" ]; then
exit 1
fi
# Build the runtime WASM
echo -e "${YELLOW}Building runtime $RUNTIME (production profile) with features: $FEATURES${NC}"
cargo build --profile production --features "$FEATURES" -p datahaven-$RUNTIME-runtime
# Build the runtime WASM and the node binary
echo -e "${YELLOW}Building runtime $RUNTIME and node (production profile) with features: $FEATURES${NC}"
cargo build --profile production --features "$FEATURES" \
-p datahaven-$RUNTIME-runtime \
-p datahaven-node
NODE_BIN="target/production/datahaven-node"
if [ ! -f "$NODE_BIN" ]; then
echo -e "${RED}Error: Node binary not found at $NODE_BIN${NC}"
exit 1
fi
# Get the WASM path
WASM_PATH="target/production/wbuild/datahaven-$RUNTIME-runtime/datahaven_${RUNTIME}_runtime.compact.compressed.wasm"
@ -97,6 +113,48 @@ mkdir -p "$WEIGHTS_DIR"
# Run benchmarks for each pallet using frame-omni-bencher
echo -e "${GREEN}Starting benchmarks...${NC}\n"
# Returns 0 if the given pallet should be benchmarked via the node binary, 1 otherwise.
requires_node_benchmark() {
local PALLET=$1
for node_pallet in "${NODE_PALLETS[@]}"; do
if [ "$node_pallet" == "$PALLET" ]; then
return 0
fi
done
return 1
}
# Benchmark a pallet via the native node binary.
# Used for pallets whose benchmarks require host functions unavailable in WASM
# (e.g. real ed25519 verification for pallet_grandpa::report_equivocation).
benchmark_pallet_via_node() {
local PALLET=$1
local OUTPUT_FILE=$2
echo -e "${YELLOW}Benchmarking $PALLET (via node binary)...${NC}"
"$NODE_BIN" benchmark pallet \
--runtime "$WASM_PATH" \
--genesis-builder runtime \
--pallet "$PALLET" \
--extrinsic "*" \
--header ../file_header.txt \
--template "$TEMPLATE_PATH" \
--output "$WEIGHTS_DIR/$OUTPUT_FILE.rs" \
--steps "$STEPS" \
--repeat "$REPEAT" 2>&1 | tee "benchmark_${PALLET}.log"
local exit_code=${PIPESTATUS[0]}
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}$PALLET benchmarked successfully (node)${NC}"
return 0
else
echo -e "${RED}✗ Error benchmarking $PALLET (node)${NC}"
return 1
fi
}
# Function to run benchmark for a pallet
benchmark_pallet() {
local PALLET=$1
@ -131,10 +189,18 @@ benchmark_pallet() {
for PALLET in "${PALLETS[@]}"; do
# Use the pallet name directly as the output file name
OUTPUT_FILE="$PALLET"
if benchmark_pallet "$PALLET" "$OUTPUT_FILE"; then
RESULTS[$PALLET]="SUCCESS"
if requires_node_benchmark "$PALLET"; then
if benchmark_pallet_via_node "$PALLET" "$OUTPUT_FILE"; then
RESULTS[$PALLET]="SUCCESS"
else
RESULTS[$PALLET]="FAILED"
fi
else
RESULTS[$PALLET]="FAILED"
if benchmark_pallet "$PALLET" "$OUTPUT_FILE"; then
RESULTS[$PALLET]="SUCCESS"
else
RESULTS[$PALLET]="FAILED"
fi
fi
echo ""
done