Skip to content

Instantly share code, notes, and snippets.

@yeguang-xue
Forked from YoRyan/excavator-driver.py
Last active June 24, 2018 20:11
Show Gist options
  • Save yeguang-xue/b32add4a041e36590c20cdf7aab23db6 to your computer and use it in GitHub Desktop.
Save yeguang-xue/b32add4a041e36590c20cdf7aab23db6 to your computer and use it in GitHub Desktop.
Cross-platform controller for NiceHash Excavator for Nvidia (Fork from @YoRyan, added support for pools outside NiceHash, like MiningPoolHub)
#!/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)
@yeguang-xue
Copy link
Author

Some comments:

  • The payrate for pools outside NiceHash are calculated based on real-time network difficulty and exchange rate.
  • IP address for the MiningPoolHub is used because the domain name seems blocked by excavator.
  • Dual mining algorithms are removed for my own convenience. If you need the feature, check the original code

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