Skip to content
2 changes: 2 additions & 0 deletions pallets/admin-utils/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ parameter_types! {
pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days
pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight.
pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks
pub const DurationOfStartCall: u64 = 7 * 24 * 60 * 60 / 12; // 7 days
}

impl pallet_subtensor::Config for Test {
Expand Down Expand Up @@ -199,6 +200,7 @@ impl pallet_subtensor::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
type DurationOfStartCall = DurationOfStartCall;
}

#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
Expand Down
33 changes: 32 additions & 1 deletion pallets/subtensor/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::Pallet as Subtensor;
use crate::*;
use frame_benchmarking::{account, benchmarks, whitelisted_caller};
use frame_support::assert_ok;
use frame_system::RawOrigin;
use frame_system::{RawOrigin, pallet_prelude::BlockNumberFor};
pub use pallet::*;
use sp_core::H256;
use sp_runtime::traits::{BlakeTwo256, Hash};
Expand Down Expand Up @@ -658,4 +658,35 @@ benchmark_burn_alpha {

}: burn_alpha(RawOrigin::Signed(coldkey), hotkey, alpha_amount, netuid)


benchmark_start_call {
let caller: T::AccountId = whitelisted_caller::<AccountIdOf<T>>();
let caller_origin = <T as frame_system::Config>::RuntimeOrigin::from(RawOrigin::Signed(caller.clone()));
let netuid: u16 = 1;
let tempo: u16 = 1;
let seed: u32 = 1;

// Set up coldkey and hotkey
let coldkey: T::AccountId = account("Test", 0, seed);
let hotkey: T::AccountId = account("Alice", 0, seed);

// Initialize network
Subtensor::<T>::init_new_network(netuid, tempo);
Subtensor::<T>::set_network_registration_allowed(netuid, true);

// Register the neuron
Subtensor::<T>::set_burn(netuid, 1);
let amount_to_be_staked = 1000000u32.into();
Subtensor::<T>::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked);

assert_ok!(Subtensor::<T>::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone()));
assert_eq!(SubnetOwner::<T>::get(netuid), coldkey.clone());
assert_eq!(FirstEmissionBlockNumber::<T>::get(netuid), None);
let current_block: u64 = Subtensor::<T>::get_current_block_as_u64();
let duration = <T as Config>::DurationOfStartCall::get();
let block: BlockNumberFor<T> = (current_block + duration).try_into().ok().expect("can't convert to block number");
frame_system::Pallet::<T>::set_block_number(block);

}: start_call(RawOrigin::Signed(coldkey), netuid)

}
3 changes: 2 additions & 1 deletion pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ impl<T: Config> Pallet<T> {
let current_block: u64 = Self::get_current_block_as_u64();
log::debug!("Current block: {:?}", current_block);

// --- 1. Get all netuids (filter out root.)
// --- 1. Get all netuids (filter out root and new subnet without first emission block)
let subnets: Vec<u16> = Self::get_all_subnet_netuids()
.into_iter()
.filter(|netuid| *netuid != 0)
.filter(|netuid| FirstEmissionBlockNumber::<T>::get(*netuid).is_some())
.collect();
log::debug!("All subnet netuids: {:?}", subnets);

Expand Down
6 changes: 5 additions & 1 deletion pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,11 @@ pub mod pallet {
/// ============================
/// ==== Subnet Parameters =====
/// ============================
#[pallet::storage] // --- MAP ( netuid ) --> subnet mechanism
/// --- MAP ( netuid ) --> block number of first emission
#[pallet::storage]
pub type FirstEmissionBlockNumber<T: Config> = StorageMap<_, Identity, u16, u64, OptionQuery>;
/// --- MAP ( netuid ) --> subnet mechanism
#[pallet::storage]
pub type SubnetMechanism<T: Config> =
StorageMap<_, Identity, u16, u16, ValueQuery, DefaultZeroU16<T>>;
#[pallet::storage]
Expand Down
3 changes: 3 additions & 0 deletions pallets/subtensor/src/macros/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,8 @@ mod config {
/// Initial EMA price halving period
#[pallet::constant]
type InitialEmaPriceHalvingPeriod: Get<u64>;
/// Block number after a new subnet accept the start call extrinsic.
#[pallet::constant]
type DurationOfStartCall: Get<u64>;
}
}
19 changes: 19 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,25 @@ mod dispatches {
Ok(())
}

/// Initiates a call on a subnet.
///
/// # Arguments
/// * `origin` - The origin of the call, which must be signed by the subnet owner.
/// * `netuid` - The unique identifier of the subnet on which the call is being initiated.
///
/// # Events
/// Emits a `FirstEmissionBlockNumberSet` event on success.
#[pallet::call_index(92)]
#[pallet::weight((
Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().reads_writes(6, 1)),
DispatchClass::Operational,
Pays::Yes
))]
pub fn start_call(origin: T::RuntimeOrigin, netuid: u16) -> DispatchResult {
Self::do_start_call(origin, netuid)?;
Ok(())
}

