diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 2290b49b8d..474f5b4ef1 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1274,9 +1274,21 @@ impl Pallet { .iter() .any(|&c| c != I32F32::saturating_from_num(0)) { - // Liquid Alpha is enabled, compute the liquid alphas matrix. - let alphas: Vec> = - Self::compute_liquid_alpha_values(netuid, weights, bonds, consensus); + // Liquid Alpha is enabled, compute the appropriate consensus for liquid alpha based on mode + let consensus_for_liquid_alpha = + Self::compute_consensus_for_liquid_alpha(netuid, consensus); + log::trace!( + "consensus_for_liquid_alpha: {:?}", + &consensus_for_liquid_alpha + ); + + // Compute the liquid alphas matrix. + let alphas: Vec> = Self::compute_liquid_alpha_values( + netuid, + weights, + bonds, + &consensus_for_liquid_alpha, + ); log::trace!("alphas: {:?}", &alphas); // Compute the Exponential Moving Average (EMA) of bonds using the provided clamped alpha values. @@ -1316,9 +1328,21 @@ impl Pallet { .iter() .any(|&c| c != I32F32::saturating_from_num(0)) { - // Liquid Alpha is enabled, compute the liquid alphas matrix. - let alphas: Vec> = - Self::compute_liquid_alpha_values_sparse(netuid, weights, bonds, consensus); + // Liquid Alpha is enabled, compute the appropriate consensus for liquid alpha based on mode + let consensus_for_liquid_alpha = + Self::compute_consensus_for_liquid_alpha(netuid, consensus); + log::trace!( + "consensus_for_liquid_alpha: {:?}", + &consensus_for_liquid_alpha + ); + + // Compute the liquid alphas matrix. + let alphas: Vec> = Self::compute_liquid_alpha_values_sparse( + netuid, + weights, + bonds, + &consensus_for_liquid_alpha, + ); log::trace!("alphas: {:?}", &alphas); // Compute the Exponential Moving Average (EMA) of bonds using the provided clamped alpha values. @@ -1332,6 +1356,53 @@ impl Pallet { } } + /// Compute the consensus to use for liquid alpha calculation based on the configured mode + /// + /// # Args: + /// * `netuid` - The network ID. + /// * `current_consensus` - The current in-memory consensus values. + /// + /// # Returns: + /// A vector of consensus values to use for liquid alpha calculation + pub fn compute_consensus_for_liquid_alpha( + netuid: NetUid, + current_consensus: &[I32F32], + ) -> Vec { + let mode = Self::get_liquid_alpha_consensus_mode(netuid); + + match mode { + ConsensusMode::Current => { + // Use the in-memory consensus (current behavior) + current_consensus.to_vec() + } + ConsensusMode::Previous => { + // Use consensus from storage + Self::get_previous_consensus_as_i32f32(netuid) + } + ConsensusMode::Auto => { + // Auto mode: Previous if bond_penalty == 1, otherwise Current + let bonds_penalty = Self::get_float_bonds_penalty(netuid); + if bonds_penalty == I32F32::from_num(1) { + Self::get_previous_consensus_as_i32f32(netuid) + } else { + current_consensus.to_vec() + } + } + } + } + + /// Convert stored consensus (u16 values) to I32F32 format + /// Used by consensus modes that need to read from storage + fn get_previous_consensus_as_i32f32(netuid: NetUid) -> Vec { + let previous_consensus_u16 = Consensus::::get(netuid); + previous_consensus_u16 + .iter() + .map(|&c| { + I32F32::saturating_from_num(c).safe_div(I32F32::saturating_from_num(u16::MAX)) + }) + .collect() + } + /// Compute liquid alphas matrix /// There is a separate alpha param for each validator-miner binding /// @@ -1539,6 +1610,19 @@ impl Pallet { Ok(()) } + pub fn do_set_liquid_alpha_consensus_mode( + origin: T::RuntimeOrigin, + netuid: NetUid, + mode: ConsensusMode, + ) -> Result<(), DispatchError> { + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + Self::set_liquid_alpha_consensus_mode_storage(netuid, mode.clone()); + + log::debug!("LiquidAlphaConsensusModeSet( netuid: {netuid:?}, mode: {mode:?} )",); + Ok(()) + } + pub fn do_reset_bonds( netuid_index: NetUidStorageIndex, account_id: &T::AccountId, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ba7e3dcbf6..de9bb476f9 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -344,6 +344,19 @@ pub mod pallet { }, } + /// Enum for consensus mode used in liquid alpha calculation + #[derive( + Encode, Decode, DecodeWithMemTracking, Default, TypeInfo, Clone, PartialEq, Eq, Debug, + )] + pub enum ConsensusMode { + /// Use current in-memory consensus (current behavior) + Current, + /// Use previous consensus from storage + Previous, + /// Auto mode: Previous if bond_penalty == 1, otherwise Current + #[default] + Auto, + } /// Default minimum root claim amount. /// This is the minimum amount of root claim that can be made. /// Any amount less than this will not be claimed. @@ -947,6 +960,12 @@ pub mod pallet { (45875, 58982) } + #[pallet::type_value] + /// Default consensus mode for liquid alpha calculation + pub fn DefaultConsensusMode() -> ConsensusMode { + ConsensusMode::default() + } + /// Default value for coldkey swap announcement delay. #[pallet::type_value] pub fn DefaultColdkeySwapAnnouncementDelay() -> BlockNumberFor { @@ -1884,8 +1903,13 @@ pub mod pallet { pub type AlphaValues = StorageMap<_, Identity, NetUid, (u16, u16), ValueQuery, DefaultAlphaValues>; - /// --- MAP ( netuid ) --> If subtoken trading enabled #[pallet::storage] + /// MAP ( netuid ) --> consensus mode for liquid alpha calculation + pub type LiquidAlphaConsensusMode = + StorageMap<_, Identity, NetUid, ConsensusMode, ValueQuery, DefaultConsensusMode>; + + #[pallet::storage] + /// --- MAP ( netuid ) --> If subtoken trading enabled pub type SubtokenEnabled = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 88b6a3f0ec..f65f8e2ccc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2599,5 +2599,36 @@ mod dispatches { ) -> DispatchResult { Self::do_subnet_buyback(origin, hotkey, netuid, amount, limit) } + + /// Sets the consensus mode for liquid alpha calculation on a subnet. + /// + /// This function can only be called by the subnet owner or root. + /// The consensus mode determines which consensus values are used for liquid alpha calculation: + /// - `Current`: Use current in-memory consensus + /// - `Previous`: Use previous consensus from storage + /// - `Auto`: Use Previous if bond_penalty == 1, otherwise Current (default) + /// + /// # Arguments: + /// * `origin` - The origin of the call, must be subnet owner or root. + /// * `netuid` - The subnet to set the mode for. + /// * `mode` - The consensus mode to use. + /// + /// # Errors: + /// * `BadOrigin` - If the origin is not the subnet owner or root. + #[pallet::call_index(133)] + #[pallet::weight(( + Weight::from_parts(10_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn set_liquid_alpha_consensus_mode( + origin: OriginFor, + netuid: NetUid, + mode: ConsensusMode, + ) -> DispatchResult { + Self::do_set_liquid_alpha_consensus_mode(origin, netuid, mode) + } } } diff --git a/pallets/subtensor/src/tests/consensus_mode.rs b/pallets/subtensor/src/tests/consensus_mode.rs new file mode 100644 index 0000000000..8bdd7a2c67 --- /dev/null +++ b/pallets/subtensor/src/tests/consensus_mode.rs @@ -0,0 +1,263 @@ +use approx::assert_abs_diff_eq; +use frame_support::{assert_err, assert_ok}; +use sp_core::U256; +use substrate_fixed::types::I32F32; +use subtensor_runtime_common::NetUid; + +use super::consensus::{fixed, fixed_proportion_to_u16}; +use super::mock::*; +use crate::*; + +// ============================================================================ +// Test Helper Functions +// ============================================================================ + +/// Sets up a network with full owner permissions (root registration + network creation) +/// Returns (netuid, hotkey, coldkey, signer) +fn setup_network_with_owner() -> (NetUid, U256, U256, RuntimeOrigin) { + let hotkey = U256::from(1); + let coldkey = U256::from(457); + let netuid = add_dynamic_network(&hotkey, &coldkey); + let signer = RuntimeOrigin::signed(coldkey); + + migrations::migrate_create_root_network::migrate_create_root_network::(); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); + assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey)); + assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); + + (netuid, hotkey, coldkey, signer) +} + +/// Sets up a basic network environment for consensus testing +/// Creates network and enables liquid alpha +fn setup_consensus_test_environment(netuid: NetUid) { + add_network(netuid, 0, 0); + SubtensorModule::set_liquid_alpha_enabled(netuid, true); +} + +/// Creates test consensus data and stores it in the system +/// Returns (current_consensus, previous_values) +fn create_test_consensus_data(netuid: NetUid) -> (Vec, Vec) { + let current_consensus: Vec = vec![ + I32F32::from_num(0.2), + I32F32::from_num(0.3), + I32F32::from_num(0.4), + I32F32::from_num(0.1), + ]; + + let previous_values: Vec = vec![0.1, 0.2, 0.3, 0.4]; + let previous_consensus_u16: Vec = previous_values + .iter() + .map(|&v| fixed_proportion_to_u16(fixed(v))) + .collect(); + Consensus::::insert(netuid, previous_consensus_u16); + + (current_consensus, previous_values) +} + +// ============================================================================ +// Tests +// ============================================================================ + +/// Test setting consensus mode when liquid alpha is disabled +#[test] +fn test_set_consensus_mode_liquid_alpha_disabled() { + new_test_ext(1).execute_with(|| { + let (netuid, _hotkey, _coldkey, signer) = setup_network_with_owner(); + + // Liquid Alpha is disabled by default + assert!(!SubtensorModule::get_liquid_alpha_enabled(netuid)); + + // Should succeed to set consensus mode even when liquid alpha is disabled + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + signer.clone(), + netuid, + ConsensusMode::Previous + )); + + // Verify the mode was set + assert_eq!( + SubtensorModule::get_liquid_alpha_consensus_mode(netuid), + ConsensusMode::Previous + ); + + // Also verify with liquid alpha enabled + SubtensorModule::set_liquid_alpha_enabled(netuid, true); + + // Should still succeed + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + signer.clone(), + netuid, + ConsensusMode::Current + )); + + // Verify the mode was updated + assert_eq!( + SubtensorModule::get_liquid_alpha_consensus_mode(netuid), + ConsensusMode::Current + ); + }); +} + +/// Test that only subnet owner or root can set consensus mode +#[test] +fn test_set_consensus_mode_permissions() { + new_test_ext(1).execute_with(|| { + let (netuid, _hotkey, _coldkey, owner_signer) = setup_network_with_owner(); + let non_owner = U256::from(999); + let non_owner_signer = RuntimeOrigin::signed(non_owner); + + // Non-owner should fail + assert_err!( + SubtensorModule::do_set_liquid_alpha_consensus_mode( + non_owner_signer, + netuid, + ConsensusMode::Previous + ), + DispatchError::BadOrigin + ); + + // Owner should succeed + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + owner_signer, + netuid, + ConsensusMode::Previous + )); + }); +} + +/// Test setting and getting all consensus modes +#[test] +fn test_set_and_get_consensus_modes() { + new_test_ext(1).execute_with(|| { + let (netuid, _hotkey, _coldkey, signer) = setup_network_with_owner(); + + // Test Current mode + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + signer.clone(), + netuid, + ConsensusMode::Current + )); + assert_eq!( + SubtensorModule::get_liquid_alpha_consensus_mode(netuid), + ConsensusMode::Current + ); + + // Test Previous mode + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + signer.clone(), + netuid, + ConsensusMode::Previous + )); + assert_eq!( + SubtensorModule::get_liquid_alpha_consensus_mode(netuid), + ConsensusMode::Previous + ); + + // Test Auto mode + assert_ok!(SubtensorModule::do_set_liquid_alpha_consensus_mode( + signer.clone(), + netuid, + ConsensusMode::Auto + )); + assert_eq!( + SubtensorModule::get_liquid_alpha_consensus_mode(netuid), + ConsensusMode::Auto + ); + }); +} + +/// Test compute_consensus_for_liquid_alpha with Current mode +#[test] +fn test_compute_consensus_current_mode() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 1.into(); + let n: usize = 4; + + // Setup network and test data + setup_consensus_test_environment(netuid); + SubtensorModule::set_liquid_alpha_consensus_mode_storage(netuid, ConsensusMode::Current); + let (current_consensus, _previous_values) = create_test_consensus_data(netuid); + + // Compute consensus for liquid alpha + let result = + SubtensorModule::compute_consensus_for_liquid_alpha(netuid, ¤t_consensus); + + // Should return current consensus (not previous) + assert_eq!(result.len(), n); + for (res, curr) in result.iter().zip(current_consensus.iter()) { + assert_eq!(res, curr); + } + }); +} + +/// Test compute_consensus_for_liquid_alpha with Previous mode +#[test] +fn test_compute_consensus_previous_mode() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 1.into(); + let n: usize = 4; + + // Setup network and test data + setup_consensus_test_environment(netuid); + SubtensorModule::set_liquid_alpha_consensus_mode_storage(netuid, ConsensusMode::Previous); + let (current_consensus, previous_values) = create_test_consensus_data(netuid); + + // Compute consensus for liquid alpha + let result = + SubtensorModule::compute_consensus_for_liquid_alpha(netuid, ¤t_consensus); + + // Should return previous consensus from storage (not current) + assert_eq!(result.len(), n); + for (res, &prev) in result.iter().zip(previous_values.iter()) { + let expected = I32F32::from_num(prev); + assert_abs_diff_eq!( + res.to_num::(), + expected.to_num::(), + epsilon = 0.001 + ); + } + }); +} + +/// Test compute_consensus_for_liquid_alpha with Auto mode +#[test] +fn test_compute_consensus_auto_mode() { + new_test_ext(1).execute_with(|| { + let netuid: NetUid = 1.into(); + let n: usize = 4; + + // Setup network and test data + setup_consensus_test_environment(netuid); + SubtensorModule::set_liquid_alpha_consensus_mode_storage(netuid, ConsensusMode::Auto); + let (current_consensus, previous_values) = create_test_consensus_data(netuid); + + // Test 1: bond_penalty != 1, should use Current + SubtensorModule::set_bonds_penalty(netuid, u16::MAX / 2); // 0.5 + let result = + SubtensorModule::compute_consensus_for_liquid_alpha(netuid, ¤t_consensus); + + assert_eq!(result.len(), n); + for (res, curr) in result.iter().zip(current_consensus.iter()) { + assert_eq!( + res, curr, + "Should use current consensus when bond_penalty != 1" + ); + } + + // Test 2: bond_penalty == 1, should use Previous + SubtensorModule::set_bonds_penalty(netuid, u16::MAX); // 1.0 + let result = + SubtensorModule::compute_consensus_for_liquid_alpha(netuid, ¤t_consensus); + + assert_eq!(result.len(), n); + for (res, &prev) in result.iter().zip(previous_values.iter()) { + let expected = I32F32::from_num(prev); + assert_abs_diff_eq!( + res.to_num::(), + expected.to_num::(), + epsilon = 0.001 + ); + } + }); +} diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 32f754f78d..dcd094da16 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -3555,6 +3555,11 @@ fn test_liquid_alpha_equal_values_against_itself() { // compute bonds with liquid alpha enabled SubtensorModule::set_liquid_alpha_enabled(netuid.into(), true); + // Set consensus mode to Current to match the original behavior (using in-memory consensus) + SubtensorModule::set_liquid_alpha_consensus_mode_storage( + netuid.into(), + ConsensusMode::Current, + ); let new_bonds_liquid_alpha_on = SubtensorModule::compute_bonds(netuid.into(), &weights, &bonds, &consensus); diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 8f07572e25..c9bee21fbe 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -4,6 +4,7 @@ mod children; mod claim_root; mod coinbase; mod consensus; +mod consensus_mode; mod delegate_info; mod difficulty; mod emission; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 609e43cf63..afe14f6004 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -776,6 +776,14 @@ impl Pallet { (converted_low, converted_high) } + pub fn get_liquid_alpha_consensus_mode(netuid: NetUid) -> ConsensusMode { + LiquidAlphaConsensusMode::::get(netuid) + } + + pub fn set_liquid_alpha_consensus_mode_storage(netuid: NetUid, mode: ConsensusMode) { + LiquidAlphaConsensusMode::::insert(netuid, mode); + } + pub fn set_alpha_sigmoid_steepness(netuid: NetUid, steepness: i16) { AlphaSigmoidSteepness::::insert(netuid, steepness); }