#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
"""Cross-platform controller for NiceHash Excavator for Nvidia.""" | |
# Example usage: | |
# $ excavator -p 3456 & | |
# $ python3 excavator-driver.py | |
# History: | |
# 2018-3-17: * Original fork from Ryan Young (rayoung@utexas.edu) | |
# 2018-3-18: * Rewrite main function to add support for MiningPoolHub | |
# * For simplicity, removed support for dual mining algorithms | |
__author__ = "Yeguang Xue" | |
__email__ = "blackholexyg@gmail.com" | |
__license__ = "public domain" | |
import json | |
import logging | |
import signal | |
import socket | |
import sys | |
import urllib | |
import urllib.request | |
from time import sleep | |
####################### Information of the Work, Address ####################### | |
#----- EXCAVATOR Settings -----# | |
EXCAVATOR_ADDRESS = ('127.0.0.1', 3456) | |
WORKER_NAME = 'desktop' | |
PROFIT_SWITCH_THRESHOLD = 0.05 | |
UPDATE_INTERVAL = 30 #60 | |
EXCAVATOR_TIMEOUT = 10 | |
#----- NiceHash -----# | |
NICEHASH_WALLET = '3B4KzAHKE3C1bdrzWYzRm1WnbFsT3QKCyW' | |
#----- MiningPoolHub -----# | |
MININGPOOLHUB_USER = 'blackholexyg' | |
#----- List of Possible Work Jobs -----# | |
jobs = [{'algo': 'equihash', 'platform': 'nicehash', 'port':'3357' }, | |
{'algo': 'lyra2rev2', 'platform': 'nicehash', 'port':'3347' }, | |
{'algo':'daggerhashimoto', 'platform': 'nicehash', 'port':'3353'}, | |
{'algo': 'equihash', 'platform': 'miningpoolhub', 'port':'20570', 'coin': 'ZEC' }, | |
{'algo': 'equihash', 'platform': 'miningpoolhub', 'port':'20575', 'coin': 'ZCL' }, | |
{'algo': 'equihash', 'platform': 'miningpoolhub', 'port':'20594', 'coin': 'ZEN'}, | |
{'algo': 'daggerhashimoto', 'platform': 'miningpoolhub', 'port':'20535', 'coin': 'ETH'} ] | |
#----- Device and Active Job Status -----# | |
active_jobs = {} | |
device_status = {} | |
device_status[0] = { | |
'bench': {'equihash': 498.567947, 'lyra2rev2': 49.148272E6, 'daggerhashimoto': 20.783207E6}, | |
'excavator_worker_id': None, 'job_id': None, 'payrate': 0.0} | |
################################ Excavator API ################################# | |
class ExcavatorError(Exception): | |
pass | |
class ExcavatorAPIError(ExcavatorError): | |
"""Exception returned by excavator.""" | |
def __init__(self, response): | |
self.response = response | |
self.error = response['error'] | |
def contact_excavator(): | |
try: | |
do_excavator_command('message', ['%s connected' % sys.argv[0]]) | |
except (socket.timeout, socket.error): | |
return False | |
else: | |
return True | |
def do_excavator_command(method, params): | |
"""Sends a command to excavator, returns the JSON-encoded response. | |
method -- name of the command to execute | |
params -- list of arguments for the command | |
""" | |
BUF_SIZE = 1024 | |
command = { | |
'id': 1, | |
'method': method, | |
'params': params | |
} | |
s = socket.create_connection(EXCAVATOR_ADDRESS, EXCAVATOR_TIMEOUT) | |
# send newline-terminated command | |
s.sendall((json.dumps(command).replace('\n', '\\n') + '\n').encode()) | |
response = '' | |
while True: | |
chunk = s.recv(BUF_SIZE).decode() | |
# excavator responses are newline-terminated too | |
if '\n' in chunk: | |
response += chunk[:chunk.index('\n')] | |
break | |
else: | |
response += chunk | |
s.close() | |
response_data = json.loads(response) | |
if response_data['error'] is None: | |
return response_data | |
else: | |
raise ExcavatorAPIError(response_data) | |
def excavator_algorithm_params(job): | |
"""Return the required list of parameters to add an algorithm to excavator. | |
algo -- the algorithm to run | |
ports -- algorithm port information from NiceHash | |
""" | |
params = [] | |
params.append(job['algo']) | |
if job['platform'] == 'nicehash': | |
authinfo = '%s.%s' %(NICEHASH_WALLET, WORKER_NAME) | |
stratum = '%s.usa.nicehash.com:%s' %(job['algo'],job['port']) | |
elif job['platform'] == 'miningpoolhub': | |
authinfo = '%s.%s' %(MININGPOOLHUB_USER, WORKER_NAME) | |
stratum = '172.104.180.119:%s' %(job['port']) | |
else: | |
stratum = '' | |
authinfo = '' | |
params.append(stratum) | |
params.append(authinfo) | |
return params | |
def dispatch_device(device_id, job_id, payrate): | |
if job_id not in active_jobs.keys(): | |
params = excavator_algorithm_params(jobs[job_id]) | |
response = do_excavator_command('algorithm.add', params) | |
excavator_algo_id = response['algorithm_id'] | |
active_jobs[job_id] = (excavator_algo_id, [device_id]) | |
else: | |
excavator_algo_id = active_jobs[job_id][0] | |
active_jobs[job_id][1].append(device_id) | |
response = do_excavator_command('worker.add', | |
[str(excavator_algo_id), str(device_id)]) | |
device_status[device_id]['excavator_worker_id'] = response['worker_id'] | |
device_status[device_id]['job_id'] = job_id | |
device_status[device_id]['payrate'] = payrate | |
def free_device(device_id): | |
if not (device_status[device_id]['excavator_worker_id'] is None): | |
excavator_worker_id = device_status[device_id]['excavator_worker_id'] | |
job_id = device_status[device_id]['job_id'] | |
do_excavator_command('worker.free', [str(excavator_worker_id)]) | |
device_status[device_id]['excavator_worker_id']=None | |
device_status[device_id]['job_id']=None | |
device_status[device_id]['payrate']=0.0 | |
active_jobs[job_id][1].remove(device_id) | |
if len(active_jobs[job_id][1]) == 0: # no more devices attached | |
excavator_algo_id = active_jobs[job_id][0] | |
do_excavator_command('algorithm.remove', [str(excavator_algo_id)]) | |
active_jobs.pop(job_id) | |
################################# NiceHash API ################################# | |
def nicehash_paying_info(): | |
nicehash_api = 'https://api.nicehash.com/api?method=simplemultialgo.info' | |
req = urllib.request.Request(nicehash_api, headers={'User-Agent': 'Mozilla/5.0'}) | |
response = urllib.request.urlopen(req, None, 20) | |
query = json.loads(response.read().decode('ascii')) #json.load(response) | |
paying = {} | |
for algorithm in query['result']['simplemultialgo']: | |
name = algorithm['name'] | |
paying[name] = float(algorithm['paying']) | |
return paying | |
################################ WhatToMine API ################################ | |
def whattomine_coins_info(): | |
whattomine_api = 'https://whattomine.com/coins.json' | |
req = urllib.request.Request(whattomine_api, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0'}) | |
response = urllib.request.urlopen(req) | |
query = json.loads(response.read().decode('ascii')) #json.load(response) | |
# json_filepath = './coins.json' | |
# json_file = open(json_filepath,'r') | |
# query = json.loads(json_file.read()) | |
# json_file.close() | |
coins_info = {} | |
for coin in query['coins'].values(): | |
name = coin['tag'] | |
netdiff = coin['difficulty'] | |
blockreward = coin['block_reward'] | |
exchange24 = coin['exchange_rate24'] | |
coins_info[name] = {'netdiff':netdiff, 'blockreward':blockreward, 'exchange24':exchange24} | |
return coins_info | |
##################################### Main ##################################### | |
if __name__ == '__main__': | |
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', | |
level=logging.INFO) | |
#### Initialize Contact | |
logging.info('connecting to excavator at %s:%d' % EXCAVATOR_ADDRESS) | |
while not contact_excavator(): | |
sleep(5) | |
logging.info('connected to excavator at %s:%d' % EXCAVATOR_ADDRESS) | |
#### Loop to check profitability and switch algorithm | |
while True: | |
logging.info('\n------------------------------------------------------') | |
job_payrates = [0.0]*len(jobs) | |
#### NiceHash Info | |
FLAG_NICEHASH = True | |
try: | |
nicehash_paying = nicehash_paying_info() | |
except: | |
logging.warning('failed to parse NiceHash stats') | |
FLAG_NICEHASH = False | |
#### whattomine Info | |
FLAG_WHATTOMINE = True | |
try: | |
coins_info = whattomine_coins_info() | |
except: | |
logging.warning('failed to parse whattomine stats') | |
FLAG_WHATTOMINE = False | |
# for device in BENCHMARKS.keys(): | |
device_id = 0 | |
#### Determine Best Platform, Algo | |
for job_id in range(0,len(jobs)): | |
current_job = jobs[job_id] | |
current_platform = current_job['platform'] | |
current_algo = current_job['algo'] | |
try: | |
current_speed = device_status[device_id]['bench'][current_algo] | |
except: | |
current_speed = 0.0 | |
if current_platform == 'nicehash' and FLAG_NICEHASH: | |
paying = nicehash_paying[current_algo] | |
payrate = paying*current_speed*(24*3600)*1e-11 | |
job_payrates[job_id] = payrate | |
if current_platform == 'miningpoolhub' and FLAG_WHATTOMINE: | |
coin_name = current_job['coin'] | |
current_coin = coins_info[coin_name] | |
netdiff = current_coin['netdiff'] | |
blockreward = current_coin['blockreward'] | |
exchange24 = current_coin['exchange24'] | |
if coin_name in ['ZEC','ZEN', 'ZCL']: | |
payrate = blockreward*current_speed/netdiff/8192.0*\ | |
(24*3600) *0.99*exchange24*1E3 | |
job_payrates[job_id] = payrate | |
elif coin_name in ['ETH']: | |
payrate = blockreward*current_speed/netdiff*\ | |
(24*3600) *0.99*exchange24*1E3 | |
job_payrates[job_id] = payrate | |
else: | |
job_payrates[job_id] = 0.0 | |
logging.info('Payrate of %s: %f' %(current_job,payrate)) | |
best_job_info = max(enumerate(job_payrates), key=(lambda x: x[1])) | |
best_job_id = best_job_info[0] | |
best_job = jobs[best_job_id] | |
best_payrate = best_job_info[1] | |
best_platform = best_job['platform'] | |
best_algo = best_job['algo'] | |
best_port = best_job['port'] | |
logging.info('Best: %s' %best_job) | |
if device_status[device_id]['excavator_worker_id'] is None: | |
logging.info( | |
'device %s initial status: %s (%.3f mBTC/day)' | |
% (device_id, best_job, best_payrate)) | |
dispatch_device(device_id, best_job_id, best_payrate) | |
else: | |
current_job_id = device_status[device_id]['job_id'] | |
current_payrate = job_payrates[current_job_id] | |
if (current_payrate < 1E-3 or \ | |
best_payrate/current_payrate >= 1.0 + PROFIT_SWITCH_THRESHOLD): | |
logging.info( | |
'device %s switching to: %s (%.3f mBTC/day)' | |
% (device_id, best_job, best_payrate)) | |
free_device(device_id) | |
dispatch_device(device_id, best_job_id, best_payrate) | |
#### Mine for a period of time | |
sleep(UPDATE_INTERVAL) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment Hide comment
Some comments:
Let me know if you find any issues! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some comments:
Let me know if you find any issues!