Weight Submission System
This document explains the weight submission mechanism in SN106, which transforms calculated miner emissions into normalized weights that are submitted to the Subtensor chain. The system implements sophisticated distribution policies, burn mechanisms, and exponential moving average (EMA) smoothing to ensure fair and stable reward distribution.
For information about how raw emissions are calculated from NFT positions, see Validator Engine. For details about multi-chain data collection that feeds into this system, see Multi-Chain Integration.
Purpose and Distribution Policy
Section titled “Purpose and Distribution Policy”The weight submission system serves as the final stage in the SN106 reward distribution pipeline. It takes calculated miner emissions and applies distribution policies before submitting weights to the Subtensor chain.
The system implements two distinct distribution policies:
- In-Range Policy: When at least one miner has positive emissions (indicating in-range NFT positions), only miners with positive emissions receive non-zero weights. All other UIDs receive zero weight.
- Out-of-Range Policy: When all miners have zero emissions (all NFT positions are out-of-range), uniform weights are distributed across all UIDs in the subnet.
graph TD
MinerEmissions["Miner Emissions<br/>Record<string, number>"]
PolicyCheck["hasPositiveEmission<br/>Check"]
InRangePolicy["In-Range Policy<br/>Zero out non-earning miners"]
OutOfRangePolicy["Out-of-Range Policy<br/>Uniform distribution"]
EMASmoothing["EMA Smoothing<br/>updateEma()"]
BurnMechanism["Burn Mechanism<br/>95% to UID 0"]
WeightNormalization["Weight Normalization<br/>Scale to u16"]
SubtensorSubmission["setWeightsOnSubtensor()"]
MinerEmissions --> PolicyCheck
PolicyCheck -->|"Some positive"| InRangePolicy
PolicyCheck -->|"All zero"| OutOfRangePolicy
InRangePolicy --> EMASmoothing
OutOfRangePolicy --> BurnMechanism
EMASmoothing --> BurnMechanism
BurnMechanism --> WeightNormalization
WeightNormalization --> SubtensorSubmission
Sources: validator/index.ts:113-151 , utils/setWeights.ts:26-48
EMA Smoothing System
Section titled “EMA Smoothing System”The system implements Exponential Moving Average (EMA) smoothing to prevent dramatic weight fluctuations between validation cycles. EMA weights are persisted globally between validator runs to maintain continuity.
graph LR
PrevEMA["Previous EMA Weights<br/>global.__sn106EmaWeights"]
CurrentWeights["Current Miner Weights<br/>minerWeightsRaw"]
EMAFunction["updateEma()<br/>α * current + (1-α) * previous"]
EMAAlpha["EMA_ALPHA<br/>CONFIG.VALIDATOR.EMA_ALPHA"]
EMAEpsilon["EMA_EPSILON<br/>Minimum threshold"]
SmoothedWeights["Smoothed Weights<br/>Applied to eligible miners"]
PrevEMA --> EMAFunction
CurrentWeights --> EMAFunction
EMAAlpha --> EMAFunction
EMAFunction --> EMAEpsilon
EMAEpsilon --> SmoothedWeights
SmoothedWeights --> PrevEMA
The EMA calculation applies the formula: next[k] = EMA_ALPHA * currVal + (1 - EMA_ALPHA) * prevVal for each miner address. Values below EMA_EPSILON are filtered out to prevent negligible weights from persisting.
Sources: validator/index.ts:34-59 , validator/index.ts:124-138
Burn Mechanism and Weight Scaling
Section titled “Burn Mechanism and Weight Scaling”The weight submission system implements a burn mechanism that allocates 95% of total weight to UID 0 (subnet owner) and distributes the remaining 5% among miners. This mechanism is applied before final normalization and scaling.
| Component | Allocation | Purpose |
|---|---|---|
| UID 0 (Burn) | 95% | Subnet owner allocation |
| Miners | 5% | Distributed based on performance |
graph TD
FloatWeights["Float Weights<br/>Normalized to sum 1.0"]
BurnCheck["burnPercentage > 0<br/>95% burn rate"]
ScaleMinerWeights["Scale Miner Weights<br/>w * minerWeight (0.05)"]
AddBurnWeight["Add Burn Weight<br/>95% to UID 0"]
ScaleToU16["Scale to u16<br/>Math.round(w * 65535)"]
EnsureSum["Ensure Sum = 65535<br/>Adjust for rounding errors"]
SubmitWeights["Submit to Subtensor<br/>setWeights extrinsic"]
FloatWeights --> BurnCheck
BurnCheck --> ScaleMinerWeights
ScaleMinerWeights --> AddBurnWeight
AddBurnWeight --> ScaleToU16
ScaleToU16 --> EnsureSum
EnsureSum --> SubmitWeights
The scaling process converts float weights (0.0-1.0) to u16 integers (0-65535) required by the Subtensor chain, with careful handling of rounding errors to ensure the total always equals 65535.
Sources: utils/setWeights.ts:26-81
Subtensor Submission Process
Section titled “Subtensor Submission Process”The setWeightsOnSubtensor function handles the complete submission pipeline, including API initialization, weight preparation, and transaction execution.
graph TD
InitAPI["subtensorClient.initialize()<br/>WebSocket connection"]
HotkeySetup["Keyring setup<br/>sr25519 hotkey"]
FilterUIDs["Filter unknown addresses<br/>addressToUid mapping"]
WeightValidation["Weight validation<br/>isFinite() checks"]
BurnApplication["Apply burn mechanism<br/>95% to UID 0"]
Normalization["Normalize weights<br/>Sum to 1.0"]
U16Scaling["Scale to u16<br/>0-65535 range"]
VersionKey["Get version key<br/>Current block number"]
CreateTx["api.tx.subtensorModule.setWeights<br/>(netuid, uids, scaled, versionKey)"]
SignAndSend["tx.signAndSend(hotkey)<br/>Submit transaction"]
SaveHistory["saveWeightDetails()<br/>weights/weights_history.json"]
InitAPI --> HotkeySetup
HotkeySetup --> FilterUIDs
FilterUIDs --> WeightValidation
WeightValidation --> BurnApplication
BurnApplication --> Normalization
Normalization --> U16Scaling
U16Scaling --> VersionKey
VersionKey --> CreateTx
CreateTx --> SignAndSend
SignAndSend --> SaveHistory
Sources: utils/setWeights.ts:7-103
Fallback Mechanisms
Section titled “Fallback Mechanisms”The system includes multiple fallback mechanisms to ensure weight submission never fails due to edge cases:
Empty Weights Fallback
Section titled “Empty Weights Fallback”When no valid miner weights are calculated, the system falls back to uniform distribution across all available UIDs:
graph TD
EmptyCheck["uids.length === 0<br/>No valid weights"]
GetAllUIDs["Use all UIDs<br/>Object.values(addressToUid)"]
UniformWeight["uniform = 1 / uids.length<br/>Equal distribution"]
FillArray["Array(uids.length).fill(uniform)<br/>Uniform weights"]
EmptyCheck --> GetAllUIDs
GetAllUIDs --> UniformWeight
UniformWeight --> FillArray
Zero Sum Fallback
Section titled “Zero Sum Fallback”When all calculated weights sum to zero or are invalid, uniform distribution is applied to the selected UIDs:
graph TD
SumCheck["sumFloat <= 0 || !isFinite(sumFloat)<br/>Invalid weight sum"]
RecalcUniform["uniform = 1 / uids.length<br/>Recalculate uniform"]
ApplyUniform["floatWeights = Array.fill(uniform)<br/>Override with uniform"]
SumCheck --> RecalcUniform
RecalcUniform --> ApplyUniform
Sources: utils/setWeights.ts:50-70
Weight History Tracking
Section titled “Weight History Tracking”The system maintains a comprehensive audit trail of all weight submissions in weights/weights_history.json. Each submission creates a record containing:
| Field | Description | Example |
|---|---|---|
timestamp | ISO timestamp of submission | ”2024-01-15T10:30:00.000Z” |
txHash | Subtensor transaction hash | ”0x1234…abcd” |
versionKey | Block number used as version | 123456 |
weights | UID-to-weight mapping | {"0": 61984, "1": 1775, "2": 1776} |
graph LR
WeightSubmission["Successful Weight Submission"]
CreateRecord["Create weight record<br/>{timestamp, txHash, versionKey, weights}"]
ReadExisting["Read existing history<br/>weights_history.json"]
AppendRecord["Append new record<br/>existingData.push(weightRecord)"]
WriteFile["Write updated data<br/>JSON.stringify with formatting"]
WeightSubmission --> CreateRecord
CreateRecord --> ReadExisting
ReadExisting --> AppendRecord
AppendRecord --> WriteFile
The weight details are saved in a UID-to-weight format where UIDs are string keys and weights are the scaled u16 values (0-65535).
Sources: utils/setWeights.ts:108-166
Integration with Validator Engine
Section titled “Integration with Validator Engine”The weight submission system is invoked as the final step in the validator execution cycle. The main validator orchestrates the entire pipeline from data collection through weight submission:
graph TD
ValidatorRun["runValidator()<br/>Main orchestration"]
DataCollection["Multi-chain data collection<br/>NFT positions + tick data"]
EmissionCalc["Emission calculation<br/>calculatePoolwiseNFTEmissions"]
MinerAggregation["Aggregate per-miner<br/>nftEmissions.reduce()"]
PolicyApplication["Apply distribution policy<br/>In-range vs out-of-range"]
EMAApplication["Apply EMA smoothing<br/>updateEma() if enabled"]
WeightSubmission["setWeightsOnSubtensor()<br/>Final submission"]
ValidatorRun --> DataCollection
DataCollection --> EmissionCalc
EmissionCalc --> MinerAggregation
MinerAggregation --> PolicyApplication
PolicyApplication --> EMAApplication
EMAApplication --> WeightSubmission
The validator runs on a configurable interval (VALIDATOR_INTERVAL_MINUTES) and maintains EMA state between runs using global variables.
Sources: validator/index.ts:17-163 , validator/index.ts:179-181