diff --git a/pallets/subtensor/src/coinbase/reveal_commits.rs b/pallets/subtensor/src/coinbase/reveal_commits.rs index 029b77227c..e7bc6dc008 100644 --- a/pallets/subtensor/src/coinbase/reveal_commits.rs +++ b/pallets/subtensor/src/coinbase/reveal_commits.rs @@ -45,9 +45,9 @@ impl Pallet { let reveal_epoch = cur_epoch.saturating_sub(reveal_period); // Clean expired commits - for (epoch, _) in CRV3WeightCommitsV2::::iter_prefix(netuid) { + for (epoch, _) in TimelockedWeightCommits::::iter_prefix(netuid) { if epoch < reveal_epoch { - CRV3WeightCommitsV2::::remove(netuid, epoch); + TimelockedWeightCommits::::remove(netuid, epoch); } } @@ -57,7 +57,7 @@ impl Pallet { return Ok(()); } - let mut entries = CRV3WeightCommitsV2::::take(netuid, reveal_epoch); + let mut entries = TimelockedWeightCommits::::take(netuid, reveal_epoch); let mut unrevealed = VecDeque::new(); // Keep popping items off the front of the queue until we successfully reveal a commit. @@ -185,11 +185,11 @@ impl Pallet { continue; } - Self::deposit_event(Event::CRV3WeightsRevealed(netuid, who)); + Self::deposit_event(Event::TimelockedWeightsRevealed(netuid, who)); } if !unrevealed.is_empty() { - CRV3WeightCommitsV2::::insert(netuid, reveal_epoch, unrevealed); + TimelockedWeightCommits::::insert(netuid, reveal_epoch, unrevealed); } Ok(()) diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index ce3d3fd208..2f302c2a5e 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -607,7 +607,7 @@ impl Pallet { } // ---------- v3 ------------------------------------------------------ - for (_epoch, q) in CRV3WeightCommitsV2::::iter_prefix(netuid) { + for (_epoch, q) in TimelockedWeightCommits::::iter_prefix(netuid) { for (who, cb, ..) in q.iter() { if !Self::is_commit_expired(netuid, *cb) { if let Some(i) = uid_of(who) { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ea4beb3f2b..c2f520c598 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1664,6 +1664,23 @@ pub mod pallet { OptionQuery, >; #[pallet::storage] + /// MAP (netuid, epoch) → VecDeque<(who, commit_block, ciphertext, reveal_round)> + /// Stores a queue of weight commits for an account on a given subnet. + pub type TimelockedWeightCommits = StorageDoubleMap< + _, + Twox64Concat, + NetUid, + Twox64Concat, + u64, // epoch key + VecDeque<( + T::AccountId, + u64, // commit_block + BoundedVec>, + RoundNumber, + )>, + ValueQuery, + >; + #[pallet::storage] /// MAP (netuid, epoch) → VecDeque<(who, ciphertext, reveal_round)> /// DEPRECATED for CRV3WeightCommitsV2 pub type CRV3WeightCommits = StorageDoubleMap< @@ -1681,7 +1698,7 @@ pub mod pallet { >; #[pallet::storage] /// MAP (netuid, epoch) → VecDeque<(who, commit_block, ciphertext, reveal_round)> - /// Stores a queue of v3 commits for an account on a given netuid. + /// DEPRECATED for TimelockedWeightCommits pub type CRV3WeightCommitsV2 = StorageDoubleMap< _, Twox64Concat, diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index d959ccd6e0..2fab5ecdb4 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -399,5 +399,19 @@ mod events { /// /// - **version**: The required version. CommitRevealVersionSet(u16), + + /// Timelocked weights have been successfully committed. + /// + /// - **who**: The account ID of the user committing the weights. + /// - **netuid**: The network identifier. + /// - **commit_hash**: The hash representing the committed weights. + /// - **reveal_round**: The round at which weights can be revealed. + TimelockedWeightsCommitted(T::AccountId, NetUid, H256, u64), + + /// Timelocked Weights have been successfully revealed. + /// + /// - **netuid**: The network identifier. + /// - **who**: The account ID of the user revealing the weights. + TimelockedWeightsRevealed(NetUid, T::AccountId), } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 7083c24e5d..99d103e829 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -129,7 +129,9 @@ mod hooks { // Migrate subnet symbols to fix the shift after subnet 81 .saturating_add(migrations::migrate_subnet_symbols::migrate_subnet_symbols::()) // Migrate CRV3 add commit_block - .saturating_add(migrations::migrate_crv3_commits_add_block::migrate_crv3_commits_add_block::()); + .saturating_add(migrations::migrate_crv3_commits_add_block::migrate_crv3_commits_add_block::()) + //Migrate CRV3 to TimelockedCommits + .saturating_add(migrations::migrate_crv3_v2_to_timelocked::migrate_crv3_v2_to_timelocked::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_crv3_v2_to_timelocked.rs b/pallets/subtensor/src/migrations/migrate_crv3_v2_to_timelocked.rs new file mode 100644 index 0000000000..7ae5a2529c --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_crv3_v2_to_timelocked.rs @@ -0,0 +1,36 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; +use scale_info::prelude::string::String; +use sp_std::vec::Vec; + +// --------------- Migration ------------------------------------------ +/// Moves every (netuid, epoch) queue from `CRV3WeightCommitsV2` into +/// `TimelockedWeightCommits`. Identical key/value layout → pure move. +pub fn migrate_crv3_v2_to_timelocked() -> Weight { + let mig_name: Vec = b"crv3_v2_to_timelocked_v1".to_vec(); + let mut total_weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&mig_name) { + log::info!( + "Migration '{}' already executed - skipping", + String::from_utf8_lossy(&mig_name) + ); + return total_weight; + } + log::info!("Running migration '{}'", String::from_utf8_lossy(&mig_name)); + + for (netuid, epoch, old_q) in CRV3WeightCommitsV2::::drain() { + total_weight = total_weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + TimelockedWeightCommits::::insert(netuid, epoch, old_q); + } + + HasMigrationRun::::insert(&mig_name, true); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{}' completed", + String::from_utf8_lossy(&mig_name) + ); + total_weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index cdf142357b..76834110e2 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -9,6 +9,7 @@ pub mod migrate_coldkey_swap_scheduled; pub mod migrate_commit_reveal_v2; pub mod migrate_create_root_network; pub mod migrate_crv3_commits_add_block; +pub mod migrate_crv3_v2_to_timelocked; pub mod migrate_delete_subnet_21; pub mod migrate_delete_subnet_3; pub mod migrate_disable_commit_reveal; diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 935bbe8667..7d49e0d40a 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -212,8 +212,8 @@ impl Pallet { /// 4. Rejects the call when the hotkey already has ≥ 10 unrevealed commits in /// the current epoch. /// 5. Appends `(hotkey, commit_block, commit, reveal_round)` to - /// `CRV3WeightCommitsV2[netuid][epoch]`. - /// 6. Emits `CRV3WeightsCommitted` with the Blake2 hash of `commit`. + /// `TimelockedWeightCommits[netuid][epoch]`. + /// 6. Emits `TimelockedWeightsCommitted` with the Blake2 hash of `commit`. /// 7. Updates `LastUpdateForUid` so subsequent rate-limit checks include this /// commit. /// @@ -225,7 +225,7 @@ impl Pallet { /// * `TooManyUnrevealedCommits` – Caller already has 10 unrevealed commits. /// /// # Events - /// * `CRV3WeightsCommitted(hotkey, netuid, commit_hash)` – Fired after the commit is successfully stored. + /// * `TimelockedWeightsCommitted(hotkey, netuid, commit_hash, reveal_round)` – Fired after the commit is successfully stored. pub fn do_commit_timelocked_weights( origin: T::RuntimeOrigin, netuid: NetUid, @@ -271,7 +271,7 @@ impl Pallet { false => Self::get_epoch_index(netuid, cur_block), }; - CRV3WeightCommitsV2::::try_mutate(netuid, cur_epoch, |commits| -> DispatchResult { + TimelockedWeightCommits::::try_mutate(netuid, cur_epoch, |commits| -> DispatchResult { // 7. Verify that the number of unrevealed commits is within the allowed limit. let unrevealed_commits_for_who = commits @@ -289,10 +289,11 @@ impl Pallet { commits.push_back((who.clone(), cur_block, commit, reveal_round)); // 9. Emit the WeightsCommitted event - Self::deposit_event(Event::CRV3WeightsCommitted( + Self::deposit_event(Event::TimelockedWeightsCommitted( who.clone(), netuid, commit_hash, + reveal_round, )); // 10. Update the last commit block for the hotkey's UID. diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index e62dacf89e..f1b220dbd4 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -1132,3 +1132,84 @@ fn test_migrate_disable_commit_reveal() { ); }); } + +#[test] +fn test_migrate_crv3_v2_to_timelocked() { + new_test_ext(1).execute_with(|| { + // ------------------------------ + // 0. Constants / helpers + // ------------------------------ + const MIG_NAME: &[u8] = b"crv3_v2_to_timelocked_v1"; + let netuid = NetUid::from(99); + let epoch: u64 = 7; + + // ------------------------------ + // 1. Simulate OLD storage (4‑tuple; V2 layout) + // ------------------------------ + let who: U256 = U256::from(0xdeadbeef_u64); + let commit_block: u64 = 12345; + let ciphertext: BoundedVec> = + vec![1u8, 2, 3].try_into().unwrap(); + let round: RoundNumber = 9; + + let old_queue: VecDeque<_> = + VecDeque::from(vec![(who, commit_block, ciphertext.clone(), round)]); + + // Insert under the deprecated alias + CRV3WeightCommitsV2::::insert(netuid, epoch, old_queue.clone()); + + // Sanity: entry decodes under old alias + assert_eq!( + CRV3WeightCommitsV2::::get(netuid, epoch), + old_queue, + "pre-migration: old queue should be present" + ); + + // Destination should be empty pre-migration + assert!( + TimelockedWeightCommits::::get(netuid, epoch).is_empty(), + "pre-migration: destination should be empty" + ); + + assert!( + !HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag should be false before run" + ); + + // ------------------------------ + // 2. Run migration + // ------------------------------ + let w = crate::migrations::migrate_crv3_v2_to_timelocked::migrate_crv3_v2_to_timelocked::< + Test, + >(); + assert!(!w.is_zero(), "weight must be non-zero"); + + // ------------------------------ + // 3. Verify results + // ------------------------------ + assert!( + HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag not set" + ); + + // Old storage must be empty (drained) + assert!( + CRV3WeightCommitsV2::::get(netuid, epoch).is_empty(), + "old queue should have been drained" + ); + + // New storage must match exactly + let new_q = TimelockedWeightCommits::::get(netuid, epoch); + assert_eq!( + new_q, old_queue, + "migrated queue must exactly match the old queue" + ); + + // Verify the front element matches what we inserted + let (who2, commit_block2, cipher2, round2) = new_q.front().cloned().unwrap(); + assert_eq!(who2, who); + assert_eq!(commit_block2, commit_block); + assert_eq!(cipher2, ciphertext); + assert_eq!(round2, round); + }); +} diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 7befc1c668..1b1003e09f 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -5303,7 +5303,7 @@ fn test_do_commit_crv3_weights_success() { let cur_epoch = SubtensorModule::get_epoch_index(netuid, SubtensorModule::get_current_block_as_u64()); - let commits = CRV3WeightCommitsV2::::get(netuid, cur_epoch); + let commits = TimelockedWeightCommits::::get(netuid, cur_epoch); assert_eq!(commits.len(), 1); assert_eq!(commits[0].0, hotkey); assert_eq!(commits[0].2, commit_data); @@ -6158,7 +6158,7 @@ fn test_multiple_commits_by_same_hotkey_within_limit() { let cur_epoch = SubtensorModule::get_epoch_index(netuid, SubtensorModule::get_current_block_as_u64()); - let commits = CRV3WeightCommitsV2::::get(netuid, cur_epoch); + let commits = TimelockedWeightCommits::::get(netuid, cur_epoch); assert_eq!( commits.len(), 10, @@ -6192,7 +6192,7 @@ fn test_reveal_crv3_commits_removes_past_epoch_commits() { for &epoch in &[past_epoch, reveal_epoch] { let bounded_commit = vec![epoch as u8; 5].try_into().expect("bounded vec"); - assert_ok!(CRV3WeightCommitsV2::::try_mutate( + assert_ok!(TimelockedWeightCommits::::try_mutate( netuid, epoch, |q| -> DispatchResult { @@ -6203,8 +6203,8 @@ fn test_reveal_crv3_commits_removes_past_epoch_commits() { } // Sanity – both epochs presently hold a commit. - assert!(!CRV3WeightCommitsV2::::get(netuid, past_epoch).is_empty()); - assert!(!CRV3WeightCommitsV2::::get(netuid, reveal_epoch).is_empty()); + assert!(!TimelockedWeightCommits::::get(netuid, past_epoch).is_empty()); + assert!(!TimelockedWeightCommits::::get(netuid, reveal_epoch).is_empty()); // --------------------------------------------------------------------- // Run the reveal pass WITHOUT a pulse – only expiry housekeeping runs. @@ -6213,13 +6213,13 @@ fn test_reveal_crv3_commits_removes_past_epoch_commits() { // past_epoch (< reveal_epoch) must be gone assert!( - CRV3WeightCommitsV2::::get(netuid, past_epoch).is_empty(), + TimelockedWeightCommits::::get(netuid, past_epoch).is_empty(), "expired epoch {past_epoch} should be cleared" ); // reveal_epoch queue is *kept* because its commit could still be revealed later. assert!( - !CRV3WeightCommitsV2::::get(netuid, reveal_epoch).is_empty(), + !TimelockedWeightCommits::::get(netuid, reveal_epoch).is_empty(), "reveal-epoch {reveal_epoch} must be retained until commit can be revealed" ); }); @@ -6895,7 +6895,7 @@ fn test_reveal_crv3_commits_retry_on_missing_pulse() { )); // epoch in which commit was stored - let stored_epoch = CRV3WeightCommitsV2::::iter_prefix(netuid) + let stored_epoch = TimelockedWeightCommits::::iter_prefix(netuid) .next() .map(|(e, _)| e) .expect("commit stored"); @@ -6909,7 +6909,7 @@ fn test_reveal_crv3_commits_retry_on_missing_pulse() { // run *one* block inside reveal epoch without pulse → commit should stay queued step_block(1); assert!( - !CRV3WeightCommitsV2::::get(netuid, stored_epoch).is_empty(), + !TimelockedWeightCommits::::get(netuid, stored_epoch).is_empty(), "commit must remain queued when pulse is missing" ); @@ -6937,7 +6937,7 @@ fn test_reveal_crv3_commits_retry_on_missing_pulse() { assert!(!weights.is_empty(), "weights must be set after pulse"); assert!( - CRV3WeightCommitsV2::::get(netuid, stored_epoch).is_empty(), + TimelockedWeightCommits::::get(netuid, stored_epoch).is_empty(), "queue should be empty after successful reveal" ); }); @@ -7080,7 +7080,7 @@ fn test_reveal_crv3_commits_legacy_payload_success() { // commit should be gone assert!( - CRV3WeightCommitsV2::::get(netuid, commit_epoch).is_empty(), + TimelockedWeightCommits::::get(netuid, commit_epoch).is_empty(), "commit storage should be cleaned after reveal" ); }); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 56a263a813..1d8e658f1c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -215,7 +215,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: 302, + spec_version: 303, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,