Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gpu challange new arch #2

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
92 changes: 92 additions & 0 deletions neurons/challenge_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

import os
import random
import importlib
from typing import Dict, List
from threading import Lock
import time
import bittensor as bt

from challenges.challenge_base import ChallengeBase # Import the base class for challenges

class ChallengeManager:
def __init__(self, validator):
self.validator = validator
self.lock = Lock()
self.challenges = self.load_challenges()
self.pow_responses = {}
self.new_pow_benchmark = {}
self.miner_credentials = {}

def load_challenges(self) -> List[ChallengeBase]:
"""
Dynamically load all challenge classes from the 'challenges' directory.
"""
challenges = []
challenges_dir = os.path.join(os.path.dirname(__file__), 'challenges')
for filename in os.listdir(challenges_dir):
if filename.endswith('.py') and filename != 'challenge_base.py':
module_name = f'challenges.{filename[:-3]}'
module = importlib.import_module(module_name)
for attr in dir(module):
challenge_class = getattr(module, attr)
if (isinstance(challenge_class, type) and
issubclass(challenge_class, ChallengeBase) and
challenge_class is not ChallengeBase):
challenges.append(challenge_class())
return challenges

def execute_challenge(self, uid, axon: bt.AxonInfo):
"""
Execute a randomly selected challenge on the miner.
"""
if not self.challenges:
bt.logging.error("No challenges available to execute.")
return

# Randomly select a challenge
challenge = random.choice(self.challenges)
bt.logging.info(f"Selected challenge: {challenge.__class__.__name__} for miner {uid}")

# Generate the task
task = challenge.generate_task()

# Execute the challenge on the miner
start_time = time.time()
result = challenge.execute_on_miner(axon, task, self.miner_credentials)
elapsed_time = time.time() - start_time

# Evaluate the result
success = challenge.evaluate_result(task, result)

# Get difficulty from the challenge
difficulty = challenge.difficulty

# Save the results
result_data = {
"ss58_address": axon.hotkey,
"success": success,
"elapsed_time": elapsed_time,
"difficulty": difficulty,
}
with self.lock:
self.pow_responses[uid] = result
self.new_pow_benchmark[uid] = result_data

def perform_challenges(self, queryable_uids: Dict[int, bt.AxonInfo]):
"""
Perform challenges on all miners.
"""
threads = []
for uid, axon in queryable_uids.items():
thread = threading.Thread(
target=self.execute_challenge,
args=(uid, axon),
name=f"challenge_thread_{uid}",
daemon=True,
)
threads.append(thread)
thread.start()

for thread in threads:
thread.join()
27 changes: 27 additions & 0 deletions neurons/challenges/challenge_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

from abc import ABC, abstractmethod

class ChallengeBase(ABC):
def __init__(self):
self.difficulty = None

@abstractmethod
def generate_task(self):
"""
Generate the task to be sent to the miner.
"""
pass

@abstractmethod
def execute_on_miner(self, axon, task):
"""
Execute the challenge on the miner via SSH and return the result.
"""
pass

@abstractmethod
def evaluate_result(self, task, result):
"""
Evaluate the result returned by the miner and return a boolean indicating success.
"""
pass
27 changes: 27 additions & 0 deletions neurons/challenges/hashcat_challenge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from .challenge_base import ChallengeBase
import subprocess
import hashlib
import secrets

class HashcatChallenge(ChallengeBase):
def __init__(self):
super().__init__()
self.difficulty = 'Medium'
self.password = None # Store the original password

def generate_task(self):
# Generate a random password and its hash
self.password = secrets.token_hex(8)
hash_to_crack = hashlib.sha256(self.password.encode()).hexdigest()
return hash_to_crack

def execute_on_miner(self, axon, task):
# Install hashcat if not installed
subprocess.run(['sudo', 'apt-get', 'install', '-y', 'hashcat'])
# Execute hashcat on the miner
result = axon.run_command(['hashcat', '-a', '0', task, 'wordlist.txt'])
return result

