Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions pallets/subtensor/src/epoch/run_epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,15 @@ impl<T: Config> Pallet<T> {
// Get the minimum stake required.
let min_stake = Self::get_stake_threshold();

// Get owner uid.
let owner_uid: Option<u16> = Self::get_owner_uid(netuid);

// Set stake of validators that doesn't meet the staking threshold to 0 as filter.
let mut filtered_stake: Vec<I64F64> = total_stake
.iter()
.map(|&s| {
if fixed64_to_u64(s) < min_stake {
.enumerate()
.map(|(uid, &s)| {
if owner_uid != Some(uid as u16) && fixed64_to_u64(s) < min_stake {
return I64F64::from(0);
}
s
Expand All @@ -648,7 +652,12 @@ impl<T: Config> Pallet<T> {
// =======================

// Get current validator permits.
let validator_permits: Vec<bool> = Self::get_validator_permit(netuid);
let mut validator_permits: Vec<bool> = Self::get_validator_permit(netuid);
if let Some(owner_uid) = owner_uid
&& let Some(owner_permit) = validator_permits.get_mut(owner_uid as usize)
{
*owner_permit = true;
}
log::trace!("validator_permits: {validator_permits:?}");

// Logical negation of validator_permits.
Expand All @@ -659,8 +668,13 @@ impl<T: Config> Pallet<T> {
log::trace!("max_allowed_validators: {max_allowed_validators:?}");

// Get new validator permits.
let new_validator_permits: Vec<bool> =
let mut new_validator_permits: Vec<bool> =
is_topk_nonzero(&stake, max_allowed_validators as usize);
if let Some(owner_uid) = owner_uid
&& let Some(owner_permit) = new_validator_permits.get_mut(owner_uid as usize)
{
*owner_permit = true;
}
log::trace!("new_validator_permits: {new_validator_permits:?}");

// ==================
Expand All @@ -683,8 +697,6 @@ impl<T: Config> Pallet<T> {
// == Weights ==
// =============

let owner_uid: Option<u16> = Self::get_owner_uid(netuid);

// Access network weights row unnormalized.
let mut weights: Vec<Vec<(u16, I32F32)>> = Self::get_weights_sparse(netuid_index);
log::trace!("Weights: {:?}", &weights);
Expand Down
8 changes: 7 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2477,11 +2477,17 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
/// Is the caller allowed to set weights
pub fn check_weights_min_stake(hotkey: &T::AccountId, netuid: NetUid) -> bool {
// Allow the subnet owner hotkey to set weights regardless of stake.
if let Some(owner_uid) = Self::get_owner_uid(netuid)
&& Uids::<T>::get(netuid, hotkey) == Some(owner_uid)
{
return true;
}

// Blacklist weights transactions for low stake peers.
let (total_stake, _, _) = Self::get_stake_weights_for_hotkey_on_subnet(hotkey, netuid);
total_stake >= Self::get_stake_threshold()
}

/// Helper function to check if register is allowed
pub fn checked_allowed_register(netuid: NetUid) -> bool {
if netuid.is_root() {
Expand Down
8 changes: 4 additions & 4 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ mod dispatches {
/// - Attempting to set weights with max value exceeding limit.
#[pallet::call_index(0)]
#[pallet::weight((Weight::from_parts(15_540_000_000, 0)
.saturating_add(T::DbWeight::get().reads(4111_u64))
.saturating_add(T::DbWeight::get().reads(4112_u64))
.saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))]
pub fn set_weights(
origin: OriginFor<T>,
Expand Down Expand Up @@ -206,7 +206,7 @@ mod dispatches {
///
#[pallet::call_index(80)]
#[pallet::weight((Weight::from_parts(95_460_000, 0)
.saturating_add(T::DbWeight::get().reads(15_u64))
.saturating_add(T::DbWeight::get().reads(16_u64))
.saturating_add(T::DbWeight::get().writes(2_u64)), DispatchClass::Normal, Pays::No))]
pub fn batch_set_weights(
origin: OriginFor<T>,
Expand Down Expand Up @@ -356,7 +356,7 @@ mod dispatches {
///
#[pallet::call_index(97)]
#[pallet::weight((Weight::from_parts(122_000_000, 0)
.saturating_add(T::DbWeight::get().reads(17_u64))
.saturating_add(T::DbWeight::get().reads(18_u64))
.saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))]
pub fn reveal_weights(
origin: OriginFor<T>,
Expand Down Expand Up @@ -569,7 +569,7 @@ mod dispatches {
/// - The input vectors are of mismatched lengths.
#[pallet::call_index(98)]
#[pallet::weight((Weight::from_parts(412_000_000, 0)
.saturating_add(T::DbWeight::get().reads(17_u64))
.saturating_add(T::DbWeight::get().reads(18_u64))
.saturating_add(T::DbWeight::get().writes(2_u64)), DispatchClass::Normal, Pays::No))]
pub fn batch_reveal_weights(
origin: OriginFor<T>,
Expand Down
9 changes: 8 additions & 1 deletion pallets/subtensor/src/subnets/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1161,10 +1161,17 @@ impl<T: Config> Pallet<T> {
if Self::is_self_weight(uid, uids, weights) {
return true;
}

// Allow the subnet owner hotkey to act as a validator regardless of permit state.
if let Some(owner_uid) = Self::get_owner_uid(netuid)
&& owner_uid == uid
{
return true;
}

// Check if uid has validator permit.
Self::get_validator_permit_for_uid(netuid, uid)
}

/// Returns True if the uids and weights are have a valid length for uid on network.
pub fn check_length(netuid: NetUid, uid: u16, uids: &[u16], weights: &[u16]) -> bool {
let subnet_n: usize = Self::get_subnetwork_n(netuid) as usize;
Expand Down
106 changes: 106 additions & 0 deletions pallets/subtensor/src/tests/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6858,3 +6858,109 @@ fn test_reveal_crv3_commits_legacy_payload_success() {
);
});
}

// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::weights::test_subnet_owner_can_validate_without_stake_or_manual_permit --exact --show-output --nocapture
#[test]
fn test_subnet_owner_can_validate_without_stake_or_manual_permit() {
new_test_ext(0).execute_with(|| {
let owner_hotkey = U256::from(10);
let owner_coldkey = U256::from(11);
let other_hotkey = U256::from(20);
let other_coldkey = U256::from(21);

// Create a real dynamic subnet whose owner hotkey is `owner_hotkey`.
let netuid = add_dynamic_network_disable_commit_reveal(&owner_hotkey, &owner_coldkey);

// Add one non-owner neuron with deterministic subnet stake.
register_ok_neuron(netuid, other_hotkey, other_coldkey, 0);
SubtensorModule::add_balance_to_coldkey_account(&other_coldkey, 1.into());
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&other_hotkey,
&other_coldkey,
netuid,
1.into(),
);

let owner_uid =
SubtensorModule::get_owner_uid(netuid).expect("subnet owner should resolve to a uid");
let registered_owner_uid =
SubtensorModule::get_uid_for_net_and_hotkey(netuid, &owner_hotkey)
.expect("owner hotkey should be registered on the subnet");
assert_eq!(registered_owner_uid, owner_uid);

let other_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &other_hotkey)
.expect("other hotkey should be registered on the subnet");

let (owner_weight_stake, _, _) =
SubtensorModule::get_stake_weights_for_hotkey_on_subnet(&owner_hotkey, netuid);
let (other_weight_stake, _, _) =
SubtensorModule::get_stake_weights_for_hotkey_on_subnet(&other_hotkey, netuid);
assert!(owner_weight_stake < other_weight_stake);

// Make the non-owner stake-qualified while the owner remains below threshold.
SubtensorModule::set_stake_threshold(1_u64);
assert!(SubtensorModule::check_weights_min_stake(
&other_hotkey,
netuid
));

// Clear all explicit permits. The owner should not rely on manual permit state.
SubtensorModule::set_validator_permit_for_uid(netuid, owner_uid, false);
SubtensorModule::set_validator_permit_for_uid(netuid, other_uid, false);
assert!(!SubtensorModule::get_validator_permit_for_uid(
netuid, owner_uid
));
assert!(!SubtensorModule::get_validator_permit_for_uid(
netuid, other_uid
));

// Sanity check: a non-owner without a permit still cannot set non-self weights.
assert!(!SubtensorModule::check_validator_permit(
netuid,
other_uid,
&[owner_uid],
&[1u16],
));
assert_eq!(
SubtensorModule::set_weights(
RuntimeOrigin::signed(other_hotkey),
netuid,
vec![owner_uid],
vec![1u16],
0,
),
Err(Error::<Test>::NeuronNoValidatorPermit.into())
);

// The subnet owner bypasses both the stake gate and the validator-permit gate.
assert!(SubtensorModule::check_weights_min_stake(
&owner_hotkey,
netuid
));
assert!(SubtensorModule::check_validator_permit(
netuid,
owner_uid,
&[other_uid],
&[1u16],
));

assert_ok!(SubtensorModule::set_weights(
RuntimeOrigin::signed(owner_hotkey),
netuid,
vec![other_uid],
vec![1u16],
0,
));

// After an epoch, the owner is still validator-eligible even though only the
step_epochs(1, netuid);
assert!(SubtensorModule::get_validator_permit_for_uid(
netuid, owner_uid
));

// The original top-k result is preserved; the owner is added on top.
assert!(SubtensorModule::get_validator_permit_for_uid(
netuid, other_uid
));
});
}
Loading