Skip to content

Commit 10d5112

Browse files
committed
fix conflict
2 parents 8ed3ba6 + fd82d9e commit 10d5112

File tree

6 files changed

+513
-54
lines changed

6 files changed

+513
-54
lines changed

node/src/mev_shield/proposer.rs

Lines changed: 252 additions & 34 deletions
Large diffs are not rendered by default.

pallets/shield/src/benchmarking.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,43 @@ mod benches {
189189
// 9) Assert: submission consumed.
190190
assert!(Submissions::<T>::get(id).is_none());
191191
}
192+
193+
/// Benchmark `mark_decryption_failed`.
194+
#[benchmark]
195+
fn mark_decryption_failed() {
196+
// Any account can be the author of the submission.
197+
let who: T::AccountId = whitelisted_caller();
198+
let submitted_in: BlockNumberFor<T> = frame_system::Pallet::<T>::block_number();
199+
200+
// Build a dummy commitment and ciphertext.
201+
let commitment: T::Hash =
202+
<T as frame_system::Config>::Hashing::hash(b"bench-mark-decryption-failed");
203+
const CT_DEFAULT_LEN: usize = 32;
204+
let ciphertext: BoundedVec<u8, ConstU32<8192>> =
205+
BoundedVec::truncate_from(vec![0u8; CT_DEFAULT_LEN]);
206+
207+
// Compute the submission id exactly like `submit_encrypted` does.
208+
let id: T::Hash =
209+
<T as frame_system::Config>::Hashing::hash_of(&(who.clone(), commitment, &ciphertext));
210+
211+
// Seed Submissions with an entry for this id.
212+
let sub = Submission::<T::AccountId, BlockNumberFor<T>, <T as frame_system::Config>::Hash> {
213+
author: who,
214+
commitment,
215+
ciphertext: ciphertext.clone(),
216+
submitted_in,
217+
};
218+
Submissions::<T>::insert(id, sub);
219+
220+
// Reason for failure.
221+
let reason: BoundedVec<u8, ConstU32<256>> =
222+
BoundedVec::truncate_from(b"benchmark-decryption-failed".to_vec());
223+
224+
// Measure: dispatch the unsigned extrinsic.
225+
#[extrinsic_call]
226+
mark_decryption_failed(RawOrigin::None, id, reason);
227+
228+
// Assert: submission is removed.
229+
assert!(Submissions::<T>::get(id).is_none());
230+
}
192231
}

pallets/shield/src/lib.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
@@ -244,12 +249,13 @@ pub mod pallet {
244249
.saturating_add(T::DbWeight::get().reads(1_u64))
245250
.saturating_add(T::DbWeight::get().writes(1_u64)),
246251
DispatchClass::Operational,
247-
Pays::No
252+
Pays::Yes
248253
))]
254+
#[allow(clippy::useless_conversion)]
249255
pub fn announce_next_key(
250256
origin: OriginFor<T>,
251257
public_key: BoundedVec<u8, ConstU32<2048>>,
252-
) -> DispatchResult {
258+
) -> DispatchResultWithPostInfo {
253259
// Only a current Aura validator may call this (signed account ∈ Aura authorities)
254260
T::AuthorityOrigin::ensure_validator(origin)?;
255261

@@ -259,9 +265,13 @@ pub mod pallet {
259265
Error::<T>::BadPublicKeyLen
260266
);
261267

262-
NextKey::<T>::put(public_key.clone());
268+
NextKey::<T>::put(public_key);
263269

264-
Ok(())
270+
// Refund the fee on success by setting pays_fee = Pays::No
271+
Ok(PostDispatchInfo {
272+
actual_weight: None,
273+
pays_fee: Pays::No,
274+
})
265275
}
266276

