From 0aa3e90086ea4f650e338387dac79b007e155f98 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 13:56:53 +0200 Subject: [PATCH 1/8] refactor(miner.py): remove challenge-related functionality and clean up imports --- neurons/miner.py | 62 ++++-------------------------------------------- 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/neurons/miner.py b/neurons/miner.py index 5bf4de1..b2a096e 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -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 @@ -45,7 +44,6 @@ calculate_next_block_time, ) from compute.utils.version import ( - check_hashcat_version, try_update, version2number, get_remote_version, @@ -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 @@ -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() @@ -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. @@ -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__ @@ -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. @@ -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() From 1abd693235287678771bc1c94c621c16355b4e40 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:02:06 +0200 Subject: [PATCH 2/8] refactor(miner_checker.py): replace SSH login method with utility function and add SSH command execution --- neurons/miner_checker.py | 32 ++++++++++---------------------- neurons/ssh_utils.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 neurons/ssh_utils.py diff --git a/neurons/miner_checker.py b/neurons/miner_checker.py index ed638b8..436f6e0 100644 --- a/neurons/miner_checker.py +++ b/neurons/miner_checker.py @@ -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): @@ -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") @@ -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() diff --git a/neurons/ssh_utils.py b/neurons/ssh_utils.py new file mode 100644 index 0000000..03b3a51 --- /dev/null +++ b/neurons/ssh_utils.py @@ -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() + results.append({'command': command, 'output': output, 'error': error}) + 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() \ No newline at end of file From 5888099eea694e5464a8970cf3a0039c9115802e Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:46:31 +0200 Subject: [PATCH 3/8] feat(validator.py): implement SSH command execution for hashcat and manage SSH credentials --- neurons/validator.py | 115 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 18 deletions(-) diff --git a/neurons/validator.py b/neurons/validator.py index 626e402..cf36e75 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -65,6 +65,12 @@ from neurons.Validator.database.allocate import update_miner_details, select_has_docker_miners_hotkey, get_miner_details from neurons.Validator.database.challenge import select_challenge_stats, update_challenge_details from neurons.Validator.database.miner import select_miners, purge_miner_entries, update_miners +from neurons.ssh_utils import execute_ssh_command +import rsa # Ensure rsa module is imported +import base64 +import json +import importlib +from challenge_manager import ChallengeManager # Import the ChallengeManager class Validator: @@ -201,7 +207,9 @@ def __init__(self): # Init the thread. self.lock = threading.Lock() - self.threads: List[threading.Thread] = [] + + # Initialize ChallengeManager + self.challenge_manager = ChallengeManager(self) @staticmethod def init_config(): @@ -577,25 +585,96 @@ def get_valid_validator_hotkeys(self): return valid_hotkeys def execute_pow_request(self, uid, axon: bt.AxonInfo, _hash, _salt, mode, chars, mask, difficulty): - dendrite = bt.dendrite(wallet=self.wallet) - start_time = time.time() - bt.logging.info(f"Querying for {Challenge.__name__} - {uid}/{axon.hotkey}/{_hash}/{difficulty}") - response = dendrite.query( - axon, - Challenge( - challenge_hash=_hash, - challenge_salt=_salt, - challenge_mode=mode, - challenge_chars=chars, - challenge_mask=mask, - challenge_difficulty=difficulty, - ), - timeout=pow_timeout, + # SSH connection parameters + host = axon.ip + port = 22 # Default SSH port + + # Initialize miner_credentials if not already done + if not hasattr(self, 'miner_credentials'): + self.miner_credentials = {} + + # Generate RSA key pair + private_key, public_key = rsa.generate_key_pair() + + # Request SSH credentials from the miner + credentials = self.miner_credentials.get(uid) + if not credentials: + # Send Allocate request to miner to obtain SSH credentials + response = self.dendrite.query( + axon, + Allocate(timeline=1, device_requirement={}, checking=False, public_key=public_key), + timeout=60, + ) + if response and response.get("status") is True: + encrypted_info = response.get("info", "") + # Decrypt SSH credentials + decrypted_info_str = rsa.decrypt_data( + private_key.encode("utf-8"), + base64.b64decode(encrypted_info) + ) + credentials = json.loads(decrypted_info_str) + self.miner_credentials[uid] = credentials + else: + bt.logging.error(f"Failed to obtain SSH credentials for miner {uid}") + return + + username = credentials.get('username') + password = credentials.get('password') + + # Commands to execute on the miner + install_hashcat_command = "if ! command -v hashcat &> /dev/null; then sudo apt-get update && sudo apt-get install -y hashcat; fi" + + # Prepare hashcat command parameters + hashcat_path = "hashcat" + hashcat_workload_profile = "3" + hashcat_extended_options = "--machine-readable --quiet" + + hash_input = f"{_hash}:{_salt}" + + # Build the hashcat command + hashcat_command = ( + f"{hashcat_path} '{hash_input}' -a 3 -D 2 -m {mode} -1 '{chars}' '{mask}' " + f"-w {hashcat_workload_profile} {hashcat_extended_options}" ) + + commands = [ + install_hashcat_command, + hashcat_command, + ] + + # Establish SSH connection and execute commands + start_time = time.time() + results = execute_ssh_command(host, port, username, password, commands) elapsed_time = time.time() - start_time - response_password = response.get("password", "") + + # Process the result from the hashcat command + challenge_result = None + for result in results: + if hashcat_command in result['command']: + output = result.get('output', '').strip() + error = result.get('error', '').strip() + exit_status = result.get('exit_status', None) + + if exit_status == 0 or exit_status == 3: + # Parse the output to get the password + for line in output.splitlines(): + if line.strip(): + parts = line.strip().split(':') + if len(parts) >= 4: + challenge_result = parts[3] + break + if not challenge_result: + bt.logging.warning(f"No password found in output for miner {uid}") + else: + bt.logging.warning(f"Hashcat failed with exit code {exit_status} for miner {uid}. Error: {error}") + break + + # Evaluate the challenge result + response_password = challenge_result hashed_response = gen_hash(response_password, _salt)[0] if response_password else "" - success = True if _hash == hashed_response else False + success = _hash == hashed_response + + # Save the results as before result_data = { "ss58_address": axon.hotkey, "success": success, @@ -603,7 +682,7 @@ def execute_pow_request(self, uid, axon: bt.AxonInfo, _hash, _salt, mode, chars, "difficulty": difficulty, } with self.lock: - self.pow_responses[uid] = response + self.pow_responses[uid] = response_password self.new_pow_benchmark[uid] = result_data def execute_miner_checking_request(self, uid, axon: bt.AxonInfo): From 33eb7ce80c38de5a66a0b9200796bcb943356914 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:47:26 +0200 Subject: [PATCH 4/8] feat(challenge_manager.py): implement ChallengeManager to dynamically load and execute challenges --- neurons/challenge_manager.py | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 neurons/challenge_manager.py diff --git a/neurons/challenge_manager.py b/neurons/challenge_manager.py new file mode 100644 index 0000000..1c04cfa --- /dev/null +++ b/neurons/challenge_manager.py @@ -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() \ No newline at end of file From a4d8958d1e23ecfb165bb67354032bd2cf67a068 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:47:35 +0200 Subject: [PATCH 5/8] feat(ssh_utils.py): include exit status in SSH command execution results --- neurons/ssh_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neurons/ssh_utils.py b/neurons/ssh_utils.py index 03b3a51..ba5bdfe 100644 --- a/neurons/ssh_utils.py +++ b/neurons/ssh_utils.py @@ -1,4 +1,3 @@ - import paramiko import bittensor as bt @@ -15,7 +14,8 @@ def execute_ssh_command(host, port, username, password, commands): stdin, stdout, stderr = ssh_client.exec_command(command) output = stdout.read().decode() error = stderr.read().decode() - results.append({'command': command, 'output': output, 'error': error}) + 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 From c56cb2535548d5d6188ab604504827bf9c52eddb Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:47:54 +0200 Subject: [PATCH 6/8] feat(test_challenges.py): add integration tests for ChallengeManager using Docker and SSH --- neurons/test_challenges.py | 178 +++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 neurons/test_challenges.py diff --git a/neurons/test_challenges.py b/neurons/test_challenges.py new file mode 100644 index 0000000..6b00563 --- /dev/null +++ b/neurons/test_challenges.py @@ -0,0 +1,178 @@ + +import os +import subprocess +import threading +import time +import paramiko +import docker +import logging + +from challenge_manager import ChallengeManager +from challenges.challenge_base import ChallengeBase + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +DOCKER_IMAGE = "ubuntu:20.04" +SSH_PORT = 2222 +SSH_USERNAME = "testuser" +SSH_PASSWORD = "testpass" + +def build_docker_image(): + """ + Builds a Docker image with SSH server installed. + """ + dockerfile = f""" + FROM {DOCKER_IMAGE} + RUN apt-get update && apt-get install -y openssh-server && \\ + mkdir /var/run/sshd && \\ + echo '{SSH_USERNAME}:{SSH_PASSWORD}' | chpasswd && \\ + sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \\ + sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \\ + mkdir /home/{SSH_USERNAME} && chown -R {SSH_USERNAME}:{SSH_USERNAME} /home/{SSH_USERNAME} + EXPOSE 22 + CMD ["/usr/sbin/sshd", "-D"] + """ + + client = docker.from_env() + try: + logger.info("Building Docker image for testing...") + client.images.build(fileobj=io.BytesIO(dockerfile.encode('utf-8')), tag='ssh_test_image', rm=True) + logger.info("Docker image built successfully.") + except docker.errors.BuildError as e: + logger.error(f"Error building Docker image: {e}") + raise + +def run_docker_container(): + """ + Runs the Docker container with SSH server. + """ + client = docker.from_env() + try: + logger.info("Starting Docker container...") + container = client.containers.run( + image='ssh_test_image', + detach=True, + ports={'22/tcp': SSH_PORT}, + name='ssh_test_container', + tty=True + ) + # Wait for SSH server to start + time.sleep(5) + logger.info(f"Docker container started with ID: {container.id}") + return container + except docker.errors.ContainerError as e: + logger.error(f"Error starting Docker container: {e}") + raise + +def stop_docker_container(container): + """ + Stops and removes the Docker container. + """ + try: + logger.info("Stopping Docker container...") + container.stop() + container.remove() + logger.info("Docker container stopped and removed.") + except Exception as e: + logger.error(f"Error stopping Docker container: {e}") + +def execute_ssh_command(host, port, username, password, command): + """ + Executes a command on the SSH server. + """ + 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) + logger.info(f"SSH connection established with {host}") + + stdin, stdout, stderr = ssh_client.exec_command(command) + output = stdout.read().decode() + error = stderr.read().decode() + exit_status = stdout.channel.recv_exit_status() + + ssh_client.close() + return {'command': command, 'output': output, 'error': error, 'exit_status': exit_status} + except Exception as e: + logger.error(f"SSH command execution failed: {e}") + return None + +class MockAxonInfo: + """ + Mock class to simulate bt.AxonInfo. + """ + def __init__(self, ip, hotkey): + self.ip = ip + self.hotkey = hotkey + +def main(): + # Build and run Docker container + build_docker_image() + container = run_docker_container() + + # Prepare mock axon with container's SSH details + axon = MockAxonInfo(ip='127.0.0.1', hotkey='mock_hotkey') + + # Initialize ChallengeManager + challenge_manager = ChallengeManager(validator=None) # No need for a validator instance in testing + + # Mock the miner credentials + challenge_manager.miner_credentials = { + 'mock_uid': { + 'host': axon.ip, + 'port': SSH_PORT, + 'username': SSH_USERNAME, + 'password': SSH_PASSWORD + } + } + + # Mock the method to execute SSH commands to interact with our container + def mock_execute_on_miner(self, axon, task, miner_credentials): + """ + Mock implementation of challenge execution on miner for testing. + """ + credentials = miner_credentials.get('mock_uid') + if not credentials: + logger.error("No SSH credentials available for the miner.") + return None + + host = credentials['host'] + port = credentials['port'] + username = credentials['username'] + password = credentials['password'] + + # Install any necessary dependencies inside the container + install_command = "apt-get update && apt-get install -y python3 python3-pip" + execute_ssh_command(host, port, username, password, install_command) + + # Assume task is a command string for testing purposes + result = execute_ssh_command(host, port, username, password, task) + return result + + # Monkey-patch the execute_on_miner method for testing + for challenge in challenge_manager.challenges: + challenge.execute_on_miner = mock_execute_on_miner.__get__(challenge, ChallengeBase) + + # Execute all challenges + try: + logger.info("Starting challenge execution...") + for challenge in challenge_manager.challenges: + uid = 'mock_uid' + logger.info(f"Executing challenge: {challenge.__class__.__name__}") + task = challenge.generate_task() + start_time = time.time() + result = challenge.execute_on_miner(axon, task, challenge_manager.miner_credentials) + elapsed_time = time.time() - start_time + success = challenge.evaluate_result(task, result) + + logger.info(f"Challenge {challenge.__class__.__name__} result: {'Success' if success else 'Failure'}") + logger.info(f"Elapsed time: {elapsed_time:.2f} seconds") + logger.info(f"Result details: {result}") + finally: + # Clean up the Docker container + stop_docker_container(container) + +if __name__ == "__main__": + main() \ No newline at end of file From e446c0495b2e0f6c9a8f80a0a8bb875e0ffa9823 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:48:07 +0200 Subject: [PATCH 7/8] feat(challenge_base.py): create abstract base class for challenge implementation --- neurons/challenges/challenge_base.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 neurons/challenges/challenge_base.py diff --git a/neurons/challenges/challenge_base.py b/neurons/challenges/challenge_base.py new file mode 100644 index 0000000..b846f29 --- /dev/null +++ b/neurons/challenges/challenge_base.py @@ -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 \ No newline at end of file From 108f2faf36c832ce9d5c5844948d507bf098bb94 Mon Sep 17 00:00:00 2001 From: Mohaned AbdElMonsef Date: Thu, 21 Nov 2024 14:57:07 +0200 Subject: [PATCH 8/8] feat(hashcat_challenge.py): implement HashcatChallenge class for password cracking tasks --- neurons/challenges/hashcat_challenge.py | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 neurons/challenges/hashcat_challenge.py diff --git a/neurons/challenges/hashcat_challenge.py b/neurons/challenges/hashcat_challenge.py new file mode 100644 index 0000000..f11f310 --- /dev/null +++ b/neurons/challenges/hashcat_challenge.py @@ -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 \ No newline at end of file