diff --git a/hyperparameters.md b/hyperparameters.md index 049ace84d2..31d7261608 100644 --- a/hyperparameters.md +++ b/hyperparameters.md @@ -34,6 +34,7 @@ MaxRegistrationsPerBlock: u16 = 1; PruningScore : u16 = u16::MAX; BondsMovingAverage: u64 = 900_000; BondsPenalty: u16 = 0; +BondsResetOn: bool = false; WeightsVersionKey: u64 = 1020; MinDifficulty: u64 = 10_000_000; MaxDifficulty: u64 = u64::MAX / 4; @@ -74,6 +75,7 @@ MaxRegistrationsPerBlock: u16 = 1; PruningScore : u16 = u16::MAX; BondsMovingAverage: u64 = 900_000; BondsPenalty: u16 = 0; +BondsResetOn: bool = false; WeightsVersionKey: u64 = 400; MinDifficulty: u64 = 10_000_000; MaxDifficulty: u64 = u64::MAX / 4; diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index d0c50a451c..f8b3e6a9b6 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -88,6 +88,7 @@ parameter_types! { pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty: u16 = u16::MAX; + pub const InitialBondsResetOn: bool = false; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. @@ -171,6 +172,7 @@ impl pallet_subtensor::Config for Test { type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; type InitialBondsPenalty = InitialBondsPenalty; + type InitialBondsResetOn = InitialBondsResetOn; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index 34bf27566a..d0d8d14c9b 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -56,6 +56,9 @@ pub mod pallet { /// Interface to access-limit metadata commitments type CanCommit: CanCommit; + /// Interface to trigger other pallets when metadata is committed + type OnMetadataCommitment: OnMetadataCommitment; + /// The maximum number of additional fields that can be added to a commitment #[pallet::constant] type MaxFields: Get + TypeInfo + 'static; @@ -72,7 +75,7 @@ pub mod pallet { type TempoInterface: GetTempoInterface; } - /// Used to retreive the given subnet's tempo + /// Used to retreive the given subnet's tempo pub trait GetTempoInterface { /// Used to retreive the epoch index for the given subnet. fn get_epoch_index(netuid: u16, cur_block: u64) -> u64; @@ -148,6 +151,19 @@ pub mod pallet { BlockNumberFor, OptionQuery, >; + + #[pallet::storage] + #[pallet::getter(fn last_bonds_reset)] + pub(super) type LastBondsReset = StorageDoubleMap< + _, + Identity, + u16, + Twox64Concat, + T::AccountId, + BlockNumberFor, + OptionQuery, + >; + #[pallet::storage] #[pallet::getter(fn revealed_commitments)] pub(super) type RevealedCommitments = StorageDoubleMap< @@ -193,7 +209,7 @@ pub mod pallet { netuid: u16, info: Box>, ) -> DispatchResult { - let who = ensure_signed(origin)?; + let who = ensure_signed(origin.clone())?; ensure!( T::CanCommit::can_commit(netuid, &who), Error::::AccountNotAllowedCommit @@ -224,6 +240,16 @@ pub mod pallet { usage.used_space = 0; } + // check if ResetBondsFlag is set in the fields + for field in info.fields.iter() { + if let Data::ResetBondsFlag = field { + // track when bonds reset was last triggered + >::insert(netuid, &who, cur_block); + T::OnMetadataCommitment::on_metadata_commitment(netuid, &who); + break; + } + } + let max_allowed = MaxSpace::::get() as u64; ensure!( usage.used_space.saturating_add(required_space) <= max_allowed, @@ -349,6 +375,14 @@ impl CanCommit for () { } } +pub trait OnMetadataCommitment { + fn on_metadata_commitment(netuid: u16, account: &AccountId); +} + +impl OnMetadataCommitment for () { + fn on_metadata_commitment(_: u16, _: &A) {} +} + /************************************************************ CallType definition ************************************************************/ diff --git a/pallets/commitments/src/mock.rs b/pallets/commitments/src/mock.rs index 8f9dbb4e5f..4e6aa123bd 100644 --- a/pallets/commitments/src/mock.rs +++ b/pallets/commitments/src/mock.rs @@ -101,6 +101,7 @@ impl pallet_commitments::Config for Test { type FieldDeposit = ConstU64<0>; type InitialDeposit = ConstU64<0>; type TempoInterface = MockTempoInterface; + type OnMetadataCommitment = (); } pub struct MockTempoInterface; diff --git a/pallets/commitments/src/tests.rs b/pallets/commitments/src/tests.rs index 5d28e0733b..55e406eb53 100644 --- a/pallets/commitments/src/tests.rs +++ b/pallets/commitments/src/tests.rs @@ -34,6 +34,7 @@ fn manual_data_type_info() { Data::ShaThree256(_) => "ShaThree256".to_string(), Data::Raw(bytes) => format!("Raw{}", bytes.len()), Data::TimelockEncrypted { .. } => "TimelockEncrypted".to_string(), + Data::ResetBondsFlag => "ResetBondsFlag".to_string(), }; if let scale_info::TypeDef::Variant(variant) = &type_info.type_def { let variant = variant @@ -63,6 +64,7 @@ fn manual_data_type_info() { let reveal_round_len = reveal_round.encode().len() as u32; // Typically 8 bytes encrypted_len + reveal_round_len } + Data::ResetBondsFlag => 0, }; assert_eq!( encoded.len() as u32 - 1, // Subtract variant byte @@ -89,6 +91,7 @@ fn manual_data_type_info() { Data::Sha256(Default::default()), Data::Keccak256(Default::default()), Data::ShaThree256(Default::default()), + Data::ResetBondsFlag, ]; // Add Raw instances for all possible sizes diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 0f1d2302a5..a537514f61 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -58,6 +58,8 @@ pub enum Data { encrypted: BoundedVec>, reveal_round: u64, }, + /// Flag to trigger bonds reset for subnet + ResetBondsFlag, } impl Data { @@ -79,6 +81,7 @@ impl Data { | Data::Keccak256(arr) | Data::ShaThree256(arr) => arr.len() as u64, Data::TimelockEncrypted { encrypted, .. } => encrypted.len() as u64, + Data::ResetBondsFlag => 0, } } } @@ -108,6 +111,7 @@ impl Decode for Data { reveal_round, } } + 135 => Data::ResetBondsFlag, _ => return Err(codec::Error::from("invalid leading byte")), }) } @@ -136,6 +140,7 @@ impl Encode for Data { r.extend_from_slice(&reveal_round.encode()); r } + Data::ResetBondsFlag => vec![135], } } } @@ -158,7 +163,9 @@ impl TypeInfo for Data { type Identity = Self; fn type_info() -> Type { - let variants = Variants::new().variant("None", |v| v.index(0)); + let variants = Variants::new() + .variant("None", |v| v.index(0)) + .variant("ResetBondsFlag", |v| v.index(135)); // create a variant for all sizes of Raw data from 0-32 let variants = data_raw_variants!( @@ -321,7 +328,8 @@ impl TypeInfo for Data { }) .field(|f| f.name("reveal_round").ty::()), ) - }); + }) + .variant("ResetBondsFlag", |v| v.index(135)); Type::builder() .path(Path::new("Data", module_path!())) diff --git a/pallets/commitments/src/weights.rs b/pallets/commitments/src/weights.rs index b91017e050..e1bd05fcc7 100644 --- a/pallets/commitments/src/weights.rs +++ b/pallets/commitments/src/weights.rs @@ -53,7 +53,7 @@ impl WeightInfo for SubstrateWeight { fn set_rate_limit() -> Weight { Weight::from_parts(10_000_000, 2000) .saturating_add(RocksDbWeight::get().reads(1_u64)) - } + } } // For backwards compatibility and tests. @@ -76,5 +76,5 @@ impl WeightInfo for () { fn set_rate_limit() -> Weight { Weight::from_parts(10_000_000, 2000) .saturating_add(RocksDbWeight::get().reads(1_u64)) - } -} \ No newline at end of file + } +} diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 667ffc17f9..1b87388b85 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1319,4 +1319,39 @@ impl Pallet { ); Ok(()) } + + pub fn do_reset_bonds(netuid: u16, account_id: &T::AccountId) -> Result<(), DispatchError> { + // check bonds reset enabled for this subnet + let bonds_reset_enabled: bool = Self::get_bonds_reset(netuid); + if !bonds_reset_enabled { + return Ok(()); + } + + if let Ok(uid) = Self::get_uid_for_net_and_hotkey(netuid, account_id) { + for (i, bonds_vec) in + as IterableStorageDoubleMap>>::iter_prefix( + netuid, + ) + { + Bonds::::insert( + netuid, + i, + bonds_vec + .clone() + .iter() + .filter(|(j, _)| *j != uid) + .collect::>(), + ); + } + log::debug!("Reset bonds for {:?}, netuid {:?}", account_id, netuid); + } else { + log::warn!( + "Uid not found for {:?}, netuid {:?} - skipping bonds reset", + account_id, + netuid + ); + } + + Ok(()) + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index eae5eba3ea..2a36458e4f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -700,8 +700,13 @@ pub mod pallet { pub fn DefaultBondsPenalty() -> u16 { T::InitialBondsPenalty::get() } + /// Default value for bonds reset - will not reset bonds #[pallet::type_value] + pub fn DefaultBondsResetOn() -> bool { + T::InitialBondsResetOn::get() + } /// Default validator prune length. + #[pallet::type_value] pub fn DefaultValidatorPruneLen() -> u64 { T::InitialValidatorPruneLen::get() } @@ -814,7 +819,6 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - #[pallet::type_value] /// Default value for coldkey swap schedule duration pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { @@ -1384,6 +1388,10 @@ pub mod pallet { /// --- MAP ( netuid ) --> bonds_penalty pub type BondsPenalty = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultBondsPenalty>; + #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_reset + pub type BondsResetOn = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultBondsResetOn>; /// --- MAP ( netuid ) --> weights_set_rate_limit #[pallet::storage] pub type WeightsSetRateLimit = diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 3fe643f5f8..4377d9f016 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -96,6 +96,9 @@ mod config { /// Initial bonds penalty. #[pallet::constant] type InitialBondsPenalty: Get; + /// Initial bonds reset. + #[pallet::constant] + type InitialBondsResetOn: Get; /// Initial target registrations per interval. #[pallet::constant] type InitialTargetRegistrationsPerInterval: Get; diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 69a8fe1646..2a8e5bc346 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -210,7 +210,7 @@ mod errors { InvalidRecoveredPublicKey, /// SubToken disabled now SubtokenDisabled, - /// Estimating the maximum stake for limited staking operations returned zero. + /// Zero max stake amount ZeroMaxStakeAmount, /// Invalid netuid duplication SameNetuid, diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index aab82b22ce..9849a517ee 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -85,6 +85,8 @@ mod events { BondsMovingAverageSet(u16, u64), /// bonds penalty is set for a subnet. BondsPenaltySet(u16, u16), + /// bonds reset is set for a subnet. + BondsResetOnSet(u16, bool), /// setting the max number of allowed validators on a subnet. MaxAllowedValidatorsSet(u16, u16), /// the axon server information is added to the network. diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 69c78c3909..a6e688625d 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -3332,3 +3332,149 @@ fn test_yuma_3_stable_miner() { }) } } + +#[test] +fn test_yuma_3_bonds_reset() { + new_test_ext(1).execute_with(|| { + let sparse: bool = true; + let n: u16 = 5; // 3 validators, 2 servers + let netuid: u16 = 1; + let max_stake: u64 = 8; + + // "Case 8 - big vali moves late, then late" + // Big dishonest lazy vali. (0.8) + // Small eager-eager vali. (0.1) + // Small eager-eager vali 2. (0.1) + let stakes: Vec = vec![8, 1, 1, 0, 0]; + + setup_yuma_3_scenario(netuid, n, sparse, max_stake, stakes); + SubtensorModule::set_bonds_reset(netuid, true); + + // target bonds and dividends for specific epoch + let targets_dividends: std::collections::HashMap<_, _> = [ + (0, vec![0.8000, 0.1000, 0.1000, 0.0000, 0.0000]), + (1, vec![0.8944, 0.0528, 0.0528, 0.0000, 0.0000]), + (2, vec![0.5230, 0.2385, 0.2385, 0.0000, 0.0000]), + (19, vec![0.7919, 0.1040, 0.1040, 0.0000, 0.0000]), + (20, vec![0.7928, 0.1036, 0.1036, 0.0000, 0.0000]), + (21, vec![0.8467, 0.0766, 0.0766, 0.0000, 0.0000]), + (40, vec![0.7928, 0.1036, 0.1036, 0.0000, 0.0000]), + ] + .into_iter() + .collect(); + let targets_bonds: std::collections::HashMap<_, _> = [ + ( + 0, + vec![ + vec![0.1013, 0.0000], + vec![0.1013, 0.0000], + vec![0.1013, 0.0000], + ], + ), + ( + 1, + vec![ + vec![0.1924, 0.0000], + vec![0.0908, 0.2987], + vec![0.0908, 0.2987], + ], + ), + ( + 2, + vec![ + vec![0.1715, 0.1013], + vec![0.0815, 0.3697], + vec![0.0815, 0.3697], + ], + ), + ( + 19, + vec![ + vec![0.0269, 0.8539], + vec![0.0131, 0.8975], + vec![0.0131, 0.8975], + ], + ), + ( + 20, + vec![ + vec![0.0000, 0.8687], + vec![0.0000, 0.9079], + vec![0.0000, 0.9079], + ], + ), + ( + 21, + vec![ + vec![0.0000, 0.8820], + vec![0.2987, 0.6386], + vec![0.2987, 0.6386], + ], + ), + ( + 40, + vec![ + vec![0.8687, 0.0578], + vec![0.9079, 0.0523], + vec![0.9079, 0.0523], + ], + ), + ] + .into_iter() + .collect(); + + for epoch in 0..=40 { + match epoch { + 0 => { + // All validators -> Server 1 + set_yuma_3_weights(netuid, vec![vec![u16::MAX, 0]; 3], vec![3, 4]); + } + 1 => { + // validators B, C switch + // Validator A -> Server 1 + // Validator B -> Server 2 + // Validator C -> Server 2 + set_yuma_3_weights( + netuid, + vec![vec![u16::MAX, 0], vec![0, u16::MAX], vec![0, u16::MAX]], + vec![3, 4], + ); + } + (2..=20) => { + // validator A copies weights + // All validators -> Server 2 + set_yuma_3_weights(netuid, vec![vec![0, u16::MAX]; 3], vec![3, 4]); + if epoch == 20 { + let hotkey = SubtensorModule::get_hotkey_for_net_and_uid(netuid, 3) + .expect("Hotkey not found"); + let _ = SubtensorModule::do_reset_bonds(netuid, &hotkey); + } + } + 21 => { + // validators B, C switch back + // Validator A -> Server 2 + // Validator B -> Server 1 + // Validator C -> Server 1 + set_yuma_3_weights( + netuid, + vec![vec![0, u16::MAX], vec![u16::MAX, 0], vec![u16::MAX, 0]], + vec![3, 4], + ); + } + _ => { + // validator A copies weights + // All validators -> Server 1 + set_yuma_3_weights(netuid, vec![vec![u16::MAX, 0]; 3], vec![3, 4]); + } + }; + + if let Some((target_dividend, target_bond)) = + targets_dividends.get(&epoch).zip(targets_bonds.get(&epoch)) + { + run_epoch_and_check_bonds_dividends(netuid, sparse, target_bond, target_dividend); + } else { + run_epoch(netuid, sparse); + } + } + }) +} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index faa337cf49..221d802ccd 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -140,6 +140,7 @@ parameter_types! { pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty:u16 = u16::MAX; + pub const InitialBondsResetOn: bool = false; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production @@ -380,6 +381,7 @@ impl crate::Config for Test { type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; type InitialBondsPenalty = InitialBondsPenalty; + type InitialBondsResetOn = InitialBondsResetOn; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index caea51d285..899fa83646 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -582,6 +582,14 @@ impl Pallet { Self::deposit_event(Event::BondsPenaltySet(netuid, bonds_penalty)); } + pub fn get_bonds_reset(netuid: u16) -> bool { + BondsResetOn::::get(netuid) + } + pub fn set_bonds_reset(netuid: u16, bonds_reset: bool) { + BondsResetOn::::insert(netuid, bonds_reset); + Self::deposit_event(Event::BondsResetOnSet(netuid, bonds_reset)); + } + pub fn get_max_registrations_per_block(netuid: u16) -> u16 { MaxRegistrationsPerBlock::::get(netuid) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e29953cbcc..01618870f4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -26,7 +26,7 @@ use frame_support::{ }, }; use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, RawOrigin}; -use pallet_commitments::CanCommit; +use pallet_commitments::{CanCommit, OnMetadataCommitment}; use pallet_grandpa::{ AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, fg_primitives, }; @@ -954,7 +954,7 @@ impl pallet_registry::Config for Runtime { } parameter_types! { - pub const MaxCommitFieldsInner: u32 = 1; + pub const MaxCommitFieldsInner: u32 = 2; pub const CommitmentInitialDeposit: Balance = 0; // Free pub const CommitmentFieldDeposit: Balance = 0; // Free } @@ -982,12 +982,24 @@ impl CanCommit for AllowCommitments { } } +pub struct ResetBondsOnCommit; +impl OnMetadataCommitment for ResetBondsOnCommit { + #[cfg(not(feature = "runtime-benchmarks"))] + fn on_metadata_commitment(netuid: u16, address: &AccountId) { + let _ = SubtensorModule::do_reset_bonds(netuid, address); + } + + #[cfg(feature = "runtime-benchmarks")] + fn on_metadata_commitment(_: u16, _: &AccountId) {} +} + impl pallet_commitments::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type WeightInfo = pallet_commitments::weights::SubstrateWeight; type CanCommit = AllowCommitments; + type OnMetadataCommitment = ResetBondsOnCommit; type MaxFields = MaxCommitFields; type InitialDeposit = CommitmentInitialDeposit; @@ -1044,6 +1056,7 @@ parameter_types! { pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; pub const SubtensorInitialBondsPenalty: u16 = u16::MAX; + pub const SubtensorInitialBondsResetOn: bool = false; pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take @@ -1101,6 +1114,7 @@ impl pallet_subtensor::Config for Runtime { type InitialMaxAllowedUids = SubtensorInitialMaxAllowedUids; type InitialBondsMovingAverage = SubtensorInitialBondsMovingAverage; type InitialBondsPenalty = SubtensorInitialBondsPenalty; + type InitialBondsResetOn = SubtensorInitialBondsResetOn; type InitialIssuance = SubtensorInitialIssuance; type InitialMinAllowedWeights = SubtensorInitialMinAllowedWeights; type InitialEmissionValue = SubtensorInitialEmissionValue;