From ce74d6c6b59ede1884d210b65ce3932ad306c80d Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:57:57 +0300 Subject: [PATCH] glutton: also increase parachain block length (#4728) 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 https://github.com/paritytech/polkadot-sdk/pull/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 --- Cargo.lock | 1 + .../glutton/glutton-westend/src/lib.rs | 1 + prdoc/pr_4728.prdoc | 17 +++++ substrate/bin/node/bench/src/import.rs | 9 ++- .../cli/tests/res/default_genesis_config.json | 1 + substrate/frame/glutton/Cargo.toml | 2 + substrate/frame/glutton/README.md | 7 +- substrate/frame/glutton/src/lib.rs | 76 +++++++++++++++++++ substrate/frame/glutton/src/mock.rs | 8 +- substrate/frame/glutton/src/tests.rs | 45 ++++++++++- 10 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_4728.prdoc diff --git a/Cargo.lock b/Cargo.lock index 4f4e0a988cec..113cfa06a84a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10477,6 +10477,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", + "sp-inherents", "sp-io", "sp-runtime", "sp-std 14.0.0", diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index 910f7569bf95..b8a328c3db69 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -296,6 +296,7 @@ pub type SignedExtra = ( frame_system::CheckGenesis, frame_system::CheckEra, frame_system::CheckNonce, + frame_system::CheckWeight, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/prdoc/pr_4728.prdoc b/prdoc/pr_4728.prdoc new file mode 100644 index 000000000000..1494fbdbb2b9 --- /dev/null +++ b/prdoc/pr_4728.prdoc @@ -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 diff --git a/substrate/bin/node/bench/src/import.rs b/substrate/bin/node/bench/src/import.rs index 78b280076e0b..e340869dea02 100644 --- a/substrate/bin/node/bench/src/import.rs +++ b/substrate/bin/node/bench/src/import.rs @@ -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 @@ -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, ); }, _ => {}, diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index e21fbb47da8c..d8713764ab21 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -74,6 +74,7 @@ "glutton": { "compute": "0", "storage": "0", + "blockLength": "0", "trashDataCount": 0 }, "assets": { diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 730c4e70935c..39d2b49b50e5 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -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" } @@ -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", diff --git a/substrate/frame/glutton/README.md b/substrate/frame/glutton/README.md index 89dbe26ec7a9..43642df19104 100644 --- a/substrate/frame/glutton/README.md +++ b/substrate/frame/glutton/README.md @@ -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`. diff --git a/substrate/frame/glutton/src/lib.rs b/substrate/frame/glutton/src/lib.rs index 344a70becaeb..5427173b486b 100644 --- a/substrate/frame/glutton/src/lib.rs +++ b/substrate/frame/glutton/src/lib.rs @@ -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] @@ -116,6 +121,13 @@ pub mod pallet { #[pallet::storage] pub(crate) type Storage = 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 = 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 @@ -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, @@ -170,6 +184,9 @@ pub mod pallet { assert!(self.storage <= RESOURCE_HARD_LIMIT, "Storage limit is insane"); >::put(self.storage); + + assert!(self.block_length <= RESOURCE_HARD_LIMIT, "Block length limit is insane"); + >::put(self.block_length); } } @@ -208,6 +225,40 @@ pub mod pallet { } } + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = sp_inherents::MakeFatalError<()>; + + const INHERENT_IDENTIFIER: InherentIdentifier = *b"bloated0"; + + fn create_inherent(_data: &InherentData) -> Option { + let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Mandatory); + let bloat_size = Length::::get().saturating_mul_int(max_block_length) as usize; + let amount_trash = bloat_size / VALUE_SIZE; + let garbage = TrashData::::iter() + .map(|(_k, v)| v) + .collect::>() + .into_iter() + .cycle() + .take(amount_trash) + .collect::>(); + + 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 Pallet { /// Initialize the pallet. Should be called once, if no genesis state was provided. @@ -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, _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, block_length: FixedU64) -> DispatchResult { + T::AdminOrigin::ensure_origin_or_root(origin)?; + + ensure!(block_length <= RESOURCE_HARD_LIMIT, Error::::InsaneLimit); + Length::::set(block_length); + + Self::deposit_event(Event::BlockLengthLimitSet { block_length }); + Ok(()) + } } impl Pallet { diff --git a/substrate/frame/glutton/src/mock.rs b/substrate/frame/glutton/src/mock.rs index 132ef5cfbcbb..7163d7c46781 100644 --- a/substrate/frame/glutton/src/mock.rs +++ b/substrate/frame/glutton/src/mock.rs @@ -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) + )); } diff --git a/substrate/frame/glutton/src/tests.rs b/substrate/frame/glutton/src/tests.rs index b72d52727725..81d228f39a93 100644 --- a/substrate/frame/glutton/src/tests.rs +++ b/substrate/frame/glutton/src/tests.rs @@ -123,6 +123,43 @@ fn setting_compute_respects_limit() { }); } +#[test] +fn setting_block_length_works() { + new_test_ext().execute_with(|| { + assert_eq!(Compute::::get(), Zero::zero()); + + assert_ok!(Glutton::set_block_length(RuntimeOrigin::root(), FixedU64::from_float(0.3))); + assert_eq!(Length::::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::::InsaneLimit + ); + }); +} + #[test] fn setting_storage_works() { new_test_ext().execute_with(|| { @@ -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)); }); @@ -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); @@ -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); @@ -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,