267277
/// Users submit an encrypted wrapper.
@@ -335,7 +345,7 @@ pub mod pallet {
335345
Weight::from_parts(77_280_000, 0)
336346
.saturating_add(T::DbWeight::get().reads(4_u64))
337347
.saturating_add(T::DbWeight::get().writes(1_u64)),
338-
DispatchClass::Operational,
348+
DispatchClass::Normal,
339349
Pays::No
340350
))]
341351
#[allow(clippy::useless_conversion)]
@@ -404,6 +414,43 @@ pub mod pallet {
404414
}
405415
}
406416
}
417+
418+
/// Marks a submission as failed to decrypt and removes it from storage.
419+
///
420+
/// Called by the block author when decryption fails at any stage (e.g., ML-KEM decapsulate
421+
/// failed, AEAD decrypt failed, invalid ciphertext format, etc.). This allows clients to be
422+
/// notified of decryption failures through on-chain events.
423+
///
424+
/// # Arguments
425+
///
426+
/// * `id` - The wrapper id (hash of (author, commitment, ciphertext))
427+
/// * `reason` - Human-readable reason for the decryption failure (e.g., "ML-KEM decapsulate failed")
428+
#[pallet::call_index(3)]
429+
#[pallet::weight((
430+
Weight::from_parts(13_260_000, 0)
431+
.saturating_add(T::DbWeight::get().reads(1_u64))
432+
.saturating_add(T::DbWeight::get().writes(1_u64)),
433+
DispatchClass::Normal,
434+
Pays::No
435+
))]
436+
pub fn mark_decryption_failed(
437+
origin: OriginFor<T>,
438+
id: T::Hash,
439+
reason: BoundedVec<u8, ConstU32<256>>,
440+
) -> DispatchResult {
441+
// Unsigned: only the author node may inject this via ValidateUnsigned.
442+
ensure_none(origin)?;
443+
444+
// Load and consume the submission.
445+
let Some(_sub) = Submissions::<T>::take(id) else {
446+
return Err(Error::<T>::MissingSubmission.into());
447+
};
448+
449+
// Emit event to notify clients
450+
Self::deposit_event(Event::DecryptionFailed { id, reason });
451+
452+
Ok(())
453+
}
407454
}
408455

409456
impl<T: Config> Pallet<T> {
@@ -439,7 +486,20 @@ pub mod pallet {
439486
// Only allow locally-submitted / already-in-block txs.
440487
TransactionSource::Local | TransactionSource::InBlock => {
441488
ValidTransaction::with_tag_prefix("mev-shield-exec")
442-
.priority(u64::MAX)
489+
.priority(1u64)
490+
.longevity(64) // long because propagate(false)
491+
.and_provides(id) // dedupe by wrapper id
492+
.propagate(false) // CRITICAL: no gossip, stays on author node
493+
.build()
494+
}
495+
_ => InvalidTransaction::Call.into(),
496+
}
497+
}
498+
Call::mark_decryption_failed { id, .. } => {
499+
match source {
500+
TransactionSource::Local | TransactionSource::InBlock => {
501+
ValidTransaction::with_tag_prefix("mev-shield-failed")
502+
.priority(1u64)
443503
.longevity(64) // long because propagate(false)
444504
.and_provides(id) // dedupe by wrapper id
445505
.propagate(false) // CRITICAL: no gossip, stays on author node
@@ -448,7 +508,6 @@ pub mod pallet {
448508
_ => InvalidTransaction::Call.into(),
449509
}
450510
}
451-
452511
_ => InvalidTransaction::Call.into(),
453512
}
454513
}

pallets/shield/src/tests.rs

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ use crate as pallet_mev_shield;
22
use crate::mock::*;
33

44
use codec::Encode;
5-
use frame_support::pallet_prelude::ValidateUnsigned;
6-
use frame_support::traits::ConstU32 as FrameConstU32;
7-
use frame_support::traits::Hooks;
8-
use frame_support::{BoundedVec, assert_noop, assert_ok};
5+
use frame_support::{
6+
BoundedVec, assert_noop, assert_ok,
7+
pallet_prelude::ValidateUnsigned,
8+
traits::{ConstU32 as FrameConstU32, Hooks},
9+
};
910
use frame_system::pallet_prelude::BlockNumberFor;
1011
use pallet_mev_shield::{
1112
Call as MevShieldCall, CurrentKey, Event as MevShieldEvent, KeyHashByBlock, NextKey,
1213
Submissions,
1314
};
14-
use sp_core::Pair;
15-
use sp_core::sr25519;
16-
use sp_runtime::traits::{Hash, SaturatedConversion};
17-
use sp_runtime::{AccountId32, MultiSignature, transaction_validity::TransactionSource};
15+
use sp_core::{Pair, sr25519};
16+
use sp_runtime::{
17+
AccountId32, MultiSignature, Vec,
18+
traits::{Hash, SaturatedConversion},
19+
transaction_validity::TransactionSource,
20+
};
21+
use sp_std::boxed::Box;
1822

