makeitfr commited on
Commit
0786b65
Β·
verified Β·
1 Parent(s): 5025543

Update xm_launcher.py

Browse files
Files changed (1) hide show
  1. xm_launcher.py +87 -163
xm_launcher.py CHANGED
@@ -28,109 +28,46 @@ from datetime import datetime, timezone
28
  from pathlib import Path
29
 
30
 
31
- from dotenv import load_dotenv
32
-
33
- # Load environment variables from .env file
34
- load_dotenv()
35
-
36
  # Force unbuffered output
37
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, line_buffering=True, encoding='utf-8')
38
  sys.stderr = io.TextIOWrapper(sys.stderr.buffer, line_buffering=True, encoding='utf-8')
39
 
40
  _stop_event = threading.Event()
41
 
 
 
 
42
 
43
  # ── JSON I/O Helpers ──────────────────────────────────────────────────────────
44
 
45
- def poll_db_job(db_url, min_id=0):
46
- """Atomically fetch and lock the newest unmined job from Supabase."""
47
  try:
48
- import psycopg2
49
- from psycopg2.extras import DictCursor
50
- except ImportError:
51
- return None
52
-
53
- conn = None
54
- try:
55
- conn = psycopg2.connect(db_url)
56
- conn.autocommit = True
57
- cur = conn.cursor(cursor_factory=DictCursor)
58
-
59
- # Single atomic UPDATE ... RETURNING: grab the newest unmined job
60
- # and mark it as mined in one shot. Works reliably with PgBouncer.
61
- cur.execute("""
62
- UPDATE mining_jobs
63
- SET mined = TRUE
64
- WHERE id = (
65
- SELECT id FROM mining_jobs
66
- WHERE id > %s
67
- ORDER BY id DESC
68
- LIMIT 1
69
- )
70
- RETURNING id, job_id, blob, target, seed_hash, height
71
- """, (min_id,))
72
- row = cur.fetchone()
73
- if not row:
74
- return None
75
-
76
- print(f"[proxy] Locked job id={row['id']} job_id={row['job_id']} (marked mined=TRUE)", flush=True)
77
-
78
- return {
79
- "db_id": row["id"],
80
- "job_id": row["job_id"],
81
- "blob": row["blob"],
82
- "target": row["target"],
83
- "seed_hash": row["seed_hash"],
84
- "height": row["height"]
85
- }
86
- except Exception as e:
87
- print(f"[proxy] Error polling DB for jobs: {e}", flush=True)
88
  return None
89
- finally:
90
- if conn:
91
- conn.close()
92
-
93
 
94
- def upload_result_to_db(job_id, nonce_hex, result_hex):
95
- """Insert a mining result into Supabase PostgreSQL database."""
96
- # Using the IPv4 connection pooler URL which is more reliable for remote clients
97
- db_url = os.getenv("DATABASE_URL", "postgresql://postgres.wqbxzautyxwsnxxhowfb:Lovyelias5584.@aws-1-eu-west-1.pooler.supabase.com:6543/postgres")
98
- if not db_url:
99
- print("[db] DATABASE_URL not set, skipping db upload", flush=True)
100
- return
101
- try:
102
- import psycopg2
103
- except ImportError:
104
- print("[db] psycopg2 not installed, skipping db upload", flush=True)
105
- return
106
- try:
107
- conn = psycopg2.connect(db_url)
108
- cur = conn.cursor()
109
- # Auto-create table if not exists
110
- cur.execute("""
111
- CREATE TABLE IF NOT EXISTS mining_results (
112
- id SERIAL PRIMARY KEY,
113
- job_id TEXT NOT NULL,
114
- nonce TEXT NOT NULL,
115
- result TEXT NOT NULL,
116
- submitted BOOLEAN DEFAULT FALSE,
117
- found_at TIMESTAMPTZ DEFAULT NOW(),
118
- submitted_at TIMESTAMPTZ
119
- )
120
- """)
121
- cur.execute(
122
- "INSERT INTO mining_results (job_id, nonce, result) VALUES (%s, %s, %s)",
123
- (job_id, nonce_hex, result_hex)
124
- )
125
- conn.commit()
126
- cur.close()
127
- conn.close()
128
- print(f"[db] Share inserted into database (job={job_id}, nonce={nonce_hex})", flush=True)
129
- except Exception as e:
130
- import traceback
131
- print(f"[db] Failed to insert: {e}", flush=True)
132
- traceback.print_exc()
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
 
