Skip to content

Instantly share code, notes, and snippets.

@rZn
Forked from YoRyan/excavator-driver.py
Created December 6, 2019 08:52
Show Gist options
  • Select an option

  • Save rZn/cefeea434ce270266115d069166f4d04 to your computer and use it in GitHub Desktop.

Select an option

Save rZn/cefeea434ce270266115d069166f4d04 to your computer and use it in GitHub Desktop.
Cross-platform controller for NiceHash Excavator for Nvidia (aka, NiceHash 2 for Linux). This is no longer maintained, please see https://github.com/YoRyan/nuxhash
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Cross-platform controller for NiceHash Excavator for Nvidia."""
# Example usage:
# $ /opt/excavator/bin/excavator -p 3456 &
# $ python3 excavator-driver.py
__author__ = "Ryan Young"
__email__ = "[email protected]"
__license__ = "public domain"
import json
import logging
import signal
import socket
import sys
import urllib.error
import urllib.request
from time import sleep
WALLET_ADDR = '1BQSMa5mfDNzut5PN9xgtJe3wqaqGEEerD'
WORKER_NAME = 'worker1'
REGION = 'usa' # eu, usa, hk, jp, in, br
EXCAVATOR_ADDRESS = ('127.0.0.1', 3456)
# copy the numbers from excavator-benchmark
BENCHMARKS = {}
# GTX 1060 6GB
BENCHMARKS[0] = {
'equihash': 300.363149,
'pascal': 673.490293,
'decred': 1.734736,
'sia': 1.070054,
'lbry': 179.286648,
'blake2s': 0, #2.509039,
'lyra2rev2': 25.908014,
'cryptonight': 435.366891,
'daggerhashimoto': 22.009473,
'daggerhashimoto_pascal': [8.298873, 464.739199],
'daggerhashimoto_decred': [20.258170, 729.296593],
'daggerhashimoto_sia': [21.359257, 273.398483],
'neoscrypt': 618.769067 # test manually
}
PROFIT_SWITCH_THRESHOLD = 0.1
UPDATE_INTERVAL = 60
EXCAVATOR_TIMEOUT = 10
NICEHASH_TIMEOUT = 20
### here be dragons
class ExcavatorError(Exception):
pass
class ExcavatorAPIError(ExcavatorError):
"""Exception returned by excavator."""
def __init__(self, response):
self.response = response
self.error = response['error']
def nicehash_multialgo_info():
"""Retrieves pay rates and connection ports for every algorithm from the NiceHash API."""
response = urllib.request.urlopen('https://api.nicehash.com/api?method=simplemultialgo.info',
None, NICEHASH_TIMEOUT)
query = json.load(response)
paying = {}
ports = {}
for algorithm in query['result']['simplemultialgo']:
name = algorithm['name']
paying[name] = float(algorithm['paying'])
ports[name] = int(algorithm['port'])
return paying, ports
def nicehash_btc_per_day(device, paying):
"""Calculates the BTC/day amount for every algorithm.
device -- excavator device id for benchmarks
paying -- algorithm pay information from NiceHash
"""
benchmarks = BENCHMARKS[device]
payrate = lambda algo, speed: paying[algo]*speed
payrate_benched = lambda algo: payrate(algo, benchmarks[algo])
# use the same (somewhat nonsensical) btc/solution units from the web interface
dual_dp = payrate('daggerhashimoto', benchmarks['daggerhashimoto_pascal'][0]*(1e-3*24*60*60)) \
+ payrate('pascal', benchmarks['daggerhashimoto_pascal'][1]*(1e-6*24*60*60*1e3))
dual_dd = payrate('daggerhashimoto', benchmarks['daggerhashimoto_decred'][0]*(1e-3*24*60*60)) \
+ payrate('decred', benchmarks['daggerhashimoto_decred'][1]*(1e-6*24*60*60*1e3))
dual_ds = payrate('daggerhashimoto', benchmarks['daggerhashimoto_sia'][0]*(1e-3*24*60*60)) \
+ payrate('sia', benchmarks['daggerhashimoto_sia'][1]*(1e-6*24*60*60*1e3))
payrates = {
'equihash': payrate_benched('equihash')*(1e-6*24*60*60*1e-3),
'pascal': payrate_benched('pascal')*(1e-6*24*60*60*1e3),
'decred': payrate_benched('decred')*(1e-3*24*60*60*1e3),
'sia': payrate_benched('sia')*(1e-3*24*60*60*1e3),
'lbry': payrate_benched('lbry')*(1e-6*24*60*60*1e3),
'blake2s': payrate_benched('blake2s')*(1e-3*24*60*60*1e3),
'lyra2rev2': payrate_benched('lyra2rev2')*(1e-6*24*60*60*1e3),
'cryptonight': payrate_benched('cryptonight')*(1e-6*24*60*60*1e-3),
'daggerhashimoto': payrate_benched('daggerhashimoto')*(1e-3*24*60*60),
'neoscrypt': payrate_benched('neoscrypt')*(24*60*60*1e-6),
'daggerhashimoto_pascal': dual_dp,
'daggerhashimoto_decred': dual_dd,
'daggerhashimoto_sia': dual_ds
}
return payrates
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 add_excavator_algorithm(algo, device, ports):
"""Runs an algorithm on a device, returns the new excavator algorithm id.
algo -- the algorithm to run
device -- excavator device id of the target device
ports -- algorithm port information from NiceHash
"""
AUTH = '%s.%s:x' % (WALLET_ADDR, WORKER_NAME)
stratum = lambda algo: '%s.%s.nicehash.com:%s' % (algo, REGION, ports[algo])
if algo == 'daggerhashimoto_decred':
add_params = [algo, stratum('daggerhashimoto'), AUTH,
stratum('decred'), AUTH]
elif algo == 'daggerhashimoto_pascal':
add_params = [algo, stratum('daggerhashimoto'), AUTH,
stratum('pascal'), AUTH]
elif algo == 'daggerhashimoto_sia':
add_params = [algo, stratum('daggerhashimoto'), AUTH,
stratum('sia'), AUTH]
else:
add_params = [algo, stratum(algo), AUTH]
response = do_excavator_command('algorithm.add', add_params)
algo_id = response['algorithm_id']
do_excavator_command('worker.add', [str(algo_id), str(device)])
return algo_id
def remove_excavator_algorithm(algo_id):
"""Removes an algorithm from excavator and all (one) associated workers.
algo_id -- excavator algorithm id to remove
"""
do_excavator_command('algorithm.remove', [str(algo_id)])
def main():
"""Main program."""
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
level=logging.INFO)
device_status = {}
def sigint_handler(signum, frame):
logging.info('cleaning up!')
for device in device_status:
current_algo_id = device_status[device][1]
remove_excavator_algorithm(current_algo_id)
sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)
while True:
try:
paying, ports = nicehash_multialgo_info()
except urllib.error.URLError as err:
logging.warning('failed to retrieve NiceHash stats: %s' % err.reason)
except urllib.error.HTTPError as err:
logging.warning('server error retrieving NiceHash stats: %s %s'
% (err.code, err.reason))
except socket.timeout:
logging.warning('failed to retrieve NiceHash stats: timed out')
else:
for device in BENCHMARKS.keys():
payrates = nicehash_btc_per_day(device, paying)
best_algo = max(payrates.keys(), key=lambda algo: payrates[algo])
if device not in device_status:
logging.info('device %s initial algorithm is %s'
% (device, best_algo))
new_algo_id = add_excavator_algorithm(best_algo, device, ports)
device_status[device] = (best_algo, new_algo_id)
else:
current_algo = device_status[device][0]
current_algo_id = device_status[device][1]
if current_algo != best_algo and \
payrates[best_algo]/payrates[current_algo] >= 1.0 + PROFIT_SWITCH_THRESHOLD:
logging.info('switching device %s to %s' % (device, best_algo))
remove_excavator_algorithm(current_algo_id)
new_algo_id = add_excavator_algorithm(best_algo, device, ports)
device_status[device] = (best_algo, new_algo_id)
sleep(UPDATE_INTERVAL)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment