def leaderboard(): users = {} lower_10, upper_10 = make_upper_lower(offset=datetime.timedelta(minutes=1)) for slc in ShareSlice.get_span(ret_query=True, lower=lower_10, upper=upper_10): try: address_version(slc.user) except Exception: pass else: user = users.setdefault(slc.user, {}) user.setdefault(slc.algo, [0, set()]) user[slc.algo][0] += slc.value user[slc.algo][1].add(slc.time) # Loop through and convert a summation of shares into a hashrate. Converts # to hashes per second for user, algo_shares in users.iteritems(): for algo_key, (shares, minutes) in algo_shares.items(): algo_obj = algos[algo_key] algo_shares[algo_key] = algo_obj.hashes_per_share * (shares / (len(minutes) * 60)) algo_shares.setdefault('normalized', 0) algo_shares['normalized'] += users[user][algo_key] * algo_obj.normalize_mult sorted_users = sorted(users.iteritems(), key=lambda x: x[1]['normalized'], reverse=True) # This is really bad.... XXX: Needs rework! if users: anon = anon_users() for i, (user, data) in enumerate(sorted_users): if user in anon: sorted_users[i] = ("Anonymous", data) cache.set("leaderboard", sorted_users, timeout=15 * 60)
def leaderboard(): users = {} lower_10, upper_10 = make_upper_lower(offset=datetime.timedelta(minutes=2)) for slc in ShareSlice.get_span(share_type=("acc", ), ret_query=True, lower=lower_10, upper=upper_10): try: address_version(slc.user) except Exception: pass else: user = users.setdefault(slc.user, {}) user.setdefault(slc.algo, [0, set()]) user[slc.algo][0] += slc.value user[slc.algo][1].add(slc.time) # Loop through and convert a summation of shares into a hashrate. Converts # to hashes per second for user, algo_shares in users.iteritems(): for algo_key, (shares, minutes) in algo_shares.items(): algo_obj = algos[algo_key] algo_shares[algo_key] = algo_obj.hashes_per_share * (shares / (len(minutes) * 60)) algo_shares.setdefault('normalized', 0) algo_shares['normalized'] += users[user][algo_key] * algo_obj.normalize_mult sorted_users = sorted(users.iteritems(), key=lambda x: x[1]['normalized'], reverse=True) # This is really bad.... XXX: Needs rework! if users: anon = anon_users() for i, (user, data) in enumerate(sorted_users): if user in anon: sorted_users[i] = ("Anonymous", data) cache.set("leaderboard", sorted_users, timeout=15 * 60)
def pool_payout(self): # Get the pools payout information for this block global_curr = global_config.pool_payout_currency pool_payout = dict(address=self.pool_payout_addr, currency=self, user=global_curr.pool_payout_addr) # If this currency has no payout address, switch to global default if pool_payout['address'] is None: pool_payout['address'] = global_curr.pool_payout_addr pool_payout['currency'] = global_curr # Double check assert self.sellable is True, "Block is un-sellable" # Double check valid. Paranoid address_version(pool_payout['address']) return pool_payout
def __init__(self, bootstrap): ConfigObject.__init__(self, bootstrap) self.coinserv = CoinserverRPC("http://{0}:{1}@{2}:{3}/".format( bootstrap['coinserv']['username'], bootstrap['coinserv']['password'], bootstrap['coinserv']['address'], bootstrap['coinserv']['port'], pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10)))) self.exchangeable = bool(self.exchangeable) self.minimum_payout = dec(self.minimum_payout) # If a pool payout addr is specified, make sure it matches the # configured address version. if self.pool_payout_addr is not None: try: ver = address_version(self.pool_payout_addr) except (KeyError, AttributeError): ver = None if ver not in self.address_version: raise ConfigurationException( "{} is not a valid {} address. Must be version {}, got version {}" .format(self.pool_payout_addr, self.key, self.address_version, ver)) # Check to make sure there is a configured pool address for # unexchangeable currencies if self.exchangeable is False and self.pool_payout_addr is None: raise ConfigurationException( "Unexchangeable currencies require a pool payout addr." "No valid address found for {}".format(self.key))
def __init__(self, bootstrap): ConfigObject.__init__(self, bootstrap) self.coinserv = CoinserverRPC( "http://{0}:{1}@{2}:{3}/" .format(bootstrap['coinserv']['username'], bootstrap['coinserv']['password'], bootstrap['coinserv']['address'], bootstrap['coinserv']['port'], pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10)))) self.exchangeable = bool(self.exchangeable) self.minimum_payout = dec(self.minimum_payout) # If a pool payout addr is specified, make sure it matches the # configured address version. if self.pool_payout_addr is not None: try: ver = address_version(self.pool_payout_addr) except (KeyError, AttributeError): ver = None if ver not in self.address_version: raise ConfigurationException( "{} is not a valid {} address. Must be version {}, got version {}" .format(self.pool_payout_addr, self.key, self.address_version, ver)) # Check to make sure there is a configured pool address for # unexchangeable currencies if self.exchangeable is False and self.pool_payout_addr is None: raise ConfigurationException( "Unexchangeable currencies require a pool payout addr." "No valid address found for {}".format(self.key))
def check_is_bcaddress(self, val): """ Helper method: Checks a value for truthiness """ try: ver = address_version(val) except (KeyError, AttributeError): raise ConfigurationException("\'{}\' is not a valid bitcoin style " "address".format(val)) return ver
def __init__(self, bootstrap): bootstrap['_algo'] = bootstrap.pop('algo', None) if bootstrap['_algo'] is None: raise ConfigurationException( "A currency in config.toml is missing an entry in " "defaults.toml! The following config may help identify it: {}" .format(bootstrap)) ConfigObject.__init__(self, bootstrap) if self.coinserv: cfg = self.coinserv self.coinserv = CoinserverRPC( "http://{0}:{1}@{2}:{3}/" .format(cfg['username'], cfg['password'], cfg['address'], cfg['port'], pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10)))) self.coinserv.config = cfg elif self.sellable or self.mineable or self.buyable: raise ConfigurationException( "Coinserver must be configured for {}!".format(self.key)) self.sellable = bool(self.sellable) self.buyable = bool(self.buyable) self.merged = bool(self.merged) self.minimum_payout = dec(self.minimum_payout) # If a pool payout addr is specified, make sure it matches the # configured address version. if self.pool_payout_addr is not None: try: ver = address_version(self.pool_payout_addr) except (KeyError, AttributeError): ver = None if ver not in self.address_version: raise ConfigurationException( "{} is not a valid {} address. Must be version {}, got version {}" .format(self.pool_payout_addr, self.key, self.address_version, ver)) # Check to make sure there is a configured pool address for # unsellable currencies if self.sellable is False and self.pool_payout_addr is None and self.mineable: raise ConfigurationException( "Unsellable currencies require a pool payout addr." "No valid address found for {}".format(self.key))
def __init__(self, bootstrap): bootstrap['_algo'] = bootstrap.pop('algo', None) if bootstrap['_algo'] is None: raise ConfigurationException( "A currency in config.toml is missing an entry in " "defaults.toml! The following config may help identify it: {}". format(bootstrap)) ConfigObject.__init__(self, bootstrap) if self.coinserv: cfg = self.coinserv self.coinserv = CoinserverRPC("http://{0}:{1}@{2}:{3}/".format( cfg['username'], cfg['password'], cfg['address'], cfg['port'], pool_kwargs=dict(maxsize=bootstrap.get('maxsize', 10)))) self.coinserv.config = cfg elif self.sellable or self.mineable or self.buyable: raise ConfigurationException( "Coinserver must be configured for {}!".format(self.key)) self.sellable = bool(self.sellable) self.buyable = bool(self.buyable) self.merged = bool(self.merged) self.minimum_payout = dec(self.minimum_payout) # If a pool payout addr is specified, make sure it matches the # configured address version. if self.pool_payout_addr is not None: try: ver = address_version(self.pool_payout_addr) except (KeyError, AttributeError): ver = None if ver not in self.address_version: raise ConfigurationException( "{} is not a valid {} address. Must be version {}, got version {}" .format(self.pool_payout_addr, self.key, self.address_version, ver)) # Check to make sure there is a configured pool address for # unsellable currencies if self.sellable is False and self.pool_payout_addr is None and self.mineable: raise ConfigurationException( "Unsellable currencies require a pool payout addr." "No valid address found for {}".format(self.key))
def validate_bc_address(self, bc_address_str): """ The go-to function for all your bitcoin style address validation needs. Expects to receive a string believed to represent a bitcoin address Raises appropriate errors if any checks are failed, otherwise returns a list of Currency objects that have the same addr version. """ # First check to make sure the address contains only alphanumeric chars if not bc_address_str.isalnum(): raise InvalidAddressException('Address should be alphanumeric') # Check to make sure str is the proper length if not len(bc_address_str) >= 33 or not len(bc_address_str) <= 35: raise InvalidAddressException('Address should be 33-35 characters long') # Check to see if the address can be looked up from the config try: ver = address_version(bc_address_str) except (AttributeError, ValueError): raise InvalidAddressException("Invalid") return ver
def validate_bc_address(self, bc_address_str): """ The go-to function for all your bitcoin style address validation needs. Expects to receive a string believed to represent a bitcoin address Raises appropriate errors if any checks are failed, otherwise returns a list of Currency objects that have the same addr version. """ # First check to make sure the address contains only alphanumeric chars if not bc_address_str.isalnum(): raise InvalidAddressException('Address should be alphanumeric') # Check to make sure str is the proper length if not len(bc_address_str) >= 33 or not len(bc_address_str) <= 35: raise InvalidAddressException( 'Address should be 33-35 characters long') # Check to see if the address can be looked up from the config try: ver = address_version(bc_address_str) except (AttributeError, ValueError): raise InvalidAddressException("Invalid") return ver
def credit_block(redis_key, simulate=False): """ Calculates credits for users from share records for the latest found block. """ # Don't do this truthiness thing if simulate is not True: simulate = False if simulate: current_app.logger.warn( "Running in simulate mode, no DB commit will be performed") current_app.logger.setLevel(logging.DEBUG) data = redis_conn.hgetall(redis_key) current_app.logger.debug("Processing block with details {}".format(data)) merged = bool(int(data.get('merged', False))) # If start_time isn't listed explicitly do our best to derive from # statistical share records if 'start_time' in data: time_started = datetime.datetime.utcfromtimestamp( float(data.get('start_time'))) else: time_started = last_block_time(data['algo'], merged=merged) if data['fees'] == "None": data['fees'] = 0 block = Block( user=data.get('address'), height=data['height'], total_value=(Decimal(data['total_subsidy']) / 100000000), transaction_fees=(Decimal(data['fees']) / 100000000), difficulty=bits_to_difficulty(data['hex_bits']), hash=data['hash'], time_started=time_started, currency=data['currency'], worker=data.get('worker'), found_at=datetime.datetime.utcfromtimestamp(float(data['solve_time'])), algo=data['algo'], merged=merged) db.session.add(block) db.session.flush() # Parse out chain results from the block key chain_data = {} chain_default = {'shares': Decimal('0')} for key, value in data.iteritems(): if key.startswith("chain_"): _, chain_id, key = key.split("_", 2) chain_id = int(chain_id) chain = chain_data.setdefault(chain_id, chain_default.copy()) chain['id'] = chain_id if key == "shares": value = Decimal(value) elif key == "solve_index": value = int(value) # XXX: Could do extra check for setting duplicate data (overrite) here chain[key] = value # Objectize the data. Use object to store all information moving forward chains = [] for id, chain in chain_data.iteritems(): if chain['shares'] == 0: continue cpo = ChainPayout(chainid=id, block=block, solve_slice=chain['solve_index'], chain_shares=chain['shares']) cpo.user_shares = {} cpo.credits = {} db.session.add(cpo) chains.append(cpo) # XXX: Would be good to check compositeprimarykey integrity here, but will # fail on other constraints #db.session.flush() # XXX: Change to a tabulate print current_app.logger.info("Parsed out chain data of {}".format(chain_data)) # Distribute total block value among chains share_distrib = {chain.chainid: chain.chain_shares for chain in chains} distrib = distributor(block.total_value, share_distrib) for chain in chains: chain.amount = distrib[chain.chainid] # Fetch the share distribution for this payout chain users = set() for chain in chains: # Actually fetch the shares from redis! chain.user_shares = chain.config_obj.calc_shares(chain) # If we have nothing, default to paying out the block finder everything if not chain.user_shares: chain.user_shares[block.user] = 1 # Add the users to the set, no dups users.update(chain.user_shares.keys()) # Record how many shares were used to payout chain.payout_shares = sum(chain.user_shares.itervalues()) # Grab all possible user based settings objects for all chains custom_settings = {} if users: custom_settings = {s.user: s for s in UserSettings.query.filter( UserSettings.user.in_(users)).all()} # XXX: Double check that currency code lookups will work relying on # currency obj hashability # The currencies that are valid to pay out in from this block. Basically, # this block currency + all buyable currencies if this block's currency is # sellable valid_currencies = [block.currency_obj] if block.currency_obj.sellable is True: valid_currencies.extend(currencies.buyable_currencies) pool_payout = block.currency_obj.pool_payout def filter_valid(user, address, currency): try: if isinstance(currency, basestring): currency = currencies[currency] except KeyError: current_app.logger.debug( "Converted user {}, addr {}, currency {} => pool addr" " because invalid currency" .format(user, address, currency)) return pool_payout if currency not in valid_currencies: current_app.logger.debug( "Converted user {}, addr {}, currency {} => pool addr" " because invalid currency" .format(user, address, currency)) return pool_payout return dict(address=address, currency=currency, user=user) # Parse usernames and user settings to build appropriate credit objects for chain in chains: for username in chain.user_shares.keys(): try: version = address_version(username) except Exception: # Give these shares to the pool, invalid address version chain.make_credit_obj(shares=chain.user_shares[username], **pool_payout) continue currency = currencies.version_map.get(version) # Check to see if we need to treat them real special :p settings = custom_settings.get(username) shares = chain.user_shares.pop(username) if settings: converted = settings.apply( shares, currency, block.currency, valid_currencies) # Check to make sure no funny business assert sum(c[2] for c in converted) == shares, "Settings apply function returned bad stuff" # Create the separate payout objects from settings return info for address, currency, shares in converted: chain.make_credit_obj( shares=shares, **filter_valid(username, address, currency)) else: # (try to) Payout directly to mining address chain.make_credit_obj( shares=shares, **filter_valid(username, username, currency)) # Calculate the portion that each user recieves for chain in chains: chain.distribute() # Another double check paid = 0 fees_collected = 0 donations_collected = 0 for chain in chains: chain_fee_perc = chain.config_obj.fee_perc for key, credit in chain.credits.items(): # don't try to payout users with zero payout if credit.amount == 0: db.session.expunge(credit) del chain.credits[key] continue # Skip fees/donations for the pool address if credit.user == pool_payout['user']: continue # To do a final check of payout amount paid += credit.amount # Fee/donation/bonus lookup fee_perc = chain_fee_perc donate_perc = Decimal('0') settings = custom_settings.get(credit.user) if settings: donate_perc = settings.pdonation_perc # Application assert isinstance(fee_perc, Decimal) assert isinstance(donate_perc, Decimal) fee_amount = credit.amount * fee_perc donate_amount = credit.amount * donate_perc credit.amount -= fee_amount credit.amount -= donate_amount # Recording credit.fee_perc = int(fee_perc * 100) credit.pd_perc = int(donate_perc * 100) # Bookkeeping donations_collected += donate_amount fees_collected += fee_amount if fees_collected > 0: p = Credit.make_credit( user=pool_payout['user'], block=block, currency=pool_payout['currency'].key, source=1, address=pool_payout['address'], amount=+fees_collected) db.session.add(p) if donations_collected > 0: p = Credit.make_credit( user=pool_payout['user'], block=block, currency=pool_payout['currency'].key, source=2, address=pool_payout['address'], amount=+donations_collected) db.session.add(p) current_app.logger.info("Collected {} {} in donation" .format(donations_collected, block.currency)) current_app.logger.info("Collected {} {} from fees" .format(fees_collected, block.currency)) current_app.logger.info( "Net swing from block {} {}" .format(fees_collected + donations_collected, block.currency)) pool_key = (pool_payout['user'], pool_payout['address'], pool_payout['currency']) for chain in chains: if pool_key not in chain.credits: continue current_app.logger.info( "Collected {} from invalid mining addresses on chain {}" .format(chain.credits[pool_key].amount, chain.chainid)) if not simulate: db.session.commit() redis_conn.delete(redis_key) else: db.session.rollback()
def credit_block(redis_key, simulate=False): """ Calculates credits for users from share records for the latest found block. """ # Don't do this truthiness thing if simulate is not True: simulate = False if simulate: current_app.logger.warn("Running in simulate mode, no commit will be performed") current_app.logger.setLevel(logging.DEBUG) data = redis_conn.hgetall(redis_key) current_app.logger.debug("Processing block with details {}".format(data)) merged = bool(int(data.get('merged', False))) # If start_time isn't listed explicitly do our best to derive from # statistical share records if 'start_time' in data: time_started = datetime.datetime.utcfromtimestamp(float(data.get('start_time'))) else: time_started = last_block_time(data['algo'], merged=merged) block = Block( user=data.get('address'), height=data['height'], total_value=(Decimal(data['total_subsidy']) / 100000000), transaction_fees=(Decimal(data['fees']) / 100000000), difficulty=bits_to_difficulty(data['hex_bits']), hash=data['hash'], time_started=time_started, currency=data['currency'], worker=data.get('worker'), found_at=datetime.datetime.utcfromtimestamp(float(data['solve_time'])), algo=data['algo'], merged=merged) db.session.add(block) db.session.flush() # Parse out chain results from the block key chain_data = {} chain_default = {'shares': Decimal('0')} for key, value in data.iteritems(): if key.startswith("chain_"): _, chain_id, key = key.split("_", 2) chain_id = int(chain_id) chain = chain_data.setdefault(chain_id, chain_default.copy()) chain['id'] = chain_id if key == "shares": value = Decimal(value) elif key == "solve_index": value = int(value) # XXX: Could do extra check for setting duplicate data (overrite) here chain[key] = value # Objectize the data. Use object to store all information moving forward chains = [] for id, chain in chain_data.iteritems(): if chain['shares'] == 0: continue cpo = ChainPayout(chainid=id, block=block, solve_slice=chain['solve_index'], chain_shares=chain['shares']) cpo.user_shares = {} cpo.credits = {} db.session.add(cpo) chains.append(cpo) # XXX: Would be good to check compositeprimarykey integrity here, but will # fail on other constraints #db.session.flush() # XXX: Change to a tabulate print current_app.logger.info("Parsed out chain data of {}".format(chain_data)) # Distribute total block value among chains share_distrib = {chain.chainid: chain.chain_shares for chain in chains} distrib = distributor(block.total_value, share_distrib) for chain in chains: chain.amount = distrib[chain.chainid] # Fetch the share distribution for this payout chain users = set() for chain in chains: # Actually fetch the shares from redis! chain.user_shares = chain.config_obj.calc_shares(chain) # If we have nothing, default to paying out the block finder everything if not chain.user_shares: chain.user_shares[block.user] = 1 # Add the users to the set, no dups users.update(chain.user_shares.keys()) # Record how many shares were used to payout chain.payout_shares = sum(chain.user_shares.itervalues()) # Grab all possible user based settings objects for all chains custom_settings = {} if users: custom_settings = {s.user: s for s in UserSettings.query.filter( UserSettings.user.in_(users)).all()} # XXX: Double check that currency code lookups will work relying on # currency obj hashability # The currencies that are valid to pay out in from this block. Basically, # this block currency + all exchangeable currencies if this block's # currency is also exchangeable valid_currencies = [block.currency_obj] if block.currency_obj.exchangeable is True: valid_currencies.extend(currencies.exchangeable_currencies) # Get the pools payout information for this block global_curr = global_config.pool_payout_currency pool_payout = dict(address=block.currency_obj.pool_payout_addr, currency=block.currency_obj, user=global_curr.pool_payout_addr) # If this currency has no payout address, switch to global default if pool_payout['address'] is None: pool_payout['address'] = global_curr.pool_payout_addr pool_payout['currency'] = global_curr assert block.currency_obj.exchangeable, "Block is un-exchangeable" # Double check valid. Paranoid address_version(pool_payout['address']) def filter_valid(user, address, currency): try: if isinstance(currency, basestring): currency = currencies[currency] except KeyError: current_app.logger.debug( "Converted user {}, addr {}, currency {} => pool addr" " because invalid currency" .format(user, address, currency)) return pool_payout if currency not in valid_currencies: current_app.logger.debug( "Converted user {}, addr {}, currency {} => pool addr" " because invalid currency" .format(user, address, currency)) return pool_payout return dict(address=address, currency=currency, user=user) # Parse usernames and user settings to build appropriate credit objects for chain in chains: for username in chain.user_shares.keys(): try: version = address_version(username) except Exception: # Give these shares to the pool, invalid address version chain.make_credit_obj(shares=chain.user_shares[username], **pool_payout) continue currency = currencies.version_map.get(version) # Check to see if we need to treat them real special :p settings = custom_settings.get(username) shares = chain.user_shares.pop(username) if settings: converted = settings.apply( shares, currency, block.currency, valid_currencies) # Check to make sure no funny business assert sum(c[2] for c in converted) == shares, "Settings apply function returned bad stuff" # Create the separate payout objects from settings return info for address, currency, shares in converted: chain.make_credit_obj( shares=shares, **filter_valid(username, address, currency)) else: # (try to) Payout directly to mining address chain.make_credit_obj( shares=shares, **filter_valid(username, username, currency)) # Calculate the portion that each user recieves for chain in chains: chain.distribute() # Another double check paid = 0 fees_collected = 0 donations_collected = 0 for chain in chains: chain_fee_perc = chain.config_obj.fee_perc for key, credit in chain.credits.items(): # don't try to payout users with zero payout if credit.amount == 0: db.session.expunge(credit) del chain.credits[key] continue # Skip fees/donations for the pool address if credit.user == pool_payout['user']: continue # To do a final check of payout amount paid += credit.amount # Fee/donation/bonus lookup fee_perc = chain_fee_perc donate_perc = Decimal('0') settings = custom_settings.get(credit.user) if settings: donate_perc = settings.pdonation_perc # Application assert isinstance(fee_perc, Decimal) assert isinstance(donate_perc, Decimal) fee_amount = credit.amount * fee_perc donate_amount = credit.amount * donate_perc credit.amount -= fee_amount credit.amount -= donate_amount # Recording credit.fee_perc = int(fee_perc * 100) credit.pd_perc = int(donate_perc * 100) # Bookkeeping donations_collected += donate_amount fees_collected += fee_amount if fees_collected > 0: p = Credit.make_credit( user=pool_payout['user'], block=block, currency=pool_payout['currency'].key, source=1, address=pool_payout['address'], amount=+fees_collected) db.session.add(p) if donations_collected > 0: p = Credit.make_credit( user=pool_payout['user'], block=block, currency=pool_payout['currency'].key, source=2, address=pool_payout['address'], amount=+donations_collected) db.session.add(p) current_app.logger.info("Collected {} in donation".format(donations_collected)) current_app.logger.info("Collected {} from fees".format(fees_collected)) current_app.logger.info("Net swing from block {}" .format(fees_collected + donations_collected)) pool_key = (pool_payout['user'], pool_payout['address'], pool_payout['currency']) for chain in chains: if pool_key not in chain.credits: continue current_app.logger.info( "Collected {} from invalid mining addresses on chain {}" .format(chain.credits[pool_key].amount, chain.chainid)) if not simulate: db.session.commit() redis_conn.delete(redis_key) else: db.session.rollback()