diff --git a/gas/utils/autoupdater.py b/gas/utils/autoupdater.py index c1c95d7d..a5fdbb4c 100644 --- a/gas/utils/autoupdater.py +++ b/gas/utils/autoupdater.py @@ -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. @@ -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() diff --git a/neurons/base.py b/neurons/base.py index 9242e0a6..ca23a441 100644 --- a/neurons/base.py +++ b/neurons/base.py @@ -1,4 +1,5 @@ import argparse +import sys from pathlib import Path from typing import Callable, List import bittensor as bt @@ -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( @@ -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): @@ -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__}" ) diff --git a/neurons/validator/validator.py b/neurons/validator/validator.py index 2daca189..c79c4b95 100644 --- a/neurons/validator/validator.py +++ b/neurons/validator/validator.py @@ -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})"