Skip to content

Taoflow2#2385

Open
ppolewicz wants to merge 16 commits intodevnet-readyfrom
taoflow2
Open

Taoflow2#2385
ppolewicz wants to merge 16 commits intodevnet-readyfrom
taoflow2

Conversation

@ppolewicz
Copy link
Collaborator

@ppolewicz ppolewicz commented Jan 31, 2026

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:

EffectiveRootProp[netuid] = root_dividends / (alpha_dividends + root_dividends)

This is computed during each epoch and stored per-subnet.

Filters

Three emission filters are applied sequentially in get_subnet_block_emissions():

  1. EffectiveRootProp scaling (disabled by default): Multiplies each subnet's share by its EffectiveRootProp, rewarding subnets with more root staker participation.
  2. Top subnet proportion: Keeps only the top X% of subnets by share (default 100% = all subnets, intended mainnet value: 50%).
  3. Absolute subnet limit: Hard cap on number of subnets receiving emission (default 0 = disabled, maybe we won't use it at all, but proportion is tricky and it's better to have both tool at our disposal).

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:

  • New subnets get very little injection of Tao when they desperately need it
  • Old subnets which have a lot of liquidity get even more
  • Since dtao subnets are generally incentivized to fight root prop (see sn89 using tricks to reduce root prop artificially) to reduce sell pressure
  • sn104 managed to cut off root validators from receiving any dividends at all by using the time gap between CHK application and immediate effectiveness of btcli stake move coupled 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.
  • sometimes subnet owners say "oh, the validator code doesn't work for you? Then CHK stake to me, I don't want to debug your environment problems" and it is unacceptable, so it'd be good to incentivize them to care that the validator code actually works

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

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Original prompt

(events were later deemed to be useless and were removed in a subsequent prompt; default was changed to 100%)

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

  • Claude 4.5 Opus
  • Codex
  • Devin

ppolewicz and others added 9 commits January 31, 2026 08:54
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]>
@ppolewicz ppolewicz marked this pull request as ready for review February 2, 2026 23:03
@ppolewicz ppolewicz added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label Feb 2, 2026
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]>
Copy link
Collaborator

@shamil-gadelshin shamil-gadelshin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests don't cover the whole run_coinbase. There are some concerns about the calculation order and other minor issues.

) -> DispatchResult {
ensure_root(origin)?;
pallet_subtensor::Pallet::<T>::set_effective_root_prop_emission_scaling(enabled);
log::debug!("set_effective_root_prop_emission_scaling( {enabled:?} )");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is noise. Please, remove unnecessary logs.

) -> DispatchResult {
ensure_root(origin)?;
ensure!(
proportion > 0 && proportion <= 10000,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we iterate through the empty array?

return; // 100% means all subnets get emission
}

let total = shares.len() as u32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we calculate the effective root prop for the next round. Is it intentional?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we calculate this value once per epoch. Could this fact be exploited?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
// });
// }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]>
ppolewicz and others added 5 commits February 6, 2026 21:39
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-cargo-audit This PR fails cargo audit but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants