def info(bot, update): logger.info("network") response = messages.markdown("<u><b>SmartNode Network<b><u>\n\n", bot.messenger) with bot.nodeList as nodeList: if nodeList.synced() and nodeList.enabled(): lastBlock = nodeList.lastBlock created = nodeList.count() enabled = nodeList.enabled() preEnabled = nodeList.preEnabled expired = nodeList.expired newStartRequired = nodeList.newStartRequired qualifiedNormal = nodeList.qualifiedNormal qualifiedUpgrade = nodeList.qualifiedUpgrade upgradeModeDuration = nodeList.remainingUpgradeModeDuration protocolRequirement = nodeList.protocolRequirement() protocolOld = nodeList.count(nodeList.oldProtocol) protocolNew = nodeList.count(nodeList.newProtocol) initialWait = nodeList.minimumUptime() minPosition = int(enabled * 0.1) aberration = bot.aberration # Fallback if for whatever reason the top node could not filtered which # should actually not happen. top10Seconds = (int( (qualifiedNormal * 55) / 0.5) * (1 + bot.aberration)) topNode = list( filter(lambda x: x.position == minPosition, nodeList.nodes.values())) if len(topNode) and topNode[0].lastPaidTime: top10FromList = time.time() - topNode[0].lastPaidTime if top10FromList < 1.2 * top10Seconds: top10Seconds = top10FromList top10Time = util.secondsToText(top10Seconds) if upgradeModeDuration: upgradeModeDuration = util.secondsToText(upgradeModeDuration) response += messages.networkState( bot.messenger, lastBlock, created, preEnabled, enabled, expired, newStartRequired, qualifiedNormal, qualifiedUpgrade, upgradeModeDuration, protocolRequirement, nodeList.oldProtocol, nodeList.newProtocol, protocolOld, protocolNew, util.secondsToText(initialWait), top10Time, aberration) else: response += messages.notSynced(bot.messenger) return response
def detail(bot, update): response = messages.markdown("<u><b>Detail<b><u>\n\n", bot.messenger) userInfo = util.crossMessengerSplit(update) userId = userInfo['user'] if 'user' in userInfo else None userName = userInfo[ 'name'] if 'name' in userInfo else update.message.from_user.name logger.debug("detail - user: {}".format(userId)) nodesFound = False user = bot.database.getUser(userId) userNodes = bot.database.getAllNodes(userId) if user == None or userNodes == None or len(userNodes) == 0: response += messages.nodesRequired(bot.messenger) else: with bot.nodeList as nodeList: minimumUptime = nodeList.minimumUptime() top10 = nodeList.enabledWithMinProtocol() * 0.1 for userNode in userNodes: masternode = nodeList.getNodes([userNode['collateral']])[0] response += messages.markdown( ("<b>" + userNode['name'] + " - " + masternode.ip + "<b>"), bot.messenger) response += "\n `Status` " + masternode.status response += "\n `Position` " + messages.markdown( masternode.positionString(minimumUptime, top10), bot.messenger) response += "\n `Payee` " + masternode.payee response += "\n `Active since` " + util.secondsToText( masternode.activeSeconds) response += "\n `Last seen` " + util.secondsToText( int(time.time()) - masternode.lastSeen) response += "\n `Last payout (Block)` " + masternode.payoutBlockString( ) response += "\n `Last payout (Time)` " + masternode.payoutTimeString( ) response += "\n `Protocol` {}".format(masternode.protocol) #response += "\n `Rank` {}".format(masternode.rank) response += "\n " + messages.link( bot.messenger, 'https://explorer3.curium.cc/address/{}'.format( masternode.payee), 'Open the explorer!') response += "\n\n" return response
def nodes(bot, update): response = messages.markdown("<u><b>Nodes<b><u>\n\n", bot.messenger) userInfo = util.crossMessengerSplit(update) userId = userInfo['user'] if 'user' in userInfo else None userName = userInfo['name'] if 'name' in userInfo else None logger.debug("nodes - user: {}".format(userId)) nodesFound = False user = bot.database.getUser(userId) userNodes = bot.database.getAllNodes(userId) if user == None or userNodes == None or len(userNodes) == 0: response += messages.nodesRequired(bot.messenger) else: with bot.nodeList as nodeList: collaterals = list(map(lambda x: x['collateral'], userNodes)) nodes = nodeList.getNodes(collaterals) minimumUptime = nodeList.minimumUptime() top10 = nodeList.enabledWithMinProtocol() * 0.1 for masternode in sorted(nodes, key=lambda x: x.position if x.position > 0 else 100000): userNode = bot.database.getNodes(masternode.collateral, user['id']) payoutText = util.secondsToText(masternode.lastPaidTime) response += messages.markdown( "<b>" + userNode['name'] + "<b> - `" + masternode.status + "`", bot.messenger) response += "\nPosition " + messages.markdown( masternode.positionString(minimumUptime, top10), bot.messenger) response += "\nLast seen " + util.secondsToText( int(time.time()) - masternode.lastSeen) response += "\nLast payout " + masternode.payoutTimeString() response += "\n" + messages.link( bot.messenger, 'https://explorer3.curium.cc/address/{}'.format( masternode.payee), 'Open the explorer!') response += "\n\n" return response
def payouts(bot, args): logger.info("payouts") response = messages.markdown("<u><b>Payout statistics<b><u>\n\n", bot.messenger) if not bot.rewardList.running: response += "Not initialized yet. Wait a bit..." return response hours = 12 if len(args): try: hours = float(args[0]) except: pass start = time.time() - (hours * 3600) firstReward = bot.rewardList.getNextReward(start) lastReward = bot.rewardList.getLastReward() if not firstReward: response += "Could not fetch the rewards in the given time range!\n" response += "The last available is at block {} from {} ago.".format( lastReward.block, util.secondsToText(time.time() - lastReward.txtime)) return response total = bot.rewardList.getRewardCount(start=start) vChain = bot.rewardList.getRewardCount(start=start, source=0, meta=0) iChain = bot.rewardList.getRewardCount(start=start, meta=1) nList = bot.rewardList.getRewardCount(start=start, source=1) err = bot.rewardList.getRewardCount(start=start, meta=-1) response += "Blocks: {}\n".format(lastReward.block - firstReward.block) response += "RT: {}\n".format( util.secondsToText(lastReward.txtime - firstReward.txtime)) response += "P: {}\n".format(total) response += "V: {}\n".format(vChain) response += "I: {}\n".format(iChain) response += "NL: {}\n".format(nList) response += "ERR: {}\n".format(err) response += "E: {}%".format(round((1 - (nList / total)) * 100, 1)) return response
def nodeUpdated(bot, update, user, userNode, node): responses = [] nodeName = userNode['name'] if update['status'] and user['status_n']: response = messages.statusNotification(bot.messenger, nodeName, node.status) responses.append(response) if update['timeout'] and user['timeout_n']: if node.timeout != -1: timeString = util.secondsToText(int(time.time()) - node.lastSeen) response = messages.panicNotification(bot.messenger, nodeName, timeString) else: response = messages.relaxNotification(bot.messenger, nodeName) responses.append(response) if update['lastPaid'] and user['reward_n']: # Prevent zero division if for any reason lastPaid is 0 calcBlock = node.lastPaidBlock if node.lastPaidBlock != 0 else bot.nodeList.lastBlock reward = 5000 * (143500 / calcBlock) * 0.1 response = messages.rewardNotification(bot.messenger, nodeName, calcBlock, reward) responses.append(response) return responses
def remainingString(self): s = self.votingDeadline seconds = self.remainingSeconds() if seconds: s = util.secondsToText(seconds) return s
def position(bot): with bot.nodeList as nodeList: if not nodeList.synced() or not nodeList.enabled(): return messages.notSynced(bot.messenger) nodes = nodeList.count() enabled = nodeList.enabled() qualified = nodeList.qualifiedNormal unqualified = nodes - qualified minPosition = int(enabled * 0.1) top10Seconds = util.secondsToText(int((qualified * 55) * (1 + bot.aberration))) topNode = list(filter(lambda x: x.position == minPosition, nodeList.nodes.values())) if len(topNode) and topNode[0].lastPaidTime: top10Seconds = time.time() - topNode[0].lastPaidTime top10Time = util.secondsToText(top10Seconds) return ( "We have currenty <b>{}<b> qualified MasterNodes. All those are in a " "virtual payout queue. Each mined block (55seconds) one " "of the nodes in the top 10% of that queue gets picked perchance and" " receives a payout. After that it moves back to the end of the queue.\n\n" "The position of your node represents the position in the queue. Once " "your node has a position <b>less<b> than (currently) <b>{}<b>" " it is in the <b>random payout zone<b> (top 10%).\n\n" "The minimum position depends on the number of enabled nodes and will change" " when the number of enabled nodes changes.\n\n" "Right now it takes <b>{}<b> to reach the payout zone.\n\n" "It's normal and to be expected that your nodes moves also backwards a " "bit in the queue from time to time. This happens each time a node that" " has received its last payment longer ago as yours becomes eligible" " for payouts (it jumpes into the queue and receives a position)." " At this moment we have <b>{}<b> MasterNodes. <b>{}<b> of them are" " <b>not<b> qualified for payouts. Each time one of the unqualified" " nodes becomes eligible due to a full match of the requirements" " it is very likely that it will jump ahead of yours.\n\n" "You can send me <cb>info<ca> to see the number of qualified nodes, " "to check your nodes positions send me <cb>detail<ca>, <cb>nodes<ca>" " <cb>top<ca> or use the <cb>lookup<ca> command.\n\n" ).format(qualified, minPosition, top10Time, nodes, unqualified)
def status(self, bot, update): response = "*Cryptopia*\n" response += "Last updated {}\n".format(util.secondsToText(int(time.time() - self.cryptopia['updated']))) response += "*Status* `{}`\n".format(self.cryptopia['status']) response += "*Message* `{}`\n\n".format(self.cryptopia['message']) response += "*HitBTC*\n" response += "Last updated {}\n".format(util.secondsToText(int(time.time() - self.hitbtc['updated']))) response += "*Deposit* `{}`\n".format(self.hitbtc['deposit']) response += "*Withdraw* `{}`\n\n".format(self.hitbtc['withdraw']) response += "*Coinexchange*\n" response += "Last updated {}\n".format(util.secondsToText(int(time.time() - self.coinexchange['updated']))) response += "*Wallet* `{}`\n".format(self.coinexchange['wallet']) self.database.addChat(update.message.chat_id) self.sendMessage(update.message.chat_id, response)
def calculateUpgradeModeDuration(self): # Start with an accuracy of 5 nodes. # Will become increased if it takes too long accuracy = 20 # Minimum required nodes to continue with normal mode requiredNodes = int(self.enabledWithMinProtocol() / 3) # Get the max active seconds to determine a start point currentCheckTime = max(list(map(lambda x: x.activeSeconds if x.protocol >= self.protocolRequirement() and x.status == 'ENABLED' else 0, self.nodes.values()))) logger.debug("Maximum uptime {}".format(currentCheckTime)) # Start value step = currentCheckTime * 0.5 currentCheckTime -= step # Start time for accuracy descrease if needed start = int(time.time()) rounds = 1 calcCount = None while accuracy < 1000: step *= 0.5 calcCount = len(list(filter(lambda x: x.protocol == self.protocolRequirement() and\ x.status == 'ENABLED' and\ (self.lastBlock - x.collateral.block) >= self.minimumConfirmations() and\ x.activeSeconds > currentCheckTime, self.nodes.values() ))) logger.debug("Current count: {}".format(calcCount)) logger.debug("Current time: {}".format(currentCheckTime)) logger.debug("Current accuracy: {}".format(accuracy)) logger.debug("Current accuracy matched: {}\n".format(abs(requiredNodes - calcCount))) if int(time.time()) - start >= 2 * rounds: rounds += 1 accuracy += 20 if abs(requiredNodes - calcCount) < accuracy: logger.info("Final accuracy {}".format(accuracy)) logger.info("Final accuracy matched {}".format(abs(requiredNodes - calcCount))) logger.info("Remaining duration: {}".format( util.secondsToText((self.minimumUptime() - currentCheckTime)))) logger.info("CalcTime: {}, Rounds: {}".format( int(time.time()) - start,rounds)) return self.minimumUptime() - currentCheckTime elif calcCount > requiredNodes: currentCheckTime += step else: currentCheckTime -= step logger.warning("Could not determine duration?!") logger.warning("Final accuracy {}".format(accuracy)) logger.warning("Final accuracy before step out {}".format(abs(requiredNodes - calcCount))) return None
def info(bot, update): logger.info("network") response = messages.markdown("<u><b>SmartNode Network<b><u>\n\n", bot.messenger) if bot.nodeList.synced() and bot.nodeList.lastBlock: bot.nodeList.acquire() lastBlock = bot.nodeList.lastBlock created = bot.nodeList.count() enabled = bot.nodeList.enabled() qualifiedNormal = bot.nodeList.qualifiedNormal qualifiedUpgrade = bot.nodeList.qualifiedUpgrade upgradeModeDuration = bot.nodeList.remainingUpgradeModeDuration protocolRequirement = bot.nodeList.protocolRequirement() protocol90024 = bot.nodeList.count(90024) protocol90025 = bot.nodeList.count(90025) initialWait = bot.nodeList.minimumUptime() if upgradeModeDuration: upgradeModeDuration = util.secondsToText(upgradeModeDuration) bot.nodeList.release() response += messages.networkState(bot.messenger, lastBlock, created, enabled, qualifiedNormal, qualifiedUpgrade, upgradeModeDuration, protocolRequirement, protocol90024, protocol90025, util.secondsToText(initialWait)) else: response += "*Sorry, the bot is currently not synced with the network. Try it again in few minutes...*" return response
def positionString(self, minimumUptime, top10 = None): if self.position == POS_CALCULATING: return "Calculating..." elif self.position == POS_UPDATE_REQUIRED: return "Node update required!" elif self.position == POS_TOO_NEW: leftMessage = util.secondsToText(minimumUptime - self.activeSeconds) return "Initial wait time! <b>{}<b> left".format(leftMessage) elif self.position == POS_COLLATERAL_AGE: return "Collateral too new!" elif self.position == POS_NOT_QUALIFIED: return "Not qualified!" elif top10 and self.position <= top10: return str(self.position) + " - <b>Payout zone<b>" else: return str(self.position)
def rewards(bot): with bot.nodeList as nodeList: if not nodeList.synced() or not nodeList.enabled(): return messages.notSynced(bot.messenger) enabled = nodeList.enabled() minPosition = int(enabled * 0.1) qualified = nodeList.qualifiedNormal lastBlock = nodeList.lastBlock # Fallback if for whatever reason the top node could not filtered which # should actually not happen. top10Seconds = (int((qualified * 55) / 0.5) * (1 + bot.aberration)) topNode = list(filter(lambda x: x.position == minPosition, nodeList.nodes.values())) if len(topNode) and topNode[0].lastPaidTime: top10FromList = time.time() - topNode[0].lastPaidTime if top10FromList < 1.2 * top10Seconds: top10Seconds = top10FromList payoutSeconds = top10Seconds + (10 * 60 * 60) payoutDays = payoutSeconds / 86400.0 interval = util.secondsToText(int(payoutSeconds)) currentReward = round(5000.0 * 143500.0 / lastBlock * 0.1,1) / 0.5 perMonth = round((30.5 / payoutDays) * currentReward,1) return ( "The SmartNode rewards are calculated by the following formula\n\n" "```reward = 5000 x 143500 / blockHeight * 0.1```\n\n" "At this moment our blockchain is at the height <b>{}<b> that means" "\n\n```5000 x 143500 / {} * 0.1 => {} SMART per block```\n\n" "Each block with an <b>even<b> blockheight one of the the nodes receive this reward for 2 blocks. With the current " "estimated payout interval of <b>{}<b> you can expect roughly" " <b>{:,} SMART<b> per month per SmartNode. This can vary a bit upwards and downwards though.\n\n" "Due to the constant increase of the <c>blockHeight<c> of the SmartCash blockchain" " the rewards will decrease a little bit every 55 seconds." " Also the increase of the number of qualified nodes will increase the payout interval." " As result your monthly payout will slightly decrease over the time.\n\n" "You can look at the chart in the link below to see the reward decrease " "for the first 4 years after the SmartNode launch.\n\n" ).format(lastBlock, lastBlock, currentReward, interval, perMonth)\ + messages.link(bot.messenger, "https://goo.gl/Va817H", "Click here to open the chart")
def lookup(self, ip): result = None node = self.getNodeByIp(ip) logger.info("lookup {} - found {}".format(ip, node != None)) if node: result = {} uptimeString = None if node.activeSeconds > 0: uptimeString = util.secondsToText(node.activeSeconds) else: uptimeString = "No uptime!" result['ip'] = node.cleanIp() result['position'] = node.position < self.enabledWithMinProtocol( ) * 0.1 and node.position > 0 result['position_string'] = node.positionString( self.minimumUptime()) result['status'] = node.status == 'ENABLED' result['status_string'] = "{}".format(node.status) result['uptime'] = node.activeSeconds >= self.minimumUptime() result['uptime_string'] = uptimeString result['protocol'] = node.protocol == self.protocolRequirement() result['protocol_string'] = "{}".format(node.protocol) result['collateral'] = (self.lastBlock - node.collateral.block ) >= self.enabledWithMinProtocol() result['collateral_string'] = "{}".format( (self.lastBlock - node.collateral.block)) result['upgrade_mode'] = self.qualifiedUpgrade != -1 return result
def initial(bot): with bot.nodeList as nodeList: if not nodeList.synced() or not nodeList.enabled(): return messages.notSynced(bot.messenger) initialWait = util.secondsToText(nodeList.minimumUptime()) return ( "When your node shows <b>Initial wait time<b> instead of a position it's" " uptime does not match the minimum uptime requirement. At this time the" " node must have a minimum uptime of <b>{}<b>." " Your uptime will be set to zero when your node was down for more than" " <b>1 hour<b> or when you issue a <b>new start<b> of the node with your desktop wallet" " by running <c>Start alias<c>.\n\n" "You can check your node's uptime when you send me <cb>detail<ca> or by" " running the <cb>lookup<ca> command.\n\n" ).format(initialWait)
def collateral(bot): with bot.nodeList as nodeList: if not nodeList.synced() or not nodeList.enabled(): return messages.notSynced(bot.messenger) confirmations = nodeList.enabledWithMinProtocol() timeString = util.secondsToText(confirmations * 55) return ( "A too new collateral means that your nodes collateral transaction (the 10k one) does not" " have the minimum required number of confirmations in the Curium blockchain." "This number of confirmations is currently <b>{}<b>.\nYour collateral gets 1 confirmation with each" " new mined block.\n\nMeans right now you need to wait {} x 55 seconds => <b>~{}<b> until" " your collateral transaction matches the requirement.\n\n" "You can check your nodes collateral confirmations" " by running the <cb>lookup<ca> command.\n\n" ).format(confirmations, confirmations, timeString)
def qualified(bot): with bot.nodeList as nodeList: if not nodeList.synced() or not nodeList.enabled(): return messages.notSynced(bot.messenger) confirmations = nodeList.enabledWithMinProtocol() initialWait = util.secondsToText(nodeList.minimumUptime()) protocolRequirement = nodeList.protocolRequirement() return ( "Your node has to match the following requirements before it's ready to" " receive payouts\n\n" "- The node's status has to be <b>ENABLED<b>\n" "- The node's collateral transaction needs to have at least <b>{}<b>" " confirmations\n" "- The node must have a minimum uptime of <b>{}<b>\n" "- The node must run at least on the protocol <b>{}<b> number\n\n" "Once your node matches <b>all<b> the above requirements it will get a" " position in the payout queue.\n\n" "You can check all this requirements for your nodes when you send me" " <cb>detail<ca> or by running the <cb>lookup<ca> command.\n\n" ).format(confirmations, initialWait, protocolRequirement)
def updateList(self): try: self.updateSyncState() except RuntimeError as e: logger.error("updateList sync exception: {}".format(e)) self.startTimer() return else: if not self.chainSynced or not self.nodeListSynced or not self.winnersListSynced: logger.error("Not synced! C {}, N {} W {}".format(self.chainSynced, self.nodeListSynced, self.winnersListSynced)) # If nodelist or winnerslist was out of sync # wait 5 minutes after sync is done # to prevent false positive timeout notifications if not self.nodeListSynced or not self.winnersListSynced: self.syncedTime = -2 self.startTimer() return if self.syncedTime == -2: self.syncedTime = time.time() logger.info("Synced now! Wait 5 minutes and then start through...") self.startTimer() return # Wait 5 minutes here to prevent timeout notifications. Past showed that # the lastseen times are not good instantly after sync. elif self.syncedTime > -1 and (time.time() - self.syncedTime) < 300: logger.info("After sync wait {}".format(util.secondsToText(time.time() - self.syncedTime))) self.startTimer() return newNodes = [] nodes = None info = None try: infoResult = subprocess.check_output(['smartcash-cli', 'getinfo']) info = json.loads(infoResult.decode('utf-8')) nodeResult = subprocess.check_output(['smartcash-cli', 'smartnodelist','full']) nodes = json.loads(nodeResult.decode('utf-8')) except Exception as e: logging.error('Error at %s', 'update list', exc_info=e) self.pushAdmin("Error at updateList") else: if not self.isValidDeamonResponse(nodes): self.pushAdmin("No valid nodeList") return if not self.isValidDeamonResponse(info): self.pushAdmin("No valid network info") return if "blocks" in info: self.lastBlock = info["blocks"] node = None currentList = [] self.lastPaidVec = [] currentTime = int(time.time()) protocolRequirement = self.protocolRequirement() dbCount = self.db.getNodeCount() # Prevent mass deletion of nodes if something is wrong # with the fetched nodelist. if dbCount and len(nodes) and ( dbCount / len(nodes) ) > 1.25: self.pushAdmin("Node count differs too much!") logger.warning("Node count differs too much! - DB {}, CLI {}".format(dbCount,len(nodes))) self.startTimer() return # Prevent reading during the calculations self.acquire() # Reset the calculation vars self.qualifiedNormal = 0 for key, data in nodes.items(): collateral = Transaction.fromRaw(key) currentList.append(collateral) if collateral not in self.nodeList: collateral.updateBlock(self.getCollateralAge(collateral.hash)) logger.info("Add node {}".format(key)) insert = SmartNode.fromRaw(collateral, data) id = self.db.addNode(collateral,insert) if id: self.nodeList[collateral] = insert newNodes.append(collateral) logger.debug(" => added with collateral {}".format(insert.collateral)) else: logger.error("Could not add the node {}".format(key)) else: node = self.nodeList[collateral] collateral = node.collateral update = node.update(data) if update['status']\ or update['protocol']\ or update['payee']\ or update['lastPaid']\ or update['timeout']: self.db.updateNode(collateral,node) if sum(map(lambda x: x, update.values())): if self.nodeChangeCB != None: self.nodeChangeCB(update, node) ##### ## Check if the collateral height is already detemined ## if not try it! ##### if collateral.block <= 0: logger.info("Collateral block missing {}".format(str(collateral))) collateral.updateBlock(self.getCollateralAge(collateral.hash)) if collateral.block > 0: self.db.updateNode(collateral,node) else: logger.warning("Could not fetch collateral block {}".format(str(collateral))) ##### ## Invoke the callback if we have new nodes ##### if len(newNodes) and self.networkCB: self.networkCB(newNodes, True) logger.info("Created: {}".format(len(nodes.values()))) logger.info("Enabled: {}\n".format(sum(map(lambda x: x.split()[STATUS_INDEX] == "ENABLED", list(nodes.values()))))) ##### ## Remove nodes from the DB that are not longer in the global list ##### dbCount = self.db.getNodeCount() if dbCount > len(nodes): removedNodes = [] logger.warning("Unequal node count - DB {}, CLI {}".format(dbCount,len(nodes))) dbNodes = self.db.getNodes(['collateral']) for dbNode in dbNodes: collateral = Transaction.fromString(dbNode['collateral']) if not collateral in currentList: logger.info("Remove node {}".format(dbNode)) removedNodes.append(dbNode['collateral']) self.db.deleteNode(collateral) self.nodeList.pop(collateral,None) if len(removedNodes) != (dbCount - len(nodes)): logger.warning("Remove nodes - something messed up.") if self.networkCB: self.networkCB(removedNodes, False) logger.info("calculatePositions start") ##### ## Update vars for calculations # #### nodes90024 = list(filter(lambda x: x.protocol == 90024, self.nodeList.values())) nodes90025 = list(filter(lambda x: x.protocol == 90025, self.nodeList.values())) self.protocol_90024 = len(nodes90024) self.protocol_90025 = len(nodes90025) self.enabled_90024 = len(list(filter(lambda x: x.status == "ENABLED", nodes90024))) self.enabled_90025 = len(list(filter(lambda x: x.status == "ENABLED", nodes90025))) ##### ## Update the the position indicator of the node # # CURRENTL MISSING: # https://github.com/SmartCash/smartcash/blob/1.1.1/src/smartnode/smartnodeman.cpp#L554 ##### def calculatePositions(upgradeMode): self.lastPaidVec = [] for collateral, node in self.nodeList.items(): if not upgradeMode and node.activeSeconds < self.minimumUptime():# https://github.com/SmartCash/smartcash/blob/1.1.1/src/smartnode/smartnodeman.cpp#L561 node.updatePosition(POS_TOO_NEW) elif node.protocol < protocolRequirement:# https://github.com/SmartCash/smartcash/blob/1.1.1/src/smartnode/smartnodeman.cpp#L545 node.updatePosition(POS_UPDATE_REQUIRED) elif (self.lastBlock - node.collateral.block) < self.enabledWithMinProtocol(): node.updatePosition(POS_COLLATERAL_AGE) elif node.status == 'ENABLED': #https://github.com/SmartCash/smartcash/blob/1.1.1/src/smartnode/smartnodeman.cpp#L539 self.lastPaidVec.append(LastPaid(node.lastPaidBlock, collateral)) else: node.updatePosition(POS_NOT_QUALIFIED) if not upgradeMode and len(self.lastPaidVec) < (self.enabledWithMinProtocol() / 3): self.qualifiedUpgrade = len(self.lastPaidVec) logger.info("Start upgradeMode calculation: {}".format(self.qualifiedUpgrade)) calculatePositions(True) return if not upgradeMode: self.qualifiedUpgrade = -1 self.qualifiedNormal = len(self.lastPaidVec) calculatePositions(False) ##### ## Update positions ##### self.lastPaidVec.sort() value = 0 for lastPaid in self.lastPaidVec: value +=1 self.nodeList[lastPaid.transaction].updatePosition(value) logger.info("calculatePositions done") if self.qualifiedUpgrade != -1: logger.info("calculateUpgradeModeDuration start") self.remainingUpgradeModeDuration = self.calculateUpgradeModeDuration() logger.info("calculateUpgradeModeDuration done {}".format("Success" if self.remainingUpgradeModeDuration else "Error?")) self.release() ##### # Disabled rank updates due to confusion of the users #self.updateRanks() ##### self.startTimer()
def updateList(self): if not self.chainSynced or not self.nodeListSynced or not self.winnersListSynced: logger.error("Not synced! C {}, N {} W {}".format( self.chainSynced, self.nodeListSynced, self.winnersListSynced)) # If nodelist or winnerslist was out of sync # wait 5 minutes after sync is done # to prevent false positive timeout notifications if not self.nodeListSynced or not self.winnersListSynced: self.syncedTime = -2 return False if self.syncedTime == -2: self.syncedTime = time.time() logger.info( "Synced now! Wait {} minutes and then start through...".format( self.waitAfterSync / 60)) return False # Wait 5 minutes here to prevent timeout notifications. Past showed that # the lastseen times are not good instantly after sync. elif self.syncedTime > -1 and (time.time() - self.syncedTime) < self.waitAfterSync: logger.info("After sync wait {}".format( util.secondsToText(time.time() - self.syncedTime))) return False newNodes = [] removedNodes = [] info = self.rpc.getInfo() rpcNodes = self.rpc.getMasterNodeList('full') if info.error: msg = "updateList getInfo: {}".format(str(info.error)) logging.error(msg) self.pushAdmin(msg) return False elif not "blocks" in info.data: self.pushAdmin("Block info missing?!") else: self.lastBlock = info.data["blocks"] if rpcNodes.error: msg = "updateList getMasterNodeList: {}".format(str( rpcNodes.error)) logging.error(msg) self.pushAdmin(msg) return False rpcNodes = rpcNodes.data node = None currentList = [] self.lastPaidVec = [] currentTime = int(time.time()) protocolRequirement = self.protocolRequirement() dbCount = self.db.getNodeCount() # Prevent mass deletion of nodes if something is wrong # with the fetched nodelist. if dbCount and len(rpcNodes) and (dbCount / len(rpcNodes)) > 1.25: self.pushAdmin("Node count differs too much!") logger.warning( "Node count differs too much! - DB {}, CLI {}".format( dbCount, len(rpcNodes))) return False # Prevent reading during the calculations self.acquire() # Reset the calculation vars self.qualifiedNormal = 0 for key, data in rpcNodes.items(): collateral = Transaction.fromRaw(key) currentList.append(collateral) if collateral not in self.nodes: collateral.updateBlock(self.getCollateralAge(collateral.hash)) logger.info("Add node {}".format(key)) insert = MasterNode.fromRaw(collateral, data) id = self.db.addNode(collateral, insert) if id: self.nodes[collateral] = insert newNodes.append(collateral) logger.debug(" => added with collateral {}".format( insert.collateral)) else: logger.error("Could not add the node {}".format(key)) else: node = self.nodes[collateral] collateral = node.collateral update = node.update(data) if update['status']\ or update['protocol']\ or update['payee']\ or update['lastPaid']\ or update['ip']\ or update['timeout']: self.db.updateNode(collateral, node) if sum(map(lambda x: x, update.values())): if self.nodeChangeCB != None: self.nodeChangeCB(update, node) ##### ## Check if the collateral height is already detemined ## if not try it! ##### if collateral.block <= 0: logger.info("Collateral block missing {}".format( str(collateral))) collateral.updateBlock(self.getCollateralAge(collateral.hash)) if collateral.block > 0: self.db.updateNode(collateral, node) else: logger.warning( "Could not fetch collateral block {}".format( str(collateral))) ##### ## Remove nodes from the DB that are not longer in the global list ##### dbCount = self.db.getNodeCount() if dbCount > len(rpcNodes): logger.warning("Unequal node count - DB {}, CLI {}".format( dbCount, len(rpcNodes))) dbNodes = self.db.getNodes(['collateral']) for dbNode in dbNodes: collateral = Transaction.fromString(dbNode['collateral']) if not collateral in currentList: logger.info("Remove node {}".format(dbNode)) removedNodes.append(dbNode['collateral']) self.db.deleteNode(collateral) self.nodes.pop(collateral, None) if len(removedNodes) != (dbCount - len(rpcNodes)): err = "Remove nodes - something messed up." self.pushAdmin(err) logger.error(err) logger.info("calculatePositions start") ##### ## Update vars for calculations # #### nodes90024 = list( filter(lambda x: x.protocol == 90024, self.nodes.values())) nodes90025 = list( filter(lambda x: x.protocol == 90025, self.nodes.values())) self.protocol_90024 = len(nodes90024) self.protocol_90025 = len(nodes90025) self.enabled_90024 = len( list(filter(lambda x: x.status == "ENABLED", nodes90024))) self.enabled_90025 = len( list(filter(lambda x: x.status == "ENABLED", nodes90025))) ##### ## Update the the position indicator of the node # # CURRENTL MISSING: # https://github.com/Curium/curium/blob/1.1.1/src/masternode/masternodeman.cpp#L554 ##### def calculatePositions(upgradeMode): self.lastPaidVec = [] for collateral, node in self.nodes.items(): if (self.lastBlock - node.collateral.block) < self.enabledWithMinProtocol(): node.updatePosition(POS_COLLATERAL_AGE) elif node.protocol < protocolRequirement: # https://github.com/Curium/curium/blob/1.1.1/src/masternode/masternodeman.cpp#L545 node.updatePosition(POS_UPDATE_REQUIRED) elif not upgradeMode and node.activeSeconds < self.minimumUptime( ): # https://github.com/Curium/curium/blob/1.1.1/src/masternode/masternodeman.cpp#L561 node.updatePosition(POS_TOO_NEW) elif node.status != 'ENABLED': #https://github.com/Curium/curium/blob/1.1.1/src/masternode/masternodeman.cpp#L539 node.updatePosition(POS_NOT_QUALIFIED) else: self.lastPaidVec.append( LastPaid(node.lastPaidBlock, collateral)) if not upgradeMode and len( self.lastPaidVec) < (self.enabledWithMinProtocol() / 3): self.qualifiedUpgrade = len(self.lastPaidVec) logger.info("Start upgradeMode calculation: {}".format( self.qualifiedUpgrade)) calculatePositions(True) return if not upgradeMode: self.qualifiedUpgrade = -1 self.qualifiedNormal = len(self.lastPaidVec) calculatePositions(False) ##### ## Update positions ##### self.lastPaidVec.sort() value = 0 for lastPaid in self.lastPaidVec: value += 1 self.nodes[lastPaid.transaction].updatePosition(value) logger.info("calculatePositions done") if self.qualifiedUpgrade != -1: logger.info("calculateUpgradeModeDuration start") self.remainingUpgradeModeDuration = self.calculateUpgradeModeDuration( ) logger.info("calculateUpgradeModeDuration done {}".format( "Success" if self.remainingUpgradeModeDuration else "Error?")) self.release() ##### ## Invoke the callback if we have new nodes or nodes left ##### if len(newNodes) and self.networkCB: self.networkCB(newNodes, True) if len(removedNodes) and self.networkCB: self.networkCB(removedNodes, False) return True
def payoutTimeString(self): if self.lastPaidTime > 0: return util.secondsToText(int(time.time()) - self.lastPaidTime) return "No payout yet."
def update(self, raw): update = { 'status': False, 'payee': False, 'timeout': False, 'lastPaid': False, 'protocol': False, 'ip': False } data = raw.split() status = data[STATUS_INDEX].replace( '_', '-') # replace _ with - to avoid md problems if self.status != status: logger.info("[{}] Status updated {} => {}".format( self.collateral, self.status, status)) update['status'] = True self.status = status if int(self.protocol) != int(data[PROTOCOL_INDEX]): logger.info("[{}] Protocol updated {} => {}".format( self.collateral, self.protocol, int(data[PROTOCOL_INDEX]))) update['protocol'] = True self.protocol = int(data[PROTOCOL_INDEX]) if self.payee != data[PAYEE_INDEX]: logger.info("[{}] Payee updated {} => {}".format( self.collateral, self.payee, data[PAYEE_INDEX])) update['payee'] = True self.payee = data[PAYEE_INDEX] self.lastSeen = int(data[SEEN_INDEX]) lastSeenDiff = (int(time.time()) - self.lastSeen) if lastSeenDiff > 1800 and\ lastSeenDiff < 3900: # > 30min < 65min if ( self.timeout == -1 or \ ( int(time.time()) - self.timeout ) > 300 ) and\ self.status == 'ENABLED': self.timeout = int(time.time()) update['timeout'] = True elif self.timeout != -1 and self.status == 'ENABLED': self.timeout = -1 update['timeout'] = True self.activeSeconds = int(data[ACTIVE_INDEX]) lastPaidBlock = int(data[PAIDBLOCK_INDEX]) if self.lastPaidBlock != lastPaidBlock and lastPaidBlock != 0: logger.info("[{}] Reward {} - Last: {}, P: {}, UP: {}".format( self.collateral, lastPaidBlock, self.payoutTimeString(), self.position, util.secondsToText(self.activeSeconds))) self.lastPaidBlock = lastPaidBlock self.lastPaidTime = int(data[PAIDTIME_INDEX]) if self.lastPaidBlock != 0 and self.lastPaidBlock != -1: update['lastPaid'] = True if self.ip != data[IPINDEX_INDEX]: logger.info("[{}] IP updated {} => {}".format( self.collateral, self.ip, data[IPINDEX_INDEX])) update['ip'] = True self.ip = data[IPINDEX_INDEX] if update['timeout']: logger.debug("[{}] Timeout updated {}".format( self.collateral, self.timeout)) return update
def run(self): self.sem.acquire() for chatId, queue in self.queues.items(): if not self.ready(): logger.debug("MessagingMachine not ready {}".format(self.leftover)) break if not queue.ready(): logger.debug("Queue not ready {}".format(queue)) continue err = True message = queue.next() if message == None: continue try: self.bot.sendMessage(chat_id=chatId, text = str(message),parse_mode=telegram.ParseMode.MARKDOWN ) except Unauthorized as e: logger.warning("Exception: Unauthorized {}".format(e)) self.database.deleteNodesForUser(chatId) self.database.deleteUser(chatId) err = False except TimedOut as e: logger.warning("Exception: TimedOut {}".format(e)) except NetworkError as e: logger.warning("Exception: NetworkError {}".format(e)) except ChatMigrated as e: logger.warning("Exception: ChatMigrated from {} to {}".format(chatId, e.new_chat_id)) except BadRequest as e: logger.warning("Exception: BadRequest {}".format(e)) except RetryAfter as e: logger.warning("Exception: RetryAfter {}".format(e)) queue.lock(e.retry_after) warnMessage = messages.rateLimitError(self.messenger, util.secondsToText(int(e.retry_after))) self.bot.sendMessage(chat_id=chatId, text = warnMessage ,parse_mode=telegram.ParseMode.MARKDOWN ) except TelegramError as e: logger.warning("Exception: TelegramError {}".format(e)) else: logger.debug("sendMessage - OK!") err = False if err: queue.error() else: queue.pop() self.leftover -= 1 self.sem.release() self.startTimer()
def handleNodeUpdate(bot, update, node): # If there is a new block available form the nodelist if update['lastPaid']: # Update the source of the reward in the rewardlist to be able to track the # number of missing blocks in the nodelist # If the reward was not available yet it gets added reward = MNReward(block=node.lastPaidBlock, txtime=node.lastPaidTime, payee=node.payee, source=1, meta=2) dbReward = bot.rewardList.getReward(node.lastPaidBlock) if not dbReward: reward = MNReward(block=node.lastPaidBlock, payee=node.payee, txtime=node.lastPaidTime, source=1) bot.rewardList.addReward(reward) else: bot.rewardList.updateSource(reward) # Create notification response messages! responses = {} for userNode in bot.database.getNodes(node.collateral): dbUser = bot.database.getUser(userNode['user_id']) if dbUser: if not dbUser['id'] in responses: responses[dbUser['id']] = [] nodeName = userNode['name'] if update['status'] and dbUser['status_n']: response = messages.statusNotification(bot.messenger, nodeName, node.status) responses[dbUser['id']].append(response) if update['timeout'] and dbUser['timeout_n']: if node.timeout != -1: timeString = util.secondsToText( int(time.time()) - node.lastSeen) response = messages.panicNotification( bot.messenger, nodeName, timeString) else: response = messages.relaxNotification( bot.messenger, nodeName) responses[dbUser['id']].append(response) return responses
def history(bot, update): response = "<u><b>History<b><u>\n\n" userInfo = util.crossMessengerSplit(update) userId = userInfo['user'] if 'user' in userInfo else None userName = userInfo['name'] if 'name' in userInfo else None logger.debug("history - user: {}".format(userId)) nodesFound = False user = bot.database.getUser(userId) userNodes = bot.database.getAllNodes(userId) if user == None or userNodes == None or len(userNodes) == 0: response += messages.nodesRequired(bot.messenger) else: with bot.nodeList as nodeList: collaterals = list(map(lambda x: x['collateral'], userNodes)) nodes = nodeList.getNodes(collaterals) time30Days = time.time() - (2592000) # now - 30d * 24h * 60m * 60s totalInvest = len(nodes) * 10000 totalProfit = 0 totalAvgInterval = 0 totalFirst = 0 countMultiplePayouts = 0 totalProfit30Days = 0 for masternode in nodes: userNode = bot.database.getNodes(masternode.collateral, user['id']) rewards = bot.rewardList.getRewardsForPayee(masternode.payee) profit = sum(map(lambda x: x.amount, rewards)) profit30Days = sum( map(lambda x: x.amount if x.txtime > time30Days else 0, rewards)) totalProfit30Days += profit30Days totalProfit += round(profit, 1) avgInterval = 0 masterPerDay = 0 first = 0 last = 0 if len(rewards) == 1: first = rewards[0].txtime if len(rewards) > 1: countMultiplePayouts += 1 payoutTimes = list(map(lambda x: x.txtime, rewards)) first = min(payoutTimes) last = max(payoutTimes) if not totalFirst or first and totalFirst > first: totalFirst = first if last: avgInterval = (last - first) / len(rewards) totalAvgInterval += avgInterval masterPerDay = round( profit / ((time.time() - first) / 86400), 1) response += "<u><b>Node - " + userNode['name'] + "<b><u>\n\n" response += "<b>Payouts<b> {}\n".format(len(rewards)) response += "<b>Profit<b> {:,} CRU\n".format(round(profit, 1)) response += "<b>Profit (30 days)<b> {:,} CRU\n".format( round(profit30Days, 1)) if avgInterval: response += "\n<b>Payout interval<b> " + util.secondsToText( avgInterval) if masterPerDay: response += "\n<b>CRU/day<b> {:,} CRU".format(masterPerDay) response += "\n<b>ROI (CRU)<b> {}%".format( round((profit / 10000.0) * 100.0, 1)) response += "\n\n" response += "<u><b>Total stats<b><u>\n\n" if totalFirst: response += "<b>First payout<b> {} ago\n\n".format( util.secondsToText(time.time() - totalFirst)) response += "<b>Profit (30 days)<b> {:,} CRU\n".format( round(totalProfit30Days, 1)) response += "<b>CRU/day (30 days)<b> {:,} CRU\n\n".format( round(totalProfit30Days / 30, 1)) if totalAvgInterval: totalAvgInterval = totalAvgInterval / countMultiplePayouts response += "<b>Total payout interval<b> {}\n".format( util.secondsToText(totalAvgInterval)) response += "<b>Total CRU/day<b> {:,} CRU\n\n".format( round(totalProfit / ((time.time() - totalFirst) / 86400), 1)) response += "<b>Total profit<b> {:,} CRU\n".format( round(totalProfit, 1)) response += "<b>Total ROI (CRU)<b> {}%\n\n".format( round((totalProfit / totalInvest) * 100, 1)) return messages.markdown(response, bot.messenger)