Example #1
0
def payout(self, simulate=False):
    """
    Calculates payouts for users from share records for found blocks.
    """
    try:
        if simulate:
            logger.debug("Running in simulate mode, no commit will be performed")
            logger.setLevel(logging.DEBUG)

        # find the oldest un-processed block
        block = (Block.query.filter_by(processed=False).
                 order_by(Block.height).first())
        if block is None:
            logger.debug("No block found, exiting...")
            return

        logger.debug("Processing block height {}".format(block.height))

        mult = int(current_app.config['last_n'])
        logger.debug("Looking for up to {} total shares".format(total_shares))
        total_shares = (bits_to_shares(block.bits) * mult)
        remain = total_shares
        start = block.last_share.id
        logger.debug("Identified last matching share id as {}".format(start))
        user_shares = {}
        for share in Share.query.order_by(Share.id.desc()).filter(Share.id <= start).yield_per(100):
            user_shares.setdefault(share.user, 0)
            if remain > share.shares:
                user_shares[share.user] += share.shares
                remain -= share.shares
            else:
                user_shares[share.user] += remain
                remain = 0
                break

        # if we found less than n, use what we found as the total
        total_shares -= remain
        logger.debug("Found {} shares".format(total_shares))
        if simulate:
            out = "\n".join(["\t".join((user, str(amount))) for user, amount in user_shares.iteritems()])
            logger.debug("Share distribution:\n{}".format(out))

        # Calculate the portion going to the miners by truncating the
        # fractional portions and giving the remainder to the pool owner
        logger.debug("Distribute_amnt: {}".format(block.total_value))
        # Below calculates the truncated portion going to each miner. because
        # of fractional pieces the total accrued wont equal the disitrubte_amnt
        # so we will distribute that round robin the miners in dictionary order
        accrued = 0
        user_payouts = {}
        for user, share_count in user_shares.iteritems():
            user_payouts.setdefault(share.user, 0)
            user_payouts[user] = (share_count * block.total_value) // total_shares
            accrued += user_payouts[user]

        logger.debug("Total accrued after trunated iteration {}; {}%"
                     .format(accrued, (accrued / float(block.total_value)) * 100))
        # loop over the dictionary indefinitely until we've distributed
        # all the remaining funds
        i = 0
        while accrued < block.total_value:
            for key in user_payouts:
                i += 1
                user_payouts[key] += 1
                accrued += 1
                # exit if we've exhausted
                if accrued >= block.total_value:
                    break

        logger.debug("Ran round robin distro {} times to finish distrib"
                     .format(i))

        # now handle donation or bonus distribution for each user
        donation_total = 0
        bonus_total = 0
        user_perc_applied = {}
        user_perc = {}
        default_perc = current_app.config.get('default_perc', 0)
        # convert our custom percentages that apply to these users into an
        # easy to access dictionary
        custom_percs = DonationPercent.query.filter(DonationPercent.user.in_(user_shares.keys()))
        custom_percs = {d.user: d.perc for d in custom_percs}
        for user, payout in user_payouts.iteritems():
            # use the custom perc, or fallback to the default
            perc = custom_percs.get(user, default_perc)
            user_perc[user] = perc

            # if the perc is greater than 0 it's calced as a donation
            if perc > 0:
                donation = int(ceil((perc / 100.0) * payout))
                logger.debug("Donation of\t{}\t({}%)\tcollected from\t{}"
                             .format(donation / 100000000.0, perc, user))
                donation_total += donation
                user_payouts[user] -= donation
                user_perc_applied[user] = donation

            # if less than zero it's a bonus payout
            elif perc < 0:
                perc *= -1
                bonus = int(floor((perc / 100.0) * payout))
                logger.debug("Bonus of\t{}\t({}%)\tpaid to\t{}"
                             .format(bonus / 100000000.0, perc, user))
                user_payouts[user] += bonus
                bonus_total += bonus
                user_perc_applied[user] = -1 * bonus

            # percentages of 0 are no-ops

        logger.info("Payed out {} in bonus payment"
                    .format(bonus_total / 100000000.0))
        logger.info("Received {} in donation payment"
                    .format(donation_total / 100000000.0))
        logger.info("Net income from block {}"
                    .format((donation_total - bonus_total) / 100000000.0))

        assert accrued == block.total_value
        logger.info("Successfully distributed all rewards among {} users."
                    .format(len(user_payouts)))

        # run another safety check
        user_sum = sum(user_payouts.values())
        assert user_sum == (block.total_value + bonus_total - donation_total)
        logger.info("Double check for payout distribution."
                    " Total user payouts {}, total block value {}."
                    .format(user_sum, block.total_value))

        if simulate:
            out = "\n".join(["\t".join((user, str(amount / 100000000.0))) for user, amount in user_payouts.iteritems()])
            logger.debug("Payout distribution:\n{}".format(out))
            db.session.rollback()
        else:
            # record the payout for each user
            for user, amount in user_payouts.iteritems():
                Payout.create(user, amount, block, user_shares[user],
                              user_perc[user], user_perc_applied.get(user, 0))
            # update the block status and collected amounts
            block.processed = True
            block.donated = donation_total
            block.bonus_payed = bonus_total
            # record the donations as a bonus payout to the donate address
            if donation_total > 0:
                donate_address = current_app.config['donate_address']
                BonusPayout.create(donate_address, donation_total,
                                   "Total donations from block {}"
                                   .format(block.height))
                logger.info("Added bonus payout to donation address {} for {}"
                            .format(donate_address, donation_total / 100000000.0))

            block_bonus = current_app.config.get('block_bonus', 0)
            if block_bonus > 0:
                BonusPayout.create(block.user, block_bonus,
                                   "Blockfinder bonus for block {}"
                                   .format(block.height))
                logger.info("Added bonus payout for blockfinder {} for {}"
                            .format(block.user, block_bonus / 100000000.0))

            db.session.commit()
    except Exception as exc:
        logger.error("Unhandled exception in payout", exc_info=True)
        db.session.rollback()
        raise self.retry(exc=exc)
