1
0
Fork 0
This repository has been archived on 2024-12-18. You can view files and clone it, but cannot push or open issues or pull requests.
mining-companion/companion/pools/flexpool.py
Julien Riou 7f5bd0a1cf
Add Flexpool tests for API failures
Signed-off-by: Julien Riou <julien@riou.xyz>
2021-02-17 16:21:08 +01:00

190 lines
8.3 KiB
Python

import logging
import flexpoolapi
from humanfriendly import format_timespan
from pools import MAX_NOTIFICATIONS_COUNT, Handler
from utils import convert_fiat, convert_weis, format_weis
logger = logging.getLogger(__name__)
MAX_BLOCKS_COUNT = 10
MAX_PAYMENTS_COUNT = 10
class Block:
def __init__(self, number, hash, time, round_time, reward, luck, exchange_rate=None, currency=None):
self.number = int(number)
self.hash = hash
self.time = time
self.round_time = format_timespan(round_time)
self.reward = format_weis(reward)
self.reward_fiat = None
if exchange_rate and currency:
self.reward_fiat = convert_fiat(amount=reward, exchange_rate=exchange_rate, currency=currency)
self.luck = f'{int(luck*100)}%'
def __lt__(self, block):
return self.number < block.number
def __repr__(self):
attributes = {'time': self.time, 'reward': self.reward, 'round_time': self.round_time, 'luck': self.luck}
if self.reward_fiat:
attributes['reward_fiat'] = self.reward_fiat
formatted_attributes = ' '.join([f'{k}="{v}"' for k, v in attributes.items()])
return f'<Block #{self.number} ({formatted_attributes})>'
class Miner:
def __init__(self, address, exchange_rate=None, currency=None):
self.address = address
miner = flexpoolapi.miner(address)
self.raw_balance = miner.balance()
self.balance = convert_weis(self.raw_balance)
self.balance_fiat = None
if exchange_rate and currency:
self.balance_fiat = convert_fiat(amount=self.raw_balance, exchange_rate=exchange_rate, currency=currency)
payout_threshold = self.get_payout_threshold(miner)
self.balance_percentage = self.format_balance_percentage(payout_threshold=payout_threshold,
balance=self.raw_balance)
self.transactions = self.get_payements(miner, exchange_rate=exchange_rate, currency=currency)
@property
def url(self):
return f'https://flexpool.io/{self.address}'
@staticmethod
def get_payout_threshold(miner):
return miner.details().min_payout_threshold
@staticmethod
def format_balance_percentage(payout_threshold, balance):
return f'{round(balance*100/payout_threshold, 2)}%'
@staticmethod
def get_payements(miner, exchange_rate=None, currency=None):
# crawl payments
transactions = []
payments_count = 0
current_page = 0
while payments_count < MAX_PAYMENTS_COUNT:
logger.debug(f'fetching payments page {current_page}')
page = miner.payments_paged(page=current_page)
if not page.contents:
break
for payment in page.contents:
# convert to transaction
transaction = Transaction(txid=payment.txid, time=payment.time, amount=payment.amount,
duration=payment.duration, exchange_rate=exchange_rate,
currency=currency)
transactions.append(transaction)
payments_count += 1
current_page += 1
if current_page > page.total_pages:
break
# sort transactions from oldest to newest
return sorted(transactions)
@property
def last_transaction(self):
if self.transactions:
return self.transactions[-1]
def __repr__(self):
attributes = {'balance': self.balance, 'raw_balance': self.raw_balance,
'balance_percentage': self.balance_percentage, 'url': self.url}
if self.balance_fiat:
attributes['balance_fiat'] = self.balance_fiat
formatted_attributes = ' '.join([f'{k}="{v}"' for k, v in attributes.items()])
return f'<Miner #{self.address} ({formatted_attributes})>'
class Transaction:
def __init__(self, txid, amount, time, duration, exchange_rate=None, currency=None):
self.txid = txid
self.time = time
self.raw_amount = amount
self.amount = format_weis(amount)
self.amount_fiat = None
self.duration = format_timespan(duration)
if exchange_rate and currency:
self.amount_fiat = convert_fiat(amount=self.raw_amount, exchange_rate=exchange_rate, currency=currency)
def __lt__(self, trx):
return self.time < trx.time
def __repr__(self):
attributes = {'time': self.time, 'amount': self.amount, 'raw_amount': self.raw_amount,
'duration': self.duration}
if self.amount_fiat:
attributes['amount_fiat'] = self.amount_fiat
formatted_attributes = ' '.join([f'{k}="{v}"' for k, v in attributes.items()])
return f'<Transaction #{self.txid} ({formatted_attributes})>'
class FlexpoolHandler(Handler):
def __init__(self, exchange_rate=None, currency=None, notifier=None, pool_name='flexpool'):
super().__init__(pool_name=pool_name, exchange_rate=exchange_rate, currency=currency, notifier=notifier)
def watch_blocks(self, last_block=None):
logger.debug('watching last blocks')
last_remote_block = None
blocks = self.get_blocks(exchange_rate=self.exchange_rate, currency=self.currency)
if blocks:
# don't spam block notification at initialization
notification_slice = MAX_NOTIFICATIONS_COUNT if len(blocks) > MAX_NOTIFICATIONS_COUNT else 0
for block in blocks[notification_slice:]:
if not last_block or last_block < block.number:
logger.info(f'new block {block.number}')
if self.notifier:
logger.debug('sending block notification')
arguments = {'pool': self.pool_name, 'number': block.number, 'hash': block.hash,
'reward': block.reward, 'time': block.time, 'round_time': block.round_time,
'luck': block.luck, 'reward_fiat': block.reward_fiat}
try:
self.notifier.notify_block(**arguments)
logger.info('block notification sent')
except Exception as err:
logger.error('failed to send notification')
logger.exception(err)
last_remote_block = block
if last_remote_block and last_remote_block.number:
return last_remote_block.number
@staticmethod
def get_blocks(exchange_rate=None, currency=None):
try:
remote_blocks = flexpoolapi.pool.last_blocks(count=MAX_BLOCKS_COUNT)
# convert to blocks
blocks = []
if remote_blocks:
for remote_block in remote_blocks:
block = Block(number=remote_block.number, hash=remote_block.hash, time=remote_block.time,
round_time=remote_block.round_time, reward=remote_block.total_rewards,
luck=remote_block.luck, exchange_rate=exchange_rate, currency=currency)
blocks.append(block)
# sort by block number
return sorted(blocks)
except flexpoolapi.exceptions.APIError as err:
logger.warning('failed to get blocks from Flexpool API')
logger.debug(err)
def watch_miner(self, address, last_balance=None, last_transaction=None):
logger.debug(f'watching miner {address}')
try:
miner = Miner(address=address, exchange_rate=self.exchange_rate, currency=self.currency)
logger.debug(miner)
last_balance = self._watch_miner_balance(miner=miner, last_balance=last_balance)
last_transaction = self._watch_miner_payments(miner=miner, last_transaction=last_transaction)
return last_balance, last_transaction
except flexpoolapi.exceptions.InvalidMinerAddress as err:
logger.error(f'miner address {address} is invalid')
logger.debug(err)
except flexpoolapi.exceptions.MinerDoesNotExist as err:
logger.error(f'miner {address} not found')
logger.debug(err)
except flexpoolapi.exceptions.APIError as err:
logger.warning('failed to get miner from Flexpool API')
logger.debug(err)
return None, None