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.

Revisions

  1. @YoRyan YoRyan revised this gist Apr 9, 2018. No changes.
  2. @YoRyan YoRyan revised this gist Jan 26, 2018. No changes.
  3. @YoRyan YoRyan revised this gist Jan 26, 2018. No changes.
  4. Ryan A Young revised this gist Jan 26, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -159,8 +159,8 @@ def main():
    # dict of device id -> excavator worker id
    worker_status = {}

    device_algorithm = lambda device: [a for a in algorithm_status.keys()
    if device in algorithm_status[a][1]][0]
    device_algorithm = lambda device: [a for a in algorithm_status.keys() if
    device in algorithm_status[a][1]][0]

    def dispatch_device(device, algo, ports):
    if algo in algorithm_status:
  5. Ryan A Young revised this gist Jan 26, 2018. 1 changed file with 61 additions and 32 deletions.
    93 changes: 61 additions & 32 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -5,9 +5,12 @@

    # Example usage:
    # $ excavator -p 3456 &
    # $ sleep 5
    # $ python3 excavator-driver.py

    # History:
    # 2017-12-03: initial version
    # 2018-01-25: group devices by common algorithm; wait for excavator on startup

    __author__ = "Ryan Young"
    __email__ = "[email protected]"
    __license__ = "public domain"
    @@ -21,7 +24,7 @@
    import urllib.request
    from time import sleep

    WALLET_ADDR = '3DzJYbtHt9QdZzQiDnq5tZD8VsDuwRnbdJ'
    WALLET_ADDR = '32RPicPbRK18S2fzY4cEwNUy17iygJyPjF'
    WORKER_NAME = 'worker1'
    REGION = 'usa' # eu, usa, hk, jp, in, br

    @@ -134,50 +137,79 @@ def do_excavator_command(method, params):
    else:
    raise ExcavatorAPIError(response_data)

    def add_excavator_algorithm(algo, device, ports):
    """Runs an algorithm on a device, returns the new excavator algorithm id.
    def excavator_algorithm_params(algo, ports):
    """Return the required list of parameters to add an algorithm to excavator.
    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])
    return [algo] + sum([[stratum(multi_algo), AUTH] for multi_algo in
    algo.split('_')], [])

    add_params = [algo] + sum([[stratum(multi_algo), AUTH] for multi_algo in
    algo.split('_')], [])
    response = do_excavator_command('algorithm.add', add_params)
    algo_id = response['algorithm_id']
    def main():
    """Main program."""
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
    level=logging.INFO)

    do_excavator_command('worker.add', [str(algo_id), str(device)])
    # dict of algorithm name -> (excavator id, [attached devices])
    algorithm_status = {}
    # dict of device id -> excavator worker id
    worker_status = {}

    return algo_id
    device_algorithm = lambda device: [a for a in algorithm_status.keys()
    if device in algorithm_status[a][1]][0]

    def remove_excavator_algorithm(algo_id):
    """Removes an algorithm from excavator and all (one) associated workers.
    def dispatch_device(device, algo, ports):
    if algo in algorithm_status:
    algo_id = algorithm_status[algo][0]
    algorithm_status[algo][1].append(device)
    else:
    response = do_excavator_command('algorithm.add',
    excavator_algorithm_params(algo, ports))
    algo_id = response['algorithm_id']
    algorithm_status[algo] = (algo_id, [device])

    algo_id -- excavator algorithm id to remove
    """
    response = do_excavator_command('worker.add', [str(algo_id), str(device)])
    worker_status[device] = response['worker_id']

    do_excavator_command('algorithm.remove', [str(algo_id)])
    def free_device(device):
    algo = device_algorithm(device)
    algorithm_status[algo][1].remove(device)
    worker_id = worker_status[device]
    worker_status.pop(device)

    def main():
    """Main program."""
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
    level=logging.INFO)
    do_excavator_command('worker.free', [str(worker_id)])

    if len(algorithm_status[algo][1]) == 0: # no more devices attached
    algo_id = algorithm_status[algo][0]
    algorithm_status.pop(algo)

    device_status = {}
    do_excavator_command('algorithm.remove', [str(algo_id)])

    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)
    active_devices = list(worker_status.keys())
    for device in active_devices:
    free_device(device)
    sys.exit(0)
    signal.signal(signal.SIGINT, sigint_handler)

    def contact_excavator():
    try:
    do_excavator_command('message', ['%s connected' % sys.argv[0]])
    except (socket.timeout, socket.error):
    return False
    else:
    return True

    logging.info('connecting to excavator at %s:%d' % EXCAVATOR_ADDRESS)

    while not contact_excavator():
    sleep(5)
    while True:
    try:
    paying, ports = nicehash_multialgo_info()
    @@ -195,25 +227,22 @@ def sigint_handler(signum, frame):
    payrates = nicehash_mbtc_per_day(device, paying)
    best_algo = max(payrates.keys(), key=lambda algo: payrates[algo])

    if device not in device_status:
    if device not in worker_status:
    logging.info('device %s initial algorithm is %s (%.2f mBTC/day)'
    % (device, best_algo, payrates[best_algo]))

    new_algo_id = add_excavator_algorithm(best_algo, device, ports)
    device_status[device] = (best_algo, new_algo_id)
    dispatch_device(device, best_algo, ports)
    else:
    current_algo = device_status[device][0]
    current_algo_id = device_status[device][1]
    current_algo = device_algorithm(device)

    if current_algo != best_algo and \
    (payrates[current_algo] == 0 or \
    payrates[best_algo]/payrates[current_algo] >= 1.0 + PROFIT_SWITCH_THRESHOLD):
    logging.info('switching device %s to %s (%.2f mBTC/day)'
    % (device, best_algo, payrates[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)
    free_device(device)
    dispatch_device(device, best_algo, ports)
    sleep(UPDATE_INTERVAL)

    if __name__ == '__main__':
  6. Ryan A Young revised this gist Jan 17, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -144,6 +144,7 @@ def add_excavator_algorithm(algo, device, ports):

    AUTH = '%s.%s:x' % (WALLET_ADDR, WORKER_NAME)
    stratum = lambda algo: '%s.%s.nicehash.com:%s' % (algo, REGION, ports[algo])

    add_params = [algo] + sum([[stratum(multi_algo), AUTH] for multi_algo in
    algo.split('_')], [])
    response = do_excavator_command('algorithm.add', add_params)
  7. Ryan A Young revised this gist Jan 17, 2018. 1 changed file with 2 additions and 12 deletions.
    14 changes: 2 additions & 12 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -144,18 +144,8 @@ def add_excavator_algorithm(algo, device, ports):

    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]

    add_params = [algo] + sum([[stratum(multi_algo), AUTH] for multi_algo in
    algo.split('_')], [])
    response = do_excavator_command('algorithm.add', add_params)
    algo_id = response['algorithm_id']

  8. Ryan A Young revised this gist Jan 17, 2018. 1 changed file with 8 additions and 16 deletions.
    24 changes: 8 additions & 16 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -92,22 +92,14 @@ def nicehash_mbtc_per_day(device, paying):

    benchmarks = BENCHMARKS[device]
    pay = lambda algo, speed: paying[algo]*speed*(24*60*60)*1e-11
    pay_benched = lambda algo: pay(algo, benchmarks[algo])

    dual_dp = pay('daggerhashimoto', benchmarks['daggerhashimoto_pascal'][0]) \
    + pay('pascal', benchmarks['daggerhashimoto_pascal'][1])
    dual_dd = pay('daggerhashimoto', benchmarks['daggerhashimoto_decred'][0]) \
    + pay('decred', benchmarks['daggerhashimoto_decred'][1])
    dual_ds = pay('daggerhashimoto', benchmarks['daggerhashimoto_sia'][0]) \
    + pay('sia', benchmarks['daggerhashimoto_sia'][1])
    payrates = dict([(algo, pay_benched(algo)) for algo in
    ['equihash', 'pascal', 'decred', 'sia', 'lbry', 'blake2s',
    'lyra2rev2', 'cryptonight', 'daggerhashimoto',
    'neoscrypt', 'nist5']])
    payrates['daggerhashimoto_pascal'] = dual_dp
    payrates['daggerhashimoto_decred'] = dual_dd
    payrates['daggerhashimoto_sia'] = dual_ds
    return payrates
    def pay_benched(algo):
    if '_' in algo:
    return sum([pay(multi_algo, benchmarks[algo][i]) for
    i, multi_algo in enumerate(algo.split('_'))])
    else:
    return pay(algo, benchmarks[algo])

    return dict([(algo, pay_benched(algo)) for algo in benchmarks.keys()])

    def do_excavator_command(method, params):
    """Sends a command to excavator, returns the JSON-encoded response.
  9. Ryan A Young revised this gist Jan 16, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -104,8 +104,8 @@ def nicehash_mbtc_per_day(device, paying):
    ['equihash', 'pascal', 'decred', 'sia', 'lbry', 'blake2s',
    'lyra2rev2', 'cryptonight', 'daggerhashimoto',
    'neoscrypt', 'nist5']])
    payrates['daggerhashimoto_pascal'] = dual_dp,
    payrates['daggerhashimoto_decred'] = dual_dd,
    payrates['daggerhashimoto_pascal'] = dual_dp
    payrates['daggerhashimoto_decred'] = dual_dd
    payrates['daggerhashimoto_sia'] = dual_ds
    return payrates

  10. Ryan A Young revised this gist Jan 16, 2018. 1 changed file with 22 additions and 29 deletions.
    51 changes: 22 additions & 29 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -36,20 +36,21 @@
    BENCHMARKS = {}
    # device 0: GTX 1060 6GB
    BENCHMARKS[0] = {
    'equihash': 300.363149,
    'pascal': 673.490293e6,
    'decred': 1.734736e9,
    'sia': 1.070054e9,
    'lbry': 179.286648e6,
    'blake2s': 2.509039e9,
    'lyra2rev2': 25.908014e6,
    'cryptonight': 435.366891,
    'daggerhashimoto': 22.009473e6,
    'daggerhashimoto_pascal': [8.298873e6, 464.739199e6],
    'daggerhashimoto_decred': [20.258170e6, 729.296593e6],
    'daggerhashimoto_sia': [21.359257e6, 273.398483e6],
    'equihash': 325.964731,
    'pascal': 687.796633e6,
    'decred': 1.896621e9,
    'sia': 1.205557e9,
    'lbry': 185.736261e6,
    'blake2s': 2.767859e9,
    'lyra2rev2': 26.157357e6,
    'cryptonight': 443.131955,
    'daggerhashimoto': 19.965252e6,
    'daggerhashimoto_pascal': [8.847941e6, 495.485485e6],
    'daggerhashimoto_decred': [19.843944e6, 714.382018e6],
    'daggerhashimoto_sia': [19.908869e6, 254.833522e6],
    # test manually
    'neoscrypt': 618.769067e3
    'neoscrypt': 732.554438e3,
    'nist5': 32.031877e6
    }

    PROFIT_SWITCH_THRESHOLD = 0.1
    @@ -99,21 +100,13 @@ def nicehash_mbtc_per_day(device, paying):
    + pay('decred', benchmarks['daggerhashimoto_decred'][1])
    dual_ds = pay('daggerhashimoto', benchmarks['daggerhashimoto_sia'][0]) \
    + pay('sia', benchmarks['daggerhashimoto_sia'][1])
    payrates = {
    'equihash': pay_benched('equihash'),
    'pascal': pay_benched('pascal'),
    'decred': pay_benched('decred'),
    'sia': pay_benched('sia'),
    'lbry': pay_benched('lbry'),
    'blake2s': pay_benched('blake2s'),
    'lyra2rev2': pay_benched('lyra2rev2'),
    'cryptonight': pay_benched('cryptonight'),
    'daggerhashimoto': pay_benched('daggerhashimoto'),
    'neoscrypt': pay_benched('neoscrypt'),
    'daggerhashimoto_pascal': dual_dp,
    'daggerhashimoto_decred': dual_dd,
    'daggerhashimoto_sia': dual_ds
    }
    payrates = dict([(algo, pay_benched(algo)) for algo in
    ['equihash', 'pascal', 'decred', 'sia', 'lbry', 'blake2s',
    'lyra2rev2', 'cryptonight', 'daggerhashimoto',
    'neoscrypt', 'nist5']])
    payrates['daggerhashimoto_pascal'] = dual_dp,
    payrates['daggerhashimoto_decred'] = dual_dd,
    payrates['daggerhashimoto_sia'] = dual_ds
    return payrates

    def do_excavator_command(method, params):
    @@ -241,4 +234,4 @@ def sigint_handler(signum, frame):
    sleep(UPDATE_INTERVAL)

    if __name__ == '__main__':
    main()
    main()
  11. @YoRyan YoRyan revised this gist Jan 8, 2018. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -220,7 +220,8 @@ def sigint_handler(signum, frame):
    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))
    logging.info('device %s initial algorithm is %s (%.2f mBTC/day)'
    % (device, best_algo, payrates[best_algo]))

    new_algo_id = add_excavator_algorithm(best_algo, device, ports)
    device_status[device] = (best_algo, new_algo_id)
    @@ -231,7 +232,8 @@ def sigint_handler(signum, frame):
    if current_algo != best_algo and \
    (payrates[current_algo] == 0 or \
    payrates[best_algo]/payrates[current_algo] >= 1.0 + PROFIT_SWITCH_THRESHOLD):
    logging.info('switching device %s to %s' % (device, best_algo))
    logging.info('switching device %s to %s (%.2f mBTC/day)'
    % (device, best_algo, payrates[best_algo]))

    remove_excavator_algorithm(current_algo_id)
    new_algo_id = add_excavator_algorithm(best_algo, device, ports)
  12. @YoRyan YoRyan revised this gist Dec 31, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -229,8 +229,8 @@ def sigint_handler(signum, frame):
    current_algo_id = device_status[device][1]

    if current_algo != best_algo and \
    payrates[current_algo] != 0 and \
    payrates[best_algo]/payrates[current_algo] >= 1.0 + PROFIT_SWITCH_THRESHOLD:
    (payrates[current_algo] == 0 or \
    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)
  13. @YoRyan YoRyan revised this gist Dec 31, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -229,6 +229,7 @@ def sigint_handler(signum, frame):
    current_algo_id = device_status[device][1]

    if current_algo != best_algo and \
    payrates[current_algo] != 0 and \
    payrates[best_algo]/payrates[current_algo] >= 1.0 + PROFIT_SWITCH_THRESHOLD:
    logging.info('switching device %s to %s' % (device, best_algo))

  14. @YoRyan YoRyan revised this gist Dec 29, 2017. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -212,16 +212,15 @@ def sigint_handler(signum, frame):
    % (err.code, err.reason))
    except socket.timeout:
    logging.warning('failed to retrieve NiceHash stats: timed out')
    except json.decoder.JSONDecodeError:
    except (json.decoder.JSONDecodeError, KeyError):
    logging.warning('failed to parse NiceHash stats')
    else:
    for device in BENCHMARKS.keys():
    payrates = nicehash_mbtc_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))
    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)
  15. @YoRyan YoRyan revised this gist Dec 28, 2017. 1 changed file with 9 additions and 6 deletions.
    15 changes: 9 additions & 6 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,9 @@
    """Cross-platform controller for NiceHash Excavator for Nvidia."""

    # Example usage:
    # $ excavator -p 3456 &
    # $ python3 excavator-driver.py
    # $ excavator -p 3456 &
    # $ sleep 5
    # $ python3 excavator-driver.py

    __author__ = "Ryan Young"
    __email__ = "[email protected]"
    @@ -20,13 +21,13 @@
    import urllib.request
    from time import sleep

    WALLET_ADDR = '1BQSMa5mfDNzut5PN9xgtJe3wqaqGEEerD'
    WALLET_ADDR = '3DzJYbtHt9QdZzQiDnq5tZD8VsDuwRnbdJ'
    WORKER_NAME = 'worker1'
    REGION = 'usa' # eu, usa, hk, jp, in, br

    EXCAVATOR_ADDRESS = ('127.0.0.1', 3456)

    # copy the numbers from excavator-benchmark
    # copy the numbers from excavator-benchmark (test one device at a time with -d <n>)
    # convert to the base unit, H/s
    # x H/s -> x
    # x kH/s -> x*1e3
    @@ -47,8 +48,8 @@
    'daggerhashimoto_pascal': [8.298873e6, 464.739199e6],
    'daggerhashimoto_decred': [20.258170e6, 729.296593e6],
    'daggerhashimoto_sia': [21.359257e6, 273.398483e6],

    'neoscrypt': 618.769067e3 # test manually
    # test manually
    'neoscrypt': 618.769067e3
    }

    PROFIT_SWITCH_THRESHOLD = 0.1
    @@ -211,6 +212,8 @@ def sigint_handler(signum, frame):
    % (err.code, err.reason))
    except socket.timeout:
    logging.warning('failed to retrieve NiceHash stats: timed out')
    except json.decoder.JSONDecodeError:
    logging.warning('failed to parse NiceHash stats')
    else:
    for device in BENCHMARKS.keys():
    payrates = nicehash_mbtc_per_day(device, paying)
  16. @YoRyan YoRyan revised this gist Dec 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -72,7 +72,7 @@ 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)
    query = json.loads(response.read().decode('ascii')) #json.load(response)
    paying = {}
    ports = {}
    for algorithm in query['result']['simplemultialgo']:
  17. @YoRyan YoRyan revised this gist Dec 4, 2017. 1 changed file with 43 additions and 39 deletions.
    82 changes: 43 additions & 39 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    """Cross-platform controller for NiceHash Excavator for Nvidia."""

    # Example usage:
    # $ /opt/excavator/bin/excavator -p 3456 &
    # $ excavator -p 3456 &
    # $ python3 excavator-driver.py

    __author__ = "Ryan Young"
    @@ -27,23 +27,28 @@
    EXCAVATOR_ADDRESS = ('127.0.0.1', 3456)

    # copy the numbers from excavator-benchmark
    # convert to the base unit, H/s
    # x H/s -> x
    # x kH/s -> x*1e3
    # x MH/s -> x*1e6
    # x GH/s -> x*1e9
    BENCHMARKS = {}
    # GTX 1060 6GB
    # device 0: GTX 1060 6GB
    BENCHMARKS[0] = {
    'equihash': 300.363149, # H/s
    'pascal': 673.490293, # MH/s
    'decred': 1.734736, # GH/s
    'sia': 1.070054, # GH/s
    'lbry': 179.286648, # MH/s
    'blake2s': 0, #2.509039, # GH/s
    'lyra2rev2': 25.908014, # MH/s
    'cryptonight': 435.366891, # H/s
    'daggerhashimoto': 22.009473, # MH/s
    'daggerhashimoto_pascal': [8.298873, 464.739199], # MH/s, MH/s
    'daggerhashimoto_decred': [20.258170, 729.296593], # MH/s
    'daggerhashimoto_sia': [21.359257, 273.398483], # MH/s

    'neoscrypt': 618.769067 # H/s # test manually
    'equihash': 300.363149,
    'pascal': 673.490293e6,
    'decred': 1.734736e9,
    'sia': 1.070054e9,
    'lbry': 179.286648e6,
    'blake2s': 2.509039e9,
    'lyra2rev2': 25.908014e6,
    'cryptonight': 435.366891,
    'daggerhashimoto': 22.009473e6,
    'daggerhashimoto_pascal': [8.298873e6, 464.739199e6],
    'daggerhashimoto_decred': [20.258170e6, 729.296593e6],
    'daggerhashimoto_sia': [21.359257e6, 273.398483e6],

    'neoscrypt': 618.769067e3 # test manually
    }

    PROFIT_SWITCH_THRESHOLD = 0.1
    @@ -67,7 +72,7 @@ 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.loads(response.read().decode('ascii')) #json.load(response)
    query = json.load(response)
    paying = {}
    ports = {}
    for algorithm in query['result']['simplemultialgo']:
    @@ -76,35 +81,34 @@ def nicehash_multialgo_info():
    ports[name] = int(algorithm['port'])
    return paying, ports

    def nicehash_btc_per_day(device, paying):
    def nicehash_mbtc_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))
    pay = lambda algo, speed: paying[algo]*speed*(24*60*60)*1e-11
    pay_benched = lambda algo: pay(algo, benchmarks[algo])

    dual_dp = pay('daggerhashimoto', benchmarks['daggerhashimoto_pascal'][0]) \
    + pay('pascal', benchmarks['daggerhashimoto_pascal'][1])
    dual_dd = pay('daggerhashimoto', benchmarks['daggerhashimoto_decred'][0]) \
    + pay('decred', benchmarks['daggerhashimoto_decred'][1])
    dual_ds = pay('daggerhashimoto', benchmarks['daggerhashimoto_sia'][0]) \
    + pay('sia', benchmarks['daggerhashimoto_sia'][1])
    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),
    'equihash': pay_benched('equihash'),
    'pascal': pay_benched('pascal'),
    'decred': pay_benched('decred'),
    'sia': pay_benched('sia'),
    'lbry': pay_benched('lbry'),
    'blake2s': pay_benched('blake2s'),
    'lyra2rev2': pay_benched('lyra2rev2'),
    'cryptonight': pay_benched('cryptonight'),
    'daggerhashimoto': pay_benched('daggerhashimoto'),
    'neoscrypt': pay_benched('neoscrypt'),
    'daggerhashimoto_pascal': dual_dp,
    'daggerhashimoto_decred': dual_dd,
    'daggerhashimoto_sia': dual_ds
    @@ -209,7 +213,7 @@ def sigint_handler(signum, frame):
    logging.warning('failed to retrieve NiceHash stats: timed out')
    else:
    for device in BENCHMARKS.keys():
    payrates = nicehash_btc_per_day(device, paying)
    payrates = nicehash_mbtc_per_day(device, paying)
    best_algo = max(payrates.keys(), key=lambda algo: payrates[algo])

    if device not in device_status:
  18. @YoRyan YoRyan revised this gist Dec 4, 2017. 1 changed file with 14 additions and 14 deletions.
    28 changes: 14 additions & 14 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -30,20 +30,20 @@
    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
    'equihash': 300.363149, # H/s
    'pascal': 673.490293, # MH/s
    'decred': 1.734736, # GH/s
    'sia': 1.070054, # GH/s
    'lbry': 179.286648, # MH/s
    'blake2s': 0, #2.509039, # GH/s
    'lyra2rev2': 25.908014, # MH/s
    'cryptonight': 435.366891, # H/s
    'daggerhashimoto': 22.009473, # MH/s
    'daggerhashimoto_pascal': [8.298873, 464.739199], # MH/s, MH/s
    'daggerhashimoto_decred': [20.258170, 729.296593], # MH/s
    'daggerhashimoto_sia': [21.359257, 273.398483], # MH/s

    'neoscrypt': 618.769067 # H/s # test manually
    }

    PROFIT_SWITCH_THRESHOLD = 0.1
  19. @YoRyan YoRyan revised this gist Dec 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -67,7 +67,7 @@ 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)
    query = json.loads(response.read().decode('ascii')) #json.load(response)
    paying = {}
    ports = {}
    for algorithm in query['result']['simplemultialgo']:
  20. @YoRyan YoRyan revised this gist Dec 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -20,7 +20,7 @@
    import urllib.request
    from time import sleep

    WALLET_ADDR = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    WALLET_ADDR = '1BQSMa5mfDNzut5PN9xgtJe3wqaqGEEerD'
    WORKER_NAME = 'worker1'
    REGION = 'usa' # eu, usa, hk, jp, in, br

  21. @YoRyan YoRyan revised this gist Dec 3, 2017. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,10 @@

    """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"
    @@ -18,7 +22,7 @@

    WALLET_ADDR = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    WORKER_NAME = 'worker1'
    REGION = 'usa'
    REGION = 'usa' # eu, usa, hk, jp, in, br

    EXCAVATOR_ADDRESS = ('127.0.0.1', 3456)

  22. @YoRyan YoRyan revised this gist Dec 3, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -149,8 +149,7 @@ def add_excavator_algorithm(algo, device, ports):
    """

    AUTH = '%s.%s:x' % (WALLET_ADDR, WORKER_NAME)
    stratum = lambda algo: '%s.%s.nicehash.com:%s' \
    % (algo, REGION, ports[algo])
    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]
    @@ -180,7 +179,8 @@ def remove_excavator_algorithm(algo_id):

    def main():
    """Main program."""
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO)
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
    level=logging.INFO)

    device_status = {}

  23. @YoRyan YoRyan created this gist Dec 3, 2017.
    231 changes: 231 additions & 0 deletions excavator-driver.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,231 @@
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    """Cross-platform controller for NiceHash Excavator for Nvidia."""

    __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 = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    WORKER_NAME = 'worker1'
    REGION = 'usa'

    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()