1923
// Type aliases for convenience in tests.
2024
type TestHash = <Test as frame_system::Config>::Hash;
@@ -588,3 +592,140 @@ fn validate_unsigned_accepts_inblock_source_for_execute_revealed() {
588592
assert_ok!(validity);
589593
});
590594
}
595+
596+
#[test]
597+
fn mark_decryption_failed_removes_submission_and_emits_event() {
598+
new_test_ext().execute_with(|| {
599+
System::set_block_number(42);
600+
let pair = test_sr25519_pair();
601+
let who: AccountId32 = pair.public().into();
602+
603+
let commitment: TestHash =
604+
<Test as frame_system::Config>::Hashing::hash(b"failed-decryption-commitment");
605+
let ciphertext_bytes = vec![5u8; 8];
606+
let ciphertext: BoundedVec<u8, FrameConstU32<8192>> =
607+
BoundedVec::truncate_from(ciphertext_bytes.clone());
608+
609+
assert_ok!(MevShield::submit_encrypted(
610+
RuntimeOrigin::signed(who.clone()),
611+
commitment,
612+
ciphertext.clone(),
613+
));
614+
615+
let id: TestHash = <Test as frame_system::Config>::Hashing::hash_of(&(
616+
who.clone(),
617+
commitment,
618+
&ciphertext,
619+
));
620+
621+
// Sanity: submission exists.
622+
assert!(Submissions::<Test>::get(id).is_some());
623+
624+
// Reason we will pass into mark_decryption_failed.
625+
let reason_bytes = b"AEAD decrypt failed".to_vec();
626+
let reason: BoundedVec<u8, FrameConstU32<256>> =
627+
BoundedVec::truncate_from(reason_bytes.clone());
628+
629+
// Call mark_decryption_failed as unsigned (RuntimeOrigin::none()).
630+
assert_ok!(MevShield::mark_decryption_failed(
631+
RuntimeOrigin::none(),
632+
id,
633+
reason.clone(),
634+
));
635+
636+
// Submission should be removed.
637+
assert!(Submissions::<Test>::get(id).is_none());
638+
639+
// Last event should be DecryptionFailed with the correct id and reason.
640+
let events = System::events();
641+
let last = events
642+
.last()
643+
.expect("an event should be emitted")
644+
.event
645+
.clone();
646+
647+
assert!(
648+
matches!(
649+
last,
650+
RuntimeEvent::MevShield(
651+
MevShieldEvent::<Test>::DecryptionFailed { id: ev_id, reason: ev_reason }
652+
)
653+
if ev_id == id && ev_reason.to_vec() == reason_bytes
654+
),
655+
"expected DecryptionFailed event with correct id & reason"
656+
);
657+
658+
// A second call with the same id should now fail with MissingSubmission.
659+
let res = MevShield::mark_decryption_failed(RuntimeOrigin::none(), id, reason);
660+
assert_noop!(res, pallet_mev_shield::Error::<Test>::MissingSubmission);
661+
});
662+
}
663+
664+
#[test]
665+
fn announce_next_key_charges_then_refunds_fee() {
666+
new_test_ext().execute_with(|| {
667+
const KYBER_PK_LEN: usize = 1184;
668+
669+
// ---------------------------------------------------------------------
670+
// 1. Seed Aura authorities with a single validator and derive account.
671+
// ---------------------------------------------------------------------
672+
let validator_pair = test_sr25519_pair();
673+
let validator_account: AccountId32 = validator_pair.public().into();
674+
let validator_aura_id: <Test as pallet_aura::Config>::AuthorityId =
675+
validator_pair.public().into();
676+
677+
let authorities: BoundedVec<
678+
<Test as pallet_aura::Config>::AuthorityId,
679+
<Test as pallet_aura::Config>::MaxAuthorities,
680+
> = BoundedVec::truncate_from(vec![validator_aura_id]);
681+
pallet_aura::Authorities::<Test>::put(authorities);
682+
683+
// ---------------------------------------------------------------------
684+
// 2. Build a valid Kyber public key and the corresponding RuntimeCall.
685+
// ---------------------------------------------------------------------
686+
let pk_bytes = vec![42u8; KYBER_PK_LEN];
687+
let bounded_pk: BoundedVec<u8, FrameConstU32<2048>> =
688+
BoundedVec::truncate_from(pk_bytes.clone());
689+
690+
let runtime_call = RuntimeCall::MevShield(MevShieldCall::<Test>::announce_next_key {
691+
public_key: bounded_pk.clone(),
692+
});
693+
694+
// ---------------------------------------------------------------------
695+
// 3. Pre-dispatch: DispatchInfo must say Pays::Yes.
696+
// ---------------------------------------------------------------------
697+
let pre_info = <RuntimeCall as frame_support::dispatch::GetDispatchInfo>::get_dispatch_info(
698+
&runtime_call,
699+
);
700+
701+
assert_eq!(
702+
pre_info.pays_fee,
703+
frame_support::dispatch::Pays::Yes,
704+
"announce_next_key must be declared as fee-paying at pre-dispatch"
705+
);
706+
707+
// ---------------------------------------------------------------------
708+
// 4. Dispatch via the pallet function.
709+
// ---------------------------------------------------------------------
710+
let post = MevShield::announce_next_key(
711+
RuntimeOrigin::signed(validator_account.clone()),
712+
bounded_pk.clone(),
713+
)
714+
.expect("announce_next_key should succeed for an Aura validator");
715+
716+
// Post-dispatch info should switch pays_fee from Yes -> No (refund).
717+
assert_eq!(
718+
post.pays_fee,
719+
frame_support::dispatch::Pays::No,
720+
"announce_next_key must refund the previously chargeable fee"
721+
);
722+
723+
// And we don't override the actual weight (None => use pre-dispatch weight).
724+
assert!(
725+
post.actual_weight.is_none(),
726+
"announce_next_key should not override actual_weight in PostDispatchInfo"
727+
);
728+
let next = NextKey::<Test>::get().expect("NextKey should be set by announce_next_key");
729+
assert_eq!(next, pk_bytes);
730+
});
731+
}