/// Recycles alpha from a cold/hot key pair, reducing AlphaOut on a subnet
///
/// # Arguments
Expand Down
4 changes: 4 additions & 0 deletions pallets/subtensor/src/macros/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ mod errors {
ActivityCutoffTooLow,
/// Call is disabled
CallDisabled,
/// FirstEmissionBlockNumber is already set.
FirstEmissionBlockNumberAlreadySet,
/// need wait for more blocks to accept the start call extrinsic.
NeedWaitingMoreBlocksToStarCall,
/// Not enough AlphaOut on the subnet to recycle
NotEnoughAlphaOutToRecycle,
}
Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ mod events {
/// Parameters:
/// (netuid, new_hotkey)
SubnetOwnerHotkeySet(u16, T::AccountId),
/// FirstEmissionBlockNumber is set via start call extrinsic
///
/// Parameters:
/// netuid
/// block number
FirstEmissionBlockNumberSet(u16, u64),

/// Alpha has been recycled, reducing AlphaOut on a subnet.
///
Expand Down
4 changes: 3 additions & 1 deletion pallets/subtensor/src/macros/hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ mod hooks {
// Remove Stake map entries
.saturating_add(migrations::migrate_remove_stake_map::migrate_remove_stake_map::<T>())
// Remove unused maps entries
.saturating_add(migrations::migrate_remove_unused_maps_and_values::migrate_remove_unused_maps_and_values::<T>());
.saturating_add(migrations::migrate_remove_unused_maps_and_values::migrate_remove_unused_maps_and_values::<T>())
// Set last emission block number for all existed subnets before start call feature applied
.saturating_add(migrations::migrate_set_first_emission_block_number::migrate_set_first_emission_block_number::<T>());
weight
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use super::*;
use crate::HasMigrationRun;
use frame_support::{traits::Get, weights::Weight};
use scale_info::prelude::string::String;

pub fn migrate_set_first_emission_block_number<T: Config>() -> Weight {
let migration_name = b"migrate_set_first_emission_block_number".to_vec();

let mut weight = T::DbWeight::get().reads(1);
if HasMigrationRun::<T>::get(&migration_name) {
log::info!(
"Migration '{:?}' has already run. Skipping.",
String::from_utf8_lossy(&migration_name)
);
return weight;
}

log::info!(
"Running migration '{:?}'",
String::from_utf8_lossy(&migration_name)
);

// ------------------------------
// Step 1: Set the first emission block for all subnets except root
// ------------------------------
let netuids = Pallet::<T>::get_all_subnet_netuids();
let current_block_number = Pallet::<T>::get_current_block_as_u64();
for netuid in netuids.iter() {
if *netuid != 0 {
FirstEmissionBlockNumber::<T>::insert(netuid, current_block_number);
}
}

// ------------------------------
// Step 2: Mark Migration as Completed
// ------------------------------

HasMigrationRun::<T>::insert(&migration_name, true);
weight = weight.saturating_add(T::DbWeight::get().reads(2));

if netuids.is_empty() {
weight = weight.saturating_add(T::DbWeight::get().writes(1_u64));
} else {
weight = weight.saturating_add(T::DbWeight::get().writes(netuids.len() as u64));
}

log::info!(
"Migration '{:?}' completed successfully.",
String::from_utf8_lossy(&migration_name)
);

weight
}
1 change: 1 addition & 0 deletions pallets/subtensor/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod migrate_populate_owned_hotkeys;
pub mod migrate_rao;
pub mod migrate_remove_stake_map;
pub mod migrate_remove_unused_maps_and_values;
pub mod migrate_set_first_emission_block_number;
pub mod migrate_set_min_burn;
pub mod migrate_set_min_difficulty;
pub mod migrate_stake_threshold;
Expand Down
54 changes: 54 additions & 0 deletions pallets/subtensor/src/subnets/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,58 @@ impl<T: Config> Pallet<T> {
);
}
}

/// Execute the start call for a subnet.
///
/// This function is used to trigger the start call process for a subnet identified by `netuid`.
/// It ensures that the subnet exists, the caller is the subnet owner,
/// and the last emission block number has not been set yet.
/// It then sets the last emission block number to the current block number.
///
/// # Parameters
///
/// * `origin`: The origin of the call, which is used to ensure the caller is the subnet owner.
/// * `netuid`: The unique identifier of the subnet for which the start call process is being initiated.
///
/// # Raises
///
/// * `Error::<T>::SubNetworkDoesNotExist`: If the subnet does not exist.
/// * `DispatchError::BadOrigin`: If the caller is not the subnet owner.
/// * `Error::<T>::FirstEmissionBlockNumberAlreadySet`: If the last emission block number has already been set.
///
/// # Returns
///
/// * `DispatchResult`: A result indicating the success or failure of the operation.
pub fn do_start_call(origin: T::RuntimeOrigin, netuid: u16) -> DispatchResult {
ensure!(
Self::if_subnet_exist(netuid),
Error::<T>::SubNetworkDoesNotExist
);
Self::ensure_subnet_owner(origin, netuid)?;
ensure!(
FirstEmissionBlockNumber::<T>::get(netuid).is_none(),
Error::<T>::FirstEmissionBlockNumberAlreadySet
);

let registration_block_number = NetworkRegisteredAt::<T>::get(netuid);
let current_block_number = Self::get_current_block_as_u64();

ensure!(
current_block_number
>= registration_block_number.saturating_add(T::DurationOfStartCall::get()),
Error::<T>::NeedWaitingMoreBlocksToStarCall
);
let next_block_number = current_block_number.saturating_add(1);

FirstEmissionBlockNumber::<T>::insert(netuid, next_block_number);
Self::deposit_event(Event::FirstEmissionBlockNumberSet(
netuid,
next_block_number,
));
Ok(())
}

