From c17b982dce7e1bfa8c64a1582bbdf4ad50b788a9 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:31:36 -0800 Subject: [PATCH 1/4] emit failure on pre-dispatch failures --- node/src/mev_shield/proposer.rs | 37 ++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/node/src/mev_shield/proposer.rs b/node/src/mev_shield/proposer.rs index 23dbfdbcb0..b2df125d09 100644 --- a/node/src/mev_shield/proposer.rs +++ b/node/src/mev_shield/proposer.rs @@ -13,6 +13,18 @@ use std::{ sync::{Arc, Mutex}, }; +/// Truncate a UTF-8 string to at most `max_bytes` bytes without splitting codepoints. +fn truncate_utf8_to_bytes(s: &str, max_bytes: usize) -> String { + if s.len() <= max_bytes { + return s.to_string(); + } + let mut end = max_bytes; + while end > 0 && !s.is_char_boundary(end) { + end = end.saturating_sub(1); + } + s[..end].to_string() +} + /// Helper to build a `mark_decryption_failed` runtime call with a bounded reason string. fn create_failed_call(id: H256, reason: &str) -> node_subtensor_runtime::RuntimeCall { use sp_runtime::BoundedVec; @@ -587,8 +599,7 @@ pub fn spawn_revealer( ss32.copy_from_slice(ss_bytes); let ss_hash = sp_core::hashing::blake2_256(&ss32); - let aead_key = - crate::mev_shield::author::derive_aead_key(&ss32); + let aead_key = crate::mev_shield::author::derive_aead_key(&ss32); let key_hash_dbg = sp_core::hashing::blake2_256(&aead_key); log::debug!( @@ -644,7 +655,6 @@ pub fn spawn_revealer( ); // Safely parse plaintext layout without panics. - if plaintext.is_empty() { let error_message = "plaintext too short"; log::debug!( @@ -731,22 +741,39 @@ pub fn spawn_revealer( ); } Err(e) => { + let err_dbg = format!("{e:?}"); + let reason = truncate_utf8_to_bytes( + &format!( + "inner extrinsic rejected by tx-pool (pre-dispatch): {err_dbg}" + ), + 240, + ); log::debug!( target: "mev-shield", - " id=0x{}: submit_one(...) FAILED: {:?}", + " id=0x{}: submit_one(...) FAILED (will mark_decryption_failed): {:?}", hex::encode(id.as_bytes()), e ); + failed_calls.push((id, create_failed_call(id, &reason))); } } } Err(e) => { + // Defensive: if we cannot even construct an OpaqueExtrinsic, mark it failed. + let err_dbg = format!("{e:?}"); + let reason = truncate_utf8_to_bytes( + &format!( + "invalid decrypted extrinsic bytes (OpaqueExtrinsic::from_bytes): {err_dbg}" + ), + 240, + ); log::debug!( target: "mev-shield", - " id=0x{}: OpaqueExtrinsic::from_bytes failed: {:?}", + " id=0x{}: OpaqueExtrinsic::from_bytes failed (will mark_decryption_failed): {:?}", hex::encode(id.as_bytes()), e ); + failed_calls.push((id, create_failed_call(id, &reason))); } } } From 8030e4be3bae117faaf30b43d0d07b2b9c3254d2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:43:58 -0800 Subject: [PATCH 2/4] 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 f4a120cbbb..e2b1645515 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -241,7 +241,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: 366, + spec_version: 368, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From efdece8ea248d7850ff9360377ddccc0c4c89c1a Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:02:03 -0800 Subject: [PATCH 3/4] replace direct indexing --- node/src/mev_shield/proposer.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/node/src/mev_shield/proposer.rs b/node/src/mev_shield/proposer.rs index b2df125d09..7a23a3fd08 100644 --- a/node/src/mev_shield/proposer.rs +++ b/node/src/mev_shield/proposer.rs @@ -18,11 +18,19 @@ fn truncate_utf8_to_bytes(s: &str, max_bytes: usize) -> String { if s.len() <= max_bytes { return s.to_string(); } - let mut end = max_bytes; - while end > 0 && !s.is_char_boundary(end) { + + let mut end = max_bytes.min(s.len()); + + // Decrement until we find a valid UTF-8 boundary. + while end > 0 { + if let Some(prefix) = s.get(..end) { + return prefix.to_string(); + } end = end.saturating_sub(1); } - s[..end].to_string() + + // If max_bytes was 0 or we couldn't find a boundary (extremely defensive), return empty. + String::new() } /// Helper to build a `mark_decryption_failed` runtime call with a bounded reason string. @@ -654,7 +662,6 @@ pub fn spawn_revealer( plaintext.len() ); - // Safely parse plaintext layout without panics. if plaintext.is_empty() { let error_message = "plaintext too short"; log::debug!( @@ -741,6 +748,8 @@ pub fn spawn_revealer( ); } Err(e) => { + // Emit an on-chain failure event even when the *inner* + // transaction fails pre-dispatch validation in the pool. let err_dbg = format!("{e:?}"); let reason = truncate_utf8_to_bytes( &format!( @@ -759,7 +768,6 @@ pub fn spawn_revealer( } } Err(e) => { - // Defensive: if we cannot even construct an OpaqueExtrinsic, mark it failed. let err_dbg = format!("{e:?}"); let reason = truncate_utf8_to_bytes( &format!( From ebd486c6dfdfd9fd5d7f589362d41ff23e8f7b7b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:51:10 -0800 Subject: [PATCH 4/4] 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 e2b1645515..99be2f09d3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -241,7 +241,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: 368, + spec_version: 369, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,