Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions gittensor/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,8 @@
# =============================================================================
RECYCLE_UID = 0

# Hardcoded emission splits per competition (replaces dynamic emissions)
OSS_EMISSION_SHARE = 0.30 # 30% to OSS contributions (PR scoring)
ISSUE_DISCOVERY_EMISSION_SHARE = 0.10 # 10% to issue discovery
RECYCLE_EMISSION_SHARE = 0.45 # 45% to recycle UID 0
# ISSUES_TREASURY_EMISSION_SHARE = 0.15 defined below (15% to smart contract treasury)
# Hardcoded emission split. Recycle receives only unclaimed scoring-pool slack.
OSS_EMISSION_SHARE = 0.90 # 90% combined OSS scoring pool

# =============================================================================
# Spam & Gaming Mitigation
Expand Down Expand Up @@ -187,5 +184,5 @@
# =============================================================================
CONTRACT_ADDRESS = '5FWNdk8YNtNcHKrAx2krqenFrFAZG7vmsd2XN2isJSew3MrD'
ISSUES_TREASURY_UID = 111 # UID of the smart contract neuron, if set to RECYCLE_UID then it's disabled
ISSUES_TREASURY_EMISSION_SHARE = 0.15 # % of emissions allocated to funding issues treasury
ISSUES_TREASURY_EMISSION_SHARE = 0.10 # % of emissions allocated to funding issues treasury
MAX_ISSUE_ID = 1_000_000 # sanity-check upper bound for any real deployment
44 changes: 16 additions & 28 deletions gittensor/validator/forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

from gittensor.classes import MinerEvaluation, MinerEvaluationCache
from gittensor.constants import (
ISSUE_DISCOVERY_EMISSION_SHARE,
ISSUES_TREASURY_EMISSION_SHARE,
ISSUES_TREASURY_UID,
OSS_EMISSION_SHARE,
RECYCLE_EMISSION_SHARE,
RECYCLE_UID,
)
from gittensor.utils.uids import get_all_uids
Expand Down Expand Up @@ -48,11 +46,10 @@ async def forward(self: 'Validator') -> None:
4. Store all evaluations to DB
5. Blend emission pools and update scores

Emission blending (hardcoded per-competition):
- OSS contributions: 30%
- Issue discovery: 30%
- Issue treasury: 15% (flat to UID 111)
- Recycle: 25% (flat to UID 0)
Emission blending:
- Combined OSS scoring pool: 90%
- Issue treasury: 10% (flat to UID 111)
- Recycle: unclaimed scoring-pool slack only
"""

if self.step % VALIDATOR_STEPS_INTERVAL == 0:
Expand Down Expand Up @@ -85,7 +82,7 @@ async def forward(self: 'Validator') -> None:
# 4. Store all evaluations to DB (includes issue discovery fields)
await self.bulk_store_evaluation(miner_evaluations, skip_uids=cached_uids)

# 5. Blend 4 emission pools into final rewards
# 5. Blend emission pools into final rewards
rewards = blend_emission_pools(oss_rewards, issue_rewards, miner_uids)

self.update_scores(rewards, miner_uids, blacklisted_uids=sorted(penalized_uids))
Expand Down Expand Up @@ -154,32 +151,23 @@ def blend_emission_pools(
issue_rewards: np.ndarray,
miner_uids: set[int],
) -> np.ndarray:
"""Blend 4 emission pools into a single rewards array.
"""Blend scoring and treasury pools into a single rewards array.

- OSS contributions: 30%
- Issue discovery: 30%
- Issue treasury: 15% (flat to UID 111)
- Recycle: 25% (flat to UID 0)
The combined scoring pool receives 90% of emissions. Recycle receives
only unclaimed scoring-pool slack when no OSS or issue scorer is active.
"""
sorted_uids = sorted(miner_uids)
rewards = np.zeros(len(sorted_uids))
recycle_extra = 0.0

# Pool 1: OSS contributions (30%)
oss_total = float(oss_rewards.sum())
if oss_total > 0:
rewards += oss_rewards * OSS_EMISSION_SHARE
scoring_rewards = oss_rewards + issue_rewards
scoring_total = float(scoring_rewards.sum())
if scoring_total > 0:
rewards += (scoring_rewards / scoring_total) * OSS_EMISSION_SHARE
else:
recycle_extra += OSS_EMISSION_SHARE

# Pool 2: Issue discovery (30%)
issue_total = float(issue_rewards.sum())
if issue_total > 0:
rewards += issue_rewards * ISSUE_DISCOVERY_EMISSION_SHARE
else:
recycle_extra += ISSUE_DISCOVERY_EMISSION_SHARE

# Pool 3: Issue treasury (15% flat to UID 111)
# Issue treasury (10% flat to UID 111)
if ISSUES_TREASURY_UID > 0 and ISSUES_TREASURY_UID in miner_uids:
treasury_idx = sorted_uids.index(ISSUES_TREASURY_UID)
rewards[treasury_idx] += ISSUES_TREASURY_EMISSION_SHARE
Expand All @@ -188,11 +176,11 @@ def blend_emission_pools(
f'{ISSUES_TREASURY_EMISSION_SHARE * 100:.0f}% of emissions'
)

# Pool 4: Recycle (25% + unclaimed from empty pools)
# Recycle unclaimed scoring-pool slack. There is no fixed recycle baseline.
if RECYCLE_UID in miner_uids:
recycle_idx = sorted_uids.index(RECYCLE_UID)
rewards[recycle_idx] += RECYCLE_EMISSION_SHARE + recycle_extra
rewards[recycle_idx] += recycle_extra
if recycle_extra > 0:
bt.logging.info(f'Recycling {recycle_extra * 100:.0f}% unclaimed emissions from empty pools')
bt.logging.info(f'Recycling {recycle_extra * 100:.0f}% unclaimed scoring-pool emissions')

return rewards
41 changes: 41 additions & 0 deletions tests/validator/test_forward_emissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import numpy as np
import pytest

from gittensor.constants import (
ISSUES_TREASURY_EMISSION_SHARE,
ISSUES_TREASURY_UID,
OSS_EMISSION_SHARE,
RECYCLE_UID,
)
from gittensor.validator.forward import blend_emission_pools


def test_collapsed_emission_constants():
assert OSS_EMISSION_SHARE == pytest.approx(0.90)
assert ISSUES_TREASURY_EMISSION_SHARE == pytest.approx(0.10)


def test_combined_scoring_pool_receives_ninety_percent():
miner_uids = {RECYCLE_UID, 1, 2, ISSUES_TREASURY_UID}
oss_rewards = np.array([0.0, 1.0, 0.0, 0.0])
issue_rewards = np.array([0.0, 0.0, 1.0, 0.0])

rewards = blend_emission_pools(oss_rewards, issue_rewards, miner_uids)

assert rewards.sum() == pytest.approx(1.0)
assert rewards[0] == pytest.approx(0.0)
assert rewards[1] == pytest.approx(0.45)
assert rewards[2] == pytest.approx(0.45)
assert rewards[3] == pytest.approx(0.10)


def test_empty_scoring_pool_recycles_ninety_percent_without_fixed_baseline():
miner_uids = {RECYCLE_UID, 1, ISSUES_TREASURY_UID}
empty_rewards = np.zeros(3)

rewards = blend_emission_pools(empty_rewards, empty_rewards, miner_uids)

assert rewards.sum() == pytest.approx(1.0)
assert rewards[0] == pytest.approx(0.90)
assert rewards[1] == pytest.approx(0.0)
assert rewards[2] == pytest.approx(0.10)