class BpowClient(object): def __init__(self): self.client = MQTTClient(loop=loop, config={ "auto_reconnect": True, "reconnect_retries": 5000, "reconnect_max_interval": 120, "keep_alive": 60, "default_qos": 0 }) self.work_handler = WorkHandler(config.worker, self.client, send_work_result, work_server_error_callback, config.async_mode, logger=logger) self.priority = {} self.running = False self.server_online = False def handle_work(self, message): try: topics = message.topic.split('/') work_type = topics[1] # If the message comes from a numbered queue, check if it's a priority queue or not. if len(topics) == 3: priority = (self.priority[work_type] == str(topics[2])) else: priority = False logger.info("did not include queue") content = message.data.decode("utf-8") block_hash, difficulty = content.split(',') except Exception as e: logger.error(f"Could not parse message {message}:\n{e}") return if len(block_hash) == 64: asyncio.ensure_future(self.work_handler.queue_work( work_type, block_hash, difficulty, priority), loop=loop) else: logger.warn(f"Invalid hash {block_hash}") def handle_cancel(self, message): try: block_hash = message.data.decode("utf-8") except: logger.error(f"Could not parse message {message}") return if len(block_hash) == 64: asyncio.ensure_future(self.work_handler.queue_cancel(block_hash), loop=loop) else: logger.warn(f"Invalid hash {block_hash}") def format_stat_message(self, block_rewarded: str, total_work_accepted: int, ondemand: int, precache: int, paid_units: int, paid_amount: float, paid_pending: float): paid_amount = math.floor(paid_amount * 100) / 100 paid_pending = math.floor(paid_pending * 100) / 100 return f"""Block Rewarded: {block_rewarded} --------------------- BoomPow Stats Update: --------------------- Overall {total_work_accepted} of your work units have been accepted by BoomPow (ondemand: {ondemand}, precache: {precache}) You have been paid for {paid_units} of those work units and have received {paid_amount} BANANO so far. So far you've earned {paid_pending} BANANO towards your next reward - TO EXIT: Press CTRL+C ---""" def handle_stats(self, message): try: stats = json.loads(message.data) ondemand = int(stats['ondemand']) if 'ondemand' in stats else 0 precache = int(stats['precache']) if 'precache' in stats else 0 total_work = ondemand + precache total_credited = int( stats['total_credited']) if 'total_credited' in stats else 0 total_paid = float( stats['total_paid']) if 'total_paid' in stats else 0.0 payment_factor = float( stats['payment_factor']) if 'payment_factor' in stats else 0.0 # Figure out estimated payout estimated_payout = (total_work - total_credited) * payment_factor logger.info( self.format_stat_message(stats['block_rewarded'], total_work, ondemand, precache, total_credited, total_paid, estimated_payout)) except Exception as e: logger.warn(f"Could not parse stats message {message}:\n{e}") def handle_heartbeat(self, message): self.time_last_heartbeat = time() def handle_message(self, message): if "cancel" in message.topic: self.handle_cancel(message) elif "work" in message.topic: self.handle_work(message) elif "client" in message.topic: self.handle_stats(message) elif "heartbeat" == message.topic: self.handle_heartbeat(message) elif "priority_response" == message.topic: self.handle_priority(message) async def setup(self): try: await self.client.connect(config.server, cleansession=False) except ConnectException as e: logger.critical("Connection exception: {}".format(e)) return False # Receive a heartbeat before continuing, this makes sure server is up await self.client.subscribe([("heartbeat", QOS_0)]) try: logger.info("Checking for server availability...") await self.client.deliver_message(timeout=2) logger.info("Server online!") self.time_last_heartbeat = time() except asyncio.TimeoutError: logger.critical("Server is offline :(") return False self.server_online = True try: await self.get_priority() except: pass # Subscribe to all necessary topics await self.subscribe() try: await self.work_handler.start() except Exception as e: logger.critical(e) return False self.running = True return True async def subscribe(self): if config.work_type == "any": desired_work = "#" await self.client.subscribe([(f"work/#", QOS_0)]) else: desired_work = config.work_type # precache or ondemand await self.client.subscribe([(f"work/{desired_work}/#", QOS_0)]) await self.client.subscribe([(f"cancel/{desired_work}", QOS_1), (f"client/{config.payout}", QOS_1)]) async def get_priority(self): # subscribe to the topic the server will respond on with the priority queue await self.client.subscribe([(f"priority_response/{config.payout}", QOS_0)]) # send a message to the server to provide you the priority queue await self.client.publish(f"get_priority/{config.work_type}", str.encode(f"{config.payout}", 'utf-8'), qos=QOS_0) try: message = await self.client.deliver_message(timeout=2) await self.handle_priority(message) except asyncio.TimeoutError: logger.error("Timeout while assigning priority for client") async def handle_priority(self, message): # user receives a topic in the message to prioritize, set that as their priority queue. prio = json.loads(message.data) if 'ondemand' in prio: self.priority['ondemand'] = str(prio['ondemand']) if 'precache' in prio: self.priority['precache'] = str(prio['precache']) async def close(self): self.running = False await self.client.publish(f"disconnect/{config.payout}", json.dumps(self.priority).encode('utf-8')) if self.client: await self.client.disconnect() if self.work_handler: await self.work_handler.stop() @asyncio.coroutine async def run(self): logger.info(WELCOME) if not await self.setup(): return await self.close() logger.info("Setup successful, waiting for work") await asyncio.gather(self.message_loop(), self.heartbeat_check_loop(), self.work_handler.loop()) @asyncio.coroutine async def heartbeat_check_loop(self): while self.running: try: await asyncio.sleep(10) if time() - self.time_last_heartbeat > 10: logger.warn( f"Server appears to be offline... {int(time () - self.time_last_heartbeat)} seconds since last heartbeat" ) self.server_online = False elif not self.server_online: logger.info(f"Server is back online") self.server_online = True except Exception as e: if self.running: logger.error(f"Heartbeat check failure: {e}") @asyncio.coroutine async def message_loop(self): while self.running: try: message = await self.client.deliver_message() self.handle_message(message) except KeyboardInterrupt: self.running = False break except Exception as e: logger.critical(f"Unknown exception: {e}") logger.info(f"Sleeping a bit before retrying") await asyncio.sleep(20) try: await self.client.reconnect(cleansession=False) logger.info("Successfully reconnected") except ConnectException as e: logger.error(f"Connection exception: {e}")
class DpowClient(object): def __init__(self): self.client = MQTTClient(loop=loop, config={ "auto_reconnect": True, "reconnect_retries": 5000, "reconnect_max_interval": 120, "keep_alive": 60, "default_qos": 0 }) self.work_handler = WorkHandler(config.worker, self.client, send_work_result, work_server_error_callback, logger=logger) self.running = False self.server_online = False def handle_work(self, message): try: work_type = message.topic.split('/')[-1] content = message.data.decode("utf-8") block_hash, difficulty = content.split(',') except Exception as e: logger.error(f"Could not parse message {message}:\n{e}") return if len(block_hash) == 64: asyncio.ensure_future(self.work_handler.queue_work( work_type, block_hash, difficulty), loop=loop) else: logger.warn(f"Invalid hash {block_hash}") def handle_cancel(self, message): try: block_hash = message.data.decode("utf-8") except: logger.error(f"Could not parse message {message}") return if len(block_hash) == 64: asyncio.ensure_future(self.work_handler.queue_cancel(block_hash), loop=loop) else: logger.warn(f"Invalid hash {block_hash}") def handle_stats(self, message): try: logger.info(f"STATS {json.loads(message.data)}") except Exception as e: logger.warn(f"Could not parse stats message {message}:\n{e}") def handle_heartbeat(self, message): self.time_last_heartbeat = time() def handle_message(self, message): if "cancel" in message.topic: self.handle_cancel(message) elif "work" in message.topic: self.handle_work(message) elif "client" in message.topic: self.handle_stats(message) elif "heartbeat" == message.topic: self.handle_heartbeat(message) async def setup(self): try: await self.client.connect(config.server, cleansession=False) except ConnectException as e: logger.critical("Connection exception: {}".format(e)) return False # Receive a heartbeat before continuing, this makes sure server is up await self.client.subscribe([("heartbeat", QOS_0)]) try: logger.info("Checking for server availability...") await self.client.deliver_message(timeout=2) logger.info("Server online!") self.time_last_heartbeat = time() except asyncio.TimeoutError: logger.critical("Server is offline :(") return False self.server_online = True # Subscribe to all necessary topics await self.subscribe() try: await self.work_handler.start() except Exception as e: logger.critical(e) return False self.running = True return True async def subscribe(self): if config.work_type == "any": desired_work = "#" else: desired_work = config.work_type # precache or ondemand await self.client.subscribe([(f"work/{desired_work}", QOS_0), (f"cancel/{desired_work}", QOS_1), (f"client/{config.payout}", QOS_1)]) async def close(self): self.running = False if self.client: await self.client.disconnect() if self.work_handler: await self.work_handler.stop() async def run(self): logger.info(WELCOME) if not await self.setup(): return await self.close() logger.info("Setup successful, waiting for work") await asyncio.gather(self.message_loop(), self.heartbeat_check_loop(), self.work_handler.loop()) async def heartbeat_check_loop(self): while self.running: try: await asyncio.sleep(10) if time() - self.time_last_heartbeat > 10: logger.warn( f"Server appears to be offline... {int(time () - self.time_last_heartbeat)} seconds since last heartbeat" ) self.server_online = False elif not self.server_online: logger.info(f"Server is back online") self.server_online = True except Exception as e: if self.running: logger.error(f"Heartbeat check failure: {e}") async def message_loop(self): while self.running: try: message = await self.client.deliver_message() self.handle_message(message) except KeyboardInterrupt: self.running = False break except Exception as e: logger.critical(f"Unknown exception: {e}") logger.info(f"Sleeping a bit before retrying") await asyncio.sleep(20) try: await self.client.reconnect(cleansession=False) logger.info("Successfully reconnected") except ConnectException as e: logger.error(f"Connection exception: {e}") await self.close()