1#![deny(missing_docs)]
28#![cfg_attr(not(feature = "std"), no_std)]
29
30#[cfg(feature = "runtime-benchmarks")]
31mod benchmarking;
32#[cfg(test)]
33mod mock;
34#[cfg(test)]
35mod tests;
36pub mod weights;
37
38extern crate alloc;
39
40use alloc::{vec, vec::Vec};
41use blake2::{Blake2b512, Digest};
42use frame_support::{pallet_prelude::*, weights::WeightMeter, DefaultNoBound};
43use frame_system::pallet_prelude::*;
44use sp_io::hashing::twox_256;
45use sp_runtime::{traits::Zero, FixedPointNumber, FixedU64};
46
47pub use pallet::*;
48pub use weights::WeightInfo;
49
50const LOG_TARGET: &str = "runtime::glutton";
51
52pub const VALUE_SIZE: usize = 1024;
54pub const MAX_TRASH_DATA_ENTRIES: u32 = 65_000;
56pub const RESOURCE_HARD_LIMIT: FixedU64 = FixedU64::from_u32(10);
58
59#[frame_support::pallet]
60pub mod pallet {
61 use super::*;
62
63 #[pallet::config]
64 pub trait Config: frame_system::Config {
65 #[allow(deprecated)]
67 type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
68
69 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
71
72 type WeightInfo: WeightInfo;
74 }
75
76 #[pallet::pallet]
77 pub struct Pallet<T>(_);
78
79 #[pallet::event]
80 #[pallet::generate_deposit(pub(super) fn deposit_event)]
81 pub enum Event {
82 PalletInitialized {
84 reinit: bool,
86 },
87 ComputationLimitSet {
89 compute: FixedU64,
91 },
92 StorageLimitSet {
94 storage: FixedU64,
96 },
97 BlockLengthLimitSet {
99 block_length: FixedU64,
101 },
102 }
103
104 #[pallet::error]
105 pub enum Error<T> {
106 AlreadyInitialized,
110
111 InsaneLimit,
113 }
114
115 #[pallet::storage]
120 pub(crate) type Compute<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
121
122 #[pallet::storage]
127 pub(crate) type Storage<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
128
129 #[pallet::storage]
134 pub(crate) type Length<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
135
136 #[pallet::storage]
145 pub(super) type TrashData<T: Config> = StorageMap<
146 Hasher = Twox64Concat,
147 Key = u32,
148 Value = [u8; VALUE_SIZE],
149 QueryKind = OptionQuery,
150 MaxValues = ConstU32<MAX_TRASH_DATA_ENTRIES>,
151 >;
152
153 #[pallet::storage]
155 pub(crate) type TrashDataCount<T: Config> = StorageValue<_, u32, ValueQuery>;
156
157 #[pallet::genesis_config]
158 #[derive(DefaultNoBound)]
159 pub struct GenesisConfig<T: Config> {
160 pub compute: FixedU64,
162 pub storage: FixedU64,
164 pub trash_data_count: u32,
166 pub block_length: FixedU64,
168 #[serde(skip)]
169 pub _config: core::marker::PhantomData<T>,
171 }
172
173 #[pallet::genesis_build]
174 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
175 fn build(&self) {
176 assert!(
177 self.trash_data_count <= MAX_TRASH_DATA_ENTRIES,
178 "number of TrashData entries cannot be bigger than {:?}",
179 MAX_TRASH_DATA_ENTRIES
180 );
181
182 (0..self.trash_data_count)
183 .for_each(|i| TrashData::<T>::insert(i, Pallet::<T>::gen_value(i)));
184
185 TrashDataCount::<T>::set(self.trash_data_count);
186
187 assert!(self.compute <= RESOURCE_HARD_LIMIT, "Compute limit is insane");
188 <Compute<T>>::put(self.compute);
189
190 assert!(self.storage <= RESOURCE_HARD_LIMIT, "Storage limit is insane");
191 <Storage<T>>::put(self.storage);
192
193 assert!(self.block_length <= RESOURCE_HARD_LIMIT, "Block length limit is insane");
194 <Length<T>>::put(self.block_length);
195 }
196 }
197
198 #[pallet::hooks]
199 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
200 fn integrity_test() {
201 assert!(
202 !T::WeightInfo::waste_ref_time_iter(1).ref_time().is_zero(),
203 "Weight zero; would get stuck in an infinite loop"
204 );
205 assert!(
206 !T::WeightInfo::waste_proof_size_some(1).proof_size().is_zero(),
207 "Weight zero; would get stuck in an infinite loop"
208 );
209 }
210
211 fn on_idle(_: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
212 log::debug!(target: LOG_TARGET, "Running `on_idle` with remaining weight: {remaining_weight:?}");
213
214 let mut meter = WeightMeter::with_limit(remaining_weight);
215 if meter.try_consume(T::WeightInfo::empty_on_idle()).is_err() {
216 return T::WeightInfo::empty_on_idle();
217 }
218
219 let proof_size_limit =
220 Storage::<T>::get().saturating_mul_int(meter.remaining().proof_size());
221 let computation_weight_limit =
222 Compute::<T>::get().saturating_mul_int(meter.remaining().ref_time());
223
224 log::debug!(target: LOG_TARGET, "Going to waste: proof_size {proof_size_limit:?}; compute {computation_weight_limit:?}");
225
226 let mut meter = WeightMeter::with_limit(Weight::from_parts(
227 computation_weight_limit,
228 proof_size_limit,
229 ));
230
231 Self::waste_at_most_proof_size(&mut meter);
232 Self::waste_at_most_ref_time(&mut meter);
233
234 meter.consumed()
235 }
236 }
237
238 #[pallet::inherent]
239 impl<T: Config> ProvideInherent for Pallet<T> {
240 type Call = Call<T>;
241 type Error = sp_inherents::MakeFatalError<()>;
242
243 const INHERENT_IDENTIFIER: InherentIdentifier = *b"bloated0";
244
245 fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
246 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Mandatory);
247 let bloat_size = Length::<T>::get().saturating_mul_int(max_block_length) as usize;
248 let amount_trash = bloat_size / VALUE_SIZE;
249 let garbage = TrashData::<T>::iter()
250 .map(|(_k, v)| v)
251 .collect::<Vec<_>>()
252 .into_iter()
253 .cycle()
254 .take(amount_trash)
255 .collect::<Vec<_>>();
256
257 Some(Call::bloat { garbage })
258 }
259
260 fn is_inherent(call: &Self::Call) -> bool {
261 matches!(call, Call::bloat { .. })
262 }
263
264 fn check_inherent(call: &Self::Call, _: &InherentData) -> Result<(), Self::Error> {
265 match call {
266 Call::bloat { .. } => Ok(()),
267 _ => unreachable!("other calls are not inherents"),
268 }
269 }
270 }
271
272 #[pallet::call(weight = T::WeightInfo)]
273 impl<T: Config> Pallet<T> {
274 #[pallet::call_index(0)]
281 #[pallet::weight(
282 T::WeightInfo::initialize_pallet_grow(witness_count.unwrap_or_default())
283 .max(T::WeightInfo::initialize_pallet_shrink(witness_count.unwrap_or_default()))
284 )]
285 pub fn initialize_pallet(
286 origin: OriginFor<T>,
287 new_count: u32,
288 witness_count: Option<u32>,
289 ) -> DispatchResult {
290 T::AdminOrigin::ensure_origin_or_root(origin)?;
291
292 let current_count = TrashDataCount::<T>::get();
293 ensure!(
294 current_count == witness_count.unwrap_or_default(),
295 Error::<T>::AlreadyInitialized
296 );
297
298 if new_count > current_count {
299 (current_count..new_count)
300 .for_each(|i| TrashData::<T>::insert(i, Self::gen_value(i)));
301 } else {
302 (new_count..current_count).for_each(TrashData::<T>::remove);
303 }
304
305 Self::deposit_event(Event::PalletInitialized { reinit: witness_count.is_some() });
306 TrashDataCount::<T>::set(new_count);
307 Ok(())
308 }
309
310 #[pallet::call_index(1)]
314 pub fn set_compute(origin: OriginFor<T>, compute: FixedU64) -> DispatchResult {
315 T::AdminOrigin::ensure_origin_or_root(origin)?;
316
317 ensure!(compute <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
318 Compute::<T>::set(compute);
319
320 Self::deposit_event(Event::ComputationLimitSet { compute });
321 Ok(())
322 }
323
324 #[pallet::call_index(2)]
332 pub fn set_storage(origin: OriginFor<T>, storage: FixedU64) -> DispatchResult {
333 T::AdminOrigin::ensure_origin_or_root(origin)?;
334
335 ensure!(storage <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
336 Storage::<T>::set(storage);
337
338 Self::deposit_event(Event::StorageLimitSet { storage });
339 Ok(())
340 }
341
342 #[pallet::call_index(3)]
344 #[pallet::weight((0, DispatchClass::Mandatory))]
345 pub fn bloat(_origin: OriginFor<T>, _garbage: Vec<[u8; VALUE_SIZE]>) -> DispatchResult {
346 Ok(())
347 }
348
349 #[pallet::call_index(4)]
356 #[pallet::weight({1})]
357 pub fn set_block_length(origin: OriginFor<T>, block_length: FixedU64) -> DispatchResult {
358 T::AdminOrigin::ensure_origin_or_root(origin)?;
359
360 ensure!(block_length <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
361 Length::<T>::set(block_length);
362
363 Self::deposit_event(Event::BlockLengthLimitSet { block_length });
364 Ok(())
365 }
366 }
367
368 impl<T: Config> Pallet<T> {
369 pub(crate) fn waste_at_most_proof_size(meter: &mut WeightMeter) {
373 let Ok(n) = Self::calculate_proof_size_iters(&meter) else { return };
374
375 meter.consume(T::WeightInfo::waste_proof_size_some(n));
376
377 (0..n).for_each(|i| {
378 TrashData::<T>::get(i);
379 });
380 }
381
382 fn calculate_proof_size_iters(meter: &WeightMeter) -> Result<u32, ()> {
384 let base = T::WeightInfo::waste_proof_size_some(0);
385 let slope = T::WeightInfo::waste_proof_size_some(1).saturating_sub(base);
386
387 let remaining = meter.remaining().saturating_sub(base);
388 let iter_by_proof_size =
389 remaining.proof_size().checked_div(slope.proof_size()).ok_or(())?;
390 let iter_by_ref_time = remaining.ref_time().checked_div(slope.ref_time()).ok_or(())?;
391
392 if iter_by_proof_size > 0 && iter_by_proof_size <= iter_by_ref_time {
393 Ok(iter_by_proof_size as u32)
394 } else {
395 Err(())
396 }
397 }
398
399 pub(crate) fn waste_at_most_ref_time(meter: &mut WeightMeter) {
403 let Ok(n) = Self::calculate_ref_time_iters(&meter) else { return };
404 meter.consume(T::WeightInfo::waste_ref_time_iter(n));
405
406 let clobber = Self::waste_ref_time_iter(vec![0u8; 64], n);
407
408 debug_assert!(clobber.len() == 64);
412 if clobber.len() == 65 {
413 TrashData::<T>::insert(0, [clobber[0] as u8; VALUE_SIZE]);
414 }
415 }
416
417 pub(crate) fn waste_ref_time_iter(clobber: Vec<u8>, i: u32) -> Vec<u8> {
421 let mut hasher = Blake2b512::new();
422
423 (0..i).for_each(|_| {
426 hasher.update(clobber.as_slice());
427 });
428
429 hasher.finalize().to_vec()
430 }
431
432 fn calculate_ref_time_iters(meter: &WeightMeter) -> Result<u32, ()> {
434 let base = T::WeightInfo::waste_ref_time_iter(0);
435 let slope = T::WeightInfo::waste_ref_time_iter(1).saturating_sub(base);
436 if !slope.proof_size().is_zero() || !base.proof_size().is_zero() {
437 return Err(());
438 }
439
440 match meter
441 .remaining()
442 .ref_time()
443 .saturating_sub(base.ref_time())
444 .checked_div(slope.ref_time())
445 {
446 Some(0) | None => Err(()),
447 Some(i) => Ok(i as u32),
448 }
449 }
450
451 pub(crate) fn gen_value(seed: u32) -> [u8; VALUE_SIZE] {
453 let mut ret = [0u8; VALUE_SIZE];
454
455 for i in 0u32..(VALUE_SIZE as u32 / 32) {
456 let hash = (seed, i).using_encoded(twox_256);
457 ret[i as usize * 32..(i + 1) as usize * 32].copy_from_slice(&hash);
458 }
459
460 ret
461 }
462 }
463}