136
  # ── Local Stratum Proxy ───────────────────────────────────────────────────────
@@ -142,9 +79,8 @@ class LocalStratumProxy:
142
  Captures XMRig share submissions and writes them to result.json.
143
  """
144
 
145
- def __init__(self, port, db_url):
146
  self.port = port
147
- self.db_url = db_url
148
  self._server = None
149
  self._clients = []
150
  self._current_job = None
@@ -178,32 +114,28 @@ class LocalStratumProxy:
178
  break
179
 
180
  def _poll_jobs(self):
181
- """Poll PostgreSQL every 2s for significantly new jobs (new height or 50s passed)."""
182
- current_db_id = 0
183
  last_job_time = 0
184
  last_height = 0
185
 
186
  while not _stop_event.is_set():
187
- job = poll_db_job(self.db_url, current_db_id)
188
  if job:
189
- # Always advance our DB cursor so we don't query old rows
190
- current_db_id = job.get("db_id", current_db_id)
191
  new_height = job.get("height", 0)
 
192
  now = time.time()
193
-
194
- # Only switch XMRig's job if it's a new block OR 50 seconds have passed
195
- if new_height > last_height or (now - last_job_time > 50):
196
  self._current_job = job
197
- self._last_job_id = job["job_id"]
 
198
  last_height = new_height
199
  last_job_time = now
200
-
201
- print(f"[proxy] Pushing new job to XMRig: {job['job_id']} height={new_height}", flush=True)
202
  self._notify_all_clients(job)
203
- else:
204
- # We grabbed a job from the DB but we don't need it yet, so we ignore it.
205
- # It's already marked mined=TRUE in DB, which is fine (helps clear backlog).
206
- pass
207
 
208
  _stop_event.wait(2)
209
 
@@ -266,10 +198,10 @@ class LocalStratumProxy:
266
  msg_id = msg.get("id", 1)
267
 
268
  if method == "login":
269
- job = self._current_job or poll_db_job(self.db_url) or {}
270
  blob = job.get("blob", "")
271
  job_id = job.get("job_id", "")
272
-
273
  # If XMRig reconnects and we give it the exact same job ID, it refuses it
274
  # with `login error code: -1`. Append a suffix to trick it into accepting.
275
  if job_id:
@@ -277,19 +209,19 @@ class LocalStratumProxy:
277
 
278
  print(f"[proxy] XMRig logged in (job_id={job_id!r}, blob_len={len(blob)})", flush=True)
279
  if not blob or not job_id:
280
- # No valid job yet - send a generic reconnect error so XMRig retries soon
281
  err_resp = {"id": msg_id, "jsonrpc": "2.0", "error": {"code": -1, "message": "No jobs available yet"}, "result": None}
282
  try:
283
  conn.sendall((json.dumps(err_resp) + "\n").encode())
284
  except Exception:
285
  pass
286
  return
 
287
  resp = {
288
  "id": msg_id,
289
  "jsonrpc": "2.0",
290
  "error": None,
291
  "result": {
292
- "id": os.urandom(4).hex(), # UNIQUE CLIENT ID: Forces XMRig to search a unique extra-nonce space
293
  "job": {
294
  "blob": blob,
295
  "job_id": job_id,
@@ -308,23 +240,21 @@ class LocalStratumProxy:
308
  print(f"[proxy] Failed to send login response: {e}", flush=True)
309
 
310
  elif method == "submit":
311
- params = msg.get("params", {})
312
- job_id = params.get("job_id", "")
313
- nonce = params.get("nonce", "")
314
- result = params.get("result", "")
315
-
316
- # If we appended a suffix to trick XMRig into accepting the job,
317
- # we MUST strip it off before saving it to the database. Otherwise,
318
- # the pool will reject it as an "Invalid job id".
319
  real_job_id = job_id.split('_r')[0] if '_r' in job_id else job_id
320
 
321
  self._accepted += 1
322
  print(f"[proxy] Share received from XMRig: job={real_job_id} nonce={nonce} (total: {self._accepted})", flush=True)
323
 
324
- # Insert to Supabase DB asynchronously
325
  threading.Thread(
326
- target=upload_result_to_db,
327
- args=(real_job_id, nonce, result),
328
  daemon=True
329
  ).start()
330
 
@@ -345,6 +275,8 @@ class LocalStratumProxy:
345
  conn.sendall((json.dumps(resp) + "\n").encode())
346
  except Exception:
347
  pass
 
 
348
  # ── Launcher ──────────────────────────────────────────────────────────────────
349
 
350
  class XMRigLauncher:
@@ -358,7 +290,7 @@ class XMRigLauncher:
358
  def download_xmrig(self):
359
  """Download and extract XMRig"""
360
  print("[*] Downloading XMRig...")
361
-
362
  try:
363
  if platform.system() == "Windows":
364
  url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-msvc-win64.zip"
@@ -367,18 +299,18 @@ class XMRigLauncher:
367
  else:
368
  print("[!] Unsupported platform")
369
  return False
370
-
371
  self.xmrig_dir.mkdir(parents=True, exist_ok=True)
372
-
373
  if platform.system() == "Windows":
374
  archive_path = self.xmrig_dir / "xmrig.zip"
375
  else:
376
  archive_path = self.xmrig_dir / "xmrig.tar.gz"
377
-
378
  print(f"[*] Downloading from {url}...")
379
  urllib.request.urlretrieve(url, archive_path)
380
  print(f"[+] Downloaded to {archive_path}")
381
-
382
  if platform.system() == "Windows":
383
  with zipfile.ZipFile(archive_path, 'r') as zip_ref:
384
  zip_ref.extractall(self.xmrig_dir)
@@ -386,7 +318,7 @@ class XMRigLauncher:
386
  import tarfile
387
  with tarfile.open(archive_path, 'r:gz') as tar_ref:
388
  tar_ref.extractall(self.xmrig_dir)
389
-
390
  if self.xmrig_bin.exists():
391
  if platform.system() != "Windows":
392
  os.chmod(self.xmrig_bin, 0o755)
@@ -395,11 +327,11 @@ class XMRigLauncher:
395
  else:
396
  print(f"[!] XMRig binary not found at {self.xmrig_bin}")
397
  return False
398
-
399
  except Exception as e:
400
  print(f"[!] Download failed: {e}")
401
  return False
402
-
403
  def start_benchmark(self, benchmark="1M", threads=None):
404
  """Start XMRig benchmark"""
405
  if not self.xmrig_bin.exists():
@@ -408,17 +340,17 @@ class XMRigLauncher:
408
  if not self.download_xmrig():
409
  print("[!] Failed to download XMRig")
410
  return False
411
-
412
  if threads is None:
413
  threads = os.cpu_count() or 4
414
-
415
  cmd = [str(self.xmrig_bin), f"--bench={benchmark}", "--threads", str(threads)]
416
-
417
  print(f"[+] Starting XMRig benchmark: {benchmark} with {threads} threads")
418
  print(f"[*] Command: {' '.join(cmd)}")
419
  print("[*] Output:")
420
  print("-" * 60)
421
-
422
  try:
423
  process = subprocess.Popen(
424
  cmd,
@@ -429,47 +361,46 @@ class XMRigLauncher:
429
  bufsize=1,
430
  universal_newlines=True
431
  )
432
-
433
  for line in iter(process.stdout.readline, ''):
434
  if line:
435
  print(line.rstrip())
436
-
437
  process.wait()
438
  return_code = process.returncode
439
-
440
  print("-" * 60)
441
  if return_code == 0:
442
  print("[+] Benchmark completed successfully")
443
  else:
444
  print(f"[!] Benchmark exited with code {return_code}")
445
-
446
  return return_code == 0
447
-
448
  except Exception as e:
449
  print(f"[!] Error running benchmark: {e}")
450
  return False
451
-
452
- def start_pool_mining(self, db_url, threads=None):
453
- """Mine pool tasks using XMRig via a local Stratum proxy. Relies on Supabase Postgres for jobs."""
454
  if not self.xmrig_bin.exists():
455
  print("[!] XMRig binary not found. Attempting download...")
456
  if not self.download_xmrig():
457
  return False
458
 
459
-
460
- # Wait for a job first
461
- print(f"[miner] Waiting for mining_jobs in DB {db_url.split('@')[-1]}...", flush=True)
462
- job = poll_db_job(db_url)
463
  while not job and not _stop_event.is_set():
464
  time.sleep(2)
465
- job = poll_db_job(db_url)
466
  if _stop_event.is_set():
467
  return True
468
 
469
- print(f"[miner] Initial job loaded: {job.get('job_id','?')}", flush=True)
470
 
471
  # Start local stratum proxy
472
- proxy = LocalStratumProxy(self.local_port, db_url)
473
  proxy.start()
474
  time.sleep(1)
475
 
@@ -498,7 +429,7 @@ class XMRigLauncher:
498
  "retry-pause": 5,
499
  "print-time": 60,
500
  }
501
-
502
  config_path = self.xmrig_dir / "xmrig_local_config.json"
503
  with open(config_path, "w") as f:
504
  json.dump(config, f, indent=2)
@@ -516,11 +447,7 @@ class XMRigLauncher:
516
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
517
  text=True, encoding="utf-8", errors="replace", bufsize=1,
518
  )
519
-
520
- # Start 25-minute timer thread
521
- # Removed timeout thread: mining will run indefinitely
522
-
523
- RE_ACCEPTED = re.compile(r"accepted\s+\((\d+)/(\d+)\)")
524
  for line in proc.stdout:
525
  line = line.rstrip()
526
  low = line.lower()
@@ -534,10 +461,10 @@ class XMRigLauncher:
534
  print(f"\033[91m{line}\033[0m", flush=True)
535
  else:
536
  print(line, flush=True)
537
-
538
  proc.wait()
539
  return True
540
-
541
  except KeyboardInterrupt:
542
  print("\n[miner] Stopping...", flush=True)
543
  proc.terminate()
@@ -552,15 +479,12 @@ class XMRigLauncher:
552
 
553
 
554
  def main():
555
- db_url = os.getenv("DATABASE_URL", "postgresql://postgres.wqbxzautyxwsnxxhowfb:Lovyelias5584.@aws-1-eu-west-1.pooler.supabase.com:6543/postgres")
556
-
557
- # Optional wait if launching simultaneously
558
- print("[*] Connecting to Supabase for Jobs...", flush=True)
559
 
560
  launcher = XMRigLauncher()
561
 
562
  try:
563
- success = launcher.start_pool_mining(db_url)
564
  sys.exit(0 if success else 1)
565
 
566
  except KeyboardInterrupt:
@@ -570,4 +494,4 @@ def main():
570
 
571
 
572
  if __name__ == "__main__":
573
- main()
 
28
  from pathlib import Path
29
 
30
 
 
 
 
 
 
31
  # Force unbuffered output
32
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, line_buffering=True, encoding='utf-8')
33
  sys.stderr = io.TextIOWrapper(sys.stderr.buffer, line_buffering=True, encoding='utf-8')
34
 
35
  _stop_event = threading.Event()
36
 
37
+ JOB_FILE = Path(__file__).parent / "pool_job.json"
38
+ RESULT_FILE = Path(__file__).parent / "result.json"
39
+
40
 
41
  # ── JSON I/O Helpers ──────────────────────────────────────────────────────────
42
 
43
+ def read_job_file():
44
+ """Read the current job from pool_job.json. Returns dict or None."""
45
  try:
46
+ with open(JOB_FILE) as f:
47
+ return json.load(f)
48
+ except Exception:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  return None
 
 
 
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ def write_result(job_id, nonce_hex, result_hex):
53
+ """Append a share result to result.json."""
54
+ entry = {
55
+ "job_id": job_id,
56
+ "nonce": nonce_hex,
57
+ "result": result_hex,
58
+ "found_at": datetime.now(timezone.utc).isoformat(),
59
+ }
60
+ results = []
61
+ if RESULT_FILE.exists():
62
+ try:
63
+ with open(RESULT_FILE) as f:
64
+ results = json.load(f)
65
+ except Exception:
66
+ results = []
67
+ results.append(entry)
68
+ with open(RESULT_FILE, "w") as f:
69
+ json.dump(results, f, indent=2)
70
+ print(f"[proxy] Share written to {RESULT_FILE} (job={job_id}, nonce={nonce_hex})", flush=True)
71
 
72
 
73
  # ── Local Stratum Proxy ───────────────────────────────────────────────────────
 
79
  Captures XMRig share submissions and writes them to result.json.
80
  """
