Conversation
Add EffectiveRootProp StorageMap (NetUid -> U64F64) computed during distribute_dividends_and_incentives() as: sum(RootAlphaDividendsPerSubnet[netuid]) / (sum(AlphaDividendsPerSubnet[netuid]) + sum(RootAlphaDividendsPerSubnet[netuid])) This measures the proportion of dividends on a subnet flowing to root stakers, stored per-subnet for efficient single-read access during emission preparation. Co-Authored-By: Claude Opus 4.5 <[email protected]>
When enabled, multiplies each subnet's emission share by its stored EffectiveRootProp value and re-normalizes. This allows root governance to weight emission toward subnets with higher root staker participation. - Storage: EffectiveRootPropEmissionScaling (bool, default false) - Admin extrinsic: sudo_set_effective_root_prop_emission_scaling (call_index 88) - Helper: normalize_shares(), apply_effective_root_prop_scaling() - Event: EffectiveRootPropEmissionScalingApplied Co-Authored-By: Claude Opus 4.5 <[email protected]>
Only the top proportion of subnets (ranked by emission share) receive emission. Default is 50% (5000 basis points). Remaining subnets have shares zeroed and redistributed to top subnets. Uses ceil(count * proportion / 10000) so a single subnet always qualifies (ceil(1 * 0.5) = 1). - Storage: EmissionTopSubnetProportion (u16, default 5000) - Admin extrinsic: sudo_set_emission_top_subnet_proportion (call_index 89) - Helpers: zero_and_redistribute_bottom_shares(), apply_top_subnet_proportion_filter() - Event: EmissionTopSubnetFilterApplied Co-Authored-By: Claude Opus 4.5 <[email protected]>
Adds absolute cap on the number of subnets that can receive emission. Default is 0 (disabled). When set to N > 0, only the top N subnets by share receive emission; the rest are zeroed and redistributed. Applied after proportion filter so the stricter constraint wins. - Storage: EmissionTopSubnetAbsoluteLimit (u16, default 0) - Admin extrinsic: sudo_set_emission_top_subnet_absolute_limit (call_index 90) - Helper: apply_top_subnet_absolute_limit() - Event: EmissionAbsoluteLimitApplied - Fixed 5 existing coinbase tests to disable proportion filter Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Replace slice indexing `sorted[top_k..]` with `.get(top_k..)` to avoid potential panic (clippy::indexing_slicing) - Replace `(total as u64) * (proportion as u64)` with `.saturating_mul()` to avoid arithmetic side effects (clippy::arithmetic_side_effects) Co-Authored-By: Claude Opus 4.5 <[email protected]>
…tests Remove workaround EmissionTopSubnetProportion::<Test>::set(10000) from existing coinbase tests that was needed when default was 50%. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…efault Remove EffectiveRootPropEmissionScalingApplied, EmissionTopSubnetFilterApplied, and EmissionAbsoluteLimitApplied events and their test assertions. Update proportion filter tests to explicitly set 5000 (50%) since default is now 100%. Co-Authored-By: Claude Opus 4.5 <[email protected]>
When subnets tie at the k-th position, all tied subnets are now kept rather than arbitrarily zeroing some. Uses a threshold-based approach: find the share value at position top_k-1 and keep all entries >= that value, avoiding deterministic bias toward lower NetUIDs. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Use min(EffectiveRootProp, RootProp) when scaling emission shares. This prevents a subnet from inflating its EffectiveRootProp above the configured RootProp by disabling alpha validators, which would cause all dividends to flow to root and artificially boost the scaling factor. Co-Authored-By: Claude Opus 4.5 <[email protected]>
shamil-gadelshin
left a comment
There was a problem hiding this comment.
The tests don't cover the whole run_coinbase. There are some concerns about the calculation order and other minor issues.
pallets/admin-utils/src/lib.rs
Outdated
| ) -> DispatchResult { | ||
| ensure_root(origin)?; | ||
| pallet_subtensor::Pallet::<T>::set_effective_root_prop_emission_scaling(enabled); | ||
| log::debug!("set_effective_root_prop_emission_scaling( {enabled:?} )"); |
There was a problem hiding this comment.
This is noise. Please, remove unnecessary logs.
pallets/admin-utils/src/lib.rs
Outdated
| ) -> DispatchResult { | ||
| ensure_root(origin)?; | ||
| ensure!( | ||
| proportion > 0 && proportion <= 10000, |
There was a problem hiding this comment.
The proportion type is counterintuitive. Consider using U64F64 and limit "0-1" or "0-100".
| let total = total_alpha_divs.saturating_add(total_root_divs); | ||
|
|
||
| let effective_root_prop = if total > zero { | ||
| U64F64::saturating_from_num(total_root_divs.checked_div(total).unwrap_or(zero)) |
There was a problem hiding this comment.
Do you need these conversions between U64F64 and u96F32? Can we use a single format?
| /// zeros shares beyond the top `limit` subnets and re-normalizes. | ||
| pub(crate) fn apply_top_subnet_absolute_limit(shares: &mut BTreeMap<NetUid, U64F64>) { | ||
| let limit = EmissionTopSubnetAbsoluteLimit::<T>::get(); | ||
| if limit == 0 { |
There was a problem hiding this comment.
Please, use rust idiomatic None for this case.
| top_k: usize, | ||
| ) { | ||
| let zero = U64F64::saturating_from_num(0); | ||
| if top_k == 0 || shares.is_empty() { |
There was a problem hiding this comment.
why do we iterate through the empty array?
| return; // 100% means all subnets get emission | ||
| } | ||
|
|
||
| let total = shares.len() as u32; |
There was a problem hiding this comment.
Incorrect type conversion - there is a different conversion below.
| root_alpha_dividends: BTreeMap<T::AccountId, U96F32>, | ||
| ) { | ||
| // Compute and store EffectiveRootProp before distributing (uses raw dividend values). | ||
| Self::compute_and_store_effective_root_prop( |
There was a problem hiding this comment.
It seems we calculate the effective root prop for the next round. Is it intentional?
There was a problem hiding this comment.
Also, we calculate this value once per epoch. Could this fact be exploited?
There was a problem hiding this comment.
We could probably place a proper stake before we calculate the value and then remove it.
| // assert_abs_diff_eq!(s1 + s2 + s3, 1.0, epsilon = 1e-9); | ||
| // }); | ||
| // } | ||
|
|
There was a problem hiding this comment.
There are no tests to verify that the new taoflow actually works as a whole.
- Remove log::debug! statements from admin extrinsics - Change EffectiveRootProp storage type from U64F64 to U96F32 to match context types and avoid unnecessary conversions in run_coinbase - Change EmissionTopSubnetProportion from u16 basis points (0-10000) to U64F64 fractional range (0.0-1.0) - Change EmissionTopSubnetAbsoluteLimit from u16 ValueQuery (0=disabled) to u16 OptionQuery (None=disabled) for idiomatic representation - Fix empty array iteration guard in zero_and_redistribute_bottom_shares - Add detailed comment explaining ERP timing (computed for next round) and exploitation mitigation via min(EffectiveRootProp, RootProp) - Update all tests for new storage types Co-Authored-By: Claude Opus 4.5 <[email protected]>
Three tests covering 2 subnets with 6 neurons each (owner, major/minor
root validators, major/minor subnet validators, miner):
1. test_basic_all_validators_set_weights_to_miners (price=0.6)
- root_sell_flag=true, root validators earn dividends
- Verifies miner incentive, validator dividend proportionality,
root validator earnings, owner cut (18%), incentive vector
2. test_no_root_sell_all_validators_set_weights_to_miners (price=0.5)
- root_sell_flag=false, root validators earn 0
- Same structure but verifies root channel is unfunded
3. test_basic_major_root_no_weights (price=0.6)
- Major root validator (5.55M TAO) does NOT set weights
- Verifies major root earns 0 while minor root still earns
Test structure: 5 blocks total (setup at block 1, epochs at blocks 3+5),
SN1 only with tempo=1, TaoWeight=0.18, SubnetOwnerCut=18%.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* wide-scope-dividend-test: (44 commits) Add wide_scope_dividend test suite for emission distribution Fix sudo_set_max_allowed_uids benchmark raise fee on `set_pending_childkey_cooldown` less sleep catch error with wrong nonce bump version add transaction replacement test case Fix try-runtime build bump version bump psdk/frontier with patch to prevent node panic Spec bump Spec bump auto-update benchmark weights bump frontier hash 2 by removing comment bump spec version bump Cargo toml frontier deps Bump spec version Add SubnetBuyback event format code test is ok ...
EffectiveRootProp now multiplies the raw dividend ratio by a utilization factor: active_root_stake / total_root_stake. This ensures subnets where most root stake is idle (validators not setting weights) get a much lower EffectiveRootProp than subnets where all root stake is active. Also enables EffectiveRootPropEmissionScaling in wide_scope_dividend tests and updates all assertions accordingly. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Change compute_and_store_effective_root_prop to use dividend-efficiency metric instead of binary active/inactive. Returns U96F32 utilization. - Move ERP computation from distribute_dividends_and_incentives to distribute_emission; add utilization scaling (< 1.0) and hard cap (< 0.5 recycles all root dividends, sets ERP to 0). - Add 555k unstaked TAO to test setup_test(). - Add 3 new tests: unstaked_tao_does_not_affect_utilization, half_weights_to_validator, half_weights_no_minor_root. - Update existing test assertions for hard cap behavior. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Skip hard cap and scaling when root_alpha_dividends is empty (e.g. root_sell_flag=false). Fixes tests where validators with root stake but no root dividend emission were incorrectly having their alpha dividends recycled. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Description
This PR introduces a system to control which subnets receive TAO emission. Previously, all eligible subnets received emission proportional to their "share." Now, three optional filters can reduce or zero-out emission for lower-performing subnets.
New metric: EffectiveRootProp
Each subnet distributes dividends to two groups: alpha stakers (staked directly on subnet) and root stakers (staked on root network). EffectiveRootProp measures what fraction flows to root stakers:
This is computed during each epoch and stored per-subnet.
Filters
Three emission filters are applied sequentially in
get_subnet_block_emissions():After each filter, shares are re-normalized to sum to 1.0. Subnets that get filtered out receive zero emission.
But why?
We had a couple of problems since Taoflow emission schedule was deployed:
btcli stake movecoupled with randomly chosen "miner" (they are self-mining, the subnet doesn't really do anything). We think they can safely inflow to increase emissions and we want to mitigate this risk.It looks like this can be fixed with EffectiveRootProp. The filters were added because they were requested and the change was on the way.
Type of Change
Checklist
./scripts/fix_rust.shto ensure my code is formatted and linted correctlyOriginal prompt
Use Plan subagent to write a step-by-step detailed development plan, which then after a final confirmation from me you'll go implement in the main agent by following the task list until it is completed. Use a fresh sonnet-based subagent for every subfeature, but do not run agents in parallel or you'll run out of memory and disk space. Work on
taoflow2 branch which starts off origin/devnet-ready. Run scripts/fix_rust.sh after every commit. Remember about checking in tests that events indicating key information are emitted. Rather than long methods use functions that have descriptive names and that are easy to test.The changes we do today will focus on
get_subnet_block_emissions() in src/coinbase/subnet_emissions.rs.Add
EffectiveRootPropEmissionScaling root hyperparameter which will allow root to enable multiplication of shares by EffectiveRootProp, which for a given netuid is sum(RootAlphaDividendsPerSubnet[netuid]) / (sum(AlphaDividendsPerSubnet[netuid]) + sum(RootAlphaDividendsPerSubnet[netuid]) (document it as such), though it should be calculated during epoch near where RootAlphaDividendsPerSubnet is being written and stored in the global vector, so that during emission preparation reading EffectiveRootProp requires a single db get for all subnets.Add root hyperparameter to ensure only a certain proportion (by default 50%) of subnets get any emission, while the rest is redistributed to other subnets (so shares must be zeroed except topk and normalized). Round up so that a single subnet counts as in top 50%.
Add another hyperparameter to limit the number of subnets which receive emission to an absolute number (there is no default and this means by default there will be no absolute limit).
AI reviewers