From e57f6f0553ef0ba7df6937e3b41916a977f781d7 Mon Sep 17 00:00:00 2001 From: boskodev790 Date: Wed, 22 Apr 2026 13:09:10 -0500 Subject: [PATCH] fix: clear Active/ValidatorTrust/ValidatorPermit on neuron replace clear_neuron zeroed the per-UID slot of Emission, Consensus, Incentive, Dividends, StakeWeight, Bonds and Weights, but missed Active, ValidatorTrust, and ValidatorPermit. All three are Vec<_>-per-netuid storage (Active and ValidatorPermit are Vec via EmptyBoolVec; ValidatorTrust is Vec via EmptyU16Vec) that append_neuron initialises on a fresh UID, so when replace_neuron reuses a UID it currently left the old occupant's active=true flag, validator_permit=true flag, and validator_trust score in place until the next epoch recomputed the vectors. Between replacement and the next epoch, RPC surfaces (delegate_info, show_subnet) and on-chain readers (trim_to_max_allowed_uids and the per-mechanism validator aggregation in mechanism.rs) observed stale validator/active state for the freshly registered neuron. Same class of stale-inheritance bug PR #755 was introduced to fix; these three fields were missed there. - clear_neuron now also zeroes ValidatorTrust[uid] and resets ValidatorPermit[uid] = false and Active[uid] = false, using the same set_element_at idiom already applied to the other vec-per-netuid fields two lines above. - Add test_replace_neuron_clears_validator_trust_and_permit and test_replace_neuron_clears_active mirroring the existing test_replace_neuron_resets_last_update style. Co-authored-by: Cursor --- pallets/subtensor/src/subnets/uids.rs | 3 + pallets/subtensor/src/tests/uids.rs | 86 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index d21509f83d..3e30a6105f 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -46,6 +46,9 @@ impl Pallet { } Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); StakeWeight::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + ValidatorTrust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + ValidatorPermit::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, false)); + Active::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, false)); } /// Replace the neuron under this uid. diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 37d9733991..b831bb783d 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -225,6 +225,92 @@ fn test_replace_neuron_resets_last_update() { }); } +#[test] +fn test_replace_neuron_clears_validator_trust_and_permit() { + new_test_ext(1).execute_with(|| { + let registration_block: u64 = 0; + let replacement_block: u64 = 123; + let netuid = NetUid::from(1); + let tempo: u16 = 13; + let hotkey_account_id = U256::from(1); + let coldkey_account_id = U256::from(1234); + let new_hotkey_account_id = U256::from(2); + + System::set_block_number(registration_block); + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 0); + + let neuron_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); + let idx = neuron_uid as usize; + + // Simulate the previous occupant having earned a validator_permit and trust score. + ValidatorTrust::::mutate(netuid, |v| { + if v.len() <= idx { + v.resize(idx + 1, 0); + } + v[idx] = 42; + }); + ValidatorPermit::::mutate(netuid, |v| { + if v.len() <= idx { + v.resize(idx + 1, false); + } + v[idx] = true; + }); + + SubtensorModule::replace_neuron( + netuid, + neuron_uid, + &new_hotkey_account_id, + replacement_block, + ); + + // The replaced neuron must not inherit the previous occupant's validator state. + assert_eq!(ValidatorTrust::::get(netuid)[idx], 0); + assert!(!ValidatorPermit::::get(netuid)[idx]); + }); +} + +#[test] +fn test_replace_neuron_clears_active() { + new_test_ext(1).execute_with(|| { + let registration_block: u64 = 0; + let replacement_block: u64 = 123; + let netuid = NetUid::from(1); + let tempo: u16 = 13; + let hotkey_account_id = U256::from(1); + let coldkey_account_id = U256::from(1234); + let new_hotkey_account_id = U256::from(2); + + System::set_block_number(registration_block); + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 0); + + let neuron_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey_account_id).unwrap(); + let idx = neuron_uid as usize; + + // Simulate the previous occupant having been marked active by a prior epoch. + Active::::mutate(netuid, |v| { + if v.len() <= idx { + v.resize(idx + 1, false); + } + v[idx] = true; + }); + + SubtensorModule::replace_neuron( + netuid, + neuron_uid, + &new_hotkey_account_id, + replacement_block, + ); + + // The replaced neuron must not inherit the previous occupant's active=true flag + // until the next epoch's vector rebuild. + assert!(!Active::::get(netuid)[idx]); + }); +} + #[test] fn test_replace_neuron_multiple_subnets() { new_test_ext(1).execute_with(|| {