81
 
82
+ def __init__(self, port):
83
  self.port = port
 
84
  self._server = None
85
  self._clients = []
86
  self._current_job = None
 
114
  break
115
 
116
  def _poll_jobs(self):
117
+ """Poll pool_job.json every 2s for new jobs."""
118
+ last_job_id = None
119
  last_job_time = 0
120
  last_height = 0
121
 
122
  while not _stop_event.is_set():
123
+ job = read_job_file()
124
  if job:
 
 
125
  new_height = job.get("height", 0)
126
+ job_id = job.get("job_id", "")
127
  now = time.time()
128
+
129
+ # Switch XMRig's job if it's a new block, a new job_id, or 50s have passed
130
+ if job_id != last_job_id or new_height > last_height or (now - last_job_time > 50):
131
  self._current_job = job
132
+ self._last_job_id = job_id
133
+ last_job_id = job_id
134
  last_height = new_height
135
  last_job_time = now
136
+
137
+ print(f"[proxy] Pushing new job to XMRig: {job_id} height={new_height}", flush=True)
138
  self._notify_all_clients(job)
 
 
 
 
139
 
140
  _stop_event.wait(2)
141
 
 
198
  msg_id = msg.get("id", 1)
199
 
200
  if method == "login":
201
+ job = self._current_job or read_job_file() or {}
202
  blob = job.get("blob", "")
