Skip to content

Weight Submission System

Relevant Source Files

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.

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

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

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.

ComponentAllocationPurpose
UID 0 (Burn)95%Subnet owner allocation
Miners5%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

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

The system includes multiple fallback mechanisms to ensure weight submission never fails due to edge cases:

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

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

The system maintains a comprehensive audit trail of all weight submissions in weights/weights_history.json. Each submission creates a record containing:

FieldDescriptionExample
timestampISO timestamp of submission”2024-01-15T10:30:00.000Z”
txHashSubtensor transaction hash”0x1234…abcd”
versionKeyBlock number used as version123456
weightsUID-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

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