From 9e3998b75782b07379e12793519d337b09faa309 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 3 Dec 2025 12:32:44 -0800 Subject: [PATCH 1/8] add `DecryptionFailed` in Event --- pallets/shield/src/lib.rs | 56 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pallets/shield/src/lib.rs b/pallets/shield/src/lib.rs index 87e06949fe..bba767e15a 100644 --- a/pallets/shield/src/lib.rs +++ b/pallets/shield/src/lib.rs @@ -142,6 +142,11 @@ pub mod pallet { id: T::Hash, reason: DispatchErrorWithPostInfo, }, + /// Decryption failed - validator could not decrypt the submission. + DecryptionFailed { + id: T::Hash, + reason: BoundedVec>, + }, } #[pallet::error] @@ -404,6 +409,43 @@ pub mod pallet { } } } + + /// Marks a submission as failed to decrypt and removes it from storage. + /// + /// Called by the block author when decryption fails at any stage (e.g., ML-KEM decapsulate + /// failed, AEAD decrypt failed, invalid ciphertext format, etc.). This allows clients to be + /// notified of decryption failures through on-chain events. + /// + /// # Arguments + /// + /// * `id` - The wrapper id (hash of (author, commitment, ciphertext)) + /// * `reason` - Human-readable reason for the decryption failure (e.g., "ML-KEM decapsulate failed") + #[pallet::call_index(3)] + #[pallet::weight(( + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::No + ))] + pub fn mark_decryption_failed( + origin: OriginFor, + id: T::Hash, + reason: BoundedVec>, + ) -> DispatchResult { + // Unsigned: only the author node may inject this via ValidateUnsigned. + ensure_none(origin)?; + + // Load and consume the submission. + let Some(_sub) = Submissions::::take(id) else { + return Err(Error::::MissingSubmission.into()); + }; + + // Emit event to notify clients + Self::deposit_event(Event::DecryptionFailed { id, reason }); + + Ok(()) + } } impl Pallet { @@ -448,7 +490,19 @@ pub mod pallet { _ => InvalidTransaction::Call.into(), } } - + Call::mark_decryption_failed { id, .. } => { + match source { + TransactionSource::Local | TransactionSource::InBlock => { + ValidTransaction::with_tag_prefix("mev-shield-failed") + .priority(u64::MAX) + .longevity(64) // long because propagate(false) + .and_provides(id) // dedupe by wrapper id + .propagate(false) // CRITICAL: no gossip, stays on author node + .build() + } + _ => InvalidTransaction::Call.into(), + } + } _ => InvalidTransaction::Call.into(), } } From f0b4ce0177eb7d3ca241cdf688ff640b56295512 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 3 Dec 2025 12:39:36 -0800 Subject: [PATCH 2/8] ensure error messages in logs match on-chain mark_decryption_failed reasons --- node/src/mev_shield/proposer.rs | 292 ++++++++++++++++++++++++++++---- 1 file changed, 258 insertions(+), 34 deletions(-) diff --git a/node/src/mev_shield/proposer.rs b/node/src/mev_shield/proposer.rs index 20fb6c790b..41b3a8e5ab 100644 --- a/node/src/mev_shield/proposer.rs +++ b/node/src/mev_shield/proposer.rs @@ -349,6 +349,27 @@ pub fn spawn_revealer( let mut to_submit: Vec<(H256, node_subtensor_runtime::RuntimeCall)> = Vec::new(); + let mut failed_calls: Vec<(H256, node_subtensor_runtime::RuntimeCall)> = + Vec::new(); + + // Helper to create mark_decryption_failed call + let create_failed_call = |id: H256, reason: &str| -> node_subtensor_runtime::RuntimeCall { + use sp_runtime::BoundedVec; + let reason_bytes = reason.as_bytes(); + let reason_bounded = BoundedVec::try_from(reason_bytes.to_vec()) + .unwrap_or_else(|_| { + // Fallback if the reason is too long + BoundedVec::try_from(b"Decryption failed".to_vec()) + .expect("Fallback reason should fit") + }); + + node_subtensor_runtime::RuntimeCall::MevShield( + pallet_shield::Call::mark_decryption_failed { + id, + reason: reason_bounded, + }, + ) + }; for (id, block_number, author, blob) in drained.into_iter() { log::debug!( @@ -363,11 +384,17 @@ pub fn spawn_revealer( // Safely parse blob: [u16 kem_len][kem_ct][nonce24][aead_ct] if blob.len() < 2 { + let error_message = "blob too short to contain kem_len"; log::debug!( target: "mev-shield", - " id=0x{}: blob too short to contain kem_len", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } @@ -377,11 +404,17 @@ pub fn spawn_revealer( let kem_len_end = match cursor.checked_add(2usize) { Some(e) => e, None => { + let error_message = "kem_len range overflow"; log::debug!( target: "mev-shield", - " id=0x{}: kem_len range overflow", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -389,13 +422,19 @@ pub fn spawn_revealer( let kem_len_slice = match blob.get(cursor..kem_len_end) { Some(s) => s, None => { + let error_message = "blob too short for kem_len bytes"; log::debug!( target: "mev-shield", - " id=0x{}: blob too short for kem_len bytes (cursor={} end={})", + " id=0x{}: {} (cursor={} end={})", hex::encode(id.as_bytes()), + error_message, cursor, kem_len_end ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -403,11 +442,17 @@ pub fn spawn_revealer( let kem_len_bytes: [u8; 2] = match kem_len_slice.try_into() { Ok(arr) => arr, Err(_) => { + let error_message = "kem_len slice not 2 bytes"; log::debug!( target: "mev-shield", - " id=0x{}: kem_len slice not 2 bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -419,13 +464,19 @@ pub fn spawn_revealer( let kem_ct_end = match cursor.checked_add(kem_len) { Some(e) => e, None => { + let error_message = "kem_ct range overflow"; log::debug!( target: "mev-shield", - " id=0x{}: kem_ct range overflow (cursor={} kem_len={})", + " id=0x{}: {} (cursor={} kem_len={})", hex::encode(id.as_bytes()), + error_message, cursor, kem_len ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -433,13 +484,19 @@ pub fn spawn_revealer( let kem_ct_bytes = match blob.get(cursor..kem_ct_end) { Some(s) => s, None => { + let error_message = "blob too short for kem_ct"; log::debug!( target: "mev-shield", - " id=0x{}: blob too short for kem_ct (cursor={} end={})", + " id=0x{}: {} (cursor={} end={})", hex::encode(id.as_bytes()), + error_message, cursor, kem_ct_end ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -450,12 +507,18 @@ pub fn spawn_revealer( let nonce_end = match cursor.checked_add(NONCE_LEN) { Some(e) => e, None => { + let error_message = "nonce range overflow"; log::debug!( target: "mev-shield", - " id=0x{}: nonce range overflow (cursor={})", + " id=0x{}: {} (cursor={})", hex::encode(id.as_bytes()), + error_message, cursor ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -463,13 +526,19 @@ pub fn spawn_revealer( let nonce_bytes = match blob.get(cursor..nonce_end) { Some(s) => s, None => { + let error_message = "blob too short for nonce24"; log::debug!( target: "mev-shield", - " id=0x{}: blob too short for nonce24 (cursor={} end={})", + " id=0x{}: {} (cursor={} end={})", hex::encode(id.as_bytes()), + error_message, cursor, nonce_end ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -479,12 +548,18 @@ pub fn spawn_revealer( let aead_body = match blob.get(cursor..) { Some(s) => s, None => { + let error_message = "blob too short for aead_body"; log::debug!( target: "mev-shield", - " id=0x{}: blob too short for aead_body (cursor={})", + " id=0x{}: {} (cursor={})", hex::encode(id.as_bytes()), + error_message, cursor ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -510,13 +585,19 @@ pub fn spawn_revealer( ) { Ok(e) => e, Err(e) => { + let error_message = "DecapsulationKey::try_from failed"; log::debug!( target: "mev-shield", - " id=0x{}: DecapsulationKey::try_from(sk_bytes) failed (len={}, err={:?})", + " id=0x{}: {} (len={}, err={:?})", hex::encode(id.as_bytes()), + error_message, curr_sk_bytes.len(), e ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -525,12 +606,18 @@ pub fn spawn_revealer( let ct = match Ciphertext::::try_from(kem_ct_bytes) { Ok(c) => c, Err(e) => { + let error_message = "Ciphertext::try_from failed"; log::debug!( target: "mev-shield", - " id=0x{}: Ciphertext::try_from failed: {:?}", + " id=0x{}: {}: {:?}", hex::encode(id.as_bytes()), + error_message, e ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -538,23 +625,35 @@ pub fn spawn_revealer( let ss = match sk.decapsulate(&ct) { Ok(s) => s, Err(_) => { + let error_message = "ML-KEM decapsulate failed"; log::debug!( target: "mev-shield", - " id=0x{}: ML-KEM decapsulate() failed", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; let ss_bytes: &[u8] = ss.as_ref(); if ss_bytes.len() != 32 { + let error_message = "shared secret length != 32"; log::debug!( target: "mev-shield", - " id=0x{}: shared secret len={} != 32; skipping", + " id=0x{}: {} (len={})", hex::encode(id.as_bytes()), + error_message, ss_bytes.len() ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } let mut ss32 = [0u8; 32]; @@ -597,12 +696,18 @@ pub fn spawn_revealer( ) { Some(pt) => pt, None => { + let error_message = "AEAD decrypt failed"; log::debug!( target: "mev-shield", - " id=0x{}: AEAD decrypt FAILED; ct_hash=0x{}", + " id=0x{}: {}; ct_hash=0x{}", hex::encode(id.as_bytes()), + error_message, hex::encode(aead_body_hash), ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -628,24 +733,36 @@ pub fn spawn_revealer( .saturating_add(64usize); if plaintext.len() < min_plain_len { + let error_message = "plaintext too short"; log::debug!( target: "mev-shield", - " id=0x{}: plaintext too short ({}) for expected layout (min={})", + " id=0x{}: {} (len={}, min={})", hex::encode(id.as_bytes()), + error_message, plaintext.len(), min_plain_len ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } let signer_raw = match plaintext.get(0..32) { Some(s) => s, None => { + let error_message = "missing signer bytes"; log::debug!( target: "mev-shield", - " id=0x{}: missing signer bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -654,11 +771,17 @@ pub fn spawn_revealer( { Some(s) if s.len() == KEY_FP_LEN => s, _ => { + let error_message = "missing or malformed key_hash bytes"; log::debug!( target: "mev-shield", - " id=0x{}: missing or malformed key_hash bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -670,12 +793,18 @@ pub fn spawn_revealer( let sig_off = match plaintext.len().checked_sub(65usize) { Some(off) if off >= sig_min_offset => off, _ => { + let error_message = "invalid plaintext length for signature split"; log::debug!( target: "mev-shield", - " id=0x{}: invalid plaintext length for signature split (len={})", + " id=0x{}: {} (len={})", hex::encode(id.as_bytes()), + error_message, plaintext.len() ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -684,11 +813,17 @@ pub fn spawn_revealer( let call_bytes = match plaintext.get(call_start..sig_off) { Some(s) if !s.is_empty() => s, _ => { + let error_message = "missing call bytes"; log::debug!( target: "mev-shield", - " id=0x{}: missing call bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -696,11 +831,17 @@ pub fn spawn_revealer( let sig_kind = match plaintext.get(sig_off) { Some(b) => *b, None => { + let error_message = "missing signature kind byte"; log::debug!( target: "mev-shield", - " id=0x{}: missing signature kind byte", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -709,11 +850,17 @@ pub fn spawn_revealer( let sig_bytes = match plaintext.get(sig_bytes_start..) { Some(s) if s.len() == 64 => s, _ => { + let error_message = "signature bytes not 64 bytes"; log::debug!( target: "mev-shield", - " id=0x{}: signature bytes not 64 bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -721,11 +868,17 @@ pub fn spawn_revealer( let signer_array: [u8; 32] = match signer_raw.try_into() { Ok(a) => a, Err(_) => { + let error_message = "signer_raw not 32 bytes"; log::debug!( target: "mev-shield", - " id=0x{}: signer_raw not 32 bytes", - hex::encode(id.as_bytes()) + " id=0x{}: {}", + hex::encode(id.as_bytes()), + error_message ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -739,13 +892,19 @@ pub fn spawn_revealer( match Decode::decode(&mut &call_bytes[..]) { Ok(c) => c, Err(e) => { + let error_message = "failed to decode RuntimeCall"; log::debug!( target: "mev-shield", - " id=0x{}: failed to decode RuntimeCall (len={}): {:?}", + " id=0x{}: {} (len={}): {:?}", hex::encode(id.as_bytes()), + error_message, call_bytes.len(), e ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; } }; @@ -756,13 +915,19 @@ pub fn spawn_revealer( raw_sig.copy_from_slice(sig_bytes); MultiSignature::from(sr25519::Signature::from_raw(raw_sig)) } else { + let error_message = "unsupported signature format"; log::debug!( target: "mev-shield", - " id=0x{}: unsupported signature format kind=0x{:02x}, len={}", + " id=0x{}: {} (kind=0x{:02x}, len={})", hex::encode(id.as_bytes()), + error_message, sig_kind, sig_bytes.len() ); + failed_calls.push(( + id, + create_failed_call(id, error_message), + )); continue; }; @@ -846,6 +1011,65 @@ pub fn spawn_revealer( } } + // Submit failed decryption calls + if !failed_calls.is_empty() { + log::debug!( + target: "mev-shield", + "revealer: submitting {} mark_decryption_failed calls at best_hash={:?}", + failed_calls.len(), + at + ); + + for (id, call) in failed_calls.into_iter() { + let uxt: node_subtensor_runtime::UncheckedExtrinsic = + node_subtensor_runtime::UncheckedExtrinsic::new_bare(call); + let xt_bytes = uxt.encode(); + + log::debug!( + target: "mev-shield", + " id=0x{}: encoded mark_decryption_failed UncheckedExtrinsic len={}", + hex::encode(id.as_bytes()), + xt_bytes.len() + ); + + match OpaqueExtrinsic::from_bytes(&xt_bytes) { + Ok(opaque) => { + match pool + .submit_one(at, TransactionSource::Local, opaque) + .await + { + Ok(_) => { + let xt_hash = + sp_core::hashing::blake2_256(&xt_bytes); + log::debug!( + target: "mev-shield", + " id=0x{}: submit_one(mark_decryption_failed) OK, xt_hash=0x{}", + hex::encode(id.as_bytes()), + hex::encode(xt_hash) + ); + } + Err(e) => { + log::warn!( + target: "mev-shield", + " id=0x{}: submit_one(mark_decryption_failed) FAILED: {:?}", + hex::encode(id.as_bytes()), + e + ); + } + } + } + Err(e) => { + log::warn!( + target: "mev-shield", + " id=0x{}: OpaqueExtrinsic::from_bytes(mark_decryption_failed) failed: {:?}", + hex::encode(id.as_bytes()), + e + ); + } + } + } + } + // Let the decrypt window elapse. if decrypt_window_ms > 0 { sleep(Duration::from_millis(decrypt_window_ms)).await; From b2cfb9f8b4a962582f55b0c7b4b945b1c1455169 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:46:36 -0800 Subject: [PATCH 3/8] clippy --- node/src/mev_shield/proposer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/node/src/mev_shield/proposer.rs b/node/src/mev_shield/proposer.rs index 41b3a8e5ab..89367a7fb1 100644 --- a/node/src/mev_shield/proposer.rs +++ b/node/src/mev_shield/proposer.rs @@ -357,10 +357,8 @@ pub fn spawn_revealer( use sp_runtime::BoundedVec; let reason_bytes = reason.as_bytes(); let reason_bounded = BoundedVec::try_from(reason_bytes.to_vec()) - .unwrap_or_else(|_| { - // Fallback if the reason is too long - BoundedVec::try_from(b"Decryption failed".to_vec()) - .expect("Fallback reason should fit") + .unwrap_or_else(|_| { // Fallback if the reason is too long + BoundedVec::try_from(b"Decryption failed".to_vec()).unwrap_or_default() }); node_subtensor_runtime::RuntimeCall::MevShield( From 3b4ed9b434c9c15f0508a4294340eeae15605bd6 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:43:33 -0800 Subject: [PATCH 4/8] fix bug --- node/src/mev_shield/proposer.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/node/src/mev_shield/proposer.rs b/node/src/mev_shield/proposer.rs index 89367a7fb1..d40fd756c3 100644 --- a/node/src/mev_shield/proposer.rs +++ b/node/src/mev_shield/proposer.rs @@ -702,10 +702,6 @@ pub fn spawn_revealer( error_message, hex::encode(aead_body_hash), ); - failed_calls.push(( - id, - create_failed_call(id, error_message), - )); continue; } }; From 6b146c58cbf808de2f8ab037c4c01aa2c2a8e800 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:14:06 -0800 Subject: [PATCH 5/8] add test mark_decryption_failed_removes_submission --- pallets/shield/src/tests.rs | 88 +++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/pallets/shield/src/tests.rs b/pallets/shield/src/tests.rs index 2a09bd43a4..5e08ef0ac3 100644 --- a/pallets/shield/src/tests.rs +++ b/pallets/shield/src/tests.rs @@ -2,19 +2,23 @@ use crate as pallet_mev_shield; use crate::mock::*; use codec::Encode; -use frame_support::pallet_prelude::ValidateUnsigned; -use frame_support::traits::ConstU32 as FrameConstU32; -use frame_support::traits::Hooks; -use frame_support::{BoundedVec, assert_noop, assert_ok}; +use frame_support::{ + BoundedVec, assert_noop, assert_ok, + pallet_prelude::ValidateUnsigned, + traits::{ConstU32 as FrameConstU32, Hooks}, +}; use frame_system::pallet_prelude::BlockNumberFor; use pallet_mev_shield::{ Call as MevShieldCall, CurrentKey, Event as MevShieldEvent, KeyHashByBlock, NextKey, Submissions, }; -use sp_core::Pair; -use sp_core::sr25519; -use sp_runtime::traits::{Hash, SaturatedConversion}; -use sp_runtime::{AccountId32, MultiSignature, transaction_validity::TransactionSource}; +use sp_core::{Pair, sr25519}; +use sp_runtime::{ + AccountId32, MultiSignature, Vec, + traits::{Hash, SaturatedConversion}, + transaction_validity::TransactionSource, +}; +use sp_std::boxed::Box; // Type aliases for convenience in tests. type TestHash = ::Hash; @@ -588,3 +592,71 @@ fn validate_unsigned_accepts_inblock_source_for_execute_revealed() { assert_ok!(validity); }); } + +#[test] +fn mark_decryption_failed_removes_submission_and_emits_event() { + new_test_ext().execute_with(|| { + System::set_block_number(42); + let pair = test_sr25519_pair(); + let who: AccountId32 = pair.public().into(); + + let commitment: TestHash = + ::Hashing::hash(b"failed-decryption-commitment"); + let ciphertext_bytes = vec![5u8; 8]; + let ciphertext: BoundedVec> = + BoundedVec::truncate_from(ciphertext_bytes.clone()); + + assert_ok!(MevShield::submit_encrypted( + RuntimeOrigin::signed(who.clone()), + commitment, + ciphertext.clone(), + )); + + let id: TestHash = ::Hashing::hash_of(&( + who.clone(), + commitment, + &ciphertext, + )); + + // Sanity: submission exists. + assert!(Submissions::::get(id).is_some()); + + // Reason we will pass into mark_decryption_failed. + let reason_bytes = b"AEAD decrypt failed".to_vec(); + let reason: BoundedVec> = + BoundedVec::truncate_from(reason_bytes.clone()); + + // Call mark_decryption_failed as unsigned (RuntimeOrigin::none()). + assert_ok!(MevShield::mark_decryption_failed( + RuntimeOrigin::none(), + id, + reason.clone(), + )); + + // Submission should be removed. + assert!(Submissions::::get(id).is_none()); + + // Last event should be DecryptionFailed with the correct id and reason. + let events = System::events(); + let last = events + .last() + .expect("an event should be emitted") + .event + .clone(); + + assert!( + matches!( + last, + RuntimeEvent::MevShield( + MevShieldEvent::::DecryptionFailed { id: ev_id, reason: ev_reason } + ) + if ev_id == id && ev_reason.to_vec() == reason_bytes + ), + "expected DecryptionFailed event with correct id & reason" + ); + + // A second call with the same id should now fail with MissingSubmission. + let res = MevShield::mark_decryption_failed(RuntimeOrigin::none(), id, reason); + assert_noop!(res, pallet_mev_shield::Error::::MissingSubmission); + }); +} From 66bbffe9c29dc289ddf1a1b5474c847ad8dafbb9 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:22:56 -0800 Subject: [PATCH 6/8] add benchmark mark_decryption_failed --- pallets/shield/src/benchmarking.rs | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pallets/shield/src/benchmarking.rs b/pallets/shield/src/benchmarking.rs index 5a82c6310d..a590acb02e 100644 --- a/pallets/shield/src/benchmarking.rs +++ b/pallets/shield/src/benchmarking.rs @@ -189,4 +189,43 @@ mod benches { // 9) Assert: submission consumed. assert!(Submissions::::get(id).is_none()); } + + /// Benchmark `mark_decryption_failed`. + #[benchmark] + fn mark_decryption_failed() { + // Any account can be the author of the submission. + let who: T::AccountId = whitelisted_caller(); + let submitted_in: BlockNumberFor = frame_system::Pallet::::block_number(); + + // Build a dummy commitment and ciphertext. + let commitment: T::Hash = + ::Hashing::hash(b"bench-mark-decryption-failed"); + const CT_DEFAULT_LEN: usize = 32; + let ciphertext: BoundedVec> = + BoundedVec::truncate_from(vec![0u8; CT_DEFAULT_LEN]); + + // Compute the submission id exactly like `submit_encrypted` does. + let id: T::Hash = + ::Hashing::hash_of(&(who.clone(), commitment, &ciphertext)); + + // Seed Submissions with an entry for this id. + let sub = Submission::, ::Hash> { + author: who, + commitment, + ciphertext: ciphertext.clone(), + submitted_in, + }; + Submissions::::insert(id, sub); + + // Reason for failure. + let reason: BoundedVec> = + BoundedVec::truncate_from(b"benchmark-decryption-failed".to_vec()); + + // Measure: dispatch the unsigned extrinsic. + #[extrinsic_call] + mark_decryption_failed(RawOrigin::None, id, reason); + + // Assert: submission is removed. + assert!(Submissions::::get(id).is_none()); + } } From 5837721b756ceb1e14f92108c981e933747c0e4b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:29:04 -0800 Subject: [PATCH 7/8] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f10d7c067b..590da36916 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -237,7 +237,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 354, + spec_version: 355, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 5c81fce22d9985543e8393b80d6cb2ea673a6fbd Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:56:51 -0800 Subject: [PATCH 8/8] update dispatch weight --- pallets/shield/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/shield/src/lib.rs b/pallets/shield/src/lib.rs index bba767e15a..997d26d190 100644 --- a/pallets/shield/src/lib.rs +++ b/pallets/shield/src/lib.rs @@ -422,7 +422,7 @@ pub mod pallet { /// * `reason` - Human-readable reason for the decryption failure (e.g., "ML-KEM decapsulate failed") #[pallet::call_index(3)] #[pallet::weight(( - Weight::from_parts(10_000_000, 0) + Weight::from_parts(13_260_000, 0) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)), DispatchClass::Operational,