pallets/subtensor/src/staking/stake_utils.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ impl<T: Config> Pallet<T> {
1818
/// # Returns
1919
/// * `u64` - The total alpha issuance for the specified subnet.
2020
pub fn get_alpha_issuance(netuid: NetUid) -> AlphaCurrency {
21-
SubnetAlphaIn::<T>::get(netuid).saturating_add(SubnetAlphaOut::<T>::get(netuid))
21+
SubnetAlphaIn::<T>::get(netuid)
22+
.saturating_add(SubnetAlphaInProvided::<T>::get(netuid))
23+
.saturating_add(SubnetAlphaOut::<T>::get(netuid))
2224
}
2325

2426
pub fn get_protocol_tao(netuid: NetUid) -> TaoCurrency {

runtime/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
237237
// `spec_version`, and `authoring_version` are the same between Wasm and native.
238238
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
239239
// the compatible custom types.
240-
spec_version: 355,
240+
spec_version: 357,
241241
impl_version: 1,
242242
apis: RUNTIME_API_VERSIONS,
243243
transaction_version: 1,
@@ -653,6 +653,9 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
653653
| RuntimeCall::SubtensorModule(
654654
pallet_subtensor::Call::remove_stake_full_limit { .. }
655655
)
656+
| RuntimeCall::SubtensorModule(
657+
pallet_subtensor::Call::set_root_claim_type { .. }
658+
)
656659
),
657660
ProxyType::Registration => matches!(
658661
c,
@@ -743,9 +746,6 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
743746
ProxyType::RootClaim => matches!(
744747
c,
745748
RuntimeCall::SubtensorModule(pallet_subtensor::Call::claim_root { .. })
746-
| RuntimeCall::SubtensorModule(
747-
pallet_subtensor::Call::set_root_claim_type { .. }
748-
)
749749
),
750750
}
751751
}

0 commit comments

Comments
 (0)