Example #2
0
def payout(hash=None, simulate=False):
    """
    Calculates payouts for users from share records for the latest found block.
    """
    if simulate:
        logger.debug("Running in simulate mode, no commit will be performed")
        logger.setLevel(logging.DEBUG)

    # find the oldest un-processed block
    if hash:
        block = Block.query.filter_by(processed=False, hash=hash).first()
    else:
        block = (Block.query.filter_by(processed=False).order_by(Block.found_at).first())

    if block is None:
        logger.debug("No block found, exiting...")
        return

    logger.debug("Processing block height {}".format(block.height))

    mult = int(current_app.config['last_n'])
    total_shares = int((bits_to_shares(block.bits) * mult) / current_app.config.get('share_multiplier', 1))
    logger.debug("Looking for up to {} total shares".format(total_shares))
    if block.last_share_id is None:
        logger.error("Can't process this block, it's shares have been deleted!")
        return
    logger.debug("Identified last matching share id as {}".format(block.last_share_id))

    # if we found less than n, use what we found as the total
    user_shares, total_shares = get_sharemap(block.last_share_id, total_shares)
    logger.debug("Found {} shares".format(total_shares))
    if simulate:
        out = "\n".join(["\t".join((user, str((amount * 100.0) / total_shares), str((amount * block.total_value) // total_shares), str(amount))) for user, amount in user_shares.iteritems()])
        logger.debug("Share distribution:\nUSR\t%\tBLK_PAY\tSHARE\n{}".format(out))

    logger.debug("Distribute_amnt: {}".format(block.total_value))
    if block.merged_type:
        merge_cfg = current_app.config['merged_cfg'][block.merged_type]
        new_user_shares = {merge_cfg['donate_address']: 0}
        # build a map of regular addresses to merged addresses
        query = (MergeAddress.query.filter_by(merged_type=block.merged_type).
                 filter(MergeAddress.user.in_(user_shares.keys())))
        merge_addr_map = {m.user: m.merge_address for m in query}
        logger.debug("Looking up merged mappings for merged_type {}, found {}"
                     .format(block.merged_type, len(merge_addr_map)))

        for user in user_shares:
            merge_addr = merge_addr_map.get(user)
            # if this user didn't set a merged mining address
            if not merge_addr:
                # give the excess to the donation address if set to not
                # distribute unassigned
                if not merge_cfg['distribute_unassigned']:
                    new_user_shares[merge_cfg['donate_address']] += user_shares[user]
                else:
                    total_shares -= user_shares[user]
                continue

            new_user_shares.setdefault(merge_addr, 0)
            new_user_shares[merge_addr] += user_shares[user]

        user_shares = new_user_shares

    assert total_shares == sum(user_shares.itervalues())

    # Below calculates the truncated portion going to each miner. because
    # of fractional pieces the total accrued wont equal the disitrubte_amnt
    # so we will distribute that round robin the miners in dictionary order
    accrued = 0
    user_payouts = {}
    for user, share_count in user_shares.iteritems():
        user_payouts[user] = float(share_count * block.total_value) // total_shares
        accrued += user_payouts[user]

    logger.debug("Total accrued after trunated iteration {}; {}%"
                 .format(accrued, (accrued / float(block.total_value)) * 100))
    # loop over the dictionary indefinitely until we've distributed
    # all the remaining funds
    i = 0
    while accrued < block.total_value:
        for key in user_payouts:
            i += 1
            user_payouts[key] += 1
            accrued += 1
            # exit if we've exhausted
            if accrued >= block.total_value:
                break

    logger.debug("Ran round robin distro {} times to finish distrib".format(i))

    # now handle donation or bonus distribution for each user
    donation_total = 0
    bonus_total = 0
    # dictionary keyed by address to hold donate/bonus percs and amnts
    user_perc_applied = {}
    user_perc = {}
    if not block.merged_type:
        default_perc = current_app.config.get('default_perc', 0)
        # convert our custom percentages that apply to these users into an
        # easy to access dictionary
        custom_percs = DonationPercent.query.filter(DonationPercent.user.in_(user_shares.keys()))
        custom_percs = {d.user: d.perc for d in custom_percs}
    else:
        default_perc = merge_cfg.get('default_perc', 0)
        custom_percs = {}

    for user, payout in user_payouts.iteritems():
        # use the custom perc, or fallback to the default
        perc = custom_percs.get(user, default_perc)
        user_perc[user] = perc

        # if the perc is greater than 0 it's calced as a donation
        if perc > 0:
            donation = int(ceil((perc / 100.0) * payout))
            logger.debug("Donation of\t{}\t({}%)\tcollected from\t{}"
                         .format(donation / 100000000.0, perc, user))
            donation_total += donation
            user_payouts[user] -= donation
            user_perc_applied[user] = donation

        # if less than zero it's a bonus payout
        elif perc < 0:
            perc *= -1
            bonus = int(floor((perc / 100.0) * payout))
            logger.debug("Bonus of\t{}\t({}%)\tpaid to\t{}"
                         .format(bonus / 100000000.0, perc, user))
            user_payouts[user] += bonus
            bonus_total += bonus
            user_perc_applied[user] = -1 * bonus

        # percentages of 0 are no-ops

    logger.info("Payed out {} in bonus payment"
                .format(bonus_total / 100000000.0))
    logger.info("Received {} in donation payment"
                .format(donation_total / 100000000.0))
    logger.info("Net income from block {}"
                .format((donation_total - bonus_total) / 100000000.0))

    assert accrued == block.total_value
    logger.info("Successfully distributed all rewards among {} users."
                .format(len(user_payouts)))

    # run another safety check
    user_sum = sum(user_payouts.values())
    assert user_sum == (block.total_value + bonus_total - donation_total)
    logger.info("Double check for payout distribution."
                " Total user payouts {}, total block value {}."
                .format(user_sum, block.total_value))

    if simulate:
        out = "\n".join(["\t".join((user, str(amount / 100000000.0))) for user, amount in user_payouts.iteritems()])
        logger.debug("Final payout distribution:\nUSR\tAMNT\n{}".format(out))
        db.session.rollback()
    else:
        # record the payout for each user
        for user, amount in user_payouts.iteritems():
            if amount == 0.0:
                logger.info("Skip zero payout for USR: {}".format(user))
                continue
            Payout.create(user, amount, block, user_shares[user],
                          user_perc[user], user_perc_applied.get(user, 0),
                          merged_type=block.merged_type)
        # update the block status and collected amounts
        block.processed = True
        block.donated = donation_total
        block.bonus_payed = bonus_total
        # record the donations as a bonus payout to the donate address
        if donation_total > 0:
            if block.merged_type:
                donate_address = merge_cfg['donate_address']
            else:
                donate_address = current_app.config['donate_address']
            BonusPayout.create(donate_address, donation_total,
                                "Total donations from block {}"
                                .format(block.height), block,
                                merged_type=block.merged_type)
            logger.info("Added bonus payout to donation address {} for {}"
                        .format(donate_address, donation_total / 100000000.0))

        block_bonus = current_app.config.get('block_bonus', 0)
        if block_bonus > 0 and not block.merged_type:
            BonusPayout.create(block.user, block_bonus,
                               "Blockfinder bonus for block {}"
                               .format(block.height), block)
            logger.info("Added bonus payout for blockfinder {} for {}"
                        .format(block.user, block_bonus / 100000000.0))

        db.session.commit()
Example #3
0
def give_bonus(user, amount, description, blockhash):
    """ Manually create a BonusPayout for a user """
    block = Block.query.filter_by(hash=blockhash).one()
    BonusPayout.create(user, amount, description, block)
    db.session.commit()
def payout(hash=None, simulate=False):
    """
    Calculates payouts for users from share records for the latest found block.
    """
    if simulate:
        logger.debug("Running in simulate mode, no commit will be performed")
        logger.setLevel(logging.DEBUG)

    # find the oldest un-processed block
    if hash:
        block = Block.query.filter_by(processed=False, hash=hash).first()
    else:
        block = (Block.query.filter_by(processed=False).order_by(
            Block.found_at).first())

    if block is None:
        logger.debug("No block found, exiting...")
        return

    logger.debug("Processing block height {}".format(block.height))

    mult = int(current_app.config['last_n'])
    total_shares = int((bits_to_shares(block.bits) * mult) /
                       current_app.config.get('share_multiplier', 1))
    logger.debug("Looking for up to {} total shares".format(total_shares))
    if block.last_share_id is None:
        logger.error(
            "Can't process this block, it's shares have been deleted!")
        return
    logger.debug("Identified last matching share id as {}".format(
        block.last_share_id))

    # if we found less than n, use what we found as the total
    user_shares, total_shares = get_sharemap(block.last_share_id, total_shares)
    logger.debug("Found {} shares".format(total_shares))
    if simulate:
        out = "\n".join([
            "\t".join((user, str((amount * 100.0) / total_shares),
                       str((amount * block.total_value) // total_shares),
                       str(amount)))
            for user, amount in user_shares.iteritems()
        ])
        logger.debug(
            "Share distribution:\nUSR\t%\tBLK_PAY\tSHARE\n{}".format(out))

    logger.debug("Distribute_amnt: {}".format(block.total_value))
    if block.merged_type:
        merge_cfg = current_app.config['merged_cfg'][block.merged_type]
        new_user_shares = {merge_cfg['donate_address']: 0}
        # build a map of regular addresses to merged addresses
        query = (MergeAddress.query.filter_by(
            merged_type=block.merged_type).filter(
                MergeAddress.user.in_(user_shares.keys())))
        merge_addr_map = {m.user: m.merge_address for m in query}
        logger.debug(
            "Looking up merged mappings for merged_type {}, found {}".format(
                block.merged_type, len(merge_addr_map)))

        for user in user_shares:
            merge_addr = merge_addr_map.get(user)
            # if this user didn't set a merged mining address
            if not merge_addr:
                # give the excess to the donation address if set to not
                # distribute unassigned
                if not merge_cfg['distribute_unassigned']:
                    new_user_shares[
                        merge_cfg['donate_address']] += user_shares[user]
                else:
                    total_shares -= user_shares[user]
                continue

            new_user_shares.setdefault(merge_addr, 0)
            new_user_shares[merge_addr] += user_shares[user]

        user_shares = new_user_shares

    assert total_shares == sum(user_shares.itervalues())

    # Below calculates the truncated portion going to each miner. because
    # of fractional pieces the total accrued wont equal the disitrubte_amnt
    # so we will distribute that round robin the miners in dictionary order
    accrued = 0
    user_payouts = {}
    for user, share_count in user_shares.iteritems():
        user_payouts[user] = float(
            share_count * block.total_value) // total_shares
        accrued += user_payouts[user]

    logger.debug("Total accrued after trunated iteration {}; {}%".format(
        accrued, (accrued / float(block.total_value)) * 100))
    # loop over the dictionary indefinitely until we've distributed
    # all the remaining funds
    i = 0
    while accrued < block.total_value:
        for key in user_payouts:
            i += 1
            user_payouts[key] += 1
            accrued += 1
            # exit if we've exhausted
            if accrued >= block.total_value:
                break

    logger.debug("Ran round robin distro {} times to finish distrib".format(i))

    # now handle donation or bonus distribution for each user
    donation_total = 0
    bonus_total = 0
    # dictionary keyed by address to hold donate/bonus percs and amnts
    user_perc_applied = {}
    user_perc = {}
    if not block.merged_type:
        default_perc = current_app.config.get('default_perc', 0)
        # convert our custom percentages that apply to these users into an
        # easy to access dictionary
        custom_percs = DonationPercent.query.filter(
            DonationPercent.user.in_(user_shares.keys()))
        custom_percs = {d.user: d.perc for d in custom_percs}
    else:
        default_perc = merge_cfg.get('default_perc', 0)
        custom_percs = {}

    for user, payout in user_payouts.iteritems():
        # use the custom perc, or fallback to the default
        perc = custom_percs.get(user, default_perc)
        user_perc[user] = perc

        # if the perc is greater than 0 it's calced as a donation
        if perc > 0:
            donation = int(ceil((perc / 100.0) * payout))
            logger.debug("Donation of\t{}\t({}%)\tcollected from\t{}".format(
                donation / 100000000.0, perc, user))
            donation_total += donation
            user_payouts[user] -= donation
            user_perc_applied[user] = donation

        # if less than zero it's a bonus payout
        elif perc < 0:
            perc *= -1
            bonus = int(floor((perc / 100.0) * payout))
            logger.debug("Bonus of\t{}\t({}%)\tpaid to\t{}".format(
                bonus / 100000000.0, perc, user))
            user_payouts[user] += bonus
            bonus_total += bonus
            user_perc_applied[user] = -1 * bonus

        # percentages of 0 are no-ops

    logger.info("Payed out {} in bonus payment".format(bonus_total /
                                                       100000000.0))
    logger.info("Received {} in donation payment".format(donation_total /
                                                         100000000.0))
    logger.info("Net income from block {}".format(
        (donation_total - bonus_total) / 100000000.0))

    assert accrued == block.total_value
    logger.info("Successfully distributed all rewards among {} users.".format(
        len(user_payouts)))

    # run another safety check
    user_sum = sum(user_payouts.values())
    assert user_sum == (block.total_value + bonus_total - donation_total)
    logger.info("Double check for payout distribution."
                " Total user payouts {}, total block value {}.".format(
                    user_sum, block.total_value))

    if simulate:
        out = "\n".join([
            "\t".join((user, str(amount / 100000000.0)))
            for user, amount in user_payouts.iteritems()
        ])
        logger.debug("Final payout distribution:\nUSR\tAMNT\n{}".format(out))
        db.session.rollback()
    else:
        # record the payout for each user
        for user, amount in user_payouts.iteritems():
            if amount == 0.0:
                logger.info("Skip zero payout for USR: {}".format(user))
                continue
            Payout.create(user,
                          amount,
                          block,
                          user_shares[user],
                          user_perc[user],
                          user_perc_applied.get(user, 0),
                          merged_type=block.merged_type)
        # update the block status and collected amounts
        block.processed = True
        block.donated = donation_total
        block.bonus_payed = bonus_total
        # record the donations as a bonus payout to the donate address
        if donation_total > 0:
            if block.merged_type:
                donate_address = merge_cfg['donate_address']
            else:
                donate_address = current_app.config['donate_address']
            BonusPayout.create(donate_address,
                               donation_total,
                               "Total donations from block {}".format(
                                   block.height),
                               block,
                               merged_type=block.merged_type)
            logger.info(
                "Added bonus payout to donation address {} for {}".format(
                    donate_address, donation_total / 100000000.0))

        block_bonus = current_app.config.get('block_bonus', 0)
        if block_bonus > 0 and not block.merged_type:
            BonusPayout.create(
                block.user, block_bonus,
                "Blockfinder bonus for block {}".format(block.height), block)
            logger.info("Added bonus payout for blockfinder {} for {}".format(
                block.user, block_bonus / 100000000.0))

        db.session.commit()
Example #5
0
def give_bonus(user, amount, description, blockhash):
    """ Manually create a BonusPayout for a user """
    block = Block.query.filter_by(hash=blockhash).one()
    BonusPayout.create(user, amount, description, block)
    db.session.commit()
Example #6
0
def give_bonus(user, amount, description):
    """ Manually create a BonusPayout for a user """
    BonusPayout.create(user, amount, description)
    db.session.commit()