From e03b730ed84932a19eead4e3b202dc3ee5dd1e2d Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 22 Sep 2025 16:53:57 -0400 Subject: [PATCH 1/6] Do not run epoch if keys have duplicate hotkeys --- .../subtensor/src/coinbase/run_coinbase.rs | 4 +- pallets/subtensor/src/epoch/run_epoch.rs | 18 ++++++- pallets/subtensor/src/tests/epoch.rs | 52 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 3f2f715df6..4cf0a0db18 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -256,7 +256,9 @@ impl Pallet { log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}"); }; // Pass on subnets that have not reached their tempo. - if Self::should_run_epoch(netuid, current_block) { + if Self::should_run_epoch(netuid, current_block) + && Self::is_epoch_input_state_consistent(netuid) + { // Restart counters. BlocksSinceLastStep::::insert(netuid, 0); LastMechansimStepBlock::::insert(netuid, current_block); diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 660690ae9f..266c5b2861 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1,6 +1,6 @@ use super::*; use crate::epoch::math::*; -use alloc::collections::BTreeMap; +use alloc::collections::{BTreeMap, BTreeSet}; use frame_support::IterableStorageDoubleMap; use safe_math::*; use sp_std::collections::btree_map::IntoIter; @@ -1617,4 +1617,20 @@ impl Pallet { Ok(()) } + + /// This function ensures major assumptions made by epoch function: + /// 1. Keys map has no duplicate hotkeys + /// + pub fn is_epoch_input_state_consistent(netuid: NetUid) -> bool { + // Check if Keys map has duplicate hotkeys or uids + let mut hotkey_set: BTreeSet = BTreeSet::new(); + // `iter_prefix` over a double map yields (uid, value) for the given first key. + for (_uid, hotkey) in Keys::::iter_prefix(netuid) { + if !hotkey_set.insert(hotkey) { + log::error!("Duplicate hotkeys detected for netuid {netuid:?}"); + return false; + } + } + true + } } diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 7c23dc2b2c..a163112f26 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -3883,3 +3883,55 @@ fn test_last_update_size_mismatch() { assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 0); }); } + +#[test] +fn empty_ok() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 155.into(); + assert!(Pallet::::is_epoch_input_state_consistent(netuid)); + }); +} + +#[test] +fn unique_hotkeys_and_uids_ok() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 155.into(); + + // (netuid, uid) -> hotkey (AccountId = U256) + Keys::::insert(netuid, 0u16, U256::from(1u64)); + Keys::::insert(netuid, 1u16, U256::from(2u64)); + Keys::::insert(netuid, 2u16, U256::from(3u64)); + + assert!(Pallet::::is_epoch_input_state_consistent(netuid)); + }); +} + +#[test] +fn duplicate_hotkey_within_same_netuid_fails() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 155.into(); + + // Same hotkey mapped from two different UIDs in the SAME netuid + let hk = U256::from(42u64); + Keys::::insert(netuid, 0u16, hk); + Keys::::insert(netuid, 1u16, U256::from(42u64)); // duplicate hotkey + + assert!(!Pallet::::is_epoch_input_state_consistent(netuid)); + }); +} + +#[test] +fn same_hotkey_across_different_netuids_is_ok() { + new_test_ext(1).execute_with(|| { + let net_a: NetUid = 10.into(); + let net_b: NetUid = 11.into(); + + // Same hotkey appears once in each netuid — each net checks independently. + let hk = U256::from(777u64); + Keys::::insert(net_a, 0u16, hk); + Keys::::insert(net_b, 0u16, hk); + + assert!(Pallet::::is_epoch_input_state_consistent(net_a)); + assert!(Pallet::::is_epoch_input_state_consistent(net_b)); + }); +} From 73f3a356f99de026dfd4abda1c6e73267a90571f Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 22 Sep 2025 17:38:00 -0400 Subject: [PATCH 2/6] Spec version bump --- 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 254bec73a3..20f4bac2b3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 318, + spec_version: 319, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 265076eb6e47d71cbc82f1e6e38f6e0b40526840 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 22 Sep 2025 17:57:28 -0400 Subject: [PATCH 3/6] Fix error output --- pallets/subtensor/src/epoch/run_epoch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 266c5b2861..461d42dd09 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1627,7 +1627,7 @@ impl Pallet { // `iter_prefix` over a double map yields (uid, value) for the given first key. for (_uid, hotkey) in Keys::::iter_prefix(netuid) { if !hotkey_set.insert(hotkey) { - log::error!("Duplicate hotkeys detected for netuid {netuid:?}"); + log::error!("Duplicate hotkeys detected for netuid {}", netuid); return false; } } From 8537cccb8f2cd363c49885aba425b6532858c4a6 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 22 Sep 2025 17:59:41 -0400 Subject: [PATCH 4/6] Fix clippy --- pallets/subtensor/src/epoch/run_epoch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 461d42dd09..c91a8ba1f3 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1627,7 +1627,7 @@ impl Pallet { // `iter_prefix` over a double map yields (uid, value) for the given first key. for (_uid, hotkey) in Keys::::iter_prefix(netuid) { if !hotkey_set.insert(hotkey) { - log::error!("Duplicate hotkeys detected for netuid {}", netuid); + log::error!("Duplicate hotkeys detected for netuid {netuid}"); return false; } } From f55332607eac4cc0143b1314a5aa35b048ce8445 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 9 Oct 2025 10:58:57 -0400 Subject: [PATCH 5/6] chore: empty commit to trigger CI From b892a2decda5829571fa8f6d73cf64bccb3f7b2f Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 9 Oct 2025 16:10:44 -0400 Subject: [PATCH 6/6] chore: empty commit to trigger CI