def evaluate_result(self, task, result):
# Compare the cracked password with the original password
return result == self.password
62 changes: 4 additions & 58 deletions neurons/miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@
validator_permit_stake,
miner_priority_specs,
miner_priority_allocate,
miner_priority_challenge,
TRUSTED_VALIDATORS_HOTKEYS,
)
from compute.axon import ComputeSubnetAxon, ComputeSubnetSubtensor
from compute.protocol import Specs, Allocate, Challenge
from compute.protocol import Specs, Allocate
from compute.utils.math import percent
from compute.utils.parser import ComputeArgPaser
from compute.utils.socket import check_port
Expand All @@ -45,7 +44,6 @@
calculate_next_block_time,
)
from compute.utils.version import (
check_hashcat_version,
try_update,
version2number,
get_remote_version,
Expand All @@ -69,7 +67,7 @@
from compute.wandb.wandb import ComputeWandb
from neurons.Miner.allocate import check_allocation, register_allocation
from neurons.Miner.http_server import start_server, stop_server
from neurons.Miner.pow import check_cuda_availability, run_miner_pow
from neurons.Miner.pow import check_cuda_availability

# from neurons.Miner.specs import RequestSpecsProcessor
from neurons.Validator.script import check_docker_availability
Expand Down Expand Up @@ -166,13 +164,6 @@ def __init__(self):

check_cuda_availability()

# Step 3: Set up hashcat for challenges
self.hashcat_path = self.config.miner_hashcat_path
self.hashcat_workload_profile = self.config.miner_hashcat_workload_profile
self.hashcat_extended_options = self.config.miner_hashcat_extended_options

check_hashcat_version(hashcat_path=self.hashcat_path)

self.uids: list = self.metagraph.uids.tolist()

self.sync_status()
Expand Down Expand Up @@ -231,15 +222,6 @@ def init_axon(self):
forward_fn=self.allocate,
blacklist_fn=self.blacklist_allocate,
priority_fn=self.priority_allocate,
).attach(
forward_fn=self.challenge,
blacklist_fn=self.blacklist_challenge,
priority_fn=self.priority_challenge,
# Disable the spec query and replaced with WanDB
# ).attach(
# forward_fn=self.specs,
# blacklist_fn=self.blacklist_specs,
# priority_fn=self.priority_specs,
)

# Serve passes the axon information to the network + netuid we are hosting on.
Expand Down Expand Up @@ -331,7 +313,7 @@ def sync_status(self):
self.init_axon()

def base_blacklist(
self, synapse: typing.Union[Specs, Allocate, Challenge]
self, synapse: typing.Union[Specs, Allocate]
) -> typing.Tuple[bool, str]:
hotkey = synapse.dendrite.hotkey
synapse_type = type(synapse).__name__
Expand Down Expand Up @@ -375,7 +357,7 @@ def base_blacklist(
)
return False, "Hotkey recognized!"

def base_priority(self, synapse: typing.Union[Specs, Allocate, Challenge]) -> float:
def base_priority(self, synapse: typing.Union[Specs, Allocate]) -> float:
caller_uid = self._metagraph.hotkeys.index(
synapse.dendrite.hotkey
) # Get the caller index.
Expand Down Expand Up @@ -486,42 +468,6 @@ def allocate(self, synapse: Allocate) -> Allocate:
synapse.output["port"] = int(self.config.ssh.port)
return synapse

# The blacklist function decides if a request should be ignored.
def blacklist_challenge(self, synapse: Challenge) -> typing.Tuple[bool, str]:
return self.base_blacklist(synapse)

# The priority function determines the order in which requests are handled.
# More valuable or higher-priority requests are processed before others.
def priority_challenge(self, synapse: Challenge) -> float:
return self.base_priority(synapse) + miner_priority_challenge

# This is the Challenge function, which decides the miner's response to a valid, high-priority request.
def challenge(self, synapse: Challenge) -> Challenge:
if synapse.challenge_difficulty <= 0:
bt.logging.warning(
f"{synapse.dendrite.hotkey}: Challenge received with a difficulty <= 0 - it can not be solved."
)
return synapse

