Skip to content
Merged
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
38 changes: 34 additions & 4 deletions gas/utils/autoupdater.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ def run_gascli_install(base_path: str, install_type: str = "py-deps", clear_venv
return False


def _find_repo_root():
"""Walk up from this file to the first directory containing .git.

Returns None if no repository root is found.
"""
path = os.path.dirname(os.path.abspath(__file__))
while True:
if os.path.isdir(os.path.join(path, ".git")):
return path
parent = os.path.dirname(path)
if parent == path:
return None
path = parent


def autoupdate(branch: str = "main", force=False, install_deps: bool = False, install_type: str = "py-deps", clear_venv: bool = True, nuke_cache: bool = False):
"""
Automatically updates the codebase to the latest version available on the specified branch.
Expand Down Expand Up @@ -200,11 +215,26 @@ def autoupdate(branch: str = "main", force=False, install_deps: bool = False, in

if latest_version > local_version or force:
bt.logging.info(f"A newer version is available. Updating...")
base_path = os.path.abspath(__file__)
while os.path.basename(base_path) != "bitmind-subnet":
base_path = os.path.dirname(base_path)
base_path = _find_repo_root()
if base_path is None:
bt.logging.error(
"Could not locate the repository root (no .git directory "
"above this file). Manual update required."
)
return

os.system(f"cd {base_path} && git pull")
pull = subprocess.run(
["git", "-C", base_path, "pull", "--ff-only"],
capture_output=True,
text=True,
timeout=300,
)
if pull.returncode != 0:
bt.logging.error(
f"git pull failed (exit {pull.returncode}): "
f"{(pull.stderr or pull.stdout).strip()[:500]}. Manual update required."
)
return

with open(os.path.join(base_path, "VERSION")) as f:
new_version = f.read().strip()
Expand Down
14 changes: 8 additions & 6 deletions neurons/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import sys
from pathlib import Path
from typing import Callable, List
import bittensor as bt
Expand Down Expand Up @@ -32,11 +33,6 @@ class BaseNeuron:
"""
config: "bt.config"
neuron_type: NeuronType
exit_context = ExitContext()
next_sync_block = None
block_callbacks: List[Callable] = []
substrate_manager: SubstrateConnectionManager = None
substrate_task = None

def check_registered(self):
if not self.subtensor.is_hotkey_registered(
Expand All @@ -47,7 +43,7 @@ def check_registered(self):
f"Wallet: {self.wallet} is not registered on netuid {self.config.netuid}."
f" Please register the hotkey using `btcli subnets register` before trying again"
)
exit()
sys.exit(1)

@on_block_interval("epoch_length")
async def maybe_sync_metagraph(self, block):
Expand Down Expand Up @@ -77,6 +73,12 @@ async def run_callbacks(self, block):
bt.logging.error(traceback.format_exc())

def __init__(self, config=None):
self.exit_context = ExitContext()
self.next_sync_block = None
self.block_callbacks: List[Callable] = []
self.substrate_manager: SubstrateConnectionManager = None
self.substrate_task = None

bt.logging.info(
f"Bittensor Version: {bt.__version__} | SN34 Version {__spec_version__}"
)
Expand Down
51 changes: 26 additions & 25 deletions neurons/validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,31 +326,32 @@ async def update_scores(self):
)
return

extend_scores = max(list(rewards.keys())) - len(self.scores) + 1
if extend_scores > 0:
self.scores = np.append(self.scores, np.zeros(extend_scores))

reward_arr = np.array([rewards.get(i, 0) for i in range(len(self.scores))])

# Alpha for generator score EMA - higher = faster decay, less reward persistence
# 0.5 = 50% new rewards, 50% historical (aggressive decay for inactive miners)
alpha = 0.5
self.scores = alpha * reward_arr + (1 - alpha) * self.scores

# Hard cutoff: zero out scores for generators not active within liveness window.
# Checks the actual last_seen timestamp, not just dict membership.
if generator_liveness:
cutoff = time.time() - max_inactive_hours * 3600
inactive_count = 0
for uid in range(len(self.scores)):
if uid < len(self.metagraph.hotkeys) and self.scores[uid] > 0:
hotkey = self.metagraph.hotkeys[uid]
last_seen = generator_liveness.get(hotkey, 0)
if last_seen < cutoff:
self.scores[uid] = 0
inactive_count += 1
if inactive_count > 0:
bt.logging.info(f"Zeroed scores for {inactive_count} inactive generators (not seen in {max_inactive_hours}h)")
async with self._state_lock:
extend_scores = max(list(rewards.keys())) - len(self.scores) + 1
if extend_scores > 0:
self.scores = np.append(self.scores, np.zeros(extend_scores))

reward_arr = np.array([rewards.get(i, 0) for i in range(len(self.scores))])

# Alpha for generator score EMA - higher = faster decay, less reward persistence
# 0.5 = 50% new rewards, 50% historical (aggressive decay for inactive miners)
alpha = 0.5
self.scores = alpha * reward_arr + (1 - alpha) * self.scores

# Hard cutoff: zero out scores for generators not active within liveness window.
# Checks the actual last_seen timestamp, not just dict membership.
if generator_liveness:
cutoff = time.time() - max_inactive_hours * 3600
inactive_count = 0
for uid in range(len(self.scores)):
if uid < len(self.metagraph.hotkeys) and self.scores[uid] > 0:
hotkey = self.metagraph.hotkeys[uid]
last_seen = generator_liveness.get(hotkey, 0)
if last_seen < cutoff:
self.scores[uid] = 0
inactive_count += 1
if inactive_count > 0:
bt.logging.info(f"Zeroed scores for {inactive_count} inactive generators (not seen in {max_inactive_hours}h)")

bt.logging.info(
f"Updated scores for {len(rewards)} miners with EMA (alpha={alpha})"
Expand Down
Loading