Kaspa Miner Script
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
# Description: Kaspa Mining Script - Supports both solo mining and pool mining with JSON configuration
|
||||
# Usage: python3 miner-kaspa.py
|
||||
# Author: Justin Oros
|
||||
# Source: https://github.com/JustinOros
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import signal
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
class KaspaMiner:
|
||||
def __init__(self, config_file: str = "miner-kaspa.json"):
|
||||
self.config_file = config_file
|
||||
self.config = self.load_config()
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
|
||||
def load_config(self) -> Dict:
|
||||
"""Load configuration from JSON file"""
|
||||
if not os.path.exists(self.config_file):
|
||||
print(f"Error: Configuration file '{self.config_file}' not found.")
|
||||
print("Creating example configuration file...")
|
||||
self.create_example_config()
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with open(self.config_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
self.validate_config(config)
|
||||
return config
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON in configuration file: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error loading configuration: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def validate_config(self, config: Dict):
|
||||
"""Validate required configuration fields"""
|
||||
required_fields = ['wallet_address', 'mining_mode']
|
||||
|
||||
for field in required_fields:
|
||||
if field not in config:
|
||||
raise ValueError(f"Missing required field: {field}")
|
||||
|
||||
if config['mining_mode'] not in ['solo', 'pool']:
|
||||
raise ValueError("mining_mode must be 'solo' or 'pool'")
|
||||
|
||||
if config['mining_mode'] == 'pool' and 'pool_address' not in config:
|
||||
raise ValueError("pool_address is required for pool mining")
|
||||
|
||||
def create_example_config(self):
|
||||
"""Create an example configuration file"""
|
||||
example_config = {
|
||||
"wallet_address": "kaspa:qz1234567890abcdefghijklmnopqrstuvwxyz",
|
||||
"mining_mode": "solo",
|
||||
"miner_executable": "kaspa-miner",
|
||||
"kaspad_address": "127.0.0.1:16110",
|
||||
"pool_address": "pool.example.com:5555",
|
||||
"worker_name": "worker1",
|
||||
"threads": 0,
|
||||
"gpu_enable": True,
|
||||
"gpu_devices": [0],
|
||||
"intensity": 20,
|
||||
"extra_args": [],
|
||||
"log_file": "kaspa-miner.log"
|
||||
}
|
||||
|
||||
with open(self.config_file, 'w') as f:
|
||||
json.dump(example_config, f, indent=4)
|
||||
|
||||
print(f"Created example configuration file: {self.config_file}")
|
||||
print("Please edit it with your mining details.")
|
||||
|
||||
def build_command(self) -> list:
|
||||
"""Build the mining command based on configuration"""
|
||||
cmd = [self.config.get('miner_executable', 'kaspa-miner')]
|
||||
|
||||
# Add wallet address
|
||||
cmd.extend(['--mining-address', self.config['wallet_address']])
|
||||
|
||||
# Configure based on mining mode
|
||||
if self.config['mining_mode'] == 'solo':
|
||||
# Solo mining - connect to local kaspad
|
||||
kaspad_addr = self.config.get('kaspad_address', '127.0.0.1:16110')
|
||||
cmd.extend(['--kaspad-address', kaspad_addr])
|
||||
print(f"Solo mining mode - connecting to kaspad at {kaspad_addr}")
|
||||
else:
|
||||
# Pool mining
|
||||
pool_addr = self.config['pool_address']
|
||||
cmd.extend(['--pool', pool_addr])
|
||||
|
||||
# Add worker name if specified
|
||||
worker = self.config.get('worker_name')
|
||||
if worker:
|
||||
cmd.extend(['--worker-name', worker])
|
||||
|
||||
print(f"Pool mining mode - connecting to pool at {pool_addr}")
|
||||
|
||||
# Thread configuration
|
||||
threads = self.config.get('threads', 0)
|
||||
if threads > 0:
|
||||
cmd.extend(['--threads', str(threads)])
|
||||
|
||||
# GPU configuration
|
||||
if self.config.get('gpu_enable', True):
|
||||
gpu_devices = self.config.get('gpu_devices', [0])
|
||||
if gpu_devices:
|
||||
cmd.extend(['--cuda-devices', ','.join(map(str, gpu_devices))])
|
||||
|
||||
intensity = self.config.get('intensity')
|
||||
if intensity:
|
||||
cmd.extend(['--intensity', str(intensity)])
|
||||
else:
|
||||
cmd.append('--no-cuda')
|
||||
|
||||
# Add any extra arguments
|
||||
extra_args = self.config.get('extra_args', [])
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
|
||||
return cmd
|
||||
|
||||
def start_mining(self):
|
||||
"""Start the mining process"""
|
||||
cmd = self.build_command()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Kaspa Miner Starting")
|
||||
print("="*60)
|
||||
print(f"Wallet: {self.config['wallet_address']}")
|
||||
print(f"Mode: {self.config['mining_mode'].upper()}")
|
||||
print(f"Command: {' '.join(cmd)}")
|
||||
print("="*60 + "\n")
|
||||
|
||||
# Setup logging if specified
|
||||
log_file = self.config.get('log_file')
|
||||
log_handle = None
|
||||
|
||||
try:
|
||||
if log_file:
|
||||
log_handle = open(log_file, 'a')
|
||||
log_handle.write(f"\n{'='*60}\n")
|
||||
log_handle.write(f"Mining started at {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
log_handle.write(f"Command: {' '.join(cmd)}\n")
|
||||
log_handle.write(f"{'='*60}\n\n")
|
||||
log_handle.flush()
|
||||
|
||||
# Start the miner process
|
||||
self.process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE if log_handle else None,
|
||||
stderr=subprocess.STDOUT if log_handle else None,
|
||||
universal_newlines=True,
|
||||
bufsize=1
|
||||
)
|
||||
|
||||
# Monitor output if logging
|
||||
if log_handle and self.process.stdout:
|
||||
try:
|
||||
for line in self.process.stdout:
|
||||
print(line, end='')
|
||||
log_handle.write(line)
|
||||
log_handle.flush()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nReceived interrupt signal. Stopping miner...")
|
||||
self.stop_mining()
|
||||
else:
|
||||
# Wait for process to complete
|
||||
self.process.wait()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Miner executable '{cmd[0]}' not found.")
|
||||
print("Please ensure the miner is installed and the path is correct.")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error starting miner: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if log_handle:
|
||||
log_handle.close()
|
||||
|
||||
def stop_mining(self):
|
||||
"""Stop the mining process"""
|
||||
if self.process:
|
||||
print("Stopping miner...")
|
||||
self.process.terminate()
|
||||
try:
|
||||
self.process.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Force killing miner process...")
|
||||
self.process.kill()
|
||||
print("Miner stopped.")
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
"""Handle interrupt signals"""
|
||||
print("\n\nShutdown signal received...")
|
||||
sys.exit(0)
|
||||
|
||||
def main():
|
||||
# Register signal handlers
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Parse command line arguments
|
||||
config_file = "miner-kaspa.json"
|
||||
if len(sys.argv) > 1:
|
||||
config_file = sys.argv[1]
|
||||
|
||||
# Create and start miner
|
||||
miner = KaspaMiner(config_file)
|
||||
|
||||
try:
|
||||
miner.start_mining()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nInterrupted by user")
|
||||
miner.stop_mining()
|
||||
except Exception as e:
|
||||
print(f"\nError: {e}")
|
||||
miner.stop_mining()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user