From 3954bf0f3f0b455bca1b44038092a154d51ef750 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Sun, 21 Dec 2025 11:47:54 +0000
Subject: [PATCH 01/11] Add sudo-only hyperparameter for start call delay
Prompt: "make a sudo-only hyperparameter for the delay before calling
start call which is hardcoded to 50400 or 7*7200 currently"
This commit converts the hardcoded DurationOfStartCall constant (50400
blocks, or ~7 days) into a runtime-configurable hyperparameter called
StartCallDelay that can be modified via sudo extrinsic.
Changes:
- Added StartCallDelay storage value with DefaultStartCallDelay type_value
- Renamed DurationOfStartCall config constant to InitialStartCallDelay
- Added StartCallDelaySet event for hyperparameter updates
- Added get_start_call_delay() and set_start_call_delay() functions
- Added sudo_set_start_call_delay() extrinsic in admin-utils (call_index 84)
- Updated subnet.rs to read from storage instead of config constant
- Updated runtime constant from DurationOfStartCall to InitialStartCallDelay
- Updated all mock files and test usages to use new parameter name
- Updated benchmarks and tests to use StartCallDelay::::get()
The delay can now be modified at runtime via the sudo_set_start_call_delay()
extrinsic without requiring a runtime upgrade, providing more flexibility
for network governance.
Users can read the current value through:
- Direct storage queries via state_getStorage RPC call
- The get_start_call_delay() getter function for internal use
- Client libraries (polkadot-js, subxt) which auto-generate storage accessors
---
chain-extensions/src/mock.rs | 4 ++--
pallets/admin-utils/src/lib.rs | 18 ++++++++++++++++++
pallets/admin-utils/src/tests/mock.rs | 4 ++--
pallets/subtensor/src/benchmarks.rs | 2 +-
pallets/subtensor/src/coinbase/root.rs | 7 +++++++
pallets/subtensor/src/lib.rs | 11 +++++++++++
pallets/subtensor/src/macros/config.rs | 4 ++--
pallets/subtensor/src/macros/events.rs | 2 ++
pallets/subtensor/src/subnets/subnet.rs | 2 +-
pallets/subtensor/src/tests/coinbase.rs | 2 +-
pallets/subtensor/src/tests/mock.rs | 4 ++--
pallets/subtensor/src/tests/subnet.rs | 10 +++++-----
pallets/transaction-fee/src/tests/mock.rs | 4 ++--
runtime/src/lib.rs | 4 ++--
14 files changed, 58 insertions(+), 20 deletions(-)
diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs
index 98ea096199..660aa0c77c 100644
--- a/chain-extensions/src/mock.rs
+++ b/chain-extensions/src/mock.rs
@@ -331,7 +331,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
+ pub const InitialStartCallDelay: u64 = 7 * 24 * 60 * 60 / 12; // Default as 7 days
pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000;
pub const HotkeySwapOnSubnetInterval: u64 = 15; // 15 block, should be bigger than subnet number, then trigger clean up for all subnets
pub const MaxContributorsPerLeaseToRemove: u32 = 3;
@@ -402,7 +402,7 @@ impl pallet_subtensor::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
- type DurationOfStartCall = DurationOfStartCall;
+ type InitialStartCallDelay = InitialStartCallDelay;
type SwapInterface = pallet_subtensor_swap::Pallet;
type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost;
type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval;
diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs
index de6ac5825b..ebb239fff9 100644
--- a/pallets/admin-utils/src/lib.rs
+++ b/pallets/admin-utils/src/lib.rs
@@ -2213,6 +2213,24 @@ pub mod pallet {
log::debug!("set_tao_flow_smoothing_factor( {smoothing_factor:?} ) ");
Ok(())
}
+
+ /// Sets the delay before a subnet can call start
+ #[pallet::call_index(84)]
+ #[pallet::weight((
+ Weight::from_parts(14_000_000, 0)
+ .saturating_add(::DbWeight::get().writes(1)),
+ DispatchClass::Operational,
+ Pays::Yes
+ ))]
+ pub fn sudo_set_start_call_delay(
+ origin: OriginFor,
+ delay: u64,
+ ) -> DispatchResult {
+ ensure_root(origin)?;
+ pallet_subtensor::Pallet::::set_start_call_delay(delay);
+ log::debug!("StartCallDelay( delay: {delay:?} ) ");
+ Ok(())
+ }
}
}
diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs
index 0140808baa..e1ab02d911 100644
--- a/pallets/admin-utils/src/tests/mock.rs
+++ b/pallets/admin-utils/src/tests/mock.rs
@@ -145,7 +145,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
+ pub const InitialStartCallDelay: u64 = 7 * 24 * 60 * 60 / 12; // 7 days
pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000;
pub const HotkeySwapOnSubnetInterval: u64 = 7 * 24 * 60 * 60 / 12; // 7 days
pub const LeaseDividendsDistributionInterval: u32 = 100; // 100 blocks
@@ -215,7 +215,7 @@ impl pallet_subtensor::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
- type DurationOfStartCall = DurationOfStartCall;
+ type InitialStartCallDelay = InitialStartCallDelay;
type SwapInterface = Swap;
type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost;
type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval;
diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs
index 223c086419..062732c51f 100644
--- a/pallets/subtensor/src/benchmarks.rs
+++ b/pallets/subtensor/src/benchmarks.rs
@@ -647,7 +647,7 @@ mod pallet_benchmarks {
assert_eq!(FirstEmissionBlockNumber::::get(netuid), None);
let current_block: u64 = Subtensor::::get_current_block_as_u64();
- let duration = ::DurationOfStartCall::get();
+ let duration = StartCallDelay::::get();
let block: BlockNumberFor = (current_block + duration)
.try_into()
.ok()
diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs
index 893f855f3e..27d927afb6 100644
--- a/pallets/subtensor/src/coinbase/root.rs
+++ b/pallets/subtensor/src/coinbase/root.rs
@@ -542,6 +542,13 @@ impl Pallet {
NetworkImmunityPeriod::::set(net_immunity_period);
Self::deposit_event(Event::NetworkImmunityPeriodSet(net_immunity_period));
}
+ pub fn get_start_call_delay() -> u64 {
+ StartCallDelay::::get()
+ }
+ pub fn set_start_call_delay(delay: u64) {
+ StartCallDelay::::set(delay);
+ Self::deposit_event(Event::StartCallDelaySet(delay));
+ }
pub fn set_network_min_lock(net_min_lock: TaoCurrency) {
NetworkMinLockCost::::set(net_min_lock);
Self::deposit_event(Event::NetworkMinLockCostSet(net_min_lock));
diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs
index bbec3ce934..1b89354edf 100644
--- a/pallets/subtensor/src/lib.rs
+++ b/pallets/subtensor/src/lib.rs
@@ -645,6 +645,12 @@ pub mod pallet {
T::InitialSubnetOwnerCut::get()
}
+ /// Default value for start call delay.
+ #[pallet::type_value]
+ pub fn DefaultStartCallDelay() -> u64 {
+ T::InitialStartCallDelay::get()
+ }
+
/// Default value for recycle or burn.
#[pallet::type_value]
pub fn DefaultRecycleOrBurn() -> RecycleOrBurnEnum {
@@ -1490,6 +1496,11 @@ pub mod pallet {
pub type NetworkImmunityPeriod =
StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>;
+ /// ITEM( start_call_delay )
+ #[pallet::storage]
+ pub type StartCallDelay =
+ StorageValue<_, u64, ValueQuery, DefaultStartCallDelay>;
+
/// ITEM( min_network_lock_cost )
#[pallet::storage]
pub type NetworkMinLockCost =
diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs
index a735bde1e1..ce6dfd57c2 100644
--- a/pallets/subtensor/src/macros/config.rs
+++ b/pallets/subtensor/src/macros/config.rs
@@ -233,9 +233,9 @@ mod config {
/// Initial EMA price halving period
#[pallet::constant]
type InitialEmaPriceHalvingPeriod: Get;
- /// Block number after a new subnet accept the start call extrinsic.
+ /// Initial block number after a new subnet accept the start call extrinsic.
#[pallet::constant]
- type DurationOfStartCall: Get;
+ type InitialStartCallDelay: Get;
/// Cost of swapping a hotkey in a subnet.
#[pallet::constant]
type KeySwapOnSubnetCost: Get;
diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs
index d015205d4d..1551ec48c4 100644
--- a/pallets/subtensor/src/macros/events.rs
+++ b/pallets/subtensor/src/macros/events.rs
@@ -147,6 +147,8 @@ mod events {
NetworkRateLimitSet(u64),
/// the network immunity period is set.
NetworkImmunityPeriodSet(u64),
+ /// the start call delay is set.
+ StartCallDelaySet(u64),
/// the network minimum locking cost is set.
NetworkMinLockCostSet(TaoCurrency),
/// the maximum number of subnets is set
diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs
index 4185aee624..0de55e61bd 100644
--- a/pallets/subtensor/src/subnets/subnet.rs
+++ b/pallets/subtensor/src/subnets/subnet.rs
@@ -358,7 +358,7 @@ impl Pallet {
ensure!(
current_block_number
- >= registration_block_number.saturating_add(T::DurationOfStartCall::get()),
+ >= registration_block_number.saturating_add(StartCallDelay::::get()),
Error::::NeedWaitingMoreBlocksToStarCall
);
let next_block_number = current_block_number.saturating_add(1);
diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs
index 2ba9f93556..a318d7941d 100644
--- a/pallets/subtensor/src/tests/coinbase.rs
+++ b/pallets/subtensor/src/tests/coinbase.rs
@@ -2691,7 +2691,7 @@ fn test_run_coinbase_not_started_start_after() {
// We expect that the epoch ran.
assert_eq!(BlocksSinceLastStep::::get(netuid), 0);
- let block_number = DurationOfStartCall::get();
+ let block_number = StartCallDelay::::get();
run_to_block_no_epoch(netuid, block_number);
let current_block = System::block_number();
diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs
index 090fcf8f75..c33be9068c 100644
--- a/pallets/subtensor/src/tests/mock.rs
+++ b/pallets/subtensor/src/tests/mock.rs
@@ -218,7 +218,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
+ pub const InitialStartCallDelay: u64 = 7 * 24 * 60 * 60 / 12; // Default as 7 days
pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000;
pub const HotkeySwapOnSubnetInterval: u64 = 15; // 15 block, should be bigger than subnet number, then trigger clean up for all subnets
pub const MaxContributorsPerLeaseToRemove: u32 = 3;
@@ -289,7 +289,7 @@ impl crate::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
- type DurationOfStartCall = DurationOfStartCall;
+ type InitialStartCallDelay = InitialStartCallDelay;
type SwapInterface = pallet_subtensor_swap::Pallet;
type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost;
type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval;
diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs
index a11eae759e..3dd94ed2a7 100644
--- a/pallets/subtensor/src/tests/subnet.rs
+++ b/pallets/subtensor/src/tests/subnet.rs
@@ -27,7 +27,7 @@ fn test_do_start_call_ok() {
// account 0 is the default owner for any subnet
assert_eq!(SubnetOwner::::get(netuid), coldkey_account_id);
- let block_number = System::block_number() + DurationOfStartCall::get();
+ let block_number = System::block_number() + StartCallDelay::::get();
System::set_block_number(block_number);
assert_ok!(SubtensorModule::start_call(
@@ -76,7 +76,7 @@ fn test_do_start_call_fail_not_owner() {
assert_eq!(SubnetOwner::::get(netuid), coldkey_account_id);
- System::set_block_number(System::block_number() + DurationOfStartCall::get());
+ System::set_block_number(System::block_number() + StartCallDelay::::get());
assert_noop!(
SubtensorModule::start_call(
@@ -143,7 +143,7 @@ fn test_do_start_call_fail_for_set_again() {
assert_eq!(SubnetOwner::::get(netuid), coldkey_account_id);
- let block_number = System::block_number() + DurationOfStartCall::get();
+ let block_number = System::block_number() + StartCallDelay::::get();
System::set_block_number(block_number);
assert_ok!(SubtensorModule::start_call(
@@ -174,7 +174,7 @@ fn test_do_start_call_ok_with_same_block_number_after_coinbase() {
assert_eq!(SubnetOwner::::get(netuid), coldkey_account_id);
- let block_number = System::block_number() + DurationOfStartCall::get();
+ let block_number = System::block_number() + StartCallDelay::::get();
System::set_block_number(block_number);
assert_ok!(SubtensorModule::start_call(
@@ -368,7 +368,7 @@ fn test_subtoken_enable() {
add_network_disable_subtoken(netuid, 10, 0);
assert!(!SubtokenEnabled::::get(netuid));
- let block_number = System::block_number() + DurationOfStartCall::get();
+ let block_number = System::block_number() + StartCallDelay::::get();
System::set_block_number(block_number);
assert_ok!(SubtensorModule::start_call(
diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs
index ee5b1693ba..75e90346b4 100644
--- a/pallets/transaction-fee/src/tests/mock.rs
+++ b/pallets/transaction-fee/src/tests/mock.rs
@@ -210,7 +210,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
+ pub const InitialStartCallDelay: u64 = 7 * 24 * 60 * 60 / 12; // 7 days
pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000;
pub const HotkeySwapOnSubnetInterval: u64 = 7 * 24 * 60 * 60 / 12; // 7 days
pub const LeaseDividendsDistributionInterval: u32 = 100; // 100 blocks
@@ -280,7 +280,7 @@ impl pallet_subtensor::Config for Test {
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialTaoWeight = InitialTaoWeight;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
- type DurationOfStartCall = DurationOfStartCall;
+ type InitialStartCallDelay = InitialStartCallDelay;
type SwapInterface = Swap;
type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost;
type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval;
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 9ece1dd025..e6fd56fb61 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -1034,7 +1034,7 @@ parameter_types! {
pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight.
pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks
// 7 * 24 * 60 * 60 / 12 = 7 days
- pub const DurationOfStartCall: u64 = prod_or_fast!(7 * 24 * 60 * 60 / 12, 10);
+ pub const InitialStartCallDelay: u64 = prod_or_fast!(7 * 24 * 60 * 60 / 12, 10);
pub const SubtensorInitialKeySwapOnSubnetCost: u64 = 1_000_000; // 0.001 TAO
pub const HotkeySwapOnSubnetInterval : BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days
pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks
@@ -1104,7 +1104,7 @@ impl pallet_subtensor::Config for Runtime {
type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration;
type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration;
type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;
- type DurationOfStartCall = DurationOfStartCall;
+ type InitialStartCallDelay = InitialStartCallDelay;
type SwapInterface = Swap;
type KeySwapOnSubnetCost = SubtensorInitialKeySwapOnSubnetCost;
type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval;
From b5ce250fa36095e72635491962b46eadbbfdb71c Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Mon, 22 Dec 2025 00:23:52 +0000
Subject: [PATCH 02/11] Add comprehensive test for start call delay
hyperparameter
Added test_sudo_set_start_call_delay_permissions_and_zero_delay which:
1. Verifies permission checking - non-root accounts cannot set the delay
2. Creates a subnet
3. Attempts to start it immediately - FAILS (delay not passed)
4. Sets delay to zero via sudo_set_start_call_delay()
5. Verifies StartCallDelaySet event is emitted
6. Attempts to start the same subnet again - SUCCEEDS (delay now zero)
7. Attempts to start it a third time - FAILS (already started)
This test validates the key scenario: a subnet that couldn't be started
due to the delay can be started after reducing the delay to zero, proving
the hyperparameter change takes immediate effect. The test calls start_call
three times to thoroughly verify the state transitions and confirms the
proper event is emitted when the delay is changed.
---
pallets/admin-utils/src/tests/mod.rs | 95 ++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs
index 1aaefc8f8d..8e61e8c1ac 100644
--- a/pallets/admin-utils/src/tests/mod.rs
+++ b/pallets/admin-utils/src/tests/mod.rs
@@ -2865,3 +2865,98 @@ fn test_sudo_set_min_allowed_uids() {
);
});
}
+
+#[test]
+fn test_sudo_set_start_call_delay_permissions_and_zero_delay() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let tempo: u16 = 13;
+ let coldkey_account_id = U256::from(0);
+ let non_root_account = U256::from(1);
+
+ // Get initial delay value (should be non-zero)
+ let initial_delay = pallet_subtensor::StartCallDelay::::get();
+ assert!(initial_delay > 0, "Initial delay should be greater than zero");
+
+ // Test 1: Non-root account should fail to set delay
+ assert_err!(
+ AdminUtils::sudo_set_start_call_delay(
+ <::RuntimeOrigin>::signed(non_root_account),
+ 0
+ ),
+ DispatchError::BadOrigin
+ );
+ assert_eq!(
+ pallet_subtensor::StartCallDelay::::get(),
+ initial_delay,
+ "Delay should not have changed"
+ );
+
+ // Test 2: Create a subnet
+ add_network(netuid, tempo);
+ assert_eq!(
+ pallet_subtensor::FirstEmissionBlockNumber::::get(netuid),
+ None,
+ "Emission block should not be set yet"
+ );
+ assert_eq!(
+ pallet_subtensor::SubnetOwner::::get(netuid),
+ coldkey_account_id,
+ "Default owner should be account 0"
+ );
+
+ // Test 3: Try to start the subnet immediately - should FAIL (delay not passed)
+ assert_err!(
+ pallet_subtensor::Pallet::::start_call(
+ <::RuntimeOrigin>::signed(coldkey_account_id),
+ netuid
+ ),
+ pallet_subtensor::Error::::NeedWaitingMoreBlocksToStarCall
+ );
+
+ // Verify emission has not been set
+ assert_eq!(
+ pallet_subtensor::FirstEmissionBlockNumber::::get(netuid),
+ None,
+ "Emission should not be set yet"
+ );
+
+ // Test 4: Root sets delay to zero
+ assert_ok!(AdminUtils::sudo_set_start_call_delay(
+ <::RuntimeOrigin>::root(),
+ 0
+ ));
+ assert_eq!(
+ pallet_subtensor::StartCallDelay::::get(),
+ 0,
+ "Delay should now be zero"
+ );
+
+ // Verify event was emitted
+ frame_system::Pallet::::assert_last_event(
+ RuntimeEvent::SubtensorModule(pallet_subtensor::Event::StartCallDelaySet(0))
+ );
+
+ // Test 5: Try to start the subnet again - should SUCCEED (delay is now zero)
+ let current_block = frame_system::Pallet::::block_number();
+ assert_ok!(pallet_subtensor::Pallet::::start_call(
+ <::RuntimeOrigin>::signed(coldkey_account_id),
+ netuid
+ ));
+
+ assert_eq!(
+ pallet_subtensor::FirstEmissionBlockNumber::::get(netuid),
+ Some(current_block + 1),
+ "Emission should start at next block"
+ );
+
+ // Test 6: Try to start it a third time - should FAIL (already started)
+ assert_err!(
+ pallet_subtensor::Pallet::::start_call(
+ <::RuntimeOrigin>::signed(coldkey_account_id),
+ netuid
+ ),
+ pallet_subtensor::Error::::FirstEmissionBlockNumberAlreadySet
+ );
+ });
+}
From 93c2e4ef0af3fbe161f9424b805ec0d4f09a49e0 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Mon, 22 Dec 2025 03:48:00 +0200
Subject: [PATCH 03/11] cargo fmt
---
pallets/admin-utils/src/lib.rs | 5 +----
pallets/admin-utils/src/tests/mod.rs | 11 +++++++----
pallets/subtensor/src/lib.rs | 3 +--
3 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs
index ebb239fff9..e717449887 100644
--- a/pallets/admin-utils/src/lib.rs
+++ b/pallets/admin-utils/src/lib.rs
@@ -2222,10 +2222,7 @@ pub mod pallet {
DispatchClass::Operational,
Pays::Yes
))]
- pub fn sudo_set_start_call_delay(
- origin: OriginFor,
- delay: u64,
- ) -> DispatchResult {
+ pub fn sudo_set_start_call_delay(origin: OriginFor, delay: u64) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::::set_start_call_delay(delay);
log::debug!("StartCallDelay( delay: {delay:?} ) ");
diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs
index 8e61e8c1ac..24932ef508 100644
--- a/pallets/admin-utils/src/tests/mod.rs
+++ b/pallets/admin-utils/src/tests/mod.rs
@@ -2876,7 +2876,10 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() {
// Get initial delay value (should be non-zero)
let initial_delay = pallet_subtensor::StartCallDelay::::get();
- assert!(initial_delay > 0, "Initial delay should be greater than zero");
+ assert!(
+ initial_delay > 0,
+ "Initial delay should be greater than zero"
+ );
// Test 1: Non-root account should fail to set delay
assert_err!(
@@ -2933,9 +2936,9 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() {
);
// Verify event was emitted
- frame_system::Pallet::::assert_last_event(
- RuntimeEvent::SubtensorModule(pallet_subtensor::Event::StartCallDelaySet(0))
- );
+ frame_system::Pallet::::assert_last_event(RuntimeEvent::SubtensorModule(
+ pallet_subtensor::Event::StartCallDelaySet(0),
+ ));
// Test 5: Try to start the subnet again - should SUCCEED (delay is now zero)
let current_block = frame_system::Pallet::::block_number();
diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs
index 1b89354edf..b3eab3665a 100644
--- a/pallets/subtensor/src/lib.rs
+++ b/pallets/subtensor/src/lib.rs
@@ -1498,8 +1498,7 @@ pub mod pallet {
/// ITEM( start_call_delay )
#[pallet::storage]
- pub type StartCallDelay =
- StorageValue<_, u64, ValueQuery, DefaultStartCallDelay>;
+ pub type StartCallDelay = StorageValue<_, u64, ValueQuery, DefaultStartCallDelay>;
/// ITEM( min_network_lock_cost )
#[pallet::storage]
From 745db21dd37b23ccc9646988299b35f2b0cb6698 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Sat, 27 Dec 2025 19:28:48 +0000
Subject: [PATCH 04/11] Update localnet_patch.sh to use InitialStartCallDelay
Replace all references to DurationOfStartCall with InitialStartCallDelay
to match the renamed constant in the codebase.
Improvements:
- Updated variable names from DurationOfStartCall to InitialStartCallDelay
- Updated grep checks to look for the new constant name
- Updated perl replacements to modify the new constant name
- Added clear error messages for all replacement failures
- Added context messages explaining that codebase may have changed
- Added progress indicator when applying patches
- Enhanced final success message with checkmark
---
scripts/localnet_patch.sh | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/scripts/localnet_patch.sh b/scripts/localnet_patch.sh
index e3bee8c5b8..127d726f37 100755
--- a/scripts/localnet_patch.sh
+++ b/scripts/localnet_patch.sh
@@ -4,29 +4,45 @@
set -e
-DurationOfStartCall="runtime/src/lib.rs"
+InitialStartCallDelay="runtime/src/lib.rs"
DefaultPendingCooldown="pallets/subtensor/src/lib.rs"
SetChildren="pallets/subtensor/src/utils/rate_limiting.rs"
# Checkers
-if ! grep -q 'pub const DurationOfStartCall: u64' "$DurationOfStartCall"; then
- echo "Error: Target string not found in $DurationOfStartCall"
+if ! grep -q 'pub const InitialStartCallDelay: u64' "$InitialStartCallDelay"; then
+ echo "Error: Target string 'pub const InitialStartCallDelay: u64' not found in $InitialStartCallDelay"
+ echo "This may indicate the codebase has changed. Please verify the target code exists."
exit 1
fi
if ! grep -q 'pub fn DefaultPendingCooldown() -> u64 {' "$DefaultPendingCooldown"; then
- echo "Error: Target function not found in $DefaultPendingCooldown"
+ echo "Error: Target function 'DefaultPendingCooldown' not found in $DefaultPendingCooldown"
+ echo "This may indicate the codebase has changed. Please verify the target code exists."
exit 1
fi
if ! grep -q 'Self::SetChildren => 150, // 30 minutes' "$SetChildren"; then
- echo "Error: Target string not found in $SetChildren"
+ echo "Error: Target string 'Self::SetChildren => 150' not found in $SetChildren"
+ echo "This may indicate the codebase has changed. Please verify the target code exists."
exit 1
fi
-# replace
-perl -0777 -i -pe 's|pub const DurationOfStartCall: u64 = prod_or_fast!\(7 \* 24 \* 60 \* 60 / 12, 10\);|pub const DurationOfStartCall: u64 = prod_or_fast!(5, 10);|' "$DurationOfStartCall"
-perl -0777 -i -pe 's|pub fn DefaultPendingCooldown\(\) -> u64 \{\s*prod_or_fast!\(7_200, 15\)\s*\}|pub fn DefaultPendingCooldown() -> u64 {\n prod_or_fast!(15, 15)\n }|g' "$DefaultPendingCooldown"
-perl -0777 -i -pe 's|Self::SetChildren => 150, // 30 minutes|Self::SetChildren => 15, // 3 min|' "$SetChildren"
+# Replace - with error handling
+echo "Applying patches..."
-echo "Patch applied successfully."
+if ! perl -0777 -i -pe 's|pub const InitialStartCallDelay: u64 = prod_or_fast!\(7 \* 24 \* 60 \* 60 / 12, 10\);|pub const InitialStartCallDelay: u64 = prod_or_fast!(5, 10);|' "$InitialStartCallDelay"; then
+ echo "Error: Failed to replace InitialStartCallDelay in $InitialStartCallDelay"
+ exit 1
+fi
+
+if ! perl -0777 -i -pe 's|pub fn DefaultPendingCooldown\(\) -> u64 \{\s*prod_or_fast!\(7_200, 15\)\s*\}|pub fn DefaultPendingCooldown() -> u64 {\n prod_or_fast!(15, 15)\n }|g' "$DefaultPendingCooldown"; then
+ echo "Error: Failed to replace DefaultPendingCooldown in $DefaultPendingCooldown"
+ exit 1
+fi
+
+if ! perl -0777 -i -pe 's|Self::SetChildren => 150, // 30 minutes|Self::SetChildren => 15, // 3 min|' "$SetChildren"; then
+ echo "Error: Failed to replace SetChildren rate limit in $SetChildren"
+ exit 1
+fi
+
+echo "✓ All patches applied successfully."
From f81c5dbb664911fa663d3b0d55a45d37ff7b1d41 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Sat, 27 Dec 2025 19:52:46 +0000
Subject: [PATCH 05/11] Refactor localnet_patch.sh to use reusable patch_file
function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Eliminates code duplication by consolidating check and replace logic into a single function, improving maintainability and making it easier to add new patches.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5
---
scripts/localnet_patch.sh | 82 +++++++++++++++++++++------------------
1 file changed, 44 insertions(+), 38 deletions(-)
diff --git a/scripts/localnet_patch.sh b/scripts/localnet_patch.sh
index 127d726f37..f36c912512 100755
--- a/scripts/localnet_patch.sh
+++ b/scripts/localnet_patch.sh
@@ -4,45 +4,51 @@
set -e
-InitialStartCallDelay="runtime/src/lib.rs"
-DefaultPendingCooldown="pallets/subtensor/src/lib.rs"
-SetChildren="pallets/subtensor/src/utils/rate_limiting.rs"
-
-# Checkers
-if ! grep -q 'pub const InitialStartCallDelay: u64' "$InitialStartCallDelay"; then
- echo "Error: Target string 'pub const InitialStartCallDelay: u64' not found in $InitialStartCallDelay"
- echo "This may indicate the codebase has changed. Please verify the target code exists."
- exit 1
-fi
-
-if ! grep -q 'pub fn DefaultPendingCooldown() -> u64 {' "$DefaultPendingCooldown"; then
- echo "Error: Target function 'DefaultPendingCooldown' not found in $DefaultPendingCooldown"
- echo "This may indicate the codebase has changed. Please verify the target code exists."
- exit 1
-fi
-
-if ! grep -q 'Self::SetChildren => 150, // 30 minutes' "$SetChildren"; then
- echo "Error: Target string 'Self::SetChildren => 150' not found in $SetChildren"
- echo "This may indicate the codebase has changed. Please verify the target code exists."
- exit 1
-fi
-
-# Replace - with error handling
-echo "Applying patches..."
-
-if ! perl -0777 -i -pe 's|pub const InitialStartCallDelay: u64 = prod_or_fast!\(7 \* 24 \* 60 \* 60 / 12, 10\);|pub const InitialStartCallDelay: u64 = prod_or_fast!(5, 10);|' "$InitialStartCallDelay"; then
- echo "Error: Failed to replace InitialStartCallDelay in $InitialStartCallDelay"
- exit 1
-fi
+# Function to check for a pattern and apply a replacement
+# Args: file_path, search_pattern, replacement_pattern, description
+patch_file() {
+ local file_path="$1"
+ local search_pattern="$2"
+ local replacement_pattern="$3"
+ local description="$4"
+
+ # Check if the search pattern exists
+ if ! grep -qF "$search_pattern" "$file_path" 2>/dev/null && ! grep -qP "$search_pattern" "$file_path" 2>/dev/null; then
+ echo "Error: Target pattern '$search_pattern' not found in $file_path"
+ echo "Description: $description"
+ echo "This may indicate the codebase has changed. Please verify the target code exists."
+ exit 1
+ fi
+
+ # Apply the replacement
+ if ! perl -0777 -i -pe "$replacement_pattern" "$file_path"; then
+ echo "Error: Failed to apply replacement in $file_path"
+ echo "Description: $description"
+ exit 1
+ fi
+}
-if ! perl -0777 -i -pe 's|pub fn DefaultPendingCooldown\(\) -> u64 \{\s*prod_or_fast!\(7_200, 15\)\s*\}|pub fn DefaultPendingCooldown() -> u64 {\n prod_or_fast!(15, 15)\n }|g' "$DefaultPendingCooldown"; then
- echo "Error: Failed to replace DefaultPendingCooldown in $DefaultPendingCooldown"
- exit 1
-fi
+echo "Applying patches..."
-if ! perl -0777 -i -pe 's|Self::SetChildren => 150, // 30 minutes|Self::SetChildren => 15, // 3 min|' "$SetChildren"; then
- echo "Error: Failed to replace SetChildren rate limit in $SetChildren"
- exit 1
-fi
+# Patch 1: InitialStartCallDelay
+patch_file \
+ "runtime/src/lib.rs" \
+ "pub const InitialStartCallDelay: u64" \
+ 's|pub const InitialStartCallDelay: u64 = prod_or_fast!\(7 \* 24 \* 60 \* 60 / 12, 10\);|pub const InitialStartCallDelay: u64 = prod_or_fast!(5, 10);|' \
+ "Reduce InitialStartCallDelay for local testing"
+
+# Patch 2: DefaultPendingCooldown
+patch_file \
+ "pallets/subtensor/src/lib.rs" \
+ "pub fn DefaultPendingCooldown() -> u64 {" \
+ 's|pub fn DefaultPendingCooldown\(\) -> u64 \{\s*prod_or_fast!\(7_200, 15\)\s*\}|pub fn DefaultPendingCooldown() -> u64 {\n prod_or_fast!(15, 15)\n }|g' \
+ "Reduce DefaultPendingCooldown for local testing"
+
+# Patch 3: SetChildren rate limit
+patch_file \
+ "pallets/subtensor/src/utils/rate_limiting.rs" \
+ "Self::SetChildren => 150, // 30 minutes" \
+ 's|Self::SetChildren => 150, // 30 minutes|Self::SetChildren => 15, // 3 min|' \
+ "Reduce SetChildren rate limit for local testing"
echo "✓ All patches applied successfully."
From 7473584436adf7c10e74974c111c4685863ff460 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Mon, 29 Dec 2025 02:14:48 +0000
Subject: [PATCH 06/11] Update contract tests to use InitialStartCallDelay
constant
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Renames DurationOfStartCall to InitialStartCallDelay in TypeScript contract tests to match the renamed runtime constant.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5
---
contract-tests/src/subtensor.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/contract-tests/src/subtensor.ts b/contract-tests/src/subtensor.ts
index 12d652a9a3..fab9e8cc10 100644
--- a/contract-tests/src/subtensor.ts
+++ b/contract-tests/src/subtensor.ts
@@ -296,7 +296,7 @@ export async function setSubtokenEnable(api: TypedApi, netuid: nu
export async function startCall(api: TypedApi, netuid: number, keypair: KeyPair) {
const registerBlock = Number(await api.query.SubtensorModule.NetworkRegisteredAt.getValue(netuid))
let currentBlock = await api.query.System.Number.getValue()
- const duration = Number(await api.constants.SubtensorModule.DurationOfStartCall)
+ const duration = Number(await api.constants.SubtensorModule.InitialStartCallDelay)
while (currentBlock - registerBlock <= duration) {
await new Promise((resolve) => setTimeout(resolve, 2000));
From c57c7c9060471357c7cdf246dec3d6383df84531 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Wed, 31 Dec 2025 22:59:43 +0000
Subject: [PATCH 07/11] Set StartCallDelay to 10 blocks in localnet genesis
config
Configures the localnet chain spec to initialize StartCallDelay to 10 blocks at genesis, allowing subnets to be started quickly in local testing environments.
---
node/src/chain_spec/localnet.rs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/node/src/chain_spec/localnet.rs b/node/src/chain_spec/localnet.rs
index 02ea8896b5..b20372e4b9 100644
--- a/node/src/chain_spec/localnet.rs
+++ b/node/src/chain_spec/localnet.rs
@@ -124,5 +124,8 @@ fn localnet_genesis(
"evmChainId": {
"chainId": 42,
},
+ "subtensorModule": {
+ "startCallDelay": 10,
+ },
})
}
From 0bd25dfa4a371372f8baa3219c70c197a42b58bb Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Thu, 8 Jan 2026 21:07:36 +0000
Subject: [PATCH 08/11] Remove get_start_call_delay getter function
The getter function is redundant since StartCallDelay storage value can be accessed directly. Only the setter is needed for the sudo extrinsic.
---
pallets/subtensor/src/coinbase/root.rs | 3 ---
1 file changed, 3 deletions(-)
diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs
index a5c7296424..83567b6f57 100644
--- a/pallets/subtensor/src/coinbase/root.rs
+++ b/pallets/subtensor/src/coinbase/root.rs
@@ -542,9 +542,6 @@ impl Pallet {
NetworkImmunityPeriod::::set(net_immunity_period);
Self::deposit_event(Event::NetworkImmunityPeriodSet(net_immunity_period));
}
- pub fn get_start_call_delay() -> u64 {
- StartCallDelay::::get()
- }
pub fn set_start_call_delay(delay: u64) {
StartCallDelay::::set(delay);
Self::deposit_event(Event::StartCallDelaySet(delay));
From bb6495b820d5d266e031a7366965cdfd2f6bb6c6 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Thu, 8 Jan 2026 21:10:46 +0000
Subject: [PATCH 09/11] Replace DefaultStartCallDelay with
T::InitialStartCallDelay
Removes the wrapper type_value function and uses the config constant directly in the storage definition, simplifying the code.
---
pallets/subtensor/src/lib.rs | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs
index b08d8d8b0b..6762a78114 100644
--- a/pallets/subtensor/src/lib.rs
+++ b/pallets/subtensor/src/lib.rs
@@ -640,11 +640,6 @@ pub mod pallet {
T::InitialSubnetOwnerCut::get()
}
- /// Default value for start call delay.
- #[pallet::type_value]
- pub fn DefaultStartCallDelay() -> u64 {
- T::InitialStartCallDelay::get()
- }
/// Default value for recycle or burn.
#[pallet::type_value]
@@ -1522,7 +1517,7 @@ pub mod pallet {
/// ITEM( start_call_delay )
#[pallet::storage]
- pub type StartCallDelay = StorageValue<_, u64, ValueQuery, DefaultStartCallDelay>;
+ pub type StartCallDelay = StorageValue<_, u64, ValueQuery, T::InitialStartCallDelay>;
/// ITEM( min_network_lock_cost )
#[pallet::storage]
From 0cb4b8830e89849c5bd89d9f7e887eca0ed548ca Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Thu, 8 Jan 2026 21:16:40 +0000
Subject: [PATCH 10/11] Add start_call_delay field to GenesisConfig for
benchmarks
Adds start_call_delay as an optional field in the Subtensor pallet's GenesisConfig, allowing the localnet chain spec to set the initial StartCallDelay value at genesis. This fixes benchmarks that depend on the genesis configuration.
---
pallets/subtensor/src/lib.rs | 3 +++
pallets/subtensor/src/macros/genesis.rs | 5 +++++
2 files changed, 8 insertions(+)
diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs
index 6762a78114..5b3816b53a 100644
--- a/pallets/subtensor/src/lib.rs
+++ b/pallets/subtensor/src/lib.rs
@@ -2381,6 +2381,8 @@ pub mod pallet {
pub stakes: Vec<(T::AccountId, Vec<(T::AccountId, (u64, u16))>)>,
/// The total issued balance in genesis
pub balances_issuance: TaoCurrency,
+ /// The delay before a subnet can call start
+ pub start_call_delay: Option,
}
impl Default for GenesisConfig {
@@ -2388,6 +2390,7 @@ pub mod pallet {
Self {
stakes: Default::default(),
balances_issuance: TaoCurrency::ZERO,
+ start_call_delay: None,
}
}
}
diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs
index 7bf8ba2a53..0014b63540 100644
--- a/pallets/subtensor/src/macros/genesis.rs
+++ b/pallets/subtensor/src/macros/genesis.rs
@@ -30,6 +30,11 @@ mod genesis {
// Set initial total issuance from balances
TotalIssuance::::put(self.balances_issuance);
+ // Set start call delay if provided in genesis config
+ if let Some(delay) = self.start_call_delay {
+ StartCallDelay::::put(delay);
+ }
+
// Set the root network as added.
NetworksAdded::::insert(NetUid::ROOT, true);
From 6b3ac04ca56a86bf88041e2698130e31894bde7a Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Thu, 8 Jan 2026 21:24:37 +0000
Subject: [PATCH 11/11] apply review comments
---
pallets/admin-utils/src/tests/mod.rs | 7 +------
pallets/subtensor/src/macros/config.rs | 2 +-
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs
index 970c24498e..365ef63ad5 100644
--- a/pallets/admin-utils/src/tests/mod.rs
+++ b/pallets/admin-utils/src/tests/mod.rs
@@ -2904,18 +2904,13 @@ fn test_sudo_set_start_call_delay_permissions_and_zero_delay() {
);
// Test 1: Non-root account should fail to set delay
- assert_err!(
+ assert_noop!(
AdminUtils::sudo_set_start_call_delay(
<::RuntimeOrigin>::signed(non_root_account),
0
),
DispatchError::BadOrigin
);
- assert_eq!(
- pallet_subtensor::StartCallDelay::::get(),
- initial_delay,
- "Delay should not have changed"
- );
// Test 2: Create a subnet
add_network(netuid, tempo);
diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs
index ce6dfd57c2..2124ec5f3f 100644
--- a/pallets/subtensor/src/macros/config.rs
+++ b/pallets/subtensor/src/macros/config.rs
@@ -233,7 +233,7 @@ mod config {
/// Initial EMA price halving period
#[pallet::constant]
type InitialEmaPriceHalvingPeriod: Get;
- /// Initial block number after a new subnet accept the start call extrinsic.
+ /// Delay after which a new subnet can dispatch start call extrinsic.
#[pallet::constant]
type InitialStartCallDelay: Get;
/// Cost of swapping a hotkey in a subnet.