Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/pending childkeys #1050

Merged
merged 13 commits into from
Dec 6, 2024
3 changes: 3 additions & 0 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ impl<T: Config> Pallet<T> {
);
log::debug!("Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", hotkey, *netuid, mining_emission, validator_emission);
}

// 4.5 Apply pending childkeys of this subnet for the next epoch
Self::do_set_pending_children(*netuid);
} else {
// No epoch, increase blocks since last step and continue
Self::set_blocks_since_last_step(
Expand Down
29 changes: 29 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ pub mod pallet {
vec![]
}
#[pallet::type_value]
/// Default pending childkeys
pub fn DefaultPendingChildkeys<T: Config>() -> (Vec<(u64, T::AccountId)>, u64) {
(vec![], 0)
}
#[pallet::type_value]
/// Default account linkage
pub fn DefaultProportion<T: Config>() -> u64 {
0
Expand Down Expand Up @@ -674,6 +679,18 @@ pub mod pallet {
T::InitialColdkeySwapScheduleDuration::get()
}

#[pallet::type_value]
/// Default value for applying pending items (e.g. childkeys).
pub fn DefaultPendingCooldown<T: Config>() -> u64 {
7200
}

#[pallet::type_value]
/// Default value for minimum stake.
pub fn DefaultMinStake<T: Config>() -> u64 {
1_000_000_000
}

#[pallet::storage]
pub type ColdkeySwapScheduleDuration<T: Config> =
StorageValue<_, BlockNumberFor<T>, ValueQuery, DefaultColdkeySwapScheduleDuration<T>>;
Expand Down Expand Up @@ -821,6 +838,18 @@ pub mod pallet {
DefaultStakeDelta<T>,
>;
#[pallet::storage]
/// DMAP ( netuid, parent ) --> (Vec<(proportion,child)>, cool_down_block)
pub type PendingChildKeys<T: Config> = StorageDoubleMap<
_,
Identity,
u16,
Blake2_128Concat,
T::AccountId,
(Vec<(u64, T::AccountId)>, u64),
ValueQuery,
DefaultPendingChildkeys<T>,
>;
#[pallet::storage]
/// DMAP ( parent, netuid ) --> Vec<(proportion,child)>
pub type ChildKeys<T: Config> = StorageDoubleMap<
_,
Expand Down
2 changes: 1 addition & 1 deletion pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ mod dispatches {
netuid: u16,
children: Vec<(u64, T::AccountId)>,
) -> DispatchResultWithPostInfo {
Self::do_set_children(origin, hotkey, netuid, children)?;
Self::do_schedule_children(origin, hotkey, netuid, children)?;
Ok(().into())
}

Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ mod errors {
NotEnoughStakeToWithdraw,
/// The caller is requesting to set weights but the caller has less than minimum stake required to set weights (less than WeightsMinStake).
NotEnoughStakeToSetWeights,
/// The parent hotkey doesn't have enough own stake to set childkeys.
NotEnoughStakeToSetChildkeys,
/// The caller is requesting adding more stake than there exists in the coldkey account. See: "[add_stake()]"
NotEnoughBalanceToStake,
/// The caller is trying to add stake, but for some reason the requested amount could not be withdrawn from the coldkey account.
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ mod events {
/// The account ID of the coldkey
coldkey: T::AccountId,
},
/// Setting of children of a hotkey have been scheduled
SetChildrenScheduled(T::AccountId, u16, u64, Vec<(u64, T::AccountId)>),
/// The children of a hotkey have been set
SetChildren(T::AccountId, u16, Vec<(u64, T::AccountId)>),
/// The hotkey emission tempo has been set
Expand Down
8 changes: 8 additions & 0 deletions pallets/subtensor/src/staking/remove_stake.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use sp_core::Get;

impl<T: Config> Pallet<T> {
/// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey.
Expand Down Expand Up @@ -90,6 +91,13 @@ impl<T: Config> Pallet<T> {
let new_stake = Self::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey);
Self::clear_small_nomination_if_required(&hotkey, &coldkey, new_stake);

// Check if stake lowered below MinStake and remove Pending children if it did
if Self::get_total_stake_for_hotkey(&hotkey) < DefaultMinStake::<T>::get() {
Self::get_all_subnet_netuids().iter().for_each(|netuid| {
PendingChildKeys::<T>::remove(netuid, &hotkey);
})
}

// Set last block for rate limiting
let block: u64 = Self::get_current_block_as_u64();
Self::set_last_tx_block(&coldkey, block);
Expand Down
205 changes: 136 additions & 69 deletions pallets/subtensor/src/staking/set_children.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
use super::*;
use sp_core::Get;
camfairchild marked this conversation as resolved.
Show resolved Hide resolved

impl<T: Config> Pallet<T> {
/// ---- The implementation for the extrinsic do_set_child_singular: Sets a single child.
///
/// This function allows a coldkey to set children keys.
///
/// # Arguments:
/// * `origin` (<T as frame_system::Config>::RuntimeOrigin):
/// - The signature of the calling coldkey. Setting a hotkey child can only be done by the coldkey.
///
/// * `hotkey` (T::AccountId):
/// - The hotkey which will be assigned the child.
///
/// * `netuid` (u16):
/// - The u16 network identifier where the child keys will exist.
///
/// * `children` Vec[(u64, T::AccountId)]:
/// - A list of children with their proportions.
/// Adds a childkey vector to the PendingChildKeys map and performs a few checks:
/// **Signature Verification**: Ensures that the caller has signed the transaction, verifying the coldkey.
/// **Root Network Check**: Ensures that the delegation is not on the root network, as child hotkeys are not valid on the root.
/// **Network Existence Check**: Ensures that the specified network exists.
/// **Ownership Verification**: Ensures that the coldkey owns the hotkey.
/// **Hotkey Account Existence Check**: Ensures that the hotkey account already exists.
/// **Child count**: Only allow to add up to 5 children per parent
/// **Child-Hotkey Distinction**: Ensures that the child is not the same as the hotkey.
/// **Minimum stake**: Ensures that the parent key has at least the minimum stake.
/// **Proportion check**: Ensure that the sum of the proportions does not exceed u64::MAX.
/// **Duplicate check**: Ensure there are no duplicates in the list of children.
///
/// # Events:
/// * `ChildrenAdded`:
/// - On successfully registering children to a hotkey.
/// * `SetChildrenScheduled`:
/// - If all checks pass and setting the childkeys is scheduled.
///
/// # Errors:
/// * `SubNetworkDoesNotExist`:
Expand All @@ -31,24 +30,16 @@ impl<T: Config> Pallet<T> {
/// - The coldkey does not own the hotkey or the child is the same as the hotkey.
/// * `HotKeyAccountNotExists`:
/// - The hotkey account does not exist.
/// * `TooManyChildren`:
/// - Too many children in request
///
/// # Detailed Explanation of Checks:
/// 1. **Signature Verification**: Ensures that the caller has signed the transaction, verifying the coldkey.
/// 2. **Root Network Check**: Ensures that the delegation is not on the root network, as child hotkeys are not valid on the root.
/// 3. **Network Existence Check**: Ensures that the specified network exists.
/// 4. **Ownership Verification**: Ensures that the coldkey owns the hotkey.
/// 5. **Hotkey Account Existence Check**: Ensures that the hotkey account already exists.
/// 6. **Child-Hotkey Distinction**: Ensures that the child is not the same as the hotkey.
/// 7. **Old Children Cleanup**: Removes the hotkey from the parent list of its old children.
/// 8. **New Children Assignment**: Assigns the new child to the hotkey and updates the parent list for the new child.
///
pub fn do_set_children(
pub fn do_schedule_children(
origin: T::RuntimeOrigin,
hotkey: T::AccountId,
netuid: u16,
children: Vec<(u64, T::AccountId)>,
) -> DispatchResult {
// --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing)
// Check that the caller has signed the transaction. (the coldkey of the pairing)
let coldkey = ensure_signed(origin)?;
log::trace!(
"do_set_children( coldkey:{:?} hotkey:{:?} netuid:{:?} children:{:?} )",
Expand Down Expand Up @@ -77,38 +68,38 @@ impl<T: Config> Pallet<T> {
current_block,
);

// --- 2. Check that this delegation is not on the root network. Child hotkeys are not valid on root.
// Check that this delegation is not on the root network. Child hotkeys are not valid on root.
ensure!(
netuid != Self::get_root_netuid(),
Error::<T>::RegistrationNotPermittedOnRootSubnet
);

// --- 3. Check that the network we are trying to create the child on exists.
// Check that the network we are trying to create the child on exists.
ensure!(
Self::if_subnet_exist(netuid),
Error::<T>::SubNetworkDoesNotExist
);

// --- 4. Check that the coldkey owns the hotkey.
// Check that the coldkey owns the hotkey.
ensure!(
Self::coldkey_owns_hotkey(&coldkey, &hotkey),
Error::<T>::NonAssociatedColdKey
);

// --- 4.1. Ensure that the number of children does not exceed 5.
// Ensure that the number of children does not exceed 5.
ensure!(children.len() <= 5, Error::<T>::TooManyChildren);

// --- 5. Ensure that each child is not the hotkey.
// Ensure that each child is not the hotkey.
for (_, child_i) in &children {
ensure!(child_i != &hotkey, Error::<T>::InvalidChild);
}
// --- 5.1. Ensure that the sum of the proportions does not exceed u64::MAX.
// Ensure that the sum of the proportions does not exceed u64::MAX.
let _total_proportion: u64 = children
.iter()
.try_fold(0u64, |acc, &(proportion, _)| acc.checked_add(proportion))
.ok_or(Error::<T>::ProportionOverflow)?;

// --- 5.2. Ensure there are no duplicates in the list of children.
// Ensure there are no duplicates in the list of children.
let mut unique_children = Vec::new();
for (_, child_i) in &children {
ensure!(
Expand All @@ -118,55 +109,131 @@ impl<T: Config> Pallet<T> {
unique_children.push(child_i.clone());
}

// --- 6. Erase myself from old children's parents.
let old_children: Vec<(u64, T::AccountId)> = ChildKeys::<T>::get(hotkey.clone(), netuid);

// --- 6.0. Iterate over all my old children and remove myself from their parent's map.
for (_, old_child_i) in old_children.clone().iter() {
// --- 6.1. Get the old child's parents on this network.
let my_old_child_parents: Vec<(u64, T::AccountId)> =
ParentKeys::<T>::get(old_child_i.clone(), netuid);

// --- 6.2. Filter my hotkey from my old children's parents list.
let filtered_parents: Vec<(u64, T::AccountId)> = my_old_child_parents
.into_iter()
.filter(|(_, parent)| *parent != hotkey)
.collect();

// --- 6.3. Update the parent list in storage
ParentKeys::<T>::insert(old_child_i, netuid, filtered_parents);
}

// --- 7.1. Insert my new children + proportion list into the map.
ChildKeys::<T>::insert(hotkey.clone(), netuid, children.clone());

// --- 7.2. Update the parents list for my new children.
for (proportion, new_child_i) in children.clone().iter() {
// --- 8.2.1. Get the child's parents on this network.
let mut new_child_previous_parents: Vec<(u64, T::AccountId)> =
ParentKeys::<T>::get(new_child_i.clone(), netuid);
// Check that the parent key has at least the minimum own stake
// (checking with check_weights_min_stake wouldn't work because it considers
// grandparent stake in this case)
ensure!(
Self::get_total_stake_for_hotkey(&hotkey) >= DefaultMinStake::<T>::get(),
Error::<T>::NotEnoughStakeToSetChildkeys
);

// --- 7.2.2. Append my hotkey and proportion to my new child's parents list.
// NOTE: There are no duplicates possible because I previously removed my self from my old children.
new_child_previous_parents.push((*proportion, hotkey.clone()));
// Calculate cool-down block
let cooldown_block =
Self::get_current_block_as_u64().saturating_add(DefaultPendingCooldown::<T>::get());

// --- 7.2.3. Update the parents list in storage.
ParentKeys::<T>::insert(new_child_i.clone(), netuid, new_child_previous_parents);
}
// Insert or update PendingChildKeys
PendingChildKeys::<T>::insert(netuid, hotkey.clone(), (children.clone(), cooldown_block));

// --- 8. Log and return.
log::trace!(
"SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )",
"SetChildrenScheduled( netuid:{:?}, cooldown_block:{:?}, hotkey:{:?}, children:{:?} )",
cooldown_block,
hotkey,
netuid,
children.clone()
);
Self::deposit_event(Event::SetChildren(hotkey.clone(), netuid, children.clone()));
Self::deposit_event(Event::SetChildrenScheduled(
hotkey.clone(),
netuid,
cooldown_block,
children.clone(),
));

// Ok and return.
Ok(())
}

/// This function executes setting children keys when called during hotkey draining.
///
/// * `netuid` (u16):
/// - The u16 network identifier where the child keys will exist.
///
/// # Events:
/// * `SetChildren`:
/// - On successfully registering children to a hotkey.
///
/// # Errors:
/// * `SubNetworkDoesNotExist`:
/// - Attempting to register to a non-existent network.
/// * `RegistrationNotPermittedOnRootSubnet`:
/// - Attempting to register a child on the root network.
/// * `NonAssociatedColdKey`:
/// - The coldkey does not own the hotkey or the child is the same as the hotkey.
/// * `HotKeyAccountNotExists`:
/// - The hotkey account does not exist.
///
/// # Detailed Explanation of actions:
/// 1. **Old Children Cleanup**: Removes the hotkey from the parent list of its old children.
/// 2. **New Children Assignment**: Assigns the new child to the hotkey and updates the parent list for the new child.
///
pub fn do_set_pending_children(netuid: u16) {
let current_block = Self::get_current_block_as_u64();

// Iterate over all pending children of this subnet and set as needed
PendingChildKeys::<T>::iter_prefix(netuid).for_each(
|(hotkey, (children, cool_down_block))| {
if cool_down_block < current_block {
// Erase myself from old children's parents.
let old_children: Vec<(u64, T::AccountId)> =
ChildKeys::<T>::get(hotkey.clone(), netuid);

// Iterate over all my old children and remove myself from their parent's map.
for (_, old_child_i) in old_children.clone().iter() {
// Get the old child's parents on this network.
let my_old_child_parents: Vec<(u64, T::AccountId)> =
ParentKeys::<T>::get(old_child_i.clone(), netuid);

// Filter my hotkey from my old children's parents list.
let filtered_parents: Vec<(u64, T::AccountId)> = my_old_child_parents
.into_iter()
.filter(|(_, parent)| *parent != hotkey)
.collect();

// Update the parent list in storage
ParentKeys::<T>::insert(old_child_i, netuid, filtered_parents);
}

// Insert my new children + proportion list into the map.
ChildKeys::<T>::insert(hotkey.clone(), netuid, children.clone());

// Update the parents list for my new children.
for (proportion, new_child_i) in children.clone().iter() {
// Get the child's parents on this network.
let mut new_child_previous_parents: Vec<(u64, T::AccountId)> =
ParentKeys::<T>::get(new_child_i.clone(), netuid);

// Append my hotkey and proportion to my new child's parents list.
// NOTE: There are no duplicates possible because I previously removed my self from my old children.
new_child_previous_parents.push((*proportion, hotkey.clone()));

// Update the parents list in storage.
ParentKeys::<T>::insert(
new_child_i.clone(),
netuid,
new_child_previous_parents,
);
}

// Log and emit event.
log::trace!(
"SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )",
hotkey,
netuid,
children.clone()
);
Self::deposit_event(Event::SetChildren(
hotkey.clone(),
netuid,
children.clone(),
));

// Remove pending children
PendingChildKeys::<T>::remove(netuid, hotkey);
}
},
);
}

/* Retrieves the list of children for a given hotkey and network.
///
/// # Arguments
Expand Down
Loading