Upload 4 files
Browse files- node_list.py +8 -0
- requirements.txt +3 -0
- solo_miner.py +204 -0
- xmrig-config.json.template +25 -0
node_list.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# List of reliable public Monero nodes
|
| 2 |
+
PUBLIC_NODES = [
|
| 3 |
+
"https://node.moneroworld.com:18089",
|
| 4 |
+
"https://node.supportxmr.com:18089",
|
| 5 |
+
"https://node.xmr.ru:18081",
|
| 6 |
+
"https://node.c3pool.com:18089",
|
| 7 |
+
"https://node.xmr.to:18081"
|
| 8 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
py-cryptonight>=0.2.0
|
| 2 |
+
requests>=2.25.1
|
| 3 |
+
rich>=10.0.0
|
solo_miner.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import json
|
| 5 |
+
import random
|
| 6 |
+
import requests
|
| 7 |
+
import platform
|
| 8 |
+
import threading
|
| 9 |
+
import subprocess
|
| 10 |
+
import multiprocessing
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
from typing import Dict, Optional, Tuple
|
| 14 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 15 |
+
from urllib.parse import urlparse
|
| 16 |
+
try:
|
| 17 |
+
from cryptonight import cryptonight
|
| 18 |
+
except ImportError:
|
| 19 |
+
print("Error: py-cryptonight not installed. Please install Python and run:")
|
| 20 |
+
print("pip install py-cryptonight requests rich")
|
| 21 |
+
sys.exit(1)
|
| 22 |
+
|
| 23 |
+
class MoneroNodeManager:
|
| 24 |
+
def __init__(self):
|
| 25 |
+
self.daemon_url = None
|
| 26 |
+
|
| 27 |
+
def find_fastest_node(self) -> str:
|
| 28 |
+
"""Find the fastest responding public node"""
|
| 29 |
+
from node_list import PUBLIC_NODES
|
| 30 |
+
best_latency = float('inf')
|
| 31 |
+
best_node = None
|
| 32 |
+
|
| 33 |
+
print("Finding fastest public node...")
|
| 34 |
+
for node in PUBLIC_NODES:
|
| 35 |
+
try:
|
| 36 |
+
start_time = time.time()
|
| 37 |
+
response = requests.get(f"{node}/get_info", timeout=5)
|
| 38 |
+
if response.status_code == 200:
|
| 39 |
+
latency = time.time() - start_time
|
| 40 |
+
if latency < best_latency:
|
| 41 |
+
best_latency = latency
|
| 42 |
+
best_node = node
|
| 43 |
+
print(f"Node {node} responded in {latency:.2f}s")
|
| 44 |
+
except:
|
| 45 |
+
print(f"Node {node} not responding")
|
| 46 |
+
continue
|
| 47 |
+
|
| 48 |
+
if best_node:
|
| 49 |
+
print(f"\nSelected fastest node: {best_node} (latency: {best_latency:.2f}s)")
|
| 50 |
+
return best_node
|
| 51 |
+
raise Exception("No responsive public nodes found")
|
| 52 |
+
|
| 53 |
+
class MoneroSoloMiner:
|
| 54 |
+
def __init__(self, daemon_url: str = None, threads: int = None):
|
| 55 |
+
self.node_manager = MoneroNodeManager()
|
| 56 |
+
self.daemon_url = daemon_url or self.node_manager.find_fastest_node()
|
| 57 |
+
self.threads = threads or (multiprocessing.cpu_count() - 1)
|
| 58 |
+
self.running = False
|
| 59 |
+
self.wallet_address = None
|
| 60 |
+
self.stats = {
|
| 61 |
+
"start_time": None,
|
| 62 |
+
"hashes": 0,
|
| 63 |
+
"blocks_found": 0,
|
| 64 |
+
"best_diff": 0,
|
| 65 |
+
"node_height": 0,
|
| 66 |
+
"network_height": 0
|
| 67 |
+
}
|
| 68 |
+
self._lock = threading.Lock()
|
| 69 |
+
|
| 70 |
+
def get_block_template(self) -> Optional[Dict]:
|
| 71 |
+
try:
|
| 72 |
+
payload = {
|
| 73 |
+
"jsonrpc": "2.0",
|
| 74 |
+
"id": "0",
|
| 75 |
+
"method": "get_block_template",
|
| 76 |
+
"params": {
|
| 77 |
+
"wallet_address": self.wallet_address,
|
| 78 |
+
"reserve_size": 1
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
response = requests.post(self.daemon_url + "/json_rpc", json=payload)
|
| 82 |
+
result = response.json().get("result", {})
|
| 83 |
+
return result
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f"Error getting block template: {e}")
|
| 86 |
+
return None
|
| 87 |
+
|
| 88 |
+
def submit_block(self, block_blob: str) -> bool:
|
| 89 |
+
try:
|
| 90 |
+
payload = {
|
| 91 |
+
"jsonrpc": "2.0",
|
| 92 |
+
"id": "0",
|
| 93 |
+
"method": "submit_block",
|
| 94 |
+
"params": [block_blob]
|
| 95 |
+
}
|
| 96 |
+
response = requests.post(self.daemon_url + "/json_rpc", json=payload)
|
| 97 |
+
result = response.json()
|
| 98 |
+
if "error" not in result:
|
| 99 |
+
return True
|
| 100 |
+
print(f"Block submission error: {result['error']}")
|
| 101 |
+
return False
|
| 102 |
+
except Exception as e:
|
| 103 |
+
print(f"Error submitting block: {e}")
|
| 104 |
+
return False
|
| 105 |
+
|
| 106 |
+
def mine_block(self, block_template: Dict):
|
| 107 |
+
blob = block_template["blockhashing_blob"]
|
| 108 |
+
difficulty = int(block_template["difficulty"])
|
| 109 |
+
height = block_template["height"]
|
| 110 |
+
|
| 111 |
+
while self.running:
|
| 112 |
+
nonce = random.getrandbits(32)
|
| 113 |
+
blob = blob[:78] + hex(nonce)[2:].zfill(8) + blob[86:]
|
| 114 |
+
|
| 115 |
+
hash_result = cryptonight(bytes.fromhex(blob))
|
| 116 |
+
result_num = int.from_bytes(hash_result, byteorder='little')
|
| 117 |
+
|
| 118 |
+
with self._lock:
|
| 119 |
+
self.stats["hashes"] += 1
|
| 120 |
+
if result_num < difficulty:
|
| 121 |
+
print(f"\nBlock found at height {height}!")
|
| 122 |
+
self.stats["blocks_found"] += 1
|
| 123 |
+
if self.submit_block(blob):
|
| 124 |
+
print("Block successfully submitted!")
|
| 125 |
+
return True
|
| 126 |
+
|
| 127 |
+
current_diff = (2**256 - 1) // result_num
|
| 128 |
+
if current_diff > self.stats["best_diff"]:
|
| 129 |
+
self.stats["best_diff"] = current_diff
|
| 130 |
+
|
| 131 |
+
def display_stats(self):
|
| 132 |
+
while self.running:
|
| 133 |
+
elapsed = time.time() - self.stats["start_time"]
|
| 134 |
+
hashrate = self.stats["hashes"] / elapsed if elapsed > 0 else 0
|
| 135 |
+
|
| 136 |
+
print("\033[H\033[J") # Clear screen
|
| 137 |
+
print(f"=== Monero Solo Mining Stats ===")
|
| 138 |
+
print(f"Runtime: {elapsed:.1f} seconds")
|
| 139 |
+
print(f"Hashrate: {hashrate:.2f} H/s")
|
| 140 |
+
print(f"Total hashes: {self.stats['hashes']}")
|
| 141 |
+
print(f"Blocks found: {self.stats['blocks_found']}")
|
| 142 |
+
print(f"Best diff: {self.stats['best_diff']}")
|
| 143 |
+
print(f"Mining with {self.threads} threads")
|
| 144 |
+
print("Press Ctrl+C to stop mining")
|
| 145 |
+
|
| 146 |
+
time.sleep(1)
|
| 147 |
+
|
| 148 |
+
def start_mining(self):
|
| 149 |
+
print("Starting Monero solo mining...")
|
| 150 |
+
self.running = True
|
| 151 |
+
self.stats["start_time"] = time.time()
|
| 152 |
+
|
| 153 |
+
# Start stats display in a separate thread
|
| 154 |
+
stats_thread = threading.Thread(target=self.display_stats)
|
| 155 |
+
stats_thread.daemon = True
|
| 156 |
+
stats_thread.start()
|
| 157 |
+
|
| 158 |
+
while self.running:
|
| 159 |
+
template = self.get_block_template()
|
| 160 |
+
if not template:
|
| 161 |
+
print("Failed to get block template. Retrying in 10 seconds...")
|
| 162 |
+
time.sleep(10)
|
| 163 |
+
continue
|
| 164 |
+
|
| 165 |
+
with ThreadPoolExecutor(max_workers=self.threads) as executor:
|
| 166 |
+
futures = [executor.submit(self.mine_block, template) for _ in range(self.threads)]
|
| 167 |
+
for future in futures:
|
| 168 |
+
if future.result():
|
| 169 |
+
break
|
| 170 |
+
|
| 171 |
+
def stop_mining(self):
|
| 172 |
+
self.running = False
|
| 173 |
+
|
| 174 |
+
if __name__ == "__main__":
|
| 175 |
+
import argparse
|
| 176 |
+
|
| 177 |
+
parser = argparse.ArgumentParser(description='Monero Solo Miner')
|
| 178 |
+
parser.add_argument('--wallet', help='Your Monero wallet address (optional for testing)')
|
| 179 |
+
parser.add_argument('--threads', type=int, help='Number of mining threads')
|
| 180 |
+
parser.add_argument('--node', help='Custom node URL (optional)')
|
| 181 |
+
|
| 182 |
+
args = parser.parse_args()
|
| 183 |
+
|
| 184 |
+
try:
|
| 185 |
+
miner = MoneroSoloMiner(
|
| 186 |
+
daemon_url=args.node,
|
| 187 |
+
threads=args.threads
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
# Use test wallet if none provided
|
| 191 |
+
miner.wallet_address = args.wallet or "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A" # Test wallet
|
| 192 |
+
|
| 193 |
+
print(f"Mining with {miner.threads} threads on node: {miner.daemon_url}")
|
| 194 |
+
miner.start_mining()
|
| 195 |
+
|
| 196 |
+
except KeyboardInterrupt:
|
| 197 |
+
print("\nStopping miner...")
|
| 198 |
+
miner.stop_mining()
|
| 199 |
+
print("\nMining stopped. Summary:")
|
| 200 |
+
print(f"Total hashes: {miner.stats['hashes']}")
|
| 201 |
+
print(f"Blocks found: {miner.stats['blocks_found']}")
|
| 202 |
+
except Exception as e:
|
| 203 |
+
print(f"Error: {e}")
|
| 204 |
+
sys.exit(1)
|
xmrig-config.json.template
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"algo": "rx/0",
|
| 3 |
+
"autosave": true,
|
| 4 |
+
"cpu": true,
|
| 5 |
+
"opencl": false,
|
| 6 |
+
"cuda": false,
|
| 7 |
+
"pools": [
|
| 8 |
+
{
|
| 9 |
+
"url": "127.0.0.1:18081",
|
| 10 |
+
"user": "{{WALLET_ADDRESS}}",
|
| 11 |
+
"pass": "x",
|
| 12 |
+
"keepalive": true,
|
| 13 |
+
"rig-id": "solo",
|
| 14 |
+
"nicehash": false,
|
| 15 |
+
"enabled": true
|
| 16 |
+
}
|
| 17 |
+
],
|
| 18 |
+
"api": {
|
| 19 |
+
"id": null,
|
| 20 |
+
"worker-id": null,
|
| 21 |
+
"port": {{API_PORT}},
|
| 22 |
+
"access-token": null,
|
| 23 |
+
"restricted": true
|
| 24 |
+
}
|
| 25 |
+
}
|