@@ -142,6 +142,11 @@ pub mod pallet {
142142 id : T :: Hash ,
143143 reason : DispatchErrorWithPostInfo < PostDispatchInfo > ,
144144 } ,
145+ /// Decryption failed - validator could not decrypt the submission.
146+ DecryptionFailed {
147+ id : T :: Hash ,
148+ reason : BoundedVec < u8 , ConstU32 < 256 > > ,
149+ } ,
145150 }
146151
147152 #[ pallet:: error]
@@ -404,6 +409,43 @@ pub mod pallet {
404409 }
405410 }
406411 }
412+
413+ /// Marks a submission as failed to decrypt and removes it from storage.
414+ ///
415+ /// Called by the block author when decryption fails at any stage (e.g., ML-KEM decapsulate
416+ /// failed, AEAD decrypt failed, invalid ciphertext format, etc.). This allows clients to be
417+ /// notified of decryption failures through on-chain events.
418+ ///
419+ /// # Arguments
420+ ///
421+ /// * `id` - The wrapper id (hash of (author, commitment, ciphertext))
422+ /// * `reason` - Human-readable reason for the decryption failure (e.g., "ML-KEM decapsulate failed")
423+ #[ pallet:: call_index( 3 ) ]
424+ #[ pallet:: weight( (
425+ Weight :: from_parts( 10_000_000 , 0 )
426+ . saturating_add( T :: DbWeight :: get( ) . reads( 1_u64 ) )
427+ . saturating_add( T :: DbWeight :: get( ) . writes( 1_u64 ) ) ,
428+ DispatchClass :: Operational ,
429+ Pays :: No
430+ ) ) ]
431+ pub fn mark_decryption_failed (
432+ origin : OriginFor < T > ,
433+ id : T :: Hash ,
434+ reason : BoundedVec < u8 , ConstU32 < 256 > > ,
435+ ) -> DispatchResult {
436+ // Unsigned: only the author node may inject this via ValidateUnsigned.
437+ ensure_none ( origin) ?;
438+
439+ // Load and consume the submission.
440+ let Some ( _sub) = Submissions :: < T > :: take ( id) else {
441+ return Err ( Error :: < T > :: MissingSubmission . into ( ) ) ;
442+ } ;
443+
444+ // Emit event to notify clients
445+ Self :: deposit_event ( Event :: DecryptionFailed { id, reason } ) ;
446+
447+ Ok ( ( ) )
448+ }
407449 }
408450
409451 impl < T : Config > Pallet < T > {
@@ -448,7 +490,19 @@ pub mod pallet {
448490 _ => InvalidTransaction :: Call . into ( ) ,
449491 }
450492 }
451-
493+ Call :: mark_decryption_failed { id, .. } => {
494+ match source {
495+ TransactionSource :: Local | TransactionSource :: InBlock => {
496+ ValidTransaction :: with_tag_prefix ( "mev-shield-failed" )
497+ . priority ( u64:: MAX )
498+ . longevity ( 64 ) // long because propagate(false)
499+ . and_provides ( id) // dedupe by wrapper id
500+ . propagate ( false ) // CRITICAL: no gossip, stays on author node
501+ . build ( )
502+ }
503+ _ => InvalidTransaction :: Call . into ( ) ,
504+ }
505+ }
452506 _ => InvalidTransaction :: Call . into ( ) ,
453507 }
454508 }
0 commit comments