diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 5e4dd1f43e..1f16ae212f 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1327,9 +1327,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. @@ -1369,9 +1381,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. @@ -1385,6 +1409,55 @@ 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 + 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() + } + 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) { + 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() + } else { + current_consensus.to_vec() + } + } + } + } + /// Compute liquid alphas matrix /// There is a separate alpha param for each validator-miner binding /// @@ -1592,6 +1665,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(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 bbec3ce934..8ff0283c39 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -346,6 +346,18 @@ pub mod pallet { Manual, } + /// Enum for consensus mode used in liquid alpha calculation + #[derive(Encode, Decode, 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. @@ -949,6 +961,11 @@ pub mod pallet { (45875, 58982) } + /// Default consensus mode for liquid alpha calculation + #[pallet::type_value] + pub fn DefaultConsensusMode() -> ConsensusMode { + ConsensusMode::default() + } /// Default value for coldkey swap schedule duration #[pallet::type_value] pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { @@ -1860,7 +1877,12 @@ pub mod pallet { #[pallet::storage] pub type AlphaValues = StorageMap<_, Identity, NetUid, (u16, u16), ValueQuery, DefaultAlphaValues>; - + + /// MAP ( netuid ) --> consensus mode for liquid alpha calculation + #[pallet::storage] + pub type LiquidAlphaConsensusMode = + StorageMap<_, Identity, NetUid, ConsensusMode, ValueQuery, DefaultConsensusMode>; + /// --- MAP ( netuid ) --> If subtoken trading enabled #[pallet::storage] pub type SubtokenEnabled = diff --git a/pallets/subtensor/src/tests/consensus_mode.rs b/pallets/subtensor/src/tests/consensus_mode.rs new file mode 100644 index 0000000000..e9aeec6368 --- /dev/null +++ b/pallets/subtensor/src/tests/consensus_mode.rs @@ -0,0 +1,271 @@ +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(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(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); + // Allow small floating point difference + let diff = if *res > expected { + *res - expected + } else { + expected - *res + }; + assert!( + diff < I32F32::from_num(0.001), + "Values should be approximately equal" + ); + } + }); +} + +/// 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(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); + let diff = if *res > expected { + *res - expected + } else { + expected - *res + }; + assert!( + diff < I32F32::from_num(0.001), + "Should use previous consensus when bond_penalty == 1" + ); + } + }); +} diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index cdf44df645..f281b0da12 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -3560,6 +3560,8 @@ 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(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 bbaf25af58..78b8c718a4 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 cf2d55d168..03eab15ee3 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -798,6 +798,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(netuid: NetUid, mode: ConsensusMode) { + LiquidAlphaConsensusMode::::insert(netuid, mode); + } + pub fn set_alpha_sigmoid_steepness(netuid: NetUid, steepness: i16) { AlphaSigmoidSteepness::::insert(netuid, steepness); }