Skip to content

Commit

Permalink
glutton: also increase parachain block length (paritytech#4728)
Browse files Browse the repository at this point in the history
Glutton currently is useful mostly for stress testing relay chain
validators. It is unusable for testing the collator networking and block
announcement and import scenarios. This PR resolves that by improving
glutton pallet to also buff up the blocks, up to the runtime configured
`BlockLength`.

### How it works
Includes an additional inherent in each parachain block. The `garbage`
argument passed to the inherent is filled with trash data. It's size is
computed by applying the newly introduced `block_length` percentage to
the maximum block length for mandatory dispatch class. After
paritytech#4765 is merged, the
length of inherent extrinsic will be added to the total block proof
size.

The remaining weight is burnt in `on_idle` as configured by the
`storage` percentage parameter.


TODO:
- [x] PRDoc
- [x] Readme update
- [x] Add tests

---------

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
  • Loading branch information
sandreim authored and Jay Pan committed Dec 27, 2024
1 parent c3aca59 commit ce74d6c
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 13 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ pub type SignedExtra = (
frame_system::CheckGenesis<Runtime>,
frame_system::CheckEra<Runtime>,
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
Expand Down
17 changes: 17 additions & 0 deletions prdoc/pr_4728.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "Glutton - add support for bloating the parachain block length"

doc:
- audience: [Runtime Dev, Runtime User]
description: |
Introduce a new configuration parameter `block_length` which can be configured via a call to
`set_block_length`. This sets the ration of the block length that is to be filled with trash.
This is implemented by an inherent that takes trash data as a parameter filling the block length.

crates:
- name: pallet-glutton
bump: major
- name: glutton-westend-runtime
bump: major
9 changes: 5 additions & 4 deletions substrate/bin/node/bench/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ impl core::Benchmark for ImportBenchmark {
match self.block_type {
BlockType::RandomTransfersKeepAlive => {
// should be 8 per signed extrinsic + 1 per unsigned
// we have 1 unsigned and the rest are signed in the block
// we have 2 unsigned (timestamp and glutton bloat) while the rest are
// signed in the block.
// those 8 events per signed are:
// - transaction paid for the transaction payment
// - withdraw (Balances::Withdraw) for charging the transaction fee
Expand All @@ -135,18 +136,18 @@ impl core::Benchmark for ImportBenchmark {
// - extrinsic success
assert_eq!(
kitchensink_runtime::System::events().len(),
(self.block.extrinsics.len() - 1) * 8 + 1,
(self.block.extrinsics.len() - 2) * 8 + 2,
);
},
BlockType::Noop => {
assert_eq!(
kitchensink_runtime::System::events().len(),
// should be 2 per signed extrinsic + 1 per unsigned
// we have 1 unsigned and the rest are signed in the block
// we have 2 unsigned and the rest are signed in the block
// those 2 events per signed are:
// - deposit event for charging transaction fee
// - extrinsic success
(self.block.extrinsics.len() - 1) * 2 + 1,
(self.block.extrinsics.len() - 2) * 2 + 2,
);
},
_ => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"glutton": {
"compute": "0",
"storage": "0",
"blockLength": "0",
"trashDataCount": 0
},
"assets": {
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/glutton/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ sp-core = { path = "../../primitives/core", default-features = false }
sp-io = { path = "../../primitives/io", default-features = false }
sp-runtime = { path = "../../primitives/runtime", default-features = false }
sp-std = { path = "../../primitives/std", default-features = false }
sp-inherents = { path = "../../primitives/inherents", default-features = false }

[dev-dependencies]
pallet-balances = { path = "../balances" }
Expand All @@ -43,6 +44,7 @@ std = [
"pallet-balances/std",
"scale-info/std",
"sp-core/std",
"sp-inherents/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
Expand Down
7 changes: 4 additions & 3 deletions substrate/frame/glutton/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
The `Glutton` pallet gets the name from its property to consume vast amounts of resources. It can be used to push
para-chains and their relay-chains to the limits. This is good for testing out theoretical limits in a practical way.

The `Glutton` can be set to consume a fraction of the available unused weight of a chain. It accomplishes this by
utilizing the `on_idle` hook and consuming a specific ration of the remaining weight. The rations can be set via
`set_compute` and `set_storage`. Initially the `Glutton` needs to be initialized once with `initialize_pallet`.
The `Glutton` can be set to consume a fraction of the available block length and unused weight of a chain. It
accomplishes this by filling the block length up to a ration and utilizing the `on_idle` hook to consume a
specific ration of the remaining weight. The rations can be set via `set_compute`, `set_storage` and `set_block_length`.
Initially the `Glutton` needs to be initialized once with `initialize_pallet`.
76 changes: 76 additions & 0 deletions substrate/frame/glutton/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ pub mod pallet {
/// The storage limit.
storage: FixedU64,
},
/// The block length limit has been updated.
BlockLengthLimitSet {
/// The block length limit.
block_length: FixedU64,
},
}

#[pallet::error]
Expand Down Expand Up @@ -116,6 +121,13 @@ pub mod pallet {
#[pallet::storage]
pub(crate) type Storage<T: Config> = StorageValue<_, FixedU64, ValueQuery>;

/// The proportion of the `block length` to consume on each block.
///
/// `1.0` is mapped to `100%`. Must be at most [`crate::RESOURCE_HARD_LIMIT`]. Setting this to
/// over `1.0` could stall the chain.
#[pallet::storage]
pub(crate) type Length<T: Config> = StorageValue<_, FixedU64, ValueQuery>;

/// Storage map used for wasting proof size.
///
/// It contains no meaningful data - hence the name "Trash". The maximal number of entries is
Expand Down Expand Up @@ -146,6 +158,8 @@ pub mod pallet {
pub storage: FixedU64,
/// The amount of trash data for wasting proof size.
pub trash_data_count: u32,
/// The block length limit.
pub block_length: FixedU64,
#[serde(skip)]
/// The required configuration field.
pub _config: sp_std::marker::PhantomData<T>,
Expand All @@ -170,6 +184,9 @@ pub mod pallet {

assert!(self.storage <= RESOURCE_HARD_LIMIT, "Storage limit is insane");
<Storage<T>>::put(self.storage);

assert!(self.block_length <= RESOURCE_HARD_LIMIT, "Block length limit is insane");
<Length<T>>::put(self.block_length);
}
}

Expand Down Expand Up @@ -208,6 +225,40 @@ pub mod pallet {
}
}

#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = sp_inherents::MakeFatalError<()>;

const INHERENT_IDENTIFIER: InherentIdentifier = *b"bloated0";

fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Mandatory);
let bloat_size = Length::<T>::get().saturating_mul_int(max_block_length) as usize;
let amount_trash = bloat_size / VALUE_SIZE;
let garbage = TrashData::<T>::iter()
.map(|(_k, v)| v)
.collect::<Vec<_>>()
.into_iter()
.cycle()
.take(amount_trash)
.collect::<Vec<_>>();

Some(Call::bloat { garbage })
}

fn is_inherent(call: &Self::Call) -> bool {
matches!(call, Call::bloat { .. })
}

fn check_inherent(call: &Self::Call, _: &InherentData) -> Result<(), Self::Error> {
match call {
Call::bloat { .. } => Ok(()),
_ => unreachable!("other calls are not inherents"),
}
}
}

#[pallet::call(weight = T::WeightInfo)]
impl<T: Config> Pallet<T> {
/// Initialize the pallet. Should be called once, if no genesis state was provided.
Expand Down Expand Up @@ -277,6 +328,31 @@ pub mod pallet {
Self::deposit_event(Event::StorageLimitSet { storage });
Ok(())
}

/// Increase the block size by including the specified garbage bytes.
#[pallet::call_index(3)]
#[pallet::weight((0, DispatchClass::Mandatory))]
pub fn bloat(_origin: OriginFor<T>, _garbage: Vec<[u8; VALUE_SIZE]>) -> DispatchResult {
Ok(())
}

/// Set how much of the block length should be filled with trash data on each block.
///
/// `1.0` means that all block should be filled. If set to `1.0`, storage proof size will
/// be close to zero.
///
/// Only callable by Root or `AdminOrigin`.
#[pallet::call_index(4)]
#[pallet::weight({1})]
pub fn set_block_length(origin: OriginFor<T>, block_length: FixedU64) -> DispatchResult {
T::AdminOrigin::ensure_origin_or_root(origin)?;

ensure!(block_length <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
Length::<T>::set(block_length);

Self::deposit_event(Event::BlockLengthLimitSet { block_length });
Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down
8 changes: 6 additions & 2 deletions substrate/frame/glutton/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
ext
}

/// Set the `compute` and `storage` limits.
/// Set the `compute`, `storage` and `block_length` limits.
///
/// `1.0` corresponds to `100%`.
pub fn set_limits(compute: f64, storage: f64) {
pub fn set_limits(compute: f64, storage: f64, block_length: f64) {
assert_ok!(Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_float(compute)));
assert_ok!(Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_float(storage)));
assert_ok!(Glutton::set_block_length(
RuntimeOrigin::root(),
FixedU64::from_float(block_length)
));
}
45 changes: 41 additions & 4 deletions substrate/frame/glutton/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,43 @@ fn setting_compute_respects_limit() {
});
}

#[test]
fn setting_block_length_works() {
new_test_ext().execute_with(|| {
assert_eq!(Compute::<Test>::get(), Zero::zero());

assert_ok!(Glutton::set_block_length(RuntimeOrigin::root(), FixedU64::from_float(0.3)));
assert_eq!(Length::<Test>::get(), FixedU64::from_float(0.3));
System::assert_last_event(
Event::BlockLengthLimitSet { block_length: FixedU64::from_float(0.3) }.into(),
);

assert_noop!(
Glutton::set_block_length(RuntimeOrigin::signed(1), FixedU64::from_float(0.5)),
DispatchError::BadOrigin
);
assert_noop!(
Glutton::set_block_length(RuntimeOrigin::none(), FixedU64::from_float(0.5)),
DispatchError::BadOrigin
);
});
}

#[test]
fn setting_block_length_respects_limit() {
new_test_ext().execute_with(|| {
// < 1000% is fine
assert_ok!(Glutton::set_block_length(RuntimeOrigin::root(), FixedU64::from_float(9.99)),);
// == 1000% is fine
assert_ok!(Glutton::set_block_length(RuntimeOrigin::root(), FixedU64::from_u32(10)),);
// > 1000% is not
assert_noop!(
Glutton::set_block_length(RuntimeOrigin::root(), FixedU64::from_float(10.01)),
Error::<Test>::InsaneLimit
);
});
}

#[test]
fn setting_storage_works() {
new_test_ext().execute_with(|| {
Expand Down Expand Up @@ -163,7 +200,7 @@ fn setting_storage_respects_limit() {
#[test]
fn on_idle_works() {
new_test_ext().execute_with(|| {
set_limits(One::one(), One::one());
set_limits(One::one(), One::one(), One::one());

Glutton::on_idle(1, Weight::from_parts(20_000_000, 0));
});
Expand All @@ -173,7 +210,7 @@ fn on_idle_works() {
#[test]
fn on_idle_weight_high_proof_is_close_enough_works() {
new_test_ext().execute_with(|| {
set_limits(One::one(), One::one());
set_limits(One::one(), One::one(), One::one());

let should = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, WEIGHT_PROOF_SIZE_PER_MB * 5);
let got = Glutton::on_idle(1, should);
Expand All @@ -196,7 +233,7 @@ fn on_idle_weight_high_proof_is_close_enough_works() {
#[test]
fn on_idle_weight_low_proof_is_close_enough_works() {
new_test_ext().execute_with(|| {
set_limits(One::one(), One::one());
set_limits(One::one(), One::one(), One::one());

let should = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, WEIGHT_PROOF_SIZE_PER_KB * 20);
let got = Glutton::on_idle(1, should);
Expand Down Expand Up @@ -224,7 +261,7 @@ fn on_idle_weight_over_unity_is_close_enough_works() {
let max_block =
Weight::from_parts(500 * WEIGHT_REF_TIME_PER_MILLIS, 5 * WEIGHT_PROOF_SIZE_PER_MB);
// But now we tell it to consume more than that.
set_limits(1.75, 1.5);
set_limits(1.75, 1.5, 0.0);
let want = Weight::from_parts(
(1.75 * max_block.ref_time() as f64) as u64,
(1.5 * max_block.proof_size() as f64) as u64,
Expand Down

0 comments on commit ce74d6c

Please sign in to comment.