v_id = synapse.dendrite.hotkey[:8]
run_id = (
f"{v_id}/{synapse.challenge_difficulty}/{synapse.challenge_hash[10:20]}"
)

result = run_miner_pow(
run_id=run_id,
_hash=synapse.challenge_hash,
salt=synapse.challenge_salt,
mode=synapse.challenge_mode,
chars=synapse.challenge_chars,
mask=synapse.challenge_mask,
hashcat_path=self.hashcat_path,
hashcat_workload_profile=self.hashcat_workload_profile,
hashcat_extended_options=self.hashcat_extended_options,
)
synapse.output = result
return synapse

def get_updated_validator(self):
try:
self.whitelist_hotkeys_version.clear()
Expand Down
32 changes: 10 additions & 22 deletions neurons/miner_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import paramiko # For SSH functionality
from compute.protocol import Allocate # Allocate is still needed for the actual allocation process
from compute.wandb.wandb import ComputeWandb # Importing ComputeWandb
from ssh_utils import execute_ssh_command # Import the new SSH utility

class MinerChecker:
def __init__(self, config):
Expand Down Expand Up @@ -100,8 +101,15 @@ def miner_checking_thread(self, axon):
private_key = private_key.encode("utf-8")
decrypted_info_str = rsa.decrypt_data(private_key, base64.b64decode(response["info"]))
info = json.loads(decrypted_info_str)
# Use the SSH check function
is_ssh_access = self.check_ssh_login(axon.ip, info['port'], info['username'], info['password'])
# Use the SSH utility function instead of check_ssh_login
commands = ['echo "Hello, World!"'] # Commands to execute on the miner
ssh_results = execute_ssh_command(axon.ip, info['port'], info['username'], info['password'], commands)

if ssh_results is None:
self.penalize_miner(axon.hotkey, "SSH_ACCESS_FAILED", "Failed to execute SSH commands")
else:
bt.logging.info(f"SSH commands executed successfully for {axon.hotkey}")
# Process ssh_results if needed
else:
# Penalize if the allocation failed
self.penalize_miner(axon.hotkey, "ALLOCATION_FAILED", "Allocation failed during resource allocation")
Expand Down Expand Up @@ -139,26 +147,6 @@ def miner_checking_thread(self, axon):
# Penalize if SSH access fails
self.penalize_miner(axon.hotkey, "SSH_ACCESS_DISABLED", "Failed SSH access")

def check_ssh_login(self, host, port, username, password):
"""Check SSH login using Paramiko."""
try:
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=host, port=port, username=username, password=password, timeout=10)
bt.logging.info(f"SSH login successful for {host}")
return True
except paramiko.AuthenticationException:
bt.logging.error(f"Authentication failed for {host}")
return False
except paramiko.SSHException as ssh_exception:
bt.logging.error(f"Unable to establish SSH connection: {ssh_exception}")
return False
except Exception as e:
bt.logging.error(f"Exception in connecting to the server: {e}")
return False
finally:
ssh_client.close()

def get_config():
"""Set up configuration using argparse."""
parser = argparse.ArgumentParser()
Expand Down
32 changes: 32 additions & 0 deletions neurons/ssh_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import paramiko
import bittensor as bt

def execute_ssh_command(host, port, username, password, commands):
"""Establishes an SSH connection and executes a list of commands."""
try:
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=host, port=port, username=username, password=password, timeout=10)
bt.logging.info(f"SSH connection established with {host}")

results = []
for command in commands:
stdin, stdout, stderr = ssh_client.exec_command(command)
output = stdout.read().decode()
error = stderr.read().decode()
exit_status = stdout.channel.recv_exit_status()
results.append({'command': command, 'output': output, 'error': error, 'exit_status': exit_status})
bt.logging.info(f"Executed command: {command}")

return results
except paramiko.AuthenticationException:
bt.logging.error(f"Authentication failed for {host}")
return None
except paramiko.SSHException as ssh_exception:
bt.logging.error(f"Unable to establish SSH connection: {ssh_exception}")
return None
except Exception as e:
bt.logging.error(f"Exception in connecting to the server: {e}")
return None
finally:
ssh_client.close()
Loading