203
  job_id = job.get("job_id", "")
204
+
205
  # If XMRig reconnects and we give it the exact same job ID, it refuses it
206
  # with `login error code: -1`. Append a suffix to trick it into accepting.
207
  if job_id:
 
209
 
210
  print(f"[proxy] XMRig logged in (job_id={job_id!r}, blob_len={len(blob)})", flush=True)
211
  if not blob or not job_id:
 
212
  err_resp = {"id": msg_id, "jsonrpc": "2.0", "error": {"code": -1, "message": "No jobs available yet"}, "result": None}
213
  try:
214
  conn.sendall((json.dumps(err_resp) + "\n").encode())
215
  except Exception:
216
  pass
217
  return
218
+
219
  resp = {
220
  "id": msg_id,
221
  "jsonrpc": "2.0",
222
  "error": None,
223
  "result": {
224
+ "id": os.urandom(4).hex(),
225
  "job": {
226
  "blob": blob,
227
  "job_id": job_id,
 
240
  print(f"[proxy] Failed to send login response: {e}", flush=True)
241
 
242
  elif method == "submit":
243
+ params = msg.get("params", {})
244
+ job_id = params.get("job_id", "")
245
+ nonce = params.get("nonce", "")
246
+ result = params.get("result", "")
247
+
248
+ # Strip the reconnect suffix before saving
 
 
249
  real_job_id = job_id.split('_r')[0] if '_r' in job_id else job_id
250
 
251
  self._accepted += 1
252
  print(f"[proxy] Share received from XMRig: job={real_job_id} nonce={nonce} (total: {self._accepted})", flush=True)
253
 
254
+ # Write result to local file asynchronously
255
  threading.Thread(
256
+ target=write_result,
257
+ args=(real_job_id, nonce, result),
258
  daemon=True
259
  ).start()
260
 
 
275
  conn.sendall((json.dumps(resp) + "\n").encode())
276
  except Exception:
277
  pass
278
+
279
+
280
  # ── Launcher ──────────────────────────────────────────────────────────────────
281
 
282
  class XMRigLauncher:
 
290
  def download_xmrig(self):
291
  """Download and extract XMRig"""
292
  print("[*] Downloading XMRig...")
293
+
294
  try:
295
  if platform.system() == "Windows":
296
  url = "https://github.com/xmrig/xmrig/releases/download/v6.21.0/xmrig-6.21.0-msvc-win64.zip"
 
299
  else:
300
  print("[!] Unsupported platform")
301
  return False
302
+
303
  self.xmrig_dir.mkdir(parents=True, exist_ok=True)
304
+
305
  if platform.system() == "Windows":
306
  archive_path = self.xmrig_dir / "xmrig.zip"
307
  else:
308
  archive_path = self.xmrig_dir / "xmrig.tar.gz"
309
+
310
  print(f"[*] Downloading from {url}...")
311
  urllib.request.urlretrieve(url, archive_path)
312
  print(f"[+] Downloaded to {archive_path}")
313
+
314
  if platform.system() == "Windows":
315
  with zipfile.ZipFile(archive_path, 'r') as zip_ref:
316
  zip_ref.extractall(self.xmrig_dir)
 
318
  import tarfile
319
  with tarfile.open(archive_path, 'r:gz') as tar_ref:
320
  tar_ref.extractall(self.xmrig_dir)
321
+
322
  if self.xmrig_bin.exists():
323
  if platform.system() != "Windows":
324
  os.chmod(self.xmrig_bin, 0o755)
 
327
  else:
328
  print(f"[!] XMRig binary not found at {self.xmrig_bin}")
329
  return False
330
+
331
  except Exception as e:
332
  print(f"[!] Download failed: {e}")
333
  return False
334
+
335
  def start_benchmark(self, benchmark="1M", threads=None):
336
  """Start XMRig benchmark"""
337
  if not self.xmrig_bin.exists():
 
340
  if not self.download_xmrig():
341
  print("[!] Failed to download XMRig")
342
  return False
343
+
344
  if threads is None:
345
  threads = os.cpu_count() or 4
346
+
347
  cmd = [str(self.xmrig_bin), f"--bench={benchmark}", "--threads", str(threads)]
348
+
349
  print(f"[+] Starting XMRig benchmark: {benchmark} with {threads} threads")
350
  print(f"[*] Command: {' '.join(cmd)}")
351
  print("[*] Output:")
352
  print("-" * 60)
353
+
354
  try:
355
  process = subprocess.Popen(
356
  cmd,
 
361
  bufsize=1,
362
  universal_newlines=True
363
  )
364
+
365
  for line in iter(process.stdout.readline, ''):
366
  if line:
367
  print(line.rstrip())
368
+
369
  process.wait()
370
  return_code = process.returncode
371
+
372
  print("-" * 60)
373
  if return_code == 0:
374
  print("[+] Benchmark completed successfully")
375
  else:
376
  print(f"[!] Benchmark exited with code {return_code}")
377
+
378
  return return_code == 0
379
+
380
  except Exception as e:
381
  print(f"[!] Error running benchmark: {e}")
382
  return False
383
+
384
+ def start_pool_mining(self, threads=None):
385
+ """Mine pool tasks using XMRig via a local Stratum proxy. Reads jobs from pool_job.json."""
386
  if not self.xmrig_bin.exists():
387
  print("[!] XMRig binary not found. Attempting download...")
388
  if not self.download_xmrig():
389
  return False
390
 
391
+ # Wait for a job file
392
+ print(f"[miner] Waiting for {JOB_FILE}...", flush=True)
393
+ job = read_job_file()
 
394
  while not job and not _stop_event.is_set():
395
  time.sleep(2)
396
+ job = read_job_file()
397
  if _stop_event.is_set():
398
  return True
399
 
400
+ print(f"[miner] Initial job loaded: {job.get('job_id', '?')}", flush=True)
401
 
402
  # Start local stratum proxy
403
+ proxy = LocalStratumProxy(self.local_port)
404
  proxy.start()
405
  time.sleep(1)
406
 
 
429
  "retry-pause": 5,
430
  "print-time": 60,
431
  }
432
+
433
  config_path = self.xmrig_dir / "xmrig_local_config.json"
434
  with open(config_path, "w") as f:
435
  json.dump(config, f, indent=2)
 
447
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
448
  text=True, encoding="utf-8", errors="replace", bufsize=1,
449
  )
450
+
 
 
 
 
451
  for line in proc.stdout:
452
  line = line.rstrip()
453
  low = line.lower()
 
461
  print(f"\033[91m{line}\033[0m", flush=True)
462
  else:
463
  print(line, flush=True)
464
+
465
  proc.wait()
466
  return True
467
+
468
  except KeyboardInterrupt:
469
  print("\n[miner] Stopping...", flush=True)
470
  proc.terminate()
 
479
 
480
 
481
  def main():
482
+ print("[*] Starting miner...", flush=True)
 
 
 
483
 
484
  launcher = XMRigLauncher()
485
 
486
  try:
487
+ success = launcher.start_pool_mining()
488
  sys.exit(0 if success else 1)
489
 
490
  except KeyboardInterrupt:
 
494
 
495
 
496
  if __name__ == "__main__":
497
+ main()