Skip to content

Commit 2dd606c

Browse files
Document core files and functions in subtensor codebase related to emissions (#74)
* wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Apply suggestions from code review Co-authored-by: Maciej Kula <[email protected]> * edits from review --------- Co-authored-by: Maciej Kula <[email protected]>
1 parent a1decf1 commit 2dd606c

File tree

10 files changed

+1643
-8
lines changed

10 files changed

+1643
-8
lines changed

docs/learn/ema.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Note that this alpha parameter is distinct from and unrelated to the usage of 'a
3535
This use of EMA smoothing protects the network's economic model from price manipulation by making emissions extremely slow to respond to price changes.
3636

3737
**How It Works**:
38-
The price EMA uses a sophisticated dynamic alpha calculation to ensure that new subnets have even slower price adaptation than mature ones.
38+
The price EMA uses a dynamic alpha calculation to ensure that new subnets have even slower price adaptation than mature ones.
3939

4040
$$
4141
\alpha = \frac{ \mathrm{base\_alpha} \times \mathrm{blocks\_since\_start}}{\mathrm{blocks\_since\_start} + \mathrm{halving\_blocks}}

docs/learn/emissions.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
77

88
# Emission
99

10-
Emission is the process by which the Bittensor network allocates TAO and alpha to participants, including miners, validators, stakers, and subnet creators.
10+
Emission is the economic heartbeat of Bittensor—the process that continuously distributes newly created [TAO](../resources/glossary.md#tao-τ) and subnet-specific alpha tokens to network participants who contribute value through [mining](../resources/glossary.md#subnet-miner), [validation](../resources/glossary.md#validator), [staking](../resources/glossary.md#staking), and [subnet creation](../resources/glossary.md#subnet-creator).
1111

12-
It unfolds in two stages:
12+
## Understanding the Two-Stage Process
1313

14-
- Injection into subnets
15-
- Extraction by participants
14+
Bittensor's emission system operates through two coordinated stages that work together to ensure fair, market-driven distribution:
1615

17-
See the [Dynamic TAO White Paper](https://drive.google.com/file/d/1vkuxOFPJyUyoY6dQzfIWwZm2_XL3AEOx/view) for a full explanation.
16+
### Stage 1: Injection into Subnets
17+
Every [block](../resources/glossary.md#block), new liquidity flows into [subnet](../resources/glossary.md#subnet) pools based on market performance. [Subnets](../resources/glossary.md#subnet) with higher-value alpha tokens attract more TAO, creating a competitive marketplace for innovation.
18+
19+
### Stage 2: Extraction by Participants
20+
Every [tempo](../resources/glossary.md#tempo) (360 blocks, ~72 minutes), accumulated rewards are distributed to participants through [Yuma Consensus](../resources/glossary.md#yuma-consensus), which evaluates performance and determines who deserves what share.
21+
22+
This two-stage approach creates stability while maintaining responsiveness—rewards accumulate gradually but are distributed based on demonstrated value creation.
23+
24+
## Technical References
25+
- **Implementation Details**: [Coinbase Implementation](../navigating-subtensor/emissions-coinbase.md)
26+
- **Consensus Mechanics**: [Yuma Consensus](./yuma-consensus.md)
27+
- **Mathematical Framework**: [Dynamic TAO White Paper](https://drive.google.com/file/d/1vkuxOFPJyUyoY6dQzfIWwZm2_XL3AEOx/view)
1828

1929
### Injection
2030

docs/learn/yuma-consensus.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ See:
1717
- [How Yuma Consensus 3 Makes Bittensor More Fair](./yc3-blog)
1818
- [Yuma Consensus 3 Migration Guide](./yuma3-migration-guide.md)
1919
- [Emissions](./emissions)
20+
- [Epoch Implementation](../navigating-subtensor/epoch.md) - Implementation details
2021
- [Subtensor Docs: Yuma Consensus](https://github.com/opentensor/subtensor/blob/main/docs/consensus.md)
2122

2223
## Clipping
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
---
2+
title: "Coinbase Implementation"
3+
---
4+
5+
# Coinbase Implementation
6+
7+
This document provides a technical deep dive into the `run_coinbase()` function that orchestrates [TAO](../resources/glossary.md#tao-τ) and alpha [emission](../resources/glossary.md#emission) distribution across [subnets](../resources/glossary.md#subnet). The coinbase mechanism serves as Bittensor's economic heartbeat, connecting [subnet validators](../resources/glossary.md#validator), [subnet miners](../resources/glossary.md#subnet-miner), and [stakers](../resources/glossary.md#staking) through emission distribution.
8+
9+
For conceptual understanding of emission mechanisms, see [Emissions](../learn/emissions.md).
10+
11+
The coinbase mechanism orchestrates Bittensor's tokenomic engine, running every 12-second [block](../resources/glossary.md#block) to ensure continuous flow of liquidity into the network.
12+
13+
Every block, the coinbase mechanism performs three critical functions:
14+
15+
1. **Liquidity Injection**: Adds TAO and subnet-specific alpha tokens to each subnet's liquidity pools.
16+
2. **Accumulation**: Builds up pending [emissions](../resources/glossary.md#emission) (also known as "alpha outstanding") bound for distribution to [subnet miners](../resources/glossary.md#subnet-miner) and [validators](../resources/glossary.md#validator) during the next [epoch](../resources/glossary.md#tempo).
17+
3. **Consensus Triggering**: Initiates each subnet's [Yuma Consensus](../resources/glossary.md#yuma-consensus) epochs, the process that distributes emissions to participants within each subnet. Epochs are staggered to avoid overloading the blockchain with the computation involved.
18+
19+
For broader conceptual understanding of emission mechanisms, see [Emissions](../learn/emissions.md).
20+
21+
## Core Function: `run_coinbase()`
22+
23+
**Location**: [`run_coinbase.rs`](https://github.com/opentensor/subtensor/blob/main/pallets/subtensor/src/coinbase/run_coinbase.rs)
24+
25+
```rust
26+
pub fn run_coinbase(block_emission: U96F32)
27+
```
28+
29+
**Parameters**:
30+
- `block_emission`: Total TAO to distribute across all subnets this block. Currently 1 $\tau$, this amount will follow a halving schedule.
31+
32+
The function implements a multistep process that handles liquidity injection, reward accumulation, epoch triggering, and EMA updates.
33+
34+
## Implementation Flow
35+
36+
### 1. Subnet Discovery and Filtering
37+
38+
The process begins with identifying subnets eligible for emissions, applying filters to ensure only active, established subnets participate in the reward distribution.
39+
40+
```rust
41+
// Get all netuids (filter out root)
42+
let subnets: Vec<NetUid> = Self::get_all_subnet_netuids()
43+
.into_iter()
44+
.filter(|netuid| *netuid != NetUid::ROOT)
45+
.collect();
46+
47+
// Filter out subnets with no first emission block number
48+
let subnets_to_emit_to: Vec<NetUid> = subnets
49+
.clone()
50+
.into_iter()
51+
.filter(|netuid| FirstEmissionBlockNumber::<T>::get(*netuid).is_some())
52+
.collect();
53+
```
54+
55+
**Subnet Eligibility Rules:**
56+
- **[Root Subnet](../resources/glossary.md#root-subnetsubnet-zero) Exclusion**: [Subnet Zero](../resources/glossary.md#root-subnetsubnet-zero) operates differently—it has no [subnet miners](../resources/glossary.md#subnet-miner) and serves as a TAO staking pool for [delegates](../resources/glossary.md#delegate), so it's excluded from direct alpha emissions
57+
- **Emission Readiness**: Only subnets that have been started (and hence been assigned a `FirstEmissionBlockNumber`) receive emissions.
58+
### 2. Emission Allocation to Subnets
59+
60+
Each subnet's share of the block's TAO emission depends on its alpha token's price, smoothed with an [exponential moving average (EMA)](../learn/ema) function to prevent price manipulation while maintaining market responsiveness.
61+
62+
```rust
63+
let mut total_moving_prices = U96F32::saturating_from_num(0.0);
64+
for netuid_i in subnets_to_emit_to.iter() {
65+
total_moving_prices = total_moving_prices
66+
.saturating_add(Self::get_moving_alpha_price(*netuid_i));
67+
}
68+
```
69+
70+
**EMA Price Smoothing Implementation:**
71+
The moving price for each subnet is calculated using a custom [EMA](../learn/ema) that adapts its responsiveness based on subnet maturity. This creates a **double-smoothing effect**: new subnets have extremely slow price adaptation (preventing launch manipulation), while mature subnets respond more quickly to legitimate market signals.
72+
73+
**Price-Driven Distribution:**
74+
Each subnet receives TAO emissions proportional to its EMA-smoothed alpha token price:
75+
76+
$$
77+
\text{tao\_allocation}_i = \text{block\_emission} \times \frac{\text{moving\_price}_i}{\sum_{j} \text{moving\_price}_j}
78+
$$
79+
80+
**EMA Update Timing:** The EMA is updated **after** being used for emission calculations in each `run_coinbase()` call ([Line 246](https://github.com/opentensor/subtensor/blob/main/pallets/subtensor/src/coinbase/run_coinbase.rs#L246)), ensuring that current block emissions are based on the previous block's smoothed prices while continuously updating the moving average for future calculations.
81+
82+
### 3. Token Pool Injections and Emissions
83+
84+
For each subnet, the coinbase calculates critical values that govern the subnet's token economics and determine how fresh liquidity flows into the system.
85+
86+
#### TAO In (`tao_in`): Fresh Liquidity Injection
87+
- Represents new TAO flowing into the subnet's liquidity pool
88+
- Calculated from the subnet's proportional share of block emissions
89+
- May be reduced through the subsidy mechanism to maintain price stability
90+
91+
#### Alpha In (`alpha_in`): Liquidity Pool Balance
92+
- Alpha tokens injected to maintain healthy AMM pool ratios
93+
- Ensures the TAO injection doesn't create excessive [slippage](../resources/glossary.md#slippage) for [stakers](../resources/glossary.md#staking)
94+
- Calculated as: `tao_in / current_price` during normal operations
95+
96+
#### Alpha Out (`alpha_out`): Participant Emissions
97+
- Alpha tokens emitted for distribution to [miners](../resources/glossary.md#subnet-miner) and [subnet validators](../resources/glossary.md#validator)
98+
- Represents the subnet's emission budget for [incentives](../resources/glossary.md#incentives) and validator dividends
99+
- Forms the reward pool that will be processed during [epochs](../resources/glossary.md#tempo)
100+
101+
#### Subsidy Mechanism
102+
103+
When a subnet's alpha price falls below its expected emission proportion, the mechanism automatically intervenes to maintain market stability:
104+
1. **Price Neutral Injection**: Downscales both TAO and ALPHA injected to provide a price neutral injection
105+
2. **Market Making**: Uses the excess TAO for buying pressure on alpha tokens
106+
107+
This encourages alpha prices to move towards their emission ratio, or to encourage the sum of prices to be at/above 1.
108+
109+
```rust
110+
for netuid_i in subnets_to_emit_to.iter() {
111+
let price_i = T::SwapInterface::current_alpha_price((*netuid_i).into());
112+
let moving_price_i: U96F32 = Self::get_moving_alpha_price(*netuid_i);
113+
114+
let default_tao_in_i: U96F32 = block_emission
115+
.saturating_mul(moving_price_i)
116+
.checked_div(total_moving_prices)
117+
.unwrap_or(asfloat!(0.0));
118+
119+
let alpha_emission_i: U96F32 = asfloat!(
120+
Self::get_block_emission_for_issuance(
121+
Self::get_alpha_issuance(*netuid_i).into()
122+
).unwrap_or(0)
123+
);
124+
125+
126+
let tao_in_ratio: U96F32 = default_tao_in_i.safe_div_or(
127+
U96F32::saturating_from_num(block_emission),
128+
U96F32::saturating_from_num(0.0),
129+
);
130+
131+
if price_i < tao_in_ratio {
132+
tao_in_i = price_i.saturating_mul(block_emission);
133+
alpha_in_i = block_emission;
134+
let difference_tao: U96F32 = default_tao_in_i.saturating_sub(tao_in_i);
135+
136+
let buy_swap_result = Self::swap_tao_for_alpha(
137+
*netuid_i,
138+
tou64!(difference_tao).into(),
139+
T::SwapInterface::max_price().into(),
140+
true, // skip fees
141+
);
142+
} else {
143+
// Normal operation
144+
tao_in_i = default_tao_in_i;
145+
alpha_in_i = tao_in_i.safe_div_or(price_i, alpha_emission_i);
146+
}
147+
}
148+
```
149+
150+
### 4. Liquidity Pool Updates
151+
152+
The coinbase updates each subnet's liquidity pools.
153+
154+
155+
**Critical State Updates:**
156+
- **`SubnetAlphaIn`**: Alpha reserves backing the AMM, enabling liquid [staking](../resources/glossary.md#staking) and unstaking operations.
157+
- **`SubnetAlphaOut`**: Accumulated emissions and/or ALPHA outside the pool (ALPHA emissions + ALPHA taken out from pool).
158+
- **`SubnetTAO`**: TAO reserves backing the AMM, providing price stability and liquidity for unstaking.
159+
- **`TotalIssuance`**: Global TAO supply (see [Issuance](../resources/glossary.md#issuance)).
160+
161+
```rust
162+
for netuid_i in subnets_to_emit_to.iter() {
163+
// Inject Alpha in (AMM liquidity)
164+
let alpha_in_i = AlphaCurrency::from(
165+
tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))
166+
);
167+
SubnetAlphaIn::<T>::mutate(*netuid_i, |total| {
168+
*total = total.saturating_add(alpha_in_i);
169+
});
170+
171+
// Inject Alpha outstanding
172+
let alpha_out_i = AlphaCurrency::from(
173+
tou64!(*alpha_out.get(netuid_i).unwrap_or(&asfloat!(0)))
174+
);
175+
SubnetAlphaOut::<T>::mutate(*netuid_i, |total| {
176+
*total = total.saturating_add(alpha_out_i);
177+
});
178+
179+
// Inject TAO in (AMM liquidity)
180+
let tao_in_i: TaoCurrency =
181+
tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into();
182+
SubnetTAO::<T>::mutate(*netuid_i, |total| {
183+
*total = total.saturating_add(tao_in_i.into());
184+
});
185+
186+
// Update global TAO supply tracking
187+
TotalIssuance::<T>::mutate(|total| {
188+
*total = total.saturating_add(tao_in_i.into());
189+
});
190+
191+
// Notify AMM of new liquidity
192+
T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i);
193+
}
194+
```
195+
196+
### 5. Subnet Owner Emissions
197+
198+
Before distributing rewards to [miners](../resources/glossary.md#subnet-miner) and [subnet validators](../resources/glossary.md#validator), the system allocates a percentage to [subnet owners](../resources/glossary.md#subnet-creator).
199+
200+
[Subnet owners](../resources/glossary.md#subnet-creator) receive 18% of alpha emissions. The subnet owner cut is calculated before other distributions so that the owner cut can be passed to `drain_pending_emission` (there was a bug before where the owner cut was incorrectly calculated after). Subnet owner emissions accumulate in `PendingOwnerCut` until the next [epoch](../resources/glossary.md#tempo).
201+
202+
203+
```rust
204+
let cut_percent: U96F32 = Self::get_float_subnet_owner_cut(); // Default: ~18%
205+
let mut owner_cuts: BTreeMap<NetUid, U96F32> = BTreeMap::new();
206+
207+
for netuid_i in subnets_to_emit_to.iter() {
208+
let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0));
209+
let owner_cut_i: U96F32 = alpha_out_i.saturating_mul(cut_percent);
210+
211+
owner_cuts.insert(*netuid_i, owner_cut_i);
212+
alpha_out.insert(*netuid_i, alpha_out_i.saturating_sub(owner_cut_i));
213+
214+
PendingOwnerCut::<T>::mutate(*netuid_i, |total| {
215+
*total = total.saturating_add(tou64!(owner_cut_i).into());
216+
});
217+
}
218+
```
219+
220+
221+
### 6. Calculating Root Proportion
222+
223+
The root proportion on each subnet determines how much of the dividends (41% of ALPHA emissions) are being sold for each block and being distributed to stakers on root.
224+
225+
$$
226+
\text{root\_proportion} = \frac{\text{root\_tao} \times \text{tao\_weight}}{\text{root\_tao} \times \text{tao\_weight} + \text{alpha\_issuance}}
227+
$$
228+
229+
Where:
230+
- `root_tao`: Total TAO [staked](../resources/glossary.md#staking) in [Root Subnet](../resources/glossary.md#root-subnetsubnet-zero)
231+
- `tao_weight`: Global parameter ([TAO Weight](../resources/glossary.md#tao-weight)) determining TAO vs alpha influence
232+
- `alpha_issuance`: Total alpha tokens for this specific subnet
233+
234+
235+
```rust
236+
for netuid_i in subnets_to_emit_to.iter() {
237+
let alpha_out_i: U96F32 = *alpha_out.get(netuid_i).unwrap_or(&asfloat!(0.0));
238+
let root_tao: U96F32 = asfloat!(SubnetTAO::<T>::get(NetUid::ROOT));
239+
let alpha_issuance: U96F32 = asfloat!(Self::get_alpha_issuance(*netuid_i));
240+
let tao_weight: U96F32 = root_tao.saturating_mul(Self::get_tao_weight());
241+
242+
// Calculate root subnet's proportional share
243+
let root_proportion: U96F32 = tao_weight
244+
.checked_div(tao_weight.saturating_add(alpha_issuance))
245+
.unwrap_or(asfloat!(0.0));
246+
247+
// 50% of proportional alpha goes to root validators
248+
let root_alpha: U96F32 = root_proportion
249+
.saturating_mul(alpha_out_i)
250+
.saturating_mul(asfloat!(0.5));
251+
252+
let pending_alpha: U96F32 = alpha_out_i.saturating_sub(root_alpha);
253+
254+
// Convert root alpha to TAO through AMM (if not subsidized)
255+
// If the subnet is subsidized by the Subsidy Mechanism then no ALPHA will be sold - so the dividends for root are stopped.
256+
if !subsidized {
257+
let swap_result = Self::swap_alpha_for_tao(
258+
*netuid_i,
259+
tou64!(root_alpha).into(),
260+
T::SwapInterface::min_price().into(),
261+
true, // skip fees
262+
);
263+
264+
if let Ok(ok_result) = swap_result {
265+
PendingRootDivs::<T>::mutate(*netuid_i, |total| {
266+
*total = total.saturating_add(ok_result.amount_paid_out.into());
267+
});
268+
}
269+
}
270+
271+
PendingEmission::<T>::mutate(*netuid_i, |total| {
272+
*total = total.saturating_add(tou64!(pending_alpha).into());
273+
});
274+
}
275+
```
276+
277+
278+
### 7. Epoch Execution
279+
280+
When each subnet's [tempo](../resources/glossary.md#tempo) interval completes, the coinbase triggers execution of its Yuma Consensus *epoch*. Epochs execute when `(block_number + netuid + 1) % (tempo + 1) == 0`, creating a predictable, staggered schedule of epoch execution.
281+
282+
The coinbase passes accumulated emissions to `drain_pending_emission()`, which executes the [full Yuma Consensus algorithm](./epoch.md) including validator weight processing, consensus calculation, bond updates, and final emission distribution to participants.
283+
284+
For detailed implementation of the consensus mechanism, validator weight processing, and emission distribution, see [Epoch Implementation](./epoch.md).
285+
286+
```rust
287+
for &netuid in subnets.iter() {
288+
// Process matured commit-reveal weight submissions
289+
if let Err(e) = Self::reveal_crv3_commits(netuid) {
290+
log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}");
291+
}
292+
293+
if Self::should_run_epoch(netuid, current_block) {
294+
// Reset epoch timing and collect accumulated emissions
295+
BlocksSinceLastStep::<T>::insert(netuid, 0);
296+
LastMechansimStepBlock::<T>::insert(netuid, current_block);
297+
298+
// Execute Yuma Consensus with accumulated rewards
299+
Self::drain_pending_emission(netuid, pending_alpha, pending_tao, pending_swapped, owner_cut);
300+
} else {
301+
BlocksSinceLastStep::<T>::mutate(netuid, |total| *total = total.saturating_add(1));
302+
}
303+
}
304+
```
305+
306+
307+

0 commit comments

Comments
 (0)