Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
revoxhere
GitHub Repository: revoxhere/duino-coin
Path: blob/master/PC_Miner.py
920 views
1
#!/usr/bin/env python3
2
"""
3
Duino-Coin Official PC Miner 4.3 © MIT licensed
4
https://duinocoin.com
5
https://github.com/revoxhere/duino-coin
6
Duino-Coin Team & Community 2019-2025
7
"""
8
9
from time import time, sleep, strptime, ctime, time_ns
10
from hashlib import sha1
11
from socket import socket
12
13
from multiprocessing import cpu_count, current_process
14
from multiprocessing import Process, Manager, Semaphore
15
from threading import Thread, Lock
16
from datetime import datetime
17
from random import randint
18
19
from os import execl, mkdir, _exit
20
from os import name as osname
21
from os import system as ossystem
22
from subprocess import DEVNULL, Popen, check_call, PIPE
23
import pip
24
import sys
25
import base64 as b64
26
import os
27
import json
28
import zipfile
29
import traceback
30
import urllib.parse
31
32
from pathlib import Path
33
from re import sub
34
from random import choice
35
from platform import machine as osprocessor
36
from platform import python_version_tuple
37
from platform import python_version
38
39
from signal import SIGINT, signal
40
from locale import getdefaultlocale
41
from configparser import ConfigParser
42
43
import io
44
45
debug = "n"
46
running_on_rpi = False
47
configparser = ConfigParser()
48
printlock = Lock()
49
50
# Python <3.5 check
51
f"Your Python version is too old. Duino-Coin Miner requires version 3.6 or above. Update your packages and try again"
52
53
54
def handler(signal_received, frame):
55
"""
56
Nicely handle CTRL+C exit
57
"""
58
if current_process().name == "MainProcess":
59
pretty_print(
60
get_string("sigint_detected")
61
+ Style.NORMAL
62
+ Fore.RESET
63
+ get_string("goodbye"),
64
"warning")
65
66
if not "raspi_leds" in user_settings:
67
user_settings["raspi_leds"] = "y"
68
69
if running_on_rpi and user_settings["raspi_leds"] == "y":
70
# Reset onboard status LEDs
71
os.system(
72
'echo mmc0 | sudo tee /sys/class/leds/led0/trigger >/dev/null 2>&1')
73
os.system(
74
'echo 1 | sudo tee /sys/class/leds/led1/brightness >/dev/null 2>&1')
75
76
if sys.platform == "win32":
77
_exit(0)
78
else:
79
Popen("kill $(ps aux | grep PC_Miner | awk '{print $2}')",
80
shell=True, stdout=PIPE)
81
82
83
def debug_output(text: str):
84
if debug == 'y':
85
print(Style.RESET_ALL + Fore.WHITE
86
+ now().strftime(Style.DIM + '%H:%M:%S.%f ')
87
+ Style.NORMAL + f'DEBUG: {text}')
88
89
90
def install(package):
91
"""
92
Automatically installs python pip package and restarts the program
93
"""
94
try:
95
pip.main(["install", package])
96
except AttributeError:
97
check_call([sys.executable, '-m', 'pip', 'install', package])
98
99
execl(sys.executable, sys.executable, *sys.argv)
100
101
try:
102
import requests
103
except ModuleNotFoundError:
104
print("Requests is not installed. "
105
+ "Miner will try to automatically install it "
106
+ "If it fails, please manually execute "
107
+ "python3 -m pip install requests")
108
install("requests")
109
110
try:
111
from colorama import Back, Fore, Style, init
112
init(autoreset=True)
113
except ModuleNotFoundError:
114
print("Colorama is not installed. "
115
+ "Miner will try to automatically install it "
116
+ "If it fails, please manually execute "
117
+ "python3 -m pip install colorama")
118
install("colorama")
119
120
try:
121
import cpuinfo
122
except ModuleNotFoundError:
123
print("Cpuinfo is not installed. "
124
+ "Miner will try to automatically install it "
125
+ "If it fails, please manually execute "
126
+ "python3 -m pip install py-cpuinfo")
127
install("py-cpuinfo")
128
129
try:
130
import psutil
131
except ModuleNotFoundError:
132
print("Psutil is not installed. "
133
+ "Miner will try to automatically install it "
134
+ "If it fails, please manually execute "
135
+ "python3 -m pip install psutil")
136
install("psutil")
137
138
try:
139
from pypresence import Presence
140
except ModuleNotFoundError:
141
print("Pypresence is not installed. "
142
+ "Miner will try to automatically install it "
143
+ "If it fails, please manually execute "
144
+ "python3 -m pip install pypresence")
145
install("pypresence")
146
147
148
class Settings:
149
"""
150
Class containing default miner and server settings
151
"""
152
ENCODING = "UTF8"
153
SEPARATOR = ","
154
VER = 4.3
155
DATA_DIR = "Duino-Coin PC Miner " + str(VER)
156
TRANSLATIONS = ("https://raw.githubusercontent.com/"
157
+ "revoxhere/"
158
+ "duino-coin/master/Resources/"
159
+ "PC_Miner_langs.json")
160
TRANSLATIONS_FILE = "/Translations.json"
161
SETTINGS_FILE = "/Settings.cfg"
162
TEMP_FOLDER = "Temp"
163
164
SOC_TIMEOUT = 10
165
REPORT_TIME = 300
166
DONATE_LVL = 0
167
RASPI_LEDS = "y"
168
RASPI_CPU_IOT = "y"
169
disable_title = False
170
171
try:
172
# Raspberry Pi latin encoding users can't display this character
173
BLOCK = " ‖ "
174
"‖".encode(sys.stdout.encoding)
175
except:
176
BLOCK = " | "
177
PICK = ""
178
COG = " @"
179
if (os.name != "nt"
180
or bool(os.name == "nt"
181
and os.environ.get("WT_SESSION"))):
182
# Windows' cmd does not support emojis, shame!
183
# Same for different encodinsg, for example the latin encoding doesn't support them
184
try:
185
"⛏ ⚙".encode(sys.stdout.encoding) # if the terminal support emoji
186
PICK = " ⛏"
187
COG = " ⚙"
188
except UnicodeEncodeError: # else
189
PICK = ""
190
COG = " @"
191
192
193
def title(title: str):
194
if not Settings.disable_title:
195
if osname == 'nt':
196
"""
197
Changing the title in Windows' cmd
198
is easy - just use the built-in
199
title command
200
"""
201
ossystem('title ' + title)
202
else:
203
"""
204
Most *nix terminals use
205
this escape sequence to change
206
the console window title
207
"""
208
try:
209
print('\33]0;' + title + '\a', end='')
210
sys.stdout.flush()
211
except Exception as e:
212
debug_output("Error setting title: " +str(e))
213
Settings.disable_title = True
214
215
216
def check_updates():
217
"""
218
Function that checks if the miner is updated.
219
Downloads the new version and restarts the miner.
220
"""
221
try:
222
data = requests.get(
223
"https://api.github.com/repos/revoxhere/duino-coin/releases/latest"
224
).json()
225
226
zip_file = "Duino-Coin_" + data["tag_name"] + "_linux.zip"
227
if sys.platform == "win32":
228
zip_file = "Duino-Coin_" + data["tag_name"] + "_windows.zip"
229
230
process = psutil.Process(os.getpid())
231
running_script = False # If the process is from script
232
if "python" in process.name():
233
running_script = True
234
235
if float(Settings.VER) < float(data["tag_name"]): # If is outdated
236
update = input(Style.BRIGHT + get_string("new_version"))
237
if update.lower() == "y" or update == "":
238
pretty_print(get_string("updating"), "warning", "sys0")
239
240
DATA_DIR = "Duino-Coin PC Miner " + str(data["tag_name"]) # Create new version config folder
241
if not Path(DATA_DIR).is_dir():
242
mkdir(DATA_DIR)
243
244
try:
245
configparser.read(str(Settings.DATA_DIR) + '/Settings.cfg') # read the previous config
246
247
configparser["PC Miner"] = {
248
"username": configparser["PC Miner"]["username"],
249
"mining_key": configparser["PC Miner"]["mining_key"],
250
"intensity": configparser["PC Miner"]["intensity"],
251
"threads": configparser["PC Miner"]["threads"],
252
"start_diff": configparser["PC Miner"]["start_diff"],
253
"donate": int(configparser["PC Miner"]["donate"]),
254
"identifier": configparser["PC Miner"]["identifier"],
255
"algorithm": configparser["PC Miner"]["algorithm"],
256
"language": configparser["PC Miner"]["language"],
257
"soc_timeout": int(configparser["PC Miner"]["soc_timeout"]),
258
"report_sec": int(configparser["PC Miner"]["report_sec"]),
259
"discord_rp": configparser["PC Miner"]["discord_rp"]
260
}
261
262
with open(str(DATA_DIR) # save it on the new version folder
263
+ '/Settings.cfg', 'w') as configfile:
264
configparser.write(configfile)
265
266
pretty_print(Style.RESET_ALL + get_string('config_saved'),
267
"success", "sys0")
268
except Exception as e:
269
pretty_print(f"Error saving configfile: {e}" + str(e),
270
"error", "sys0")
271
pretty_print("Config won't be carried to the next version",
272
"warning", "sys0")
273
274
if not os.path.exists(Settings.TEMP_FOLDER): # Make the Temp folder
275
os.makedirs(Settings.TEMP_FOLDER)
276
277
file_path = os.path.join(Settings.TEMP_FOLDER, zip_file)
278
download_url = "https://github.com/revoxhere/duino-coin/releases/download/" + data["tag_name"] + "/" + zip_file
279
280
if running_script:
281
file_path = os.path.join(".", "PC_Miner_"+data["tag_name"]+".py")
282
download_url = "https://raw.githubusercontent.com/revoxhere/duino-coin/master/PC_Miner.py"
283
284
r = requests.get(download_url, stream=True)
285
if r.ok:
286
start = time()
287
dl = 0
288
file_size = int(r.headers["Content-Length"]) # Get file size
289
pretty_print(f"Saving update to: {os.path.abspath(file_path)}",
290
"warning", "sys0")
291
with open(file_path, 'wb') as f:
292
for chunk in r.iter_content(chunk_size=1024 * 8): # Download file in chunks
293
if chunk:
294
dl += len(chunk)
295
done = int(50 * dl / file_size)
296
dl_perc = str(int(100 * dl / file_size))
297
298
if running_script:
299
done = int(12.5 * dl / file_size)
300
dl_perc = str(int(22.5 * dl / file_size))
301
302
sys.stdout.write(
303
"\r%s [%s%s] %s %s" % (
304
dl_perc + "%",
305
'#' * done,
306
' ' * (50-done),
307
str(round(os.path.getsize(file_path) / 1024 / 1024, 2)) + " MB ",
308
str((dl // (time() - start)) // 1024) + " KB/s")) # ProgressBar
309
sys.stdout.flush()
310
f.write(chunk)
311
f.flush()
312
os.fsync(f.fileno())
313
pretty_print("Download complete", "success", "sys0")
314
if not running_script:
315
pretty_print("Unpacking archive", "warning", "sys0")
316
with zipfile.ZipFile(file_path, 'r') as zip_ref: # Unzip the file
317
for file in zip_ref.infolist():
318
if "PC_Miner" in file.filename:
319
if sys.platform == "win32":
320
file.filename = "PC_Miner_"+data["tag_name"]+".exe" # Rename the file
321
else:
322
file.filename = "PC_Miner_"+data["tag_name"]
323
zip_ref.extract(file, ".")
324
pretty_print("Unpacking complete", "success", "sys0")
325
os.remove(file_path) # Delete the zip file
326
os.rmdir(Settings.TEMP_FOLDER) # Delete the temp folder
327
328
if sys.platform == "win32":
329
os.startfile(os.getcwd() + "\\PC_Miner_"+data["tag_name"]+".exe") # Start the miner
330
else: # os.startfile is only for windows
331
os.system(os.getcwd() + "/PC_Miner_"+data["tag_name"])
332
else:
333
if sys.platform == "win32":
334
os.system(file_path)
335
else:
336
os.system("python3 " + file_path)
337
sys.exit() # Exit the program
338
else: # HTTP status code 4XX/5XX
339
pretty_print(f"Update failed: {r.status_code}: {r.text}",
340
"error", "sys0")
341
else:
342
pretty_print("Update aborted", "warning", "sys0")
343
except Exception as e:
344
print(e)
345
346
347
class Algorithms:
348
"""
349
Class containing algorithms used by the miner
350
For more info about the implementation refer to the Duino whitepaper:
351
https://github.com/revoxhere/duino-coin/blob/gh-pages/assets/whitepaper.pdf
352
"""
353
def DUCOS1(last_h: str, exp_h: str, diff: int, eff: int):
354
try:
355
import libducohasher
356
fasthash_supported = True
357
except:
358
fasthash_supported = False
359
360
if fasthash_supported:
361
time_start = time_ns()
362
363
hasher = libducohasher.DUCOHasher(bytes(last_h, encoding='ascii'))
364
nonce = hasher.DUCOS1(
365
bytes(bytearray.fromhex(exp_h)), diff, int(eff))
366
367
time_elapsed = time_ns() - time_start
368
if time_elapsed > 0:
369
hashrate = 1e9 * nonce / time_elapsed
370
else:
371
return [nonce,0]
372
373
return [nonce, hashrate]
374
else:
375
time_start = time_ns()
376
base_hash = sha1(last_h.encode('ascii'))
377
378
for nonce in range(100 * diff + 1):
379
temp_h = base_hash.copy()
380
temp_h.update(str(nonce).encode('ascii'))
381
d_res = temp_h.hexdigest()
382
383
if eff != 0:
384
if nonce % 5000 == 0:
385
sleep(eff / 100)
386
387
if d_res == exp_h:
388
time_elapsed = time_ns() - time_start
389
if time_elapsed > 0:
390
hashrate = 1e9 * nonce / time_elapsed
391
else:
392
return [nonce,0]
393
394
return [nonce, hashrate]
395
396
return [0, 0]
397
398
399
class Client:
400
"""
401
Class helping to organize socket connections
402
"""
403
def connect(pool: tuple):
404
global s
405
s = socket()
406
s.settimeout(Settings.SOC_TIMEOUT)
407
s.connect((pool))
408
409
def send(msg: str):
410
sent = s.sendall(str(msg).encode(Settings.ENCODING))
411
return sent
412
413
def recv(limit: int = 128):
414
data = s.recv(limit).decode(Settings.ENCODING).rstrip("\n")
415
return data
416
417
def fetch_pool(retry_count=1):
418
"""
419
Fetches the best pool from the /getPool API endpoint
420
"""
421
422
while True:
423
if retry_count > 60:
424
retry_count = 60
425
426
try:
427
pretty_print(get_string("connection_search"),
428
"info", "net0")
429
response = requests.get(
430
"https://server.duinocoin.com/getPool",
431
timeout=Settings.SOC_TIMEOUT).json()
432
433
if response["success"] == True:
434
pretty_print(get_string("connecting_node")
435
+ response["name"],
436
"info", "net0")
437
438
NODE_ADDRESS = response["ip"]
439
NODE_PORT = response["port"]
440
441
return (NODE_ADDRESS, NODE_PORT)
442
443
elif "message" in response:
444
pretty_print(f"Warning: {response['message']}")
445
+ (f", retrying in {retry_count*2}s",
446
"warning", "net0")
447
448
else:
449
raise Exception("no response - IP ban or connection error")
450
except Exception as e:
451
if "Expecting value" in str(e):
452
pretty_print(get_string("node_picker_unavailable")
453
+ f"{retry_count*2}s {Style.RESET_ALL}({e})",
454
"warning", "net0")
455
else:
456
pretty_print(get_string("node_picker_error")
457
+ f"{retry_count*2}s {Style.RESET_ALL}({e})",
458
"error", "net0")
459
sleep(retry_count * 2)
460
retry_count += 1
461
462
463
class Donate:
464
def load(donation_level):
465
if donation_level > 0:
466
if os.name == 'nt':
467
if not Path(
468
f"{Settings.DATA_DIR}/Donate.exe").is_file():
469
url = ('https://server.duinocoin.com/'
470
+ 'donations/DonateExecutableWindows.exe')
471
r = requests.get(url, timeout=Settings.SOC_TIMEOUT)
472
with open(f"{Settings.DATA_DIR}/Donate.exe",
473
'wb') as f:
474
f.write(r.content)
475
return
476
elif os.name == "posix":
477
if osprocessor() == "aarch64":
478
url = ('https://server.duinocoin.com/'
479
+ 'donations/DonateExecutableAARCH64')
480
elif osprocessor() == "armv7l":
481
url = ('https://server.duinocoin.com/'
482
+ 'donations/DonateExecutableAARCH32')
483
elif osprocessor() == "x86_64":
484
url = ('https://server.duinocoin.com/'
485
+ 'donations/DonateExecutableLinux')
486
else:
487
pretty_print(
488
"Donate executable unavailable: "
489
+ f"{os.name} {osprocessor()}")
490
return
491
if not Path(
492
f"{Settings.DATA_DIR}/Donate").is_file():
493
r = requests.get(url, timeout=Settings.SOC_TIMEOUT)
494
with open(f"{Settings.DATA_DIR}/Donate",
495
"wb") as f:
496
f.write(r.content)
497
return
498
499
def start(donation_level):
500
donation_settings = requests.get(
501
"https://server.duinocoin.com/donations/settings.json").json()
502
503
if os.name == 'nt':
504
cmd = (f'cd "{Settings.DATA_DIR}" & Donate.exe '
505
+ f'-o {donation_settings["url"]} '
506
+ f'-u {donation_settings["user"]} '
507
+ f'-p {donation_settings["pwd"]} '
508
+ f'-s 4 -e {donation_level*5}')
509
elif os.name == 'posix':
510
cmd = (f'cd "{Settings.DATA_DIR}" && chmod +x Donate '
511
+ '&& nice -20 ./Donate '
512
+ f'-o {donation_settings["url"]} '
513
+ f'-u {donation_settings["user"]} '
514
+ f'-p {donation_settings["pwd"]} '
515
+ f'-s 4 -e {donation_level*5}')
516
517
if donation_level <= 0:
518
pretty_print(
519
Fore.YELLOW + get_string('free_network_warning').lstrip()
520
+ get_string('donate_warning').replace("\n", "\n\t\t")
521
+ Fore.GREEN + 'https://duinocoin.com/donate'
522
+ Fore.YELLOW + get_string('learn_more_donate'),
523
'warning', 'sys0')
524
sleep(5)
525
526
if donation_level > 0:
527
donateExecutable = Popen(cmd, shell=True, stderr=DEVNULL)
528
pretty_print(get_string('thanks_donation').replace("\n", "\n\t\t"),
529
'error', 'sys0')
530
531
532
def get_prefix(symbol: str,
533
val: float,
534
accuracy: int):
535
"""
536
H/s, 1000 => 1 kH/s
537
"""
538
if val >= 1_000_000_000_000: # Really?
539
val = str(round((val / 1_000_000_000_000), accuracy)) + " T"
540
elif val >= 1_000_000_000:
541
val = str(round((val / 1_000_000_000), accuracy)) + " G"
542
elif val >= 1_000_000:
543
val = str(round((val / 1_000_000), accuracy)) + " M"
544
elif val >= 1_000:
545
val = str(round((val / 1_000))) + " k"
546
else:
547
val = str(round(val)) + " "
548
return val + symbol
549
550
551
def get_rpi_temperature():
552
output = Popen(args='cat /sys/class/thermal/thermal_zone0/temp',
553
stdout=PIPE,
554
shell=True).communicate()[0].decode()
555
return round(int(output) / 1000, 2)
556
557
558
def periodic_report(start_time, end_time, shares,
559
blocks, hashrate, uptime):
560
"""
561
Displays nicely formated uptime stats
562
"""
563
raspi_iot_reading = ""
564
565
if running_on_rpi and user_settings["raspi_cpu_iot"] == "y":
566
raspi_iot_reading = f"{get_string('rpi_cpu_temp')} {get_rpi_temperature()}°C"
567
568
seconds = round(end_time - start_time)
569
pretty_print(get_string("periodic_mining_report")
570
+ Fore.RESET + Style.NORMAL
571
+ get_string("report_period")
572
+ str(seconds) + get_string("report_time")
573
+ get_string("report_body1")
574
+ str(shares) + get_string("report_body2")
575
+ str(round(shares/seconds, 1))
576
+ get_string("report_body3")
577
+ get_string("report_body7")
578
+ str(blocks)
579
+ get_string("report_body4")
580
+ str(get_prefix("H/s", hashrate, 2))
581
+ get_string("report_body5")
582
+ str(int(hashrate*seconds))
583
+ get_string("report_body6")
584
+ get_string("total_mining_time")
585
+ str(uptime)
586
+ raspi_iot_reading + "\n", "success")
587
588
589
def calculate_uptime(start_time):
590
"""
591
Returns seconds, minutes or hours passed since timestamp
592
"""
593
uptime = time() - start_time
594
if uptime >= 7200: # 2 hours, plural
595
return str(uptime // 3600) + get_string('uptime_hours')
596
elif uptime >= 3600: # 1 hour, not plural
597
return str(uptime // 3600) + get_string('uptime_hour')
598
elif uptime >= 120: # 2 minutes, plural
599
return str(uptime // 60) + get_string('uptime_minutes')
600
elif uptime >= 60: # 1 minute, not plural
601
return str(uptime // 60) + get_string('uptime_minute')
602
else: # less than 1 minute
603
return str(round(uptime)) + get_string('uptime_seconds')
604
605
606
def pretty_print(msg: str = None,
607
state: str = "success",
608
sender: str = "sys0",
609
print_queue = None):
610
"""
611
Produces nicely formatted CLI output for messages:
612
HH:MM:S |sender| msg
613
"""
614
if sender.startswith("net"):
615
bg_color = Back.BLUE
616
elif sender.startswith("cpu"):
617
bg_color = Back.YELLOW
618
elif sender.startswith("sys"):
619
bg_color = Back.GREEN
620
621
if state == "success":
622
fg_color = Fore.GREEN
623
elif state == "info":
624
fg_color = Fore.BLUE
625
elif state == "error":
626
fg_color = Fore.RED
627
else:
628
fg_color = Fore.YELLOW
629
630
if print_queue != None:
631
print_queue.append(
632
Fore.WHITE + datetime.now().strftime(Style.DIM + "%H:%M:%S ")
633
+ Style.RESET_ALL + Style.BRIGHT + bg_color + " " + sender + " "
634
+ Style.NORMAL + Back.RESET + " " + fg_color + msg.strip())
635
else:
636
print(
637
Fore.WHITE + datetime.now().strftime(Style.DIM + "%H:%M:%S ")
638
+ Style.RESET_ALL + Style.BRIGHT + bg_color + " " + sender + " "
639
+ Style.NORMAL + Back.RESET + " " + fg_color + msg.strip())
640
641
642
def share_print(id, type,
643
accept, reject,
644
thread_hashrate, total_hashrate,
645
computetime, diff, ping,
646
back_color, reject_cause=None,
647
print_queue = None):
648
"""
649
Produces nicely formatted CLI output for shares:
650
HH:MM:S |cpuN| ⛏ Accepted 0/0 (100%) ∙ 0.0s ∙ 0 kH/s ⚙ diff 0 k ∙ ping 0ms
651
"""
652
thread_hashrate = get_prefix("H/s", thread_hashrate, 2)
653
total_hashrate = get_prefix("H/s", total_hashrate, 1)
654
diff = get_prefix("", int(diff), 0)
655
656
def _blink_builtin(led="green"):
657
if led == "green":
658
os.system(
659
'echo 1 | sudo tee /sys/class/leds/led0/brightness >/dev/null 2>&1')
660
sleep(0.1)
661
os.system(
662
'echo 0 | sudo tee /sys/class/leds/led0/brightness >/dev/null 2>&1')
663
else:
664
os.system(
665
'echo 1 | sudo tee /sys/class/leds/led1/brightness >/dev/null 2>&1')
666
sleep(0.1)
667
os.system(
668
'echo 0 | sudo tee /sys/class/leds/led1/brightness >/dev/null 2>&1')
669
670
if type == "accept":
671
if running_on_rpi and user_settings["raspi_leds"] == "y":
672
_blink_builtin()
673
share_str = get_string("accepted")
674
fg_color = Fore.GREEN
675
elif type == "block":
676
if running_on_rpi and user_settings["raspi_leds"] == "y":
677
_blink_builtin()
678
share_str = get_string("block_found")
679
fg_color = Fore.YELLOW
680
else:
681
if running_on_rpi and user_settings["raspi_leds"] == "y":
682
_blink_builtin("red")
683
share_str = get_string("rejected")
684
if reject_cause:
685
share_str += f"{Style.NORMAL}({reject_cause}) "
686
fg_color = Fore.RED
687
688
print_queue.append(Fore.WHITE + datetime.now().strftime(Style.DIM + "%H:%M:%S ")
689
+ Style.RESET_ALL + Fore.WHITE + Style.BRIGHT + back_color
690
+ f" cpu{id} " + Back.RESET + fg_color + Settings.PICK
691
+ share_str + Fore.RESET + f"{accept}/{(accept + reject)}"
692
+ Fore.YELLOW
693
+ f" ({(round(accept / (accept + reject) * 100))}%)"
694
+ Style.NORMAL + Fore.RESET
695
+ f" ∙ {('%04.1f' % float(computetime))}s"
696
+ Style.NORMAL + " ∙ " + Fore.BLUE + Style.BRIGHT
697
+ f"{thread_hashrate}" + Style.DIM
698
+ f" ({total_hashrate} {get_string('hashrate_total')})" + Fore.RESET + Style.NORMAL
699
+ Settings.COG + f" {get_string('diff')} {diff} ∙ " + Fore.CYAN
700
+ f"ping {(int(ping))}ms")
701
702
703
def print_queue_handler(print_queue):
704
"""
705
Prevents broken console logs with many threads
706
"""
707
while True:
708
if len(print_queue):
709
message = print_queue[0]
710
with printlock:
711
print(message)
712
print_queue.pop(0)
713
sleep(0.01)
714
715
716
def get_string(string_name):
717
"""
718
Gets a string from the language file
719
"""
720
if string_name in lang_file[lang]:
721
return lang_file[lang][string_name]
722
elif string_name in lang_file["english"]:
723
return lang_file["english"][string_name]
724
else:
725
return string_name
726
727
728
def has_mining_key(username):
729
try:
730
response = requests.get(
731
"https://server.duinocoin.com/mining_key"
732
+ "?u=" + username,
733
timeout=10
734
).json()
735
return response["has_key"]
736
except Exception as e:
737
debug_output("Error checking for mining key: " + str(e))
738
return False
739
740
741
def check_mining_key(user_settings):
742
if user_settings["mining_key"] != "None":
743
key = '&k=' + urllib.parse.quote(b64.b64decode(user_settings["mining_key"]).decode('utf-8'))
744
else:
745
key = ''
746
747
response = requests.get(
748
"https://server.duinocoin.com/mining_key"
749
+ "?u=" + user_settings["username"]
750
+ key,
751
timeout=Settings.SOC_TIMEOUT
752
).json()
753
debug_output(response)
754
755
if response["success"] and not response["has_key"]:
756
# If user doesn't have a mining key
757
758
user_settings["mining_key"] = "None"
759
760
with open(Settings.DATA_DIR + Settings.SETTINGS_FILE,
761
"w") as configfile:
762
configparser.write(configfile)
763
print(Style.RESET_ALL + get_string("config_saved"))
764
sleep(1.5)
765
return
766
767
if not response["success"]:
768
if response["message"] == "Too many requests":
769
debug_output("Skipping mining key check - getting 429")
770
return
771
if user_settings["mining_key"] == "None":
772
pretty_print(get_string("mining_key_required"), "warning")
773
mining_key = input("\t\t" + get_string("ask_mining_key")
774
+ Style.BRIGHT + Fore.YELLOW)
775
if mining_key == "": mining_key = "None" #replace empty input with "None" key
776
user_settings["mining_key"] = b64.b64encode(
777
mining_key.encode("utf-8")).decode('utf-8')
778
configparser["PC Miner"] = user_settings
779
780
with open(Settings.DATA_DIR + Settings.SETTINGS_FILE,
781
"w") as configfile:
782
configparser.write(configfile)
783
print(Style.RESET_ALL + get_string("config_saved"))
784
sleep(1.5)
785
check_mining_key(user_settings)
786
else:
787
pretty_print(get_string("invalid_mining_key"), "error")
788
retry = input(get_string("key_retry"))
789
if not retry or retry == "y" or retry == "Y":
790
mining_key = input(get_string("ask_mining_key"))
791
if mining_key == "": mining_key = "None" #replace empty input with "None" key
792
user_settings["mining_key"] = b64.b64encode(
793
mining_key.encode("utf-8")).decode('utf-8')
794
configparser["PC Miner"] = user_settings
795
796
with open(Settings.DATA_DIR + Settings.SETTINGS_FILE,
797
"w") as configfile:
798
configparser.write(configfile)
799
print(Style.RESET_ALL + get_string("config_saved"))
800
sleep(1.5)
801
check_mining_key(user_settings)
802
else:
803
return
804
805
806
class Miner:
807
def greeting():
808
diff_str = get_string("net_diff_short")
809
if user_settings["start_diff"] == "LOW":
810
diff_str = get_string("low_diff_short")
811
elif user_settings["start_diff"] == "MEDIUM":
812
diff_str = get_string("medium_diff_short")
813
814
current_hour = strptime(ctime(time())).tm_hour
815
greeting = get_string("greeting_back")
816
if current_hour < 12:
817
greeting = get_string("greeting_morning")
818
elif current_hour == 12:
819
greeting = get_string("greeting_noon")
820
elif current_hour > 12 and current_hour < 18:
821
greeting = get_string("greeting_afternoon")
822
elif current_hour >= 18:
823
greeting = get_string("greeting_evening")
824
825
print("\n" + Style.DIM + Fore.YELLOW + Settings.BLOCK + Fore.YELLOW
826
+ Style.BRIGHT + get_string("banner") + Style.RESET_ALL
827
+ Fore.MAGENTA + " (" + str(Settings.VER) + ") "
828
+ Fore.RESET + "2019-2025")
829
830
print(Style.DIM + Fore.YELLOW + Settings.BLOCK + Style.NORMAL
831
+ Fore.YELLOW + "https://github.com/revoxhere/duino-coin")
832
833
if lang != "english":
834
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
835
+ Style.NORMAL + Fore.RESET
836
+ get_string("translation") + Fore.YELLOW
837
+ get_string("translation_autor"))
838
839
try:
840
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
841
+ Style.NORMAL + Fore.RESET + "CPU: " + Style.BRIGHT
842
+ Fore.YELLOW + str(user_settings["threads"])
843
+ "x " + str(cpu["brand_raw"]))
844
except:
845
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
846
+ Style.NORMAL + Fore.RESET + "CPU: " + Style.BRIGHT
847
+ Fore.YELLOW + str(user_settings["threads"])
848
+ "x threads")
849
850
if os.name == "nt" or os.name == "posix":
851
print(Style.DIM + Fore.YELLOW
852
+ Settings.BLOCK + Style.NORMAL + Fore.RESET
853
+ get_string("donation_level") + Style.BRIGHT
854
+ Fore.YELLOW + str(user_settings["donate"]))
855
856
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
857
+ Style.NORMAL + Fore.RESET + get_string("algorithm")
858
+ Style.BRIGHT + Fore.YELLOW + user_settings["algorithm"]
859
+ Settings.COG + " " + diff_str)
860
861
if user_settings["identifier"] != "None":
862
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
863
+ Style.NORMAL + Fore.RESET + get_string("rig_identifier")
864
+ Style.BRIGHT + Fore.YELLOW + user_settings["identifier"])
865
866
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
867
+ Style.NORMAL + Fore.RESET + get_string("using_config")
868
+ Style.BRIGHT + Fore.YELLOW
869
+ str(Settings.DATA_DIR + Settings.SETTINGS_FILE))
870
871
print(Style.DIM + Fore.YELLOW + Settings.BLOCK
872
+ Style.NORMAL + Fore.RESET + str(greeting)
873
+ ", " + Style.BRIGHT + Fore.YELLOW
874
+ str(user_settings["username"]) + "!\n")
875
876
def preload():
877
"""
878
Creates needed directories and files for the miner
879
"""
880
global lang_file
881
global lang
882
883
if not Path(Settings.DATA_DIR).is_dir():
884
mkdir(Settings.DATA_DIR)
885
886
if not Path(Settings.DATA_DIR + Settings.TRANSLATIONS_FILE).is_file():
887
with open(Settings.DATA_DIR + Settings.TRANSLATIONS_FILE,
888
"wb") as f:
889
f.write(requests.get(Settings.TRANSLATIONS,
890
timeout=Settings.SOC_TIMEOUT).content)
891
892
with open(Settings.DATA_DIR + Settings.TRANSLATIONS_FILE, "r",
893
encoding=Settings.ENCODING) as file:
894
lang_file = json.load(file)
895
896
try:
897
if not Path(Settings.DATA_DIR + Settings.SETTINGS_FILE).is_file():
898
locale = getdefaultlocale()[0]
899
if locale.startswith("es"):
900
lang = "spanish"
901
elif locale.startswith("pl"):
902
lang = "polish"
903
elif locale.startswith("fr"):
904
lang = "french"
905
elif locale.startswith("jp"):
906
lang = "japanese"
907
elif locale.startswith("fa"):
908
lang = "farsi"
909
elif locale.startswith("mt"):
910
lang = "maltese"
911
elif locale.startswith("ru"):
912
lang = "russian"
913
elif locale.startswith("uk"):
914
lang = "ukrainian"
915
elif locale.startswith("de"):
916
lang = "german"
917
elif locale.startswith("tr"):
918
lang = "turkish"
919
elif locale.startswith("pr"):
920
lang = "portuguese"
921
elif locale.startswith("it"):
922
lang = "italian"
923
elif locale.startswith("sk"):
924
lang = "slovak"
925
if locale.startswith("zh_TW"):
926
lang = "chinese_Traditional"
927
elif locale.startswith("zh"):
928
lang = "chinese_simplified"
929
elif locale.startswith("th"):
930
lang = "thai"
931
elif locale.startswith("ko"):
932
lang = "korean"
933
elif locale.startswith("id"):
934
lang = "indonesian"
935
elif locale.startswith("cz"):
936
lang = "czech"
937
elif locale.startswith("fi"):
938
lang = "finnish"
939
else:
940
lang = "english"
941
else:
942
try:
943
configparser.read(Settings.DATA_DIR
944
+ Settings.SETTINGS_FILE)
945
lang = configparser["PC Miner"]["language"]
946
except Exception:
947
lang = "english"
948
except Exception as e:
949
print("Error with lang file, falling back to english: " + str(e))
950
lang = "english"
951
952
def load_cfg():
953
"""
954
Loads miner settings file or starts the config tool
955
"""
956
if not Path(Settings.DATA_DIR + Settings.SETTINGS_FILE).is_file():
957
print(Style.BRIGHT
958
+ get_string("basic_config_tool")
959
+ Settings.DATA_DIR
960
+ get_string("edit_config_file_warning")
961
+ "\n"
962
+ Style.RESET_ALL
963
+ get_string("dont_have_account")
964
+ Fore.YELLOW
965
+ get_string("wallet")
966
+ Fore.RESET
967
+ get_string("register_warning"))
968
969
correct_username = False
970
while not correct_username:
971
username = input(get_string("ask_username") + Style.BRIGHT)
972
if not username:
973
username = choice(["revox", "Bilaboz"])
974
975
r = requests.get(f"https://server.duinocoin.com/users/{username}",
976
timeout=Settings.SOC_TIMEOUT).json()
977
correct_username = r["success"]
978
if not correct_username:
979
print(get_string("incorrect_username"))
980
981
mining_key = "None"
982
if has_mining_key(username):
983
mining_key = input(Style.RESET_ALL +
984
get_string("ask_mining_key") +
985
Style.BRIGHT)
986
mining_key = b64.b64encode(mining_key.encode("utf-8")).decode('utf-8')
987
988
algorithm = "DUCO-S1"
989
990
intensity = sub(r"\D", "",
991
input(Style.NORMAL +
992
get_string("ask_intensity") +
993
Style.BRIGHT))
994
995
if not intensity:
996
intensity = 95
997
elif float(intensity) > 100:
998
intensity = 100
999
elif float(intensity) < 1:
1000
intensity = 1
1001
1002
threads = sub(r"\D", "",
1003
input(Style.NORMAL + get_string("ask_threads")
1004
+ str(cpu_count()) + "): " + Style.BRIGHT))
1005
if not threads:
1006
threads = cpu_count()
1007
1008
if int(threads) > 16:
1009
threads = 16
1010
print(Style.BRIGHT + Fore.BLUE
1011
+ get_string("max_threads_notice")
1012
+ Style.RESET_ALL)
1013
elif int(threads) < 1:
1014
threads = 1
1015
1016
print(Style.BRIGHT
1017
+ "1" + Style.NORMAL + " - " + get_string("low_diff")
1018
+ "\n" + Style.BRIGHT
1019
+ "2" + Style.NORMAL + " - " + get_string("medium_diff")
1020
+ "\n" + Style.BRIGHT
1021
+ "3" + Style.NORMAL + " - " + get_string("net_diff"))
1022
start_diff = sub(r"\D", "",
1023
input(Style.NORMAL + get_string("ask_difficulty")
1024
+ Style.BRIGHT))
1025
if start_diff == "1":
1026
start_diff = "LOW"
1027
elif start_diff == "3":
1028
start_diff = "NET"
1029
else:
1030
start_diff = "MEDIUM"
1031
1032
rig_id = input(Style.NORMAL + get_string("ask_rig_identifier")
1033
+ Style.BRIGHT)
1034
if rig_id.lower() == "y":
1035
rig_id = str(input(Style.NORMAL + get_string("ask_rig_name")
1036
+ Style.BRIGHT))
1037
else:
1038
rig_id = "None"
1039
1040
donation_level = '0'
1041
if os.name == 'nt' or os.name == 'posix':
1042
donation_level = input(Style.NORMAL
1043
+ get_string('ask_donation_level')
1044
+ Style.BRIGHT)
1045
1046
donation_level = sub(r'\D', '', donation_level)
1047
if donation_level == '':
1048
donation_level = 1
1049
if float(donation_level) > int(5):
1050
donation_level = 5
1051
if float(donation_level) < int(0):
1052
donation_level = 0
1053
1054
configparser["PC Miner"] = {
1055
"username": username,
1056
"mining_key": mining_key,
1057
"intensity": intensity,
1058
"threads": threads,
1059
"start_diff": start_diff,
1060
"donate": int(donation_level),
1061
"identifier": rig_id,
1062
"algorithm": algorithm,
1063
"language": lang,
1064
"soc_timeout": Settings.SOC_TIMEOUT,
1065
"report_sec": Settings.REPORT_TIME,
1066
"raspi_leds": Settings.RASPI_LEDS,
1067
"raspi_cpu_iot": Settings.RASPI_CPU_IOT,
1068
"discord_rp": "y"}
1069
1070
with open(Settings.DATA_DIR + Settings.SETTINGS_FILE,
1071
"w") as configfile:
1072
configparser.write(configfile)
1073
print(Style.RESET_ALL + get_string("config_saved"))
1074
1075
configparser.read(Settings.DATA_DIR
1076
+ Settings.SETTINGS_FILE)
1077
return configparser["PC Miner"]
1078
1079
def m_connect(id, pool):
1080
retry_count = 0
1081
while True:
1082
try:
1083
if retry_count > 3:
1084
pool = Client.fetch_pool()
1085
retry_count = 0
1086
1087
socket_connection = Client.connect(pool)
1088
POOL_VER = Client.recv(5)
1089
1090
if id == 0:
1091
Client.send("MOTD")
1092
motd = Client.recv(512).replace("\n", "\n\t\t")
1093
1094
pretty_print(get_string("motd") + Fore.RESET + Style.NORMAL
1095
+ str(motd), "success", "net" + str(id))
1096
1097
if float(POOL_VER) <= Settings.VER:
1098
pretty_print(get_string("connected") + Fore.RESET
1099
+ Style.NORMAL +
1100
get_string("connected_server")
1101
+ str(POOL_VER) + ", " + pool[0] +")",
1102
"success", "net" + str(id))
1103
else:
1104
pretty_print(get_string("outdated_miner")
1105
+ str(Settings.VER) + ") -"
1106
+ get_string("server_is_on_version")
1107
+ str(POOL_VER) + Style.NORMAL
1108
+ Fore.RESET +
1109
get_string("update_warning"),
1110
"warning", "net" + str(id))
1111
sleep(5)
1112
break
1113
except Exception as e:
1114
pretty_print(get_string('connecting_error')
1115
+ Style.NORMAL + f' (connection err: {e})',
1116
'error', 'net0')
1117
retry_count += 1
1118
sleep(10)
1119
1120
def mine(id: int, user_settings: list,
1121
blocks: int, pool: tuple,
1122
accept: int, reject: int,
1123
hashrate: list,
1124
single_miner_id: str,
1125
print_queue):
1126
"""
1127
Main section that executes the functionalities from the sections above.
1128
"""
1129
using_algo = get_string("using_algo")
1130
pretty_print(get_string("mining_thread") + str(id)
1131
+ get_string("mining_thread_starting")
1132
+ Style.NORMAL + Fore.RESET + using_algo + Fore.YELLOW
1133
+ str(user_settings["intensity"])
1134
+ "% " + get_string("efficiency"),
1135
"success", "sys"+str(id), print_queue=print_queue)
1136
1137
last_report = time()
1138
r_shares, last_shares = 0, 0
1139
while True:
1140
accept.value = 0
1141
reject.value = 0
1142
try:
1143
Miner.m_connect(id, pool)
1144
while True:
1145
try:
1146
if user_settings["mining_key"] != "None":
1147
key = b64.b64decode(user_settings["mining_key"]).decode('utf-8')
1148
else:
1149
key = user_settings["mining_key"]
1150
1151
raspi_iot_reading = ""
1152
if user_settings["raspi_cpu_iot"] == "y" and running_on_rpi:
1153
# * instead of the degree symbol because nodes use basic encoding
1154
raspi_iot_reading = f"CPU temperature:{get_rpi_temperature()}*C"
1155
1156
while True:
1157
job_req = "JOB"
1158
Client.send(job_req
1159
+ Settings.SEPARATOR
1160
+ str(user_settings["username"])
1161
+ Settings.SEPARATOR
1162
+ str(user_settings["start_diff"])
1163
+ Settings.SEPARATOR
1164
+ str(key)
1165
+ Settings.SEPARATOR
1166
+ str(raspi_iot_reading))
1167
1168
job = Client.recv().split(Settings.SEPARATOR)
1169
if len(job) == 3:
1170
break
1171
else:
1172
pretty_print(
1173
"Node message: " + str(job[1]),
1174
"warning", print_queue=print_queue)
1175
sleep(3)
1176
1177
while True:
1178
time_start = time()
1179
back_color = Back.YELLOW
1180
1181
eff = 0
1182
eff_setting = int(user_settings["intensity"])
1183
if 99 > eff_setting >= 90:
1184
eff = 0.005
1185
elif 90 > eff_setting >= 70:
1186
eff = 0.1
1187
elif 70 > eff_setting >= 50:
1188
eff = 0.8
1189
elif 50 > eff_setting >= 30:
1190
eff = 1.8
1191
elif 30 > eff_setting >= 1:
1192
eff = 3
1193
1194
result = Algorithms.DUCOS1(
1195
job[0], job[1], int(job[2]), eff)
1196
computetime = time() - time_start
1197
1198
hashrate[id] = result[1]
1199
total_hashrate = sum(hashrate.values())
1200
prep_identifier = user_settings['identifier']
1201
if running_on_rpi:
1202
if prep_identifier != "None":
1203
prep_identifier += " - RPi"
1204
else:
1205
prep_identifier = "Raspberry Pi"
1206
1207
while True:
1208
Client.send(f"{result[0]}"
1209
+ Settings.SEPARATOR
1210
+ f"{result[1]}"
1211
+ Settings.SEPARATOR
1212
+ "Official PC Miner"
1213
+ f" {Settings.VER}"
1214
+ Settings.SEPARATOR
1215
+ f"{prep_identifier}"
1216
+ Settings.SEPARATOR
1217
+ Settings.SEPARATOR
1218
+ f"{single_miner_id}")
1219
1220
time_start = time()
1221
feedback = Client.recv().split(Settings.SEPARATOR)
1222
ping = (time() - time_start) * 1000
1223
1224
if feedback[0] == "GOOD":
1225
accept.value += 1
1226
share_print(id, "accept",
1227
accept.value, reject.value,
1228
hashrate[id],total_hashrate,
1229
computetime, job[2], ping,
1230
back_color,
1231
print_queue=print_queue)
1232
1233
elif feedback[0] == "BLOCK":
1234
accept.value += 1
1235
blocks.value += 1
1236
share_print(id, "block",
1237
accept.value, reject.value,
1238
hashrate[id],total_hashrate,
1239
computetime, job[2], ping,
1240
back_color,
1241
print_queue=print_queue)
1242
1243
elif feedback[0] == "BAD":
1244
reject.value += 1
1245
share_print(id, "reject",
1246
accept.value, reject.value,
1247
hashrate[id], total_hashrate,
1248
computetime, job[2], ping,
1249
back_color, feedback[1],
1250
print_queue=print_queue)
1251
1252
if accept.value % 100 == 0 and accept.value > 1:
1253
pretty_print(
1254
f"{get_string('surpassed')} {accept.value} {get_string('surpassed_shares')}",
1255
"success", "sys0", print_queue=print_queue)
1256
1257
title(get_string('duco_python_miner') + str(Settings.VER)
1258
+ f') - {accept.value}/{(accept.value + reject.value)}'
1259
+ get_string('accepted_shares'))
1260
1261
if id == 0:
1262
end_time = time()
1263
elapsed_time = end_time - last_report
1264
if elapsed_time >= int(user_settings["report_sec"]):
1265
r_shares = accept.value - last_shares
1266
uptime = calculate_uptime(
1267
mining_start_time)
1268
periodic_report(last_report, end_time,
1269
r_shares, blocks.value,
1270
sum(hashrate.values()),
1271
uptime)
1272
last_report = time()
1273
last_shares = accept.value
1274
break
1275
break
1276
except Exception as e:
1277
pretty_print(get_string("error_while_mining")
1278
+ " " + str(e), "error", "net" + str(id),
1279
print_queue=print_queue)
1280
sleep(5)
1281
break
1282
except Exception as e:
1283
pretty_print(get_string("error_while_mining")
1284
+ " " + str(e), "error", "net" + str(id),
1285
print_queue=print_queue)
1286
1287
1288
class Discord_rp:
1289
def connect():
1290
global RPC
1291
try:
1292
RPC = Presence(808045598447632384)
1293
RPC.connect()
1294
Thread(target=Discord_rp.update).start()
1295
except Exception as e:
1296
pretty_print(
1297
get_string("discord_launch_error") +
1298
Style.NORMAL + Fore.RESET + " " + str(e),
1299
"warning")
1300
1301
1302
def update():
1303
while True:
1304
try:
1305
total_hashrate = get_prefix("H/s", sum(hashrate.values()), 2)
1306
RPC.update(details="Hashrate: " + str(total_hashrate),
1307
start=mining_start_time,
1308
state=str(accept.value) + "/"
1309
+ str(reject.value + accept.value)
1310
+ " accepted shares",
1311
large_image="ducol",
1312
large_text="Duino-Coin, "
1313
+ "a coin that can be mined with almost everything"
1314
+ ", including Arduino boards",
1315
buttons=[{"label": "Learn more",
1316
"url": "https://duinocoin.com"},
1317
{"label": "Join the Duino Discord",
1318
"url": "https://discord.gg/k48Ht5y"}])
1319
except Exception as e:
1320
pretty_print(
1321
get_string("discord_update_error" +
1322
Style.NORMAL + Fore.RESET + " " + str(e)),
1323
"warning")
1324
sleep(15)
1325
1326
1327
class Fasthash:
1328
def init():
1329
try:
1330
"""
1331
Check whether libducohash fasthash is available
1332
to speed up the DUCOS1 work, created by @HGEpro
1333
"""
1334
import libducohasher
1335
pretty_print(get_string("fasthash_available"), "info")
1336
except Exception as e:
1337
if int(python_version_tuple()[1]) <= 6:
1338
pretty_print(
1339
(f"Your Python version is too old ({python_version()}).\n"
1340
+ "Fasthash accelerations and other features may not work"
1341
+ " on your outdated installation.\n"
1342
+ "We suggest updating your python to version 3.7 or higher."
1343
).replace("\n", "\n\t\t"), 'warning', 'sys0')
1344
else:
1345
pretty_print(
1346
("Fasthash accelerations are not available for your OS.\n"
1347
+ "If you wish to compile them for your system, visit:\n"
1348
+ "https://github.com/revoxhere/duino-coin/wiki/"
1349
+ "How-to-compile-fasthash-accelerations\n"
1350
+ f"(Libducohash couldn't be loaded: {str(e)})"
1351
).replace("\n", "\n\t\t"), 'warning', 'sys0')
1352
1353
def load():
1354
if os.name == 'nt':
1355
if not Path("libducohasher.pyd").is_file():
1356
pretty_print(get_string("fasthash_download"), "info")
1357
url = ('https://server.duinocoin.com/'
1358
+ 'fasthash/libducohashWindows.pyd')
1359
r = requests.get(url, timeout=Settings.SOC_TIMEOUT)
1360
with open(f"libducohasher.pyd", 'wb') as f:
1361
f.write(r.content)
1362
return
1363
elif os.name == "posix":
1364
if osprocessor() == "aarch64":
1365
url = ('https://server.duinocoin.com/'
1366
+ 'fasthash/libducohashPi4.so')
1367
elif osprocessor() == "armv7l":
1368
url = ('https://server.duinocoin.com/'
1369
+ 'fasthash/libducohashPi4_32.so')
1370
elif osprocessor() == "armv6l":
1371
url = ('https://server.duinocoin.com/'
1372
+ 'fasthash/libducohashPiZero.so')
1373
elif osprocessor() == "x86_64":
1374
url = ('https://server.duinocoin.com/'
1375
+ 'fasthash/libducohashLinux.so')
1376
else:
1377
pretty_print(
1378
("Fasthash accelerations are not available for your OS.\n"
1379
+ "If you wish to compile them for your system, visit:\n"
1380
+ "https://github.com/revoxhere/duino-coin/wiki/"
1381
+ "How-to-compile-fasthash-accelerations\n"
1382
+ f"(Invalid processor architecture: {osprocessor()})"
1383
).replace("\n", "\n\t\t"), 'warning', 'sys0')
1384
return
1385
if not Path("libducohasher.so").is_file():
1386
pretty_print(get_string("fasthash_download"), "info")
1387
r = requests.get(url, timeout=Settings.SOC_TIMEOUT)
1388
with open("libducohasher.so", "wb") as f:
1389
f.write(r.content)
1390
return
1391
else:
1392
pretty_print(
1393
("Fasthash accelerations are not available for your OS.\n"
1394
+ "If you wish to compile them for your system, visit:\n"
1395
+ "https://github.com/revoxhere/duino-coin/wiki/"
1396
+ "How-to-compile-fasthash-accelerations\n"
1397
+ f"(Invalid OS: {os.name})"
1398
).replace("\n", "\n\t\t"), 'warning', 'sys0')
1399
return
1400
1401
1402
Miner.preload()
1403
p_list = []
1404
mining_start_time = time()
1405
1406
if __name__ == "__main__":
1407
from multiprocessing import freeze_support
1408
freeze_support()
1409
signal(SIGINT, handler)
1410
title(f"{get_string('duco_python_miner')}{str(Settings.VER)})")
1411
1412
if sys.platform == "win32":
1413
os.system('') # Enable VT100 Escape Sequence for WINDOWS 10 Ver. 1607
1414
1415
check_updates()
1416
1417
cpu = cpuinfo.get_cpu_info()
1418
accept = Manager().Value("i", 0)
1419
reject = Manager().Value("i", 0)
1420
blocks = Manager().Value("i", 0)
1421
hashrate = Manager().dict()
1422
print_queue = Manager().list()
1423
Thread(target=print_queue_handler, args=[print_queue]).start()
1424
1425
user_settings = Miner.load_cfg()
1426
Miner.greeting()
1427
1428
Fasthash.load()
1429
Fasthash.init()
1430
1431
if not "raspi_leds" in user_settings:
1432
user_settings["raspi_leds"] = "y"
1433
if not "raspi_cpu_iot" in user_settings:
1434
user_settings["raspi_cpu_iot"] = "y"
1435
1436
if user_settings["raspi_leds"] == "y":
1437
try:
1438
with io.open('/sys/firmware/devicetree/base/model', 'r') as m:
1439
if 'raspberry pi' in m.read().lower():
1440
running_on_rpi = True
1441
pretty_print(
1442
get_string("running_on_rpi") +
1443
Style.NORMAL + Fore.RESET + " " +
1444
get_string("running_on_rpi2"), "success")
1445
except:
1446
running_on_rpi = False
1447
1448
if running_on_rpi:
1449
# Prepare onboard LEDs to be controlled
1450
os.system(
1451
'echo gpio | sudo tee /sys/class/leds/led1/trigger >/dev/null 2>&1')
1452
os.system(
1453
'echo gpio | sudo tee /sys/class/leds/led0/trigger >/dev/null 2>&1')
1454
1455
if user_settings["raspi_cpu_iot"] == "y" and running_on_rpi:
1456
try:
1457
temp = get_rpi_temperature()
1458
pretty_print(get_string("iot_on_rpi") +
1459
Style.NORMAL + Fore.RESET + " " +
1460
f"{get_string('iot_on_rpi2')} {temp}°C",
1461
"success")
1462
except Exception as e:
1463
print(e)
1464
user_settings["raspi_cpu_iot"] = "n"
1465
1466
try:
1467
check_mining_key(user_settings)
1468
except Exception as e:
1469
print("Error checking mining key:", e)
1470
1471
Donate.load(int(user_settings["donate"]))
1472
Donate.start(int(user_settings["donate"]))
1473
1474
"""
1475
Generate a random number that's used to
1476
group miners with many threads in the wallet
1477
"""
1478
single_miner_id = randint(0, 2811)
1479
1480
threads = int(user_settings["threads"])
1481
if threads > 16:
1482
threads = 16
1483
pretty_print(Style.BRIGHT
1484
+ get_string("max_threads_notice"))
1485
if threads > cpu_count():
1486
pretty_print(Style.BRIGHT
1487
+ get_string("system_threads_notice"),
1488
"warning")
1489
sleep(10)
1490
1491
fastest_pool = Client.fetch_pool()
1492
1493
for i in range(threads):
1494
p = Process(target=Miner.mine,
1495
args=[i, user_settings, blocks,
1496
fastest_pool, accept, reject,
1497
hashrate, single_miner_id,
1498
print_queue])
1499
p_list.append(p)
1500
p.start()
1501
1502
if user_settings["discord_rp"] == 'y':
1503
Discord_rp.connect()
1504
1505
for p in p_list:
1506
p.join()
1507
1508