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
50pub const VALUE_SIZE: usize = 1024;
52pub const MAX_TRASH_DATA_ENTRIES: u32 = 65_000;
54pub const RESOURCE_HARD_LIMIT: FixedU64 = FixedU64::from_u32(10);
56
57#[frame_support::pallet]
58pub mod pallet {
59 use super::*;
60
61 #[pallet::config]
62 pub trait Config: frame_system::Config {
63 #[allow(deprecated)]
65 type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
66
67 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
69
70 type WeightInfo: WeightInfo;
72 }
73
74 #[pallet::pallet]
75 pub struct Pallet<T>(_);
76
77 #[pallet::event]
78 #[pallet::generate_deposit(pub(super) fn deposit_event)]
79 pub enum Event {
80 PalletInitialized {
82 reinit: bool,
84 },
85 ComputationLimitSet {
87 compute: FixedU64,
89 },
90 StorageLimitSet {
92 storage: FixedU64,
94 },
95 BlockLengthLimitSet {
97 block_length: FixedU64,
99 },
100 }
101
102 #[pallet::error]
103 pub enum Error<T> {
104 AlreadyInitialized,
108
109 InsaneLimit,
111 }
112
113 #[pallet::storage]
118 pub(crate) type Compute<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
119
120 #[pallet::storage]
125 pub(crate) type Storage<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
126
127 #[pallet::storage]
132 pub(crate) type Length<T: Config> = StorageValue<_, FixedU64, ValueQuery>;
133
134 #[pallet::storage]
143 pub(super) type TrashData<T: Config> = StorageMap<
144 Hasher = Twox64Concat,
145 Key = u32,
146 Value = [u8; VALUE_SIZE],
147 QueryKind = OptionQuery,
148 MaxValues = ConstU32<MAX_TRASH_DATA_ENTRIES>,
149 >;
150
151 #[pallet::storage]
153 pub(crate) type TrashDataCount<T: Config> = StorageValue<_, u32, ValueQuery>;
154
155 #[pallet::genesis_config]
156 #[derive(DefaultNoBound)]
157 pub struct GenesisConfig<T: Config> {
158 pub compute: FixedU64,
160 pub storage: FixedU64,
162 pub trash_data_count: u32,
164 pub block_length: FixedU64,
166 #[serde(skip)]
167 pub _config: core::marker::PhantomData<T>,
169 }
170
171 #[pallet::genesis_build]
172 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
173 fn build(&self) {
174 assert!(
175 self.trash_data_count <= MAX_TRASH_DATA_ENTRIES,
176 "number of TrashData entries cannot be bigger than {:?}",
177 MAX_TRASH_DATA_ENTRIES
178 );
179
180 (0..self.trash_data_count)
181 .for_each(|i| TrashData::<T>::insert(i, Pallet::<T>::gen_value(i)));
182
183 TrashDataCount::<T>::set(self.trash_data_count);
184
185 assert!(self.compute <= RESOURCE_HARD_LIMIT, "Compute limit is insane");
186 <Compute<T>>::put(self.compute);
187
188 assert!(self.storage <= RESOURCE_HARD_LIMIT, "Storage limit is insane");
189 <Storage<T>>::put(self.storage);
190
191 assert!(self.block_length <= RESOURCE_HARD_LIMIT, "Block length limit is insane");
192 <Length<T>>::put(self.block_length);
193 }
194 }
195
196 #[pallet::hooks]
197 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
198 fn integrity_test() {
199 assert!(
200 !T::WeightInfo::waste_ref_time_iter(1).ref_time().is_zero(),
201 "Weight zero; would get stuck in an infinite loop"
202 );
203 assert!(
204 !T::WeightInfo::waste_proof_size_some(1).proof_size().is_zero(),
205 "Weight zero; would get stuck in an infinite loop"
206 );
207 }
208
209 fn on_idle(_: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
210 let mut meter = WeightMeter::with_limit(remaining_weight);
211 if meter.try_consume(T::WeightInfo::empty_on_idle()).is_err() {
212 return T::WeightInfo::empty_on_idle()
213 }
214
215 let proof_size_limit =
216 Storage::<T>::get().saturating_mul_int(meter.remaining().proof_size());
217 let computation_weight_limit =
218 Compute::<T>::get().saturating_mul_int(meter.remaining().ref_time());
219 let mut meter = WeightMeter::with_limit(Weight::from_parts(
220 computation_weight_limit,
221 proof_size_limit,
222 ));
223
224 Self::waste_at_most_proof_size(&mut meter);
225 Self::waste_at_most_ref_time(&mut meter);
226
227 meter.consumed()
228 }
229 }
230
231 #[pallet::inherent]
232 impl<T: Config> ProvideInherent for Pallet<T> {
233 type Call = Call<T>;
234 type Error = sp_inherents::MakeFatalError<()>;
235
236 const INHERENT_IDENTIFIER: InherentIdentifier = *b"bloated0";
237
238 fn create_inherent(_data: &InherentData) -> Option<Self::Call> {
239 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Mandatory);
240 let bloat_size = Length::<T>::get().saturating_mul_int(max_block_length) as usize;
241 let amount_trash = bloat_size / VALUE_SIZE;
242 let garbage = TrashData::<T>::iter()
243 .map(|(_k, v)| v)
244 .collect::<Vec<_>>()
245 .into_iter()
246 .cycle()
247 .take(amount_trash)
248 .collect::<Vec<_>>();
249
250 Some(Call::bloat { garbage })
251 }
252
253 fn is_inherent(call: &Self::Call) -> bool {
254 matches!(call, Call::bloat { .. })
255 }
256
257 fn check_inherent(call: &Self::Call, _: &InherentData) -> Result<(), Self::Error> {
258 match call {
259 Call::bloat { .. } => Ok(()),
260 _ => unreachable!("other calls are not inherents"),
261 }
262 }
263 }
264
265 #[pallet::call(weight = T::WeightInfo)]
266 impl<T: Config> Pallet<T> {
267 #[pallet::call_index(0)]
274 #[pallet::weight(
275 T::WeightInfo::initialize_pallet_grow(witness_count.unwrap_or_default())
276 .max(T::WeightInfo::initialize_pallet_shrink(witness_count.unwrap_or_default()))
277 )]
278 pub fn initialize_pallet(
279 origin: OriginFor<T>,
280 new_count: u32,
281 witness_count: Option<u32>,
282 ) -> DispatchResult {
283 T::AdminOrigin::ensure_origin_or_root(origin)?;
284
285 let current_count = TrashDataCount::<T>::get();
286 ensure!(
287 current_count == witness_count.unwrap_or_default(),
288 Error::<T>::AlreadyInitialized
289 );
290
291 if new_count > current_count {
292 (current_count..new_count)
293 .for_each(|i| TrashData::<T>::insert(i, Self::gen_value(i)));
294 } else {
295 (new_count..current_count).for_each(TrashData::<T>::remove);
296 }
297
298 Self::deposit_event(Event::PalletInitialized { reinit: witness_count.is_some() });
299 TrashDataCount::<T>::set(new_count);
300 Ok(())
301 }
302
303 #[pallet::call_index(1)]
307 pub fn set_compute(origin: OriginFor<T>, compute: FixedU64) -> DispatchResult {
308 T::AdminOrigin::ensure_origin_or_root(origin)?;
309
310 ensure!(compute <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
311 Compute::<T>::set(compute);
312
313 Self::deposit_event(Event::ComputationLimitSet { compute });
314 Ok(())
315 }
316
317 #[pallet::call_index(2)]
325 pub fn set_storage(origin: OriginFor<T>, storage: FixedU64) -> DispatchResult {
326 T::AdminOrigin::ensure_origin_or_root(origin)?;
327
328 ensure!(storage <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
329 Storage::<T>::set(storage);
330
331 Self::deposit_event(Event::StorageLimitSet { storage });
332 Ok(())
333 }
334
335 #[pallet::call_index(3)]
337 #[pallet::weight((0, DispatchClass::Mandatory))]
338 pub fn bloat(_origin: OriginFor<T>, _garbage: Vec<[u8; VALUE_SIZE]>) -> DispatchResult {
339 Ok(())
340 }
341
342 #[pallet::call_index(4)]
349 #[pallet::weight({1})]
350 pub fn set_block_length(origin: OriginFor<T>, block_length: FixedU64) -> DispatchResult {
351 T::AdminOrigin::ensure_origin_or_root(origin)?;
352
353 ensure!(block_length <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
354 Length::<T>::set(block_length);
355
356 Self::deposit_event(Event::BlockLengthLimitSet { block_length });
357 Ok(())
358 }
359 }
360
361 impl<T: Config> Pallet<T> {
362 pub(crate) fn waste_at_most_proof_size(meter: &mut WeightMeter) {
366 let Ok(n) = Self::calculate_proof_size_iters(&meter) else { return };
367
368 meter.consume(T::WeightInfo::waste_proof_size_some(n));
369
370 (0..n).for_each(|i| {
371 TrashData::<T>::get(i);
372 });
373 }
374
375 fn calculate_proof_size_iters(meter: &WeightMeter) -> Result<u32, ()> {
377 let base = T::WeightInfo::waste_proof_size_some(0);
378 let slope = T::WeightInfo::waste_proof_size_some(1).saturating_sub(base);
379
380 let remaining = meter.remaining().saturating_sub(base);
381 let iter_by_proof_size =
382 remaining.proof_size().checked_div(slope.proof_size()).ok_or(())?;
383 let iter_by_ref_time = remaining.ref_time().checked_div(slope.ref_time()).ok_or(())?;
384
385 if iter_by_proof_size > 0 && iter_by_proof_size <= iter_by_ref_time {
386 Ok(iter_by_proof_size as u32)
387 } else {
388 Err(())
389 }
390 }
391
392 pub(crate) fn waste_at_most_ref_time(meter: &mut WeightMeter) {
396 let Ok(n) = Self::calculate_ref_time_iters(&meter) else { return };
397 meter.consume(T::WeightInfo::waste_ref_time_iter(n));
398
399 let clobber = Self::waste_ref_time_iter(vec![0u8; 64], n);
400
401 debug_assert!(clobber.len() == 64);
405 if clobber.len() == 65 {
406 TrashData::<T>::insert(0, [clobber[0] as u8; VALUE_SIZE]);
407 }
408 }
409
410 pub(crate) fn waste_ref_time_iter(clobber: Vec<u8>, i: u32) -> Vec<u8> {
414 let mut hasher = Blake2b512::new();
415
416 (0..i).for_each(|_| {
419 hasher.update(clobber.as_slice());
420 });
421
422 hasher.finalize().to_vec()
423 }
424
425 fn calculate_ref_time_iters(meter: &WeightMeter) -> Result<u32, ()> {
427 let base = T::WeightInfo::waste_ref_time_iter(0);
428 let slope = T::WeightInfo::waste_ref_time_iter(1).saturating_sub(base);
429 if !slope.proof_size().is_zero() || !base.proof_size().is_zero() {
430 return Err(())
431 }
432
433 match meter
434 .remaining()
435 .ref_time()
436 .saturating_sub(base.ref_time())
437 .checked_div(slope.ref_time())
438 {
439 Some(0) | None => Err(()),
440 Some(i) => Ok(i as u32),
441 }
442 }
443
444 pub(crate) fn gen_value(seed: u32) -> [u8; VALUE_SIZE] {
446 let mut ret = [0u8; VALUE_SIZE];
447
448 for i in 0u32..(VALUE_SIZE as u32 / 32) {
449 let hash = (seed, i).using_encoded(twox_256);
450 ret[i as usize * 32..(i + 1) as usize * 32].copy_from_slice(&hash);
451 }
452
453 ret
454 }
455 }
456}