|
#native module |
|
import os, sys |
|
import socket |
|
import threading as th |
|
from json import loads, dumps |
|
from time import sleep |
|
from itertools import combinations |
|
from subprocess import Popen, PIPE |
|
|
|
#3rd-party module |
|
from requests import get |
|
|
|
|
|
''' |
|
Constant |
|
''' |
|
#template for stratum protocol's mining.notify |
|
NOTIFY_TEMPLATE = { |
|
"id": None, |
|
"method": "mining.notify", |
|
"params": [ |
|
"{}", |
|
"3960633ee64ba81fc408ced0e1a6a8bf0d2513cccab2d6eb78cb2c0e50ee4ded", |
|
"03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff200364ee0204709b9f6108", |
|
"0d2f6e6f64655374726174756d2f00000000068fea7648170000001976a91414abbb7ccb2822390579b2ad6703bf736f7a4c9088aca3ba1dd2050000001976a914cc36a8de36f6ff61dbfb798982e8b47354ad4e0e88acc20b5a16000000001976a914019e66f413066ca54ca58181fae890e636d6541f88aceb349564000000001976a9145b0ebe871b1ccea91909f435804d3a3f879e016988aceb349564000000001976a9146229d9b8f61eab84120c9b9ae46db9c3b2b1e21988ac027a3970560000001976a914ef126063b86e7c1d2f673bf7d1f785f2864e3ae088ac0000000046020064ee020047a9ad69c5fdac75bbe05c904331b53bb826955c3f94ce2c17e7b97997156363458f8c5a524f2f68c3e3395ed360547ba4a75cdb9feca60d32be828c929a1f67", |
|
[], |
|
"20000000", |
|
"1c237b3d", |
|
"619f9b70", |
|
True |
|
] |
|
} |
|
|
|
CONF = { |
|
"http": { |
|
"enabled": True, |
|
"host": "127.0.0.1", |
|
"port": 9999, |
|
"access-token": "KBlueleaf Benchmark", |
|
"restricted": True |
|
}, |
|
"pools": [ |
|
{ |
|
"algo": "ghostrider", |
|
"url": "127.0.0.1:8888", |
|
"user": "", |
|
"pass": "", |
|
"keepalive": True, |
|
"enabled": True, |
|
} |
|
] |
|
} |
|
#code for CN algos |
|
ALGO_CODE = [ |
|
'00', |
|
'11', |
|
'22', |
|
'33', |
|
'44', |
|
'55' |
|
] |
|
|
|
#weights for calculated the "Expected Average Hashrate"(from cpuminer-gr) |
|
WEIGHTS = [ |
|
0.094555, |
|
0.055475, |
|
0.021256, |
|
0.022461, |
|
0.128275, |
|
0.044810, |
|
0.063186, |
|
0.037458, |
|
0.035620, |
|
0.010150, |
|
0.111971, |
|
0.051201, |
|
0.058180, |
|
0.044812, |
|
0.029149, |
|
0.009918, |
|
0.074940, |
|
0.058862, |
|
0.032738, |
|
0.014982 |
|
] |
|
|
|
# get runing path |
|
CWD_PATH = os.getcwd() |
|
|
|
|
|
''' |
|
utils |
|
''' |
|
# encode and decode to/from utf-8 |
|
encode = lambda x:bytes(x, encoding='utf-8') |
|
decode = lambda x:x.decode(encoding='utf-8') |
|
|
|
def make_header(algo_choose, sub_rotation): |
|
''' |
|
make RTM mining header for specific rotation of CN algos |
|
''' |
|
header = ALGO_CODE[algo_choose[0]]*8 |
|
if sub_rotation: |
|
header += ALGO_CODE[algo_choose[2]]*8 |
|
header += ALGO_CODE[algo_choose[1]]*8 |
|
else: |
|
header += ALGO_CODE[algo_choose[1]]*8 |
|
header += ALGO_CODE[algo_choose[2]]*8 |
|
header += os.urandom(8).hex() |
|
return header |
|
|
|
def get_job(job_id, algo_choose, sub_rotation): |
|
''' |
|
get notify string |
|
''' |
|
new_notify = eval(repr(NOTIFY_TEMPLATE)) |
|
new_notify['params'][0].format(job_id) |
|
new_notify['params'][1] = make_header(algo_choose, sub_rotation) |
|
|
|
return dumps(new_notify)+'\n' |
|
|
|
def get_hash_rate(period = 0): |
|
''' |
|
get hashrate from xmrig api |
|
''' |
|
try: |
|
summary = get('http://127.0.0.1:9999/1/summary', headers={'Authorization': 'Bearer KBlueleaf Benchmark'}).content |
|
hash_rate = loads(summary)['hashrate']['total'][period] |
|
return hash_rate |
|
except: |
|
return 0 |
|
|
|
def bench_log(*string): |
|
''' |
|
log string |
|
''' |
|
print( |
|
'============benchmark info============', |
|
''.join(string), |
|
'======================================', |
|
sep='\n' |
|
) |
|
|
|
|
|
class StratumHandler(th.Thread): |
|
''' |
|
A thread object for handling the client(Miner) |
|
''' |
|
def __init__(self, *args, **kwargs): |
|
super(StratumHandler, self).__init__(*args, **kwargs) |
|
self.stop_flag = False |
|
|
|
def stop(self): |
|
self.stop_flag = True |
|
|
|
def run(self): |
|
global authed, conn |
|
|
|
while not self.stop_flag: |
|
try: |
|
#check message every 0.5sec |
|
#when self.stop is called, this setting can break the loop |
|
conn.settimeout(0.5) |
|
datas = decode(conn.recv(10000)).split('\n') |
|
except TimeoutError: |
|
datas = [] |
|
except socket.timeout: |
|
datas = [] |
|
except ConnectionResetError: |
|
break |
|
|
|
#read all jsonrpc line |
|
for i in datas: |
|
if i: |
|
#jsonrpc data |
|
data = loads(i) |
|
|
|
response = None |
|
|
|
if 'id' in data: |
|
id = data['id'] |
|
|
|
#check method |
|
if data['method'] == 'mining.subscribe': |
|
#make response |
|
response = [ |
|
f'{{"id": {id},"result": [[["mining.set_difficulty","deadbeefcafebabe3b00000000000000"],["mining.notify","deadbeefcafebabe3b00000000000000"]],"",4],"error": null}}\n', |
|
'{"id": null, "method": "mining.set_difficulty", "params":[10000000.0]}\n' |
|
] |
|
elif data['method'] == 'mining.authorize': |
|
response = [ |
|
f'{{"id": {id}, "result": true, "error": null}}\n' |
|
] |
|
authed = True |
|
|
|
if response is not None: |
|
if isinstance(response, list): |
|
for re in response: |
|
conn.sendall(encode(re)) |
|
sleep(0.1) |
|
else: |
|
conn.sendall(encode(response)) |
|
|
|
|
|
''' |
|
Main program |
|
''' |
|
authed = False |
|
conn = None |
|
def main(): |
|
''' |
|
Main function |
|
''' |
|
|
|
global conn |
|
''' |
|
setup |
|
''' |
|
# parse args |
|
if len(sys.argv)>1: |
|
benchtime = sys.argv[1] |
|
if benchtime not in {'-8m','-20m'}: |
|
print('Invalid Arg') |
|
return |
|
else: |
|
benchtime = '-8m' |
|
|
|
# read existing config file for cpu config |
|
config = None |
|
if os.path.isfile(f'{CWD_PATH}/config.json'): |
|
with open(f'{CWD_PATH}/config.json', 'r', encoding='utf-8') as f: |
|
config = loads(f.read()) |
|
if config and 'cpu' in config: |
|
CONF['cpu'] = config['cpu'] |
|
|
|
# write config file for benchmark |
|
with open('./_benchamrk.json', 'w', encoding='utf-8') as f: |
|
f.write(dumps(CONF)) |
|
|
|
# create custom stratum server |
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
server.bind(('127.0.0.1',8888)) |
|
server.listen(1) |
|
server.settimeout(3) |
|
|
|
# create miner process |
|
if sys.platform == 'win32': |
|
miner = Popen([f'{CWD_PATH}/xmrig.exe', '--config', '_benchamrk.json'], stdin=PIPE) |
|
else: |
|
miner = Popen([f'{CWD_PATH}/xmrig', '--config','_benchamrk.json'], stdin=PIPE) |
|
|
|
# connect with miner |
|
while True: |
|
try: |
|
conn, addr = server.accept() |
|
print(addr) |
|
break |
|
except TimeoutError: |
|
continue |
|
|
|
# start stratum handler |
|
handler = StratumHandler() |
|
handler.start() |
|
|
|
''' |
|
benchmark |
|
''' |
|
try: |
|
# wait for auth |
|
while not authed: |
|
sleep(0.1) |
|
|
|
# warm up for 5 secs |
|
bench_log('Warm up') |
|
conn.sendall(encode(get_job(-1, [2,3,1], 0))) |
|
sleep(5) |
|
|
|
# start benchmark |
|
bench_log( |
|
'Start Benchmark\n', |
|
f'Set benchmark config to {benchtime} version\n', |
|
'It may take ' , |
|
('8 minutes' if benchtime=='-8m' else '21 minutes'), |
|
' to complete', |
|
) |
|
all_data = [] |
|
total = 0 |
|
|
|
# get all combination for all 6 algos |
|
for id, algos in enumerate(combinations(range(6), 3)): |
|
# every combination has 2 rotation pattern |
|
for sub_rotation in range(2): |
|
bench_log( |
|
'Now Benchmark for:\n' |
|
f'Rotation {id+1}.{sub_rotation+1}' |
|
) |
|
|
|
# send custom job to miner |
|
conn.sendall(encode(get_job(id, algos, sub_rotation))) |
|
sleep(2) |
|
|
|
if benchtime=='-20m': |
|
hash_rate = 0 |
|
for i in range(3): |
|
sleep(10) |
|
hash_rate += get_hash_rate(0) |
|
hash_rate /= 3 |
|
elif benchtime=='-8m': |
|
sleep(10) |
|
hash_rate = get_hash_rate(0) |
|
|
|
bench_log( |
|
f'Rotation {id+1}.{sub_rotation+1} result:\n' |
|
f'Hashrate: {hash_rate:.1f}H/s' |
|
) |
|
|
|
total += hash_rate*WEIGHTS[id] |
|
all_data.append(hash_rate) |
|
|
|
# print result |
|
bench_log( |
|
'Total Result\n' |
|
f'Expected Average Hashrate: {total/2:.1f}H/s' |
|
) |
|
|
|
finally: |
|
handler.stop() |
|
miner.kill() |
|
|
|
|
|
if __name__ == '__main__': |
|
try: |
|
main() |
|
except KeyboardInterrupt: |
|
pass |
|
except Exception as e: |
|
print(e) |
|
finally: |
|
if os.path.isfile(f'./_benchmark.json'): |
|
os.remove(f'./_benchamrk.json') |