-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathnano-dpow-client
executable file
·246 lines (202 loc) · 9.95 KB
/
nano-dpow-client
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
#!/usr/bin/env python3
import requests, argparse, time, sys, configparser, json, os
import websocket #websocket-client, exceptions
from websocket import create_connection
from datetime import datetime # print timestamp
WORK_TYPES = ['urgent_only', 'precache_only', 'any']
NANO_DIFFICULTY = 'ffffffc000000000'
CONFIG_FILE = os.path.expanduser('~/.nano-dpow-client.conf')
TIMEOUT_RECONNECT = 5*60 # this timeout prevents the client being shadow disconnected
def get_socket():
ws = create_connection(dpow_server)
ws.settimeout(20.0) # this causes a TimeOutException after 20 seconds stuck in .recv()
return ws
def get_work_lib(from_hash, difficulty):
work = format(mpow.generate(bytes.fromhex(from_hash), int(difficulty, 16)), '016x')
return work
def get_work_node(server, from_hash, difficulty):
get_work = '{ "action" : "work_generate", "hash" : "%s", "difficulty": "%s", "use_peers": "true" }' % ( from_hash, difficulty )
r = requests.post(server, data = get_work)
resulting_work = r.json()
work = resulting_work['work'].lower()
return work
def is_desired_work_type(work, preferred):
if preferred == 'any':
return work in ['urgent', 'precache']
if preferred == 'urgent_only':
return work == 'urgent'
if preferred == 'precache_only':
return work == 'precache'
return False
# returns empty str to signal that the option is not there
def config_safe_get_str(config, section, option):
try:
return config.get(section, option)
except:
return ''
class Stats(object):
def __init__(self, time_window):
assert time_window > 0
print('Stats - using time window of {:d} seconds'.format(time_window))
if time_window < 30:
print('Use at least a window of 30 seconds for accurate benchmarking')
sys.exit(1)
self.time_window = time_window
self.times = list()
self.last_tick = time.perf_counter()
def get_window_perf_ratio(self):
sum_work = sum( [work_time for (stamp,work_time) in self.times] )
return sum_work / float(self.time_window)
def clear_old(self):
oldest_allowed = time.time() - self.time_window
self.times = [ (stamp, work_time) for (stamp,work_time) in self.times if stamp > oldest_allowed ]
def update(self, work_time):
self.clear_old()
self.times.append( (time.time(), work_time ) )
return self
# ---- MAIN ---- #
if __name__ == "__main__":
# get arguments that were not given via config file
parser = argparse.ArgumentParser()
parser.add_argument('--address', type=str, required=False, metavar='XRB/NANO_ADDRESS', help='Payout address.')
parser.add_argument('--work-type', type=str, action='store', choices=WORK_TYPES, required=False, metavar='WORK_TYPE', help='Desired work type. Options: any, urgent_only, precache_only (default).')
parser.add_argument('--node', action='store_true', help='Compute work on the node. Default is to use libmpow.')
parser.add_argument('--save-config', action='store_true', help='Save the current settings as default.')
parser.add_argument('--worker-ip', type=str, default='127.0.0.1', help='The IP of the work-server listening for RPC calls')
parser.add_argument('--worker-port', type=int, default='7076', help='The port where the work-server is listening for RPC calls')
parser.add_argument('--dpow-server', type=str, default='ws://yapraiwallet.space:5000/group/', help='The full URI of the DPoW server to connect to')
args = parser.parse_args()
work_server = "http://{}:{}".format(args.worker_ip , str(args.worker_port))
dpow_server = args.dpow_server
# parse config file
config = configparser.ConfigParser()
config.read(CONFIG_FILE)
# argparse has priority, if not given go for config file
payout_address = args.address or config_safe_get_str(config, 'DEFAULT', 'address')
desired_work_type = args.work_type or config_safe_get_str(config, 'DEFAULT','work_type')
# payout address required
if not payout_address:
print('The payout address is required (use --address YOUR_NANO_ADDRESS)')
sys.exit()
# default value for work_type, cant be checked in argparse because there's no way to know if the arg was given or defaulted if precache_only was chosen
if desired_work_type == '': desired_work_type = 'precache_only'
# check if valid work type (should be handled by argparse, but not by the configparse
if desired_work_type not in WORK_TYPES:
print('Invalid work_type set in config ( {} ). Choose from {} '.format(desired_work_type, WORK_TYPES))
sys.exit()
# save configuration
if args.save_config:
config.set('DEFAULT', 'address', payout_address)
config.set('DEFAULT', 'work_type', desired_work_type)
with open(CONFIG_FILE, 'w+') as f:
config.write(f)
print('\n! Your configuration has been saved to {}\n'.format(CONFIG_FILE))
print("Welcome to Distributed Nano Proof of Work System")
print("All payouts will go to %s" % payout_address)
print("You have chosen to do work of type %s" % desired_work_type)
if not args.node:
print("You have selected local PoW processor (libmpow)")
import mpow
else:
print("You have selected node PoW processor (work_server or nanocurrency node)")
try:
ws = get_socket()
set_work_type = '{"work_type" : "%s", "address": "%s"}' % (desired_work_type, payout_address)
ws.send(set_work_type)
except Exception as e:
print('\nError - unable to connect to backend server\nTry again later or change the server in config.ini.\n{}'.format(e))
sys.exit()
stats = Stats(2*60) # 2 minutes window
waiting = True
time_last_msg = time.time()
while 1:
try:
if not waiting:
print("Waiting for work...", end='', flush=True)
waiting = True
try:
work_request = json.loads(str(ws.recv()))
waiting = False
time_last_msg = time.time()
except websocket.WebSocketTimeoutException as e:
if time.time() - time_last_msg > TIMEOUT_RECONNECT:
print("Too long since a server message, reconnecting...")
try:
ws.close()
ws = get_socket()
set_work_type = '{"work_type" : "%s", "address": "%s"}' % (desired_work_type, payout_address)
ws.send(set_work_type)
print("Connected")
time_last_msg = time.time()
except:
pass
else:
print('.', end='', flush=True)
ws.ping()
continue
is_work = 'hash' in work_request
if not is_work:
print("Status message received: {}".format(work_request))
if 'status' in work_request and work_request['status'] == 'error':
print("Reconnecting due to error...")
time.sleep(2)
try:
ws.close()
ws = get_socket()
set_work_type = '{"work_type" : "%s", "address": "%s"}' % (desired_work_type, payout_address)
ws.send(set_work_type)
print("Connected")
time_last_msg = time.time()
except:
print("Failed to reconnect - closing")
break
continue
has_type = 'type' in work_request
has_difficulty = 'difficulty' in work_request
print("\n{} \tGot work ({} - {})".format(datetime.now(),
work_request['type'] if has_type else 'unknown',
work_request['difficulty'] if has_difficulty else 'no_difficulty'), flush=True)
# check if work type is the expected
if has_type and not is_desired_work_type(work_request['type'], desired_work_type):
print("\t\t\t\t! Unexpected/Undesired work type: {}".format(work_request['type']))
# default to NANO difficulty if not provided
if not has_difficulty:
work_th = NANO_DIFFICULTY
else:
work_th = work_request['difficulty']
t = time.perf_counter()
try:
if not args.node:
work = get_work_lib(work_request['hash'], work_th)
else:
work = get_work_node(work_server, work_request['hash'], work_th)
except Exception as e:
print(work_request)
raise Exception('While getting work - {}'.format(e))
else:
work_time = time.perf_counter()-t
print("\t\t\t\t{} - took {:.2f}s".format(work, work_time))
try:
ratio = stats.update(work_time).get_window_perf_ratio()
print("\t\t\t\tUsage ({:d}s) = {:.2f}%".format(stats.time_window, ratio*100.0))
except:
pass
json_request = '{"hash" : "%s", "work" : "%s", "address" : "%s"}' % (work_request['hash'], work, payout_address)
ws.send(json_request)
except KeyboardInterrupt:
print("\nCtrl-C detected, canceled by user\n")
break
except Exception as e:
print("Error: {}".format(e))
print("Reconnecting in 15 seconds...")
time.sleep(15)
try:
if ws:
ws.close()
ws = None
ws = get_socket()
set_work_type = '{"work_type" : "%s", "address": "%s"}' % (desired_work_type, payout_address)
ws.send(set_work_type)
print("Connected")
except:
continue