From 74fb4e0f89d9859048d0145bebdf6d4f13dd372f Mon Sep 17 00:00:00 2001 From: unconst Date: Fri, 19 Dec 2025 22:27:23 -0600 Subject: [PATCH] adds a parameterized delay on realized flow --- pallets/subtensor/src/coinbase/block_step.rs | 9 +- .../subtensor/src/coinbase/run_coinbase.rs | 1 + .../src/coinbase/subnet_emissions.rs | 90 +++++++++++++------ pallets/subtensor/src/lib.rs | 40 +++++++++ 4 files changed, 106 insertions(+), 34 deletions(-) diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 6081edad19..1b9c8ac2fb 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -33,7 +33,6 @@ impl Pallet { Self::run_auto_claim_root_divs(last_block_hash); // --- 9. Populate root coldkey maps. Self::populate_root_coldkey_staking_maps(); - // Return ok. Ok(()) } @@ -229,9 +228,9 @@ impl Pallet { if next_value >= U110F18::saturating_from_num(Self::get_max_difficulty(netuid)) { Self::get_max_difficulty(netuid) } else if next_value <= U110F18::saturating_from_num(Self::get_min_difficulty(netuid)) { - Self::get_min_difficulty(netuid) + return Self::get_min_difficulty(netuid); } else { - next_value.saturating_to_num::() + return next_value.saturating_to_num::(); } } @@ -263,9 +262,9 @@ impl Pallet { if next_value >= U110F18::saturating_from_num(Self::get_max_burn(netuid)) { Self::get_max_burn(netuid) } else if next_value <= U110F18::saturating_from_num(Self::get_min_burn(netuid)) { - Self::get_min_burn(netuid) + return Self::get_min_burn(netuid); } else { - next_value.saturating_to_num::().into() + return next_value.saturating_to_num::().into(); } } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 2091946598..997cb57dee 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -22,6 +22,7 @@ impl Pallet { pub fn run_coinbase(block_emission: U96F32) { // --- 0. Get current block. let current_block: u64 = Self::get_current_block_as_u64(); + Self::update_flows(current_block); log::debug!( "Running coinbase for block {current_block:?} with block emission: {block_emission:?}" ); diff --git a/pallets/subtensor/src/coinbase/subnet_emissions.rs b/pallets/subtensor/src/coinbase/subnet_emissions.rs index 477a678864..87d49e2ca5 100644 --- a/pallets/subtensor/src/coinbase/subnet_emissions.rs +++ b/pallets/subtensor/src/coinbase/subnet_emissions.rs @@ -39,6 +39,62 @@ impl Pallet { .collect::>() } + pub fn update_flows(block_u64: u64) { + let subnets: Vec = Self::get_all_subnet_netuids() + .into_iter() + .filter(|netuid| *netuid != NetUid::ROOT) + .collect(); + for netuid_i in subnets.iter() { + Self::update_delayed_flows(*netuid_i, block_u64); + } + } + + pub fn update_delayed_flows(netuid: NetUid, block_u64: u64) { + let tick_len: u64 = FlowTickLen::::get(); + if tick_len == 0 { return; } + + let delay_len: u64 = FlowDelay::::get(); + + // Drain flow for this block (per-block semantics) + let block_flow: i64 = SubnetTaoFlow::::take(netuid); + + // Schedule maturity (rounded up using current tick_len) + if block_flow != 0 { + let delayed_until = block_u64.saturating_add(delay_len); + let maturity_block = + ((delayed_until.saturating_add(tick_len - 1)) / tick_len).saturating_mul(tick_len); + + SubnetFlowAccumulator::::mutate(netuid, maturity_block, |v| { + *v = v.saturating_add(block_flow); + }); + } + + // Pop matured flow for this exact block number + let delayed_flow: i64 = SubnetFlowAccumulator::::take(netuid, block_u64); + + // Per-step alpha in [0,1] + let alpha: I64F64 = I64F64::saturating_from_num(FlowEmaSmoothingFactor::::get()) + .safe_div(I64F64::saturating_from_num(i64::MAX)); + let one: I64F64 = I64F64::saturating_from_num(1); + + // Load previous EMA (or initialize) + let ema_prev: I64F64 = match SubnetEmaTaoFlow::::get(netuid) { + Some((_b, prev)) => prev, + None => { + let init = I64F64::saturating_from_num(delayed_flow); + SubnetEmaTaoFlow::::insert(netuid, (block_u64, init)); + return; + } + }; + + // Standard EMA step + let ema_next: I64F64 = + (one.saturating_sub(alpha)).saturating_mul(ema_prev) + .saturating_add(alpha.saturating_mul(I64F64::saturating_from_num(delayed_flow))); + + SubnetEmaTaoFlow::::insert(netuid, (block_u64, ema_next)); + } + pub fn record_tao_inflow(netuid: NetUid, tao: TaoCurrency) { SubnetTaoFlow::::mutate(netuid, |flow| { *flow = flow.saturating_add(u64::from(tao) as i64); @@ -47,43 +103,19 @@ impl Pallet { pub fn record_tao_outflow(netuid: NetUid, tao: TaoCurrency) { SubnetTaoFlow::::mutate(netuid, |flow| { - *flow = flow.saturating_sub(u64::from(tao) as i64) + *flow = flow.saturating_sub(u64::from(tao) as i64); }); } - pub fn reset_tao_outflow(netuid: NetUid) { - SubnetTaoFlow::::remove(netuid); - } - // Update SubnetEmaTaoFlow if needed and return its value for // the current block #[allow(dead_code)] - fn get_ema_flow(netuid: NetUid) -> I64F64 { - let current_block: u64 = Self::get_current_block_as_u64(); - - // Calculate net ema flow for the next block - let block_flow = I64F64::saturating_from_num(SubnetTaoFlow::::get(netuid)); - let (last_block, last_block_ema) = - SubnetEmaTaoFlow::::get(netuid).unwrap_or((0, I64F64::saturating_from_num(0))); - - // EMA flow already initialized - if last_block != current_block { - let flow_alpha = I64F64::saturating_from_num(FlowEmaSmoothingFactor::::get()) - .safe_div(I64F64::saturating_from_num(i64::MAX)); - let one = I64F64::saturating_from_num(1); - let ema_flow = (one.saturating_sub(flow_alpha)) - .saturating_mul(last_block_ema) - .saturating_add(flow_alpha.saturating_mul(block_flow)); - SubnetEmaTaoFlow::::insert(netuid, (current_block, ema_flow)); - - // Drop the accumulated flow in the last block - Self::reset_tao_outflow(netuid); - ema_flow - } else { - last_block_ema - } + pub fn get_ema_flow(netuid: NetUid) -> I64F64 { + let (_, last_block_ema) = SubnetEmaTaoFlow::::get(netuid).unwrap_or((0, I64F64::saturating_from_num(0))); + last_block_ema } + // Either the minimal EMA flow L = min{Si}, or an artificial // cut off at some higher value A (TaoFlowCutoff) // L = max {A, min{min{S[i], 0}}} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ef2d44e68b..d3593c69c1 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1457,6 +1457,46 @@ pub mod pallet { pub type SubnetEmaTaoFlow = StorageMap<_, Identity, NetUid, (u64, I64F64), OptionQuery>; + /// Flow tick len + #[pallet::type_value] + pub fn DefaultFlowTickLen() -> u64 { + 1 + } + /// --- ITEM ( flow tick len ) + #[pallet::storage] + pub type FlowTickLen = StorageValue<_, u64, ValueQuery, DefaultFlowTickLen>; + + /// Flow delay + #[pallet::type_value] + pub fn DefaultFlowDelay() -> u64 { + 0 + } + /// --- ITEM ( flow tick len ) + #[pallet::storage] + pub type FlowDelay = StorageValue<_, u64, ValueQuery, DefaultFlowDelay>; + + // Per-day Tao flow for subnets + #[pallet::storage] + pub type SubnetFlowAccumulator = StorageDoubleMap< + _, + Identity, + NetUid, // subnet id + Identity, + u64, // day + i64, // taoflow + ValueQuery, + DefaultZeroI64 + >; + + /// Flow delay + #[pallet::type_value] + pub fn DefaultEmissionCap() -> u16 { + u16::MAX + } + /// --- ITEM ( max emission value ) + #[pallet::storage] + pub type EmissionCap = StorageValue<_, u16, ValueQuery, DefaultEmissionCap>; + /// Default value for flow cutoff. #[pallet::type_value] pub fn DefaultFlowCutoff() -> I64F64 {