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,