pub fn is_valid_subnet_for_emission(netuid: u16) -> bool {
FirstEmissionBlockNumber::<T>::get(netuid).is_some()
}
}
22 changes: 22 additions & 0 deletions pallets/subtensor/src/tests/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use frame_support::{
traits::{StorageInstance, StoredMap},
weights::Weight,
};

use frame_system::Config;
use sp_core::{H256, U256, crypto::Ss58Codec};
use sp_io::hashing::twox_128;
Expand Down Expand Up @@ -416,3 +417,24 @@ fn test_migrate_subnet_volume() {
assert_eq!(new_value, Some(old_value as u128));
});
}

#[test]
fn test_migrate_set_first_emission_block_number() {
new_test_ext(1).execute_with(|| {
let netuids: [u16; 3] = [1, 2, 3];
let block_number = 100;
for netuid in netuids.iter() {
add_network(*netuid, 1, 0);
}
run_to_block(block_number);
let weight = crate::migrations::migrate_set_first_emission_block_number::migrate_set_first_emission_block_number::<Test>();

let expected_weight: Weight = <Test as Config>::DbWeight::get().reads(3) + <Test as Config>::DbWeight::get().writes(netuids.len() as u64);
assert_eq!(weight, expected_weight);

assert_eq!(FirstEmissionBlockNumber::<Test>::get(0), None);
for netuid in netuids.iter() {
assert_eq!(FirstEmissionBlockNumber::<Test>::get(netuid), Some(block_number));
}
});
}
26 changes: 26 additions & 0 deletions pallets/subtensor/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ parameter_types! {
pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days
pub const InitialTaoWeight: u64 = 0; // 100% global weight.
pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks
pub const DurationOfStartCall: u64 = 7 * 24 * 60 * 60 / 12; // Default as 7 days
}

// Configure collective pallet for council
Expand Down Expand Up @@ -408,6 +409,7 @@ impl crate::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
type DurationOfStartCall = DurationOfStartCall;
}

pub struct OriginPrivilegeCmp;
Expand Down Expand Up @@ -662,6 +664,14 @@ pub fn add_network(netuid: u16, tempo: u16, _modality: u16) {
SubtensorModule::init_new_network(netuid, tempo);
SubtensorModule::set_network_registration_allowed(netuid, true);
SubtensorModule::set_network_pow_registration_allowed(netuid, true);
FirstEmissionBlockNumber::<Test>::insert(netuid, 1);
}

#[allow(dead_code)]
pub fn add_network_without_emission_block(netuid: u16, tempo: u16, _modality: u16) {
SubtensorModule::init_new_network(netuid, tempo);
SubtensorModule::set_network_registration_allowed(netuid, true);
SubtensorModule::set_network_pow_registration_allowed(netuid, true);
}

#[allow(dead_code)]
Expand All @@ -670,6 +680,22 @@ pub fn add_dynamic_network(hotkey: &U256, coldkey: &U256) -> u16 {
let lock_cost = SubtensorModule::get_network_lock_cost();
SubtensorModule::add_balance_to_coldkey_account(coldkey, lock_cost);

assert_ok!(SubtensorModule::register_network(
RawOrigin::Signed(*coldkey).into(),
*hotkey
));
NetworkRegistrationAllowed::<Test>::insert(netuid, true);
NetworkPowRegistrationAllowed::<Test>::insert(netuid, true);
FirstEmissionBlockNumber::<Test>::insert(netuid, 0);
netuid
}

#[allow(dead_code)]
pub fn add_dynamic_network_without_emission_block(hotkey: &U256, coldkey: &U256) -> u16 {
let netuid = SubtensorModule::get_next_netuid();
let lock_cost = SubtensorModule::get_network_lock_cost();
SubtensorModule::add_balance_to_coldkey_account(coldkey, lock_cost);

assert_ok!(SubtensorModule::register_network(
RawOrigin::Signed(*coldkey).into(),
*hotkey
Expand Down
1 change: 1 addition & 0 deletions pallets/subtensor/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod senate;
mod serving;
mod staking;
mod staking2;
mod subnet;
mod swap_coldkey;
mod swap_hotkey;
mod uids;
Expand Down
Loading
Loading