-
Notifications
You must be signed in to change notification settings - Fork 0
/
MinerSwitcher.py
executable file
·394 lines (314 loc) · 12.1 KB
/
MinerSwitcher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#!/usr/bin/env python
# coding=iso-8859-1
# MinerSwitcher.py: profitability-based mining farm switcher
#
# Copyright © 2014-2015 Scott Alfter
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
import sys
from ProfitLib import ProfitLib
from pycgminer import CgminerAPI
import time
import json
from decimal import *
import operator
import nmap
from pushover import init, Client
from tabulate import tabulate
import os
# check coin daemons
def CheckDaemons(daemons, pushover_keys):
down=""
for i, daemon in enumerate(daemons):
if (daemons[daemon]["active"]==1):
if (PortIsOpen(daemons[daemon]["host"], daemons[daemon]["port"])==False):
down+=daemon+" "
if (down!=""):
SendNotification(pushover_keys, "Coin Daemons Down", "These coin daemons are not responding: "+down)
print now()+": dead coin daemons: "+down
return False
else:
return True
# send Pushover notification
def SendNotification(pushover_keys, title, msg):
if (pushover_keys!=None):
init(pushover_keys["app_key"]) # Pushover API key for MiningSwitcher
client=Client(pushover_keys["user_key"]).send_message(msg, title=title, priority=1)
# see if a port is open
def PortIsOpen(host, port):
scan=nmap.PortScanner().scan(hosts=host, ports=str(port), arguments="-Pn")["scan"]
open=False
for i, ipaddr in enumerate(scan):
if (scan[ipaddr]["tcp"][port]["state"]=="open"):
open=True
return open
# verify that at least one pool configured for a coin is up
def CheckPools(coin, pools):
open=False
# iterate through available pools
for i, pool in enumerate(pools):
if (pools[pool]["coin"]==coin and pools[pool]["active"]==1):
if (PortIsOpen(pools[pool]["hostname"], pools[pool]["port"])):
open=True
return open
# reconfigure a cgminer/bfgminer instance to mine on another pool
# (by default, remove all other pools)
def SwitchPool(hostname, port, pool_url, pool_worker, pool_passwd, clear=1):
a=CgminerAPI(hostname, port)
# add pool
a.addpool(pool_url+","+pool_worker+","+pool_passwd)
time.sleep(1)
# wait for the connection
live=0
while (live==0):
for i, pool in enumerate(a.pools()["POOLS"]):
if (pool["URL"]==pool_url and pool["User"]==pool_worker):
live=1
if (live==0):
time.sleep(1)
# switch to new pool and find it
pools=a.pools()["POOLS"]
delete_list=[]
found=0
for i, pool in enumerate(pools):
if (pool["URL"]==pool_url and pool["User"]==pool_worker and found==0):
found=1
a.switchpool(pool["POOL"])
else:
delete_list.append(pool["POOL"])
# remove other pool entries
if (clear==1):
delete_list.sort(reverse=True)
for i, num in enumerate(delete_list):
a.removepool(num)
# switch all miners of a given type to a different coin
def SwitchCoin(coin, algo, miners, pools, pushover_key):
# enumerate compatible miners
for i, miner in enumerate(miners):
if (miners[miner]["algo"]==algo):
hostname=miners[miner]["hostname"]
rpc_port=miners[miner]["rpc_port"]
# enumerate available pools
pool_priorities={}
for j, pool in enumerate(pools):
if (pools[pool]["coin"]==coin and pools[pool]["active"]==1):
pool_priorities[pool]=pools[pool]["priority"]
sorted_priorities=sorted(pool_priorities.items(), key=operator.itemgetter(1))
for k, pool in enumerate(sorted_priorities):
# switch a miner to the pool
pool_url=pools[pool[0]]["protocol"]+"://"+pools[pool[0]]["hostname"]+":"+str(pools[pool[0]]["port"])
pool_worker=pools[pool[0]]["worker_prefix"]+pools[pool[0]]["worker_separator"]+miner
pool_worker_pass=pools[pool[0]]["worker_password"]
if (k==0):
clear=1
else:
clear=0
print now()+": switching "+miner+" to "+pool[0]
try:
SwitchPool(hostname, rpc_port, pool_url, pool_worker, pool_worker_pass, clear)
except:
if (pushover_key!=None):
SendNotification(pushover_key, miner+" Down", "MinerSwitcher was unable to switch "+miner+" to "+coin+" mining. Is it down?")
print now()+": unable to switch "+miner+" to "+coin+"...miner down?"
# get current date & time
def now():
return time.strftime("%c")
# dump ProfitLib results as a table
# (cribbed from ProfitLib/profit.py)
def MakeTable(algo, profit):
result={}
for i, coin in enumerate(profit):
if (profit[coin]["algo"]==algo and profit[coin]["merged"]==[]):
result[coin]=Decimal(profit[coin]["daily_revenue_btc"])/100000000
for j, mergecoin in enumerate(profit):
if (profit[mergecoin]["algo"]==algo and profit[mergecoin]["merged"]!=[]):
for k, basecoin in enumerate(profit[mergecoin]["merged"]):
if (basecoin==coin):
result[coin]+=Decimal(profit[mergecoin]["daily_revenue_btc"])/100000000
sorted_result=sorted(result.items(), key=operator.itemgetter(1), reverse=True)
tbl=[]
for i, r in enumerate(sorted_result):
tbl.append([r[0], Decimal(r[1])])
print tabulate(tbl)
return sorted_result
# find currently active pool
def FindActivePool(miner):
a=CgminerAPI(miner["hostname"], miner["rpc_port"])
pools=a.pools()["POOLS"]
for i in range(0, len(pools)):
try:
if (pools[i]["Stratum Active"]==True):
return pools[i]["Stratum URL"]
except:
pass
# TODO: add checks for non-stratum servers
return ""
# make sure we're making some sort of progress
def CheckMiners(miners, accepted, rejected):
rtnval={}
for i, miner in enumerate(miners):
try:
a=CgminerAPI(miners[miner]["hostname"], miners[miner]["rpc_port"])
summary=a.summary()["SUMMARY"]
if (accepted[miner]==summary[0]["Accepted"] and rejected[miner]==summary[0]["Rejected"] and accepted[miner]!=-1 and rejected[miner]!=-1):
rtnval[miner]=FindActivePool(miners[miner])
accepted[miner]=summary[0]["Accepted"]
rejected[miner]=summary[0]["Rejected"]
except:
pass
return rtnval
# C-style main() to eliminate global scope
def main(argc, argv):
# load config files
exchanges=None
miners=None
pools=None
daemons=None
pushover_key=None
for loc in os.curdir, os.path.join(os.path.expanduser("~"), ".MinerSwitcher"), "/etc/MinerSwitcher":
try:
exchanges=json.loads(open(os.path.join(loc, "exchange_config.json")).read())
miners=json.loads(open(os.path.join(loc, "miner_config.json")).read())
pools=json.loads(open(os.path.join(loc, "pool_config.json")).read())
daemons=json.loads(open(os.path.join(loc, "daemon_config.json")).read())
pushover_key=json.loads(open(os.path.join(loc, "pushover_config.json")).read())
except IOError:
pass
if (exchanges==None or miners==None or pools==None or daemons==None):
raise FileNotFoundError("required config file(s) missing")
# find algos supported by active miners
miner_algos={}
for i, miner in enumerate(miners):
miner_algos[miners[miner]["algo"]]=True
# initialize accepted/rejected counters
accepted={}
rejected={}
pool_up={}
for i, miner in enumerate(miners):
accepted[miner]=-1
rejected[miner]=-1
pool_up[miner]=True
# override hashrates in daemons dict with totals from miners dict
hashrates={}
for i, miner in enumerate(miners): # init on 1st pass
hashrates[miners[miner]["algo"]]=0
for i, miner in enumerate(miners): # sum on 2nd pass
hashrates[miners[miner]["algo"]]+=miners[miner]["hashrate"]
for i, coin in enumerate(daemons): # update daemons
try:
daemons[coin]["hashespersec"]=hashrates[daemons[coin]["algo"]]
except:
pass
pl=ProfitLib(daemons, exchanges)
# main loop
last_coin={}
counts={}
while (0==0):
# check coin daemons
print now()+": checking coin daemons"
if (CheckDaemons(daemons, pushover_key)==False):
print now()+": suspending operation until all daemons are running"
while (0==0):
time.sleep(60)
if (CheckDaemons(daemons, None)):
break
# get updated profitability
ok=False
while (ok==False):
print now()+": running ProfitLib"
try:
profit=pl.Calculate()
ok=True
except:
print now()+": caught an exception, trying again...daemons slow to respond?"
# find algo types
algos={}
for i, coin in enumerate(profit):
algos[profit[coin]["algo"]]=profit[coin]["algo"]
try:
z=counts[profit[coin]["algo"]] # see if it exists
except:
counts[profit[coin]["algo"]]={} # create it if it doesn't
try:
z=last_coin[profit[coin]["algo"]] # see if it exists
except:
last_coin[profit[coin]["algo"]]="" # create it if it doesn't
# loop on available algos
for i, algo in enumerate(algos):
if (miner_algos.has_key(algo)):
# print profitability table, and find the most profitable coin
tbl=MakeTable(algo, profit)
# pick the most profitable for which the pools are up and running
running=False
for j, coin in enumerate(tbl):
print now()+": checking "+coin[0]+" pools"
if (CheckPools(coin[0], pools) and running==False):
running=True
coin_max=coin[0]
break
else:
if (pushover_key!=None):
SendNotification(pushover_key, coin[0]+" Pools Down", "None of the pools you have configured for "+coin[0]+" are responding.")
print now()+": all "+coin[0]+" pools down...skipping!"
if (running==False):
if (pushover_key!=None):
SendNotification(pushover_key, "ALL POOLS DOWN!!!", "None of your configured pools are responding.")
print now()+": ALL POOLS DOWN!!!"
else:
# do we need to switch?
if (last_coin[algo]!=coin_max):
SwitchCoin(coin_max, algo, miners, pools, pushover_key)
last_coin[algo]=coin_max
try:
counts[algo][coin_max]+=1
except:
counts[algo][coin_max]=1
# wait 30 minutes
# check miners every other minute to make sure they're working
for i, algo in enumerate(counts):
tbl=[[],[]]
#print algo+":"
tbl[0].append(algo+":")
tbl[1].append("")
for j, coin in enumerate(counts[algo]):
#print " "+coin+": "+str(counts[algo][coin])
tbl[0].append(coin)
tbl[1].append(counts[algo][coin])
print tabulate(tbl)
print now()+": sleep for 30 minutes"
for i in range(0, 6):
time.sleep(300)
down=CheckMiners(miners, accepted, rejected)
if (len(down)>0):
for i, miner in enumerate(down):
print now()+": no progress made at "+down[miner]+" by "+miner
if (pool_up[miner]==True):
SendNotification(pushover_key, "Pool Issue", "No progress made at "+down[miner]+" by "+miner+".")
pool_up[miner]=False
for i, miner in enumerate(pool_up):
try:
t=down[miner]
except:
pool_up[miner]=True
# log completed blocks: append a line
for i, algo in enumerate(algos):
if (last_coin[algo]!=""):
with open("log_"+last_coin[algo], "a") as log:
log.write("\n")
main(len(sys.argv), sys.argv)