diff --git a/pallets/shield/src/benchmarking.rs b/pallets/shield/src/benchmarking.rs index 1414779314..23c98685d8 100644 --- a/pallets/shield/src/benchmarking.rs +++ b/pallets/shield/src/benchmarking.rs @@ -14,10 +14,10 @@ use sp_std::vec; // } /// Helper to build bounded bytes (ciphertext) of a given length. -fn bounded_ct(len: usize) -> BoundedVec> { - let v = vec![0u8; len]; - BoundedVec::>::try_from(v).expect("within bound; qed") -} +// fn bounded_ct(len: usize) -> BoundedVec> { +// let v = vec![0u8; len]; +// BoundedVec::>::try_from(v).expect("within bound; qed") +// } // /// Seed Aura authorities so `EnsureAuraAuthority` passes for a given sr25519 pubkey. // /// @@ -70,38 +70,38 @@ mod benches { // assert_eq!(stored, public_key); // } - /// Benchmark `submit_encrypted`. - #[benchmark] - fn submit_encrypted() { - // Any whitelisted caller is fine (no authority requirement). - let who: T::AccountId = whitelisted_caller(); + // Benchmark `submit_encrypted`. + // #[benchmark] + // fn submit_encrypted() { + // // Any whitelisted caller is fine (no authority requirement). + // let who: T::AccountId = whitelisted_caller(); - // Dummy commitment and ciphertext (bounded to 8192). - let commitment: T::Hash = ::Hashing::hash(b"bench-commitment"); - const CT_DEFAULT_LEN: usize = 256; - let ciphertext: BoundedVec> = super::bounded_ct::<8192>(CT_DEFAULT_LEN); + // // Dummy commitment and ciphertext (bounded to 8192). + // let commitment: T::Hash = ::Hashing::hash(b"bench-commitment"); + // const CT_DEFAULT_LEN: usize = 256; + // let ciphertext: BoundedVec> = super::bounded_ct::<8192>(CT_DEFAULT_LEN); - // Pre-compute expected id to assert postconditions. - let id: T::Hash = - ::Hashing::hash_of(&(who.clone(), commitment, &ciphertext)); + // // Pre-compute expected id to assert postconditions. + // let id: T::Hash = + // ::Hashing::hash_of(&(who.clone(), commitment, &ciphertext)); - // Measure: dispatch the extrinsic. - #[extrinsic_call] - submit_encrypted( - RawOrigin::Signed(who.clone()), - commitment, - ciphertext.clone(), - ); - - // Assert: stored under expected id. - let got = Submissions::::get(id).expect("submission must exist"); - assert_eq!(got.author, who); - assert_eq!( - got.commitment, - ::Hashing::hash(b"bench-commitment") - ); - assert_eq!(got.ciphertext.as_slice(), ciphertext.as_slice()); - } + // // Measure: dispatch the extrinsic. + // #[extrinsic_call] + // submit_encrypted( + // RawOrigin::Signed(who.clone()), + // commitment, + // ciphertext.clone(), + // ); + + // // Assert: stored under expected id. + // let got = Submissions::::get(id).expect("submission must exist"); + // assert_eq!(got.author, who); + // assert_eq!( + // got.commitment, + // ::Hashing::hash(b"bench-commitment") + // ); + // assert_eq!(got.ciphertext.as_slice(), ciphertext.as_slice()); + // } /// Benchmark `mark_decryption_failed`. #[benchmark] diff --git a/pallets/shield/src/lib.rs b/pallets/shield/src/lib.rs index eed0161f20..a74f76a0ce 100644 --- a/pallets/shield/src/lib.rs +++ b/pallets/shield/src/lib.rs @@ -167,6 +167,8 @@ pub mod pallet { KeyExpired, /// The provided `key_hash` does not match the expected epoch key hash. KeyHashMismatch, + /// The shield is disabled while upgrading. + ShieldDisabledWhileUpgrading, } // ----------------- Hooks ----------------- @@ -250,7 +252,7 @@ pub mod pallet { Weight::from_parts(20_999_999_999, 0) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] #[allow(clippy::useless_conversion)] @@ -301,26 +303,11 @@ pub mod pallet { Pays::Yes, ))] pub fn submit_encrypted( - origin: OriginFor, - commitment: T::Hash, - ciphertext: BoundedVec>, + _origin: OriginFor, + _commitment: T::Hash, + _ciphertext: BoundedVec>, ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let id: T::Hash = T::Hashing::hash_of(&(who.clone(), commitment, &ciphertext)); - let sub = Submission::, T::Hash> { - author: who.clone(), - commitment, - ciphertext, - submitted_in: >::block_number(), - }; - ensure!( - !Submissions::::contains_key(id), - Error::::SubmissionAlreadyExists - ); - Submissions::::insert(id, sub); - Self::deposit_event(Event::EncryptedSubmitted { id, who }); - Ok(()) + Err(Error::::ShieldDisabledWhileUpgrading.into()) } /// Marks a submission as failed to decrypt and removes it from storage. diff --git a/pallets/shield/src/tests.rs b/pallets/shield/src/tests.rs index 18eda7eacc..d438aeef8c 100644 --- a/pallets/shield/src/tests.rs +++ b/pallets/shield/src/tests.rs @@ -6,10 +6,7 @@ use frame_support::{ 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 pallet_mev_shield::{Call as MevShieldCall, CurrentKey, KeyHashByBlock, NextKey}; use sp_core::{Pair, sr25519}; use sp_runtime::{ AccountId32, @@ -138,53 +135,53 @@ fn announce_next_key_rejects_non_validator_origins() { }); } -#[test] -fn submit_encrypted_stores_submission_and_emits_event() { - new_test_ext().execute_with(|| { - let pair = test_sr25519_pair(); - let who: AccountId32 = pair.public().into(); - - System::set_block_number(10); - - let commitment = - ::Hashing::hash(b"test-mevshield-commitment"); - let ciphertext_bytes = vec![1u8, 2, 3, 4]; - let ciphertext: BoundedVec> = - BoundedVec::truncate_from(ciphertext_bytes.clone()); - - assert_ok!(MevShield::submit_encrypted( - RuntimeOrigin::signed(who.clone()), - commitment, - ciphertext.clone(), - )); - - let id = ::Hashing::hash_of(&( - who.clone(), - commitment, - &ciphertext, - )); - - let stored = Submissions::::get(id).expect("submission stored"); - assert_eq!(stored.author, who); - assert_eq!(stored.commitment, commitment); - assert_eq!(stored.ciphertext.to_vec(), ciphertext_bytes); - assert_eq!(stored.submitted_in, 10); - - let events = System::events(); - let last = events.last().expect("at least one event").event.clone(); - - assert!( - matches!( - last, - RuntimeEvent::MevShield( - MevShieldEvent::::EncryptedSubmitted { id: ev_id, who: ev_who } - ) - if ev_id == id && ev_who == who - ), - "expected EncryptedSubmitted event with correct id & who", - ); - }); -} +// #[test] +// fn submit_encrypted_stores_submission_and_emits_event() { +// new_test_ext().execute_with(|| { +// let pair = test_sr25519_pair(); +// let who: AccountId32 = pair.public().into(); + +// System::set_block_number(10); + +// let commitment = +// ::Hashing::hash(b"test-mevshield-commitment"); +// let ciphertext_bytes = vec![1u8, 2, 3, 4]; +// let ciphertext: BoundedVec> = +// BoundedVec::truncate_from(ciphertext_bytes.clone()); + +// assert_ok!(MevShield::submit_encrypted( +// RuntimeOrigin::signed(who.clone()), +// commitment, +// ciphertext.clone(), +// )); + +// let id = ::Hashing::hash_of(&( +// who.clone(), +// commitment, +// &ciphertext, +// )); + +// let stored = Submissions::::get(id).expect("submission stored"); +// assert_eq!(stored.author, who); +// assert_eq!(stored.commitment, commitment); +// assert_eq!(stored.ciphertext.to_vec(), ciphertext_bytes); +// assert_eq!(stored.submitted_in, 10); + +// let events = System::events(); +// let last = events.last().expect("at least one event").event.clone(); + +// assert!( +// matches!( +// last, +// RuntimeEvent::MevShield( +// MevShieldEvent::::EncryptedSubmitted { id: ev_id, who: ev_who } +// ) +// if ev_id == id && ev_who == who +// ), +// "expected EncryptedSubmitted event with correct id & who", +// ); +// }); +// } #[test] fn key_hash_by_block_prunes_old_entries() { @@ -243,135 +240,135 @@ fn key_hash_by_block_prunes_old_entries() { }); } -#[test] -fn submissions_pruned_after_ttl_window() { - new_test_ext().execute_with(|| { - // This must match KEY_EPOCH_HISTORY in the pallet. - const KEEP: u64 = 100; - const TOTAL: u64 = KEEP + 5; - - let pair = test_sr25519_pair(); - let who: AccountId32 = pair.public().into(); - - // Helper: create a submission at a specific block with a tagged commitment. - let make_submission = |block: u64, tag: &[u8]| -> TestHash { - System::set_block_number(block); - let commitment: TestHash = ::Hashing::hash(tag); - let ciphertext_bytes = vec![block as u8; 4]; - let ciphertext: BoundedVec> = - BoundedVec::truncate_from(ciphertext_bytes); - - assert_ok!(MevShield::submit_encrypted( - RuntimeOrigin::signed(who.clone()), - commitment, - ciphertext.clone(), - )); - - ::Hashing::hash_of(&( - who.clone(), - commitment, - &ciphertext, - )) - }; - - // With n = TOTAL and depth = KEEP, prune_before = n - KEEP = 5. - let stale_block1: u64 = 1; // < 5, should be pruned - let stale_block2: u64 = 4; // < 5, should be pruned - let keep_block1: u64 = 5; // == prune_before, should be kept - let keep_block2: u64 = TOTAL; // latest, should be kept - - let id_stale1 = make_submission(stale_block1, b"stale-1"); - let id_stale2 = make_submission(stale_block2, b"stale-2"); - let id_keep1 = make_submission(keep_block1, b"keep-1"); - let id_keep2 = make_submission(keep_block2, b"keep-2"); - - // Sanity: all are present before pruning. - assert!(Submissions::::get(id_stale1).is_some()); - assert!(Submissions::::get(id_stale2).is_some()); - assert!(Submissions::::get(id_keep1).is_some()); - assert!(Submissions::::get(id_keep2).is_some()); - - // Run on_initialize at block TOTAL, triggering TTL pruning over Submissions. - let n_final: TestBlockNumber = TOTAL.saturated_into(); - MevShield::on_initialize(n_final); - - // Submissions with submitted_in < prune_before (5) should be gone. - assert!(Submissions::::get(id_stale1).is_none()); - assert!(Submissions::::get(id_stale2).is_none()); - - // Submissions at or after prune_before should remain. - assert!(Submissions::::get(id_keep1).is_some()); - assert!(Submissions::::get(id_keep2).is_some()); - }); -} - -#[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); - }); -} +// #[test] +// fn submissions_pruned_after_ttl_window() { +// new_test_ext().execute_with(|| { +// // This must match KEY_EPOCH_HISTORY in the pallet. +// const KEEP: u64 = 100; +// const TOTAL: u64 = KEEP + 5; + +// let pair = test_sr25519_pair(); +// let who: AccountId32 = pair.public().into(); + +// // Helper: create a submission at a specific block with a tagged commitment. +// let make_submission = |block: u64, tag: &[u8]| -> TestHash { +// System::set_block_number(block); +// let commitment: TestHash = ::Hashing::hash(tag); +// let ciphertext_bytes = vec![block as u8; 4]; +// let ciphertext: BoundedVec> = +// BoundedVec::truncate_from(ciphertext_bytes); + +// assert_ok!(MevShield::submit_encrypted( +// RuntimeOrigin::signed(who.clone()), +// commitment, +// ciphertext.clone(), +// )); + +// ::Hashing::hash_of(&( +// who.clone(), +// commitment, +// &ciphertext, +// )) +// }; + +// // With n = TOTAL and depth = KEEP, prune_before = n - KEEP = 5. +// let stale_block1: u64 = 1; // < 5, should be pruned +// let stale_block2: u64 = 4; // < 5, should be pruned +// let keep_block1: u64 = 5; // == prune_before, should be kept +// let keep_block2: u64 = TOTAL; // latest, should be kept + +// let id_stale1 = make_submission(stale_block1, b"stale-1"); +// let id_stale2 = make_submission(stale_block2, b"stale-2"); +// let id_keep1 = make_submission(keep_block1, b"keep-1"); +// let id_keep2 = make_submission(keep_block2, b"keep-2"); + +// // Sanity: all are present before pruning. +// assert!(Submissions::::get(id_stale1).is_some()); +// assert!(Submissions::::get(id_stale2).is_some()); +// assert!(Submissions::::get(id_keep1).is_some()); +// assert!(Submissions::::get(id_keep2).is_some()); + +// // Run on_initialize at block TOTAL, triggering TTL pruning over Submissions. +// let n_final: TestBlockNumber = TOTAL.saturated_into(); +// MevShield::on_initialize(n_final); + +// // Submissions with submitted_in < prune_before (5) should be gone. +// assert!(Submissions::::get(id_stale1).is_none()); +// assert!(Submissions::::get(id_stale2).is_none()); + +// // Submissions at or after prune_before should remain. +// assert!(Submissions::::get(id_keep1).is_some()); +// assert!(Submissions::::get(id_keep2).is_some()); +// }); +// } + +// #[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); +// }); +// } #[test] fn announce_next_key_charges_then_refunds_fee() { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 919468c1ef..4e5dbc8376 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: 377, + spec_version: 378, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,