@@ -14,6 +14,17 @@ mod tests;
1414mod benchmarking;
1515pub mod weights;
1616
17+ use codec:: { Decode , Encode } ;
18+ use scale_info:: TypeInfo ;
19+ use sp_runtime:: {
20+ traits:: { DispatchInfoOf , SignedExtension } ,
21+ transaction_validity:: {
22+ InvalidTransaction , TransactionValidity , TransactionValidityError , ValidTransaction ,
23+ } ,
24+ } ;
25+
26+ use frame_support:: traits:: { Get , IsSubType } ;
27+
1728#[ frame_support:: pallet]
1829pub mod pallet {
1930 pub use crate :: weights:: WeightInfo ;
@@ -153,7 +164,10 @@ pub mod pallet {
153164 //
154165 // To the submit_blob weight is added a flat cost relative to the on_finalize execution
155166 // the amount is equal to the entire weight of on_finalized divided by 1/4 of the MaxBlobs
156- // this covers perfectly the on_finalize cost if on avarage 1/4 of the possible blobs are submitted in one block
167+ // this covers perfectly the on_finalize cost if on average 1/4 of the possible blobs are submitted in one block
168+ //
169+ // Note: this PANICS if the size of the blob, the total size of all blobs, or the total number of blobs submitted
170+ // exceed their respective configured limits. These panics are intended to be protected against by the [`crate::PrevalidateBlobs`] extension.
157171 #[ pallet:: weight(
158172 T :: WeightInfo :: submit_blob( T :: MaxBlobs :: get( ) / 2 , blob. len( ) as u32 )
159173 . saturating_add( T :: WeightInfo :: on_finalize( 0 ) / ( T :: MaxBlobs :: get( ) / 4 ) as u64 )
@@ -165,20 +179,25 @@ pub mod pallet {
165179 ) -> DispatchResultWithPostInfo {
166180 let who = ensure_signed ( origin) ?;
167181
182+ let blob_len = blob. len ( ) as u32 ;
183+ if blob_len > T :: MaxBlobSize :: get ( ) {
184+ panic ! ( "Blob size limit exceeded" ) ;
185+ }
186+
168187 let Some ( extrinsic_index) = <frame_system:: Pallet < T > >:: extrinsic_index ( ) else {
169188 return Err ( Error :: < T > :: NoExtrinsicIndex . into ( ) ) ;
170189 } ;
171190
172191 let total_blobs = TotalBlobs :: < T > :: get ( ) ;
173192 if total_blobs + 1 > T :: MaxBlobs :: get ( ) {
174- return Err ( Error :: < T > :: MaxBlobsReached . into ( ) ) ;
193+ panic ! ( "Maximum blob limit exceeded" ) ;
175194 }
176195 TotalBlobs :: < T > :: put ( total_blobs + 1 ) ;
177196
178197 let blob_len = blob. len ( ) as u32 ;
179198 let total_blobs_size = TotalBlobsSize :: < T > :: get ( ) ;
180199 if total_blobs_size + blob_len > T :: MaxTotalBlobSize :: get ( ) {
181- return Err ( Error :: < T > :: MaxTotalBlobsSizeReached . into ( ) ) ;
200+ panic ! ( "Maximum total blob size exceeded" ) ;
182201 }
183202 TotalBlobsSize :: < T > :: put ( total_blobs_size + blob_len) ;
184203
@@ -208,3 +227,96 @@ pub mod pallet {
208227 sha2:: Sha256 :: digest ( data) . into ( )
209228 }
210229}
230+
231+ /// Prevalidates blob limits
232+ #[ derive( Encode , Decode , Clone , Eq , PartialEq , TypeInfo ) ]
233+ #[ scale_info( skip_type_params( T ) ) ]
234+ pub struct PrevalidateBlobs < T > ( sp_std:: marker:: PhantomData < T > ) ;
235+
236+ impl < T > PrevalidateBlobs < T > {
237+ /// Create new `SignedExtension` to prevalidate blob sizes.
238+ pub fn new ( ) -> Self {
239+ Self ( sp_std:: marker:: PhantomData )
240+ }
241+ }
242+
243+ impl < T > sp_std:: fmt:: Debug for PrevalidateBlobs < T > {
244+ #[ cfg( feature = "std" ) ]
245+ fn fmt ( & self , f : & mut sp_std:: fmt:: Formatter ) -> sp_std:: fmt:: Result {
246+ write ! ( f, "PrevalidateBlobs" )
247+ }
248+
249+ #[ cfg( not( feature = "std" ) ) ]
250+ fn fmt ( & self , _: & mut sp_std:: fmt:: Formatter ) -> sp_std:: fmt:: Result {
251+ Ok ( ( ) )
252+ }
253+ }
254+
255+ impl < T : Config + Send + Sync > SignedExtension for PrevalidateBlobs < T >
256+ where
257+ <T as frame_system:: Config >:: RuntimeCall : IsSubType < Call < T > > ,
258+ {
259+ type AccountId = T :: AccountId ;
260+ type Call = <T as frame_system:: Config >:: RuntimeCall ;
261+ type AdditionalSigned = ( ) ;
262+ type Pre = ( ) ;
263+ const IDENTIFIER : & ' static str = "PrevalidateBlobs" ;
264+
265+ fn additional_signed ( & self ) -> Result < Self :: AdditionalSigned , TransactionValidityError > {
266+ Ok ( ( ) )
267+ }
268+
269+ fn pre_dispatch (
270+ self ,
271+ who : & Self :: AccountId ,
272+ call : & Self :: Call ,
273+ info : & DispatchInfoOf < Self :: Call > ,
274+ len : usize ,
275+ ) -> Result < Self :: Pre , TransactionValidityError > {
276+ // This is what's called prior to dispatching within an actual block.
277+
278+ // Check individual blob size limits
279+ self . validate ( who, call, info, len) ?;
280+
281+ // Here, we return `ExhaustsResources` if the total amount or size of blobs
282+ // within a block is disrespected.
283+ //
284+ // This will cause honest nodes authoring blocks to skip the transaction without
285+ // expunging it from their transaction pool.
286+ if let Some ( local_call) = call. is_sub_type ( ) {
287+ if let Call :: submit_blob { blob, .. } = local_call {
288+ if TotalBlobs :: < T > :: get ( ) + 1 > T :: MaxBlobs :: get ( ) {
289+ return Err ( InvalidTransaction :: ExhaustsResources . into ( ) ) ;
290+ }
291+
292+ if TotalBlobsSize :: < T > :: get ( ) + blob. len ( ) as u32 > T :: MaxTotalBlobSize :: get ( ) {
293+ return Err ( InvalidTransaction :: ExhaustsResources . into ( ) ) ;
294+ }
295+ }
296+ }
297+
298+ Ok ( ( ) )
299+ }
300+
301+ fn validate (
302+ & self ,
303+ _who : & Self :: AccountId ,
304+ call : & Self :: Call ,
305+ _info : & DispatchInfoOf < Self :: Call > ,
306+ _len : usize ,
307+ ) -> TransactionValidity {
308+ // This is what's called when evaluating transactions within the pool.
309+
310+ if let Some ( local_call) = call. is_sub_type ( ) {
311+ if let Call :: submit_blob { blob, .. } = local_call {
312+ if blob. len ( ) as u32 > T :: MaxBlobSize :: get ( ) {
313+ // This causes the transaction to be expunged from the transaction pool.
314+ // It will not be valid unless the configured limit is increased by governance,
315+ // which is a rare event.
316+ return Err ( InvalidTransaction :: Custom ( 0 ) . into ( ) ) ;
317+ }
318+ }
319+ }
320+ Ok ( ValidTransaction :: default ( ) )
321+ }
322+ }
0 commit comments