示例#1
0
    def test_process(self, mock_sendtoaddress, mock_sleep):
        # a successfull run

        Transaction(miner=self.miner1, amount=30, network="test").save()

        self.miner1.update_balance()
        self.miner1.save()

        Transaction(miner=self.miner1, amount=10, network="test").save()

        send_autopayments("test")

        miner1 = Miner.objects.get(pk=self.miner1.id)
        self.assertEqual(miner1.balance, 0)

        self.assertEqual(Transaction.objects.filter(category='TX').count(), 1)

        tx = Transaction.objects.get(category='TX')
        self.assertEqual(tx.amount, -44)
        self.assertEqual(tx.miner.id, miner1.id)
        self.assertEqual(tx.network, "test")

        # even with a new balance, should not pay again, as we do not pay so early again
        Transaction(miner=self.miner1, amount=16, network="test").save()

        self.miner1.update_balance()
        self.miner1.save()

        send_autopayments("test")

        miner1 = Miner.objects.get(pk=self.miner1.id)
        self.assertEqual(miner1.balance, 16)

        self.assertEqual(Transaction.objects.filter(category='TX').count(), 1)

        tx = Transaction.objects.get(category='TX')
        self.assertEqual(tx.amount, -44)
        self.assertEqual(tx.miner.id, miner1.id)
        self.assertEqual(tx.network, "test")

        # but if first_trans is older, then the second one should be paid
        tx.inserted_at = timezone.now() - datetime.timedelta(hours=18)
        tx.save()

        send_autopayments("test")

        miner1 = Miner.objects.get(pk=self.miner1.id)
        self.assertEqual(miner1.balance, 0)

        self.assertEqual(Transaction.objects.filter(category='TX').count(), 2)

        tx = Transaction.objects.filter(category='TX').order_by('-id')[0]
        self.assertEqual(tx.amount, -16)
        self.assertEqual(tx.miner.id, miner1.id)
        self.assertEqual(tx.network, "test")
示例#2
0
    def setUp(self):
        # the miner 1 starts with a fake balance
        self.miner1 = Miner(network="test",
                            balance=20,
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxxx")
        self.miner1.save()

        # as the user above, but with not enough balance
        self.miner2 = Miner(network="test",
                            balance=1,
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxx2")
        self.miner2.save()

        Transaction(miner=self.miner1, amount=4, network="test").save()
        Transaction(miner=self.miner2, amount=1, network="test").save()
示例#3
0
    def test_process_failed(self, mock_sendtoaddress):
        mock_sendtoaddress.side_effect = Exception()

        # a successfull run

        Transaction(miner=self.miner1, amount=30, network="test").save()

        self.miner1.update_balance()
        self.miner1.save()

        Transaction(miner=self.miner1, amount=10, network="test").save()

        send_autopayments("test")

        miner1 = Miner.objects.get(pk=self.miner1.id)
        self.assertEqual(miner1.balance, 34)  # transaction wasn't done

        self.assertEqual(Transaction.objects.filter(category='TX').count(), 0)

        self.assertEqual(TransactionError.objects.all().count(), 1)
示例#4
0
    def test_outdated_value(self, mock_sendtoaddress, mock_sleep):
        # balance is high enough, but not right

        Transaction(miner=self.miner1, amount=10, network="test").save()

        send_autopayments("test")

        miner1 = Miner.objects.get(pk=self.miner1.id)
        self.assertEqual(miner1.balance, 0)

        self.assertEqual(Transaction.objects.filter(category='TX').count(), 1)

        tx = Transaction.objects.get(category='TX')
        self.assertEqual(tx.amount, -14)
        self.assertEqual(tx.miner.id, miner1.id)
        self.assertEqual(tx.network, "test")
示例#5
0
    def setUp(self):
        self.miner1 = Miner(network="test",
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxxx")
        self.miner1.save()

        self.miner2 = Miner(network="test",
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxx2")
        self.miner2.save()

        Transaction(miner=self.miner1, network="test", amount=10).save()
        Transaction(miner=self.miner1, network="test", amount=5).save()
        Transaction(miner=self.miner1, network="test", amount=-2).save()

        # should not happen, but better safe then sorry
        Transaction(miner=self.miner1, network="main", amount=100).save()

        Transaction(miner=self.miner2, network="test", amount=99).save()
        Transaction(miner=self.miner2, network="test", amount=-90).save()
示例#6
0
    def setUp(self):
        self.miner1 = Miner(network="test",
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxxx")
        self.miner1.save()

        self.worker1 = Worker(miner=self.miner1)
        self.worker1.save()

        self.miner2 = Miner(network="test",
                            address="B91RjV9UoZa5qLNbWZFXJ42sFWbJCyxxx2")
        self.miner2.save()

        self.worker2 = Worker(miner=self.miner2)
        self.worker2.save()

        self.insert_age = timezone.now() - datetime.timedelta(days=3)

        Transaction(miner=self.miner2, amount=60, network="test").save()

        # test network
        #----------------

        # valid
        block1 = Block(height=1,
                       network='test',
                       process_status='BP',
                       pool_block=True,
                       subsidy=100)
        block1.save()

        # not a pool block
        block2 = Block(height=2, network='test', pool_block=False, subsidy=1)
        block2.save()

        # wrong status
        block3 = Block(height=3,
                       network='test',
                       process_status='OP',
                       pool_block=True,
                       subsidy=1)
        block3.save()

        Block.objects.all().update(inserted_at=self.insert_age)

        # not old enough
        block4 = Block(height=4,
                       network='test',
                       process_status='BP',
                       pool_block=True,
                       subsidy=1)
        block4.save()

        work_test1 = Work(worker=self.worker1, ip="1.1.1.1")
        work_test1.save()

        work_test2 = Work(worker=self.worker1, ip="1.1.1.1")
        work_test2.save()

        Solution(network='test',
                 block=block1,
                 bible_hash="1",
                 miner=self.miner1,
                 work=work_test1).save()
        Solution(network='test',
                 block=block1,
                 bible_hash="2",
                 miner=self.miner1,
                 work=work_test1).save()
        Solution(network='test',
                 block=block1,
                 bible_hash="3",
                 miner=self.miner1,
                 work=work_test1).save()

        Solution(network='test',
                 block=block1,
                 bible_hash="4",
                 miner=self.miner2,
                 work=work_test1).save()
        Solution(network='test',
                 block=block1,
                 bible_hash="4.5",
                 miner=self.miner2,
                 work=work_test1).save()

        Solution(network='test',
                 block=block2,
                 bible_hash="5",
                 miner=self.miner1,
                 work=work_test1).save()
        Solution(network='test',
                 block=block3,
                 bible_hash="6",
                 miner=self.miner1,
                 work=work_test1).save()
        Solution(network='test',
                 block=block4,
                 bible_hash="7",
                 miner=self.miner1,
                 work=work_test1).save()

        # main network
        # -----------------
        block1 = Block(height=1,
                       network='main',
                       process_status='BP',
                       pool_block=True,
                       subsidy=1)
        block1.save()

        Block.objects.filter(network="main").update(
            inserted_at=self.insert_age)

        work_main = Work(worker=self.worker1, ip="1.1.1.1")
        work_main.save()

        Solution(network='main',
                 block=block2,
                 bible_hash="8",
                 miner=self.miner1,
                 work=work_main).save()
示例#7
0
def shareout_next_block(network, block_height=None, dry_run=False):
    """ Tries to find the oldest block that had matured and was not yet processed for
        shares. Optional, a specific block can be given.
        This task takes this block, calculates the shares of all users that contributed
        to the block and creates the transactions with there bbps.
        Important: The bbp will not send here, this is an extra step, done when the
        transaction limit is reached for a user

        Dry run will not save anything to the database.
        """

    if settings.TASK_DEBUG:
        print("Debug | ", "Shareout started with network", network,
              "on block ", block_height, " in dry-run", dry_run)

    # we only start here, if no block is currently processed
    # not important in dry_run
    if not dry_run and Block.objects.filter(
            network=network, pool_block=True, process_status='PS').count() > 0:
        if settings.TASK_DEBUG:
            print("Debug | ",
                  "Another block is currently processed/has status 'PS'")
        return

    # try to find the oldest block then is OLDER then 1 day
    # (so it had matured -> the network had accepted it) and take it
    age = timezone.now() - datetime.timedelta(
        hours=settings.POOL_BLOCK_MATURE_HOURS[network])

    if block_height is None:
        lowest_pool_waiting_height = Block.objects.filter(
            network=network,
            pool_block=True,
            process_status='BP',
            inserted_at__lt=age).aggregate(Min('height'))['height__min']

        if lowest_pool_waiting_height is None:  # no unprocessed block found
            return
    else:
        lowest_pool_waiting_height = block_height

    block = Block.objects.get(network=network,
                              height=lowest_pool_waiting_height)

    if settings.TASK_DEBUG:
        print("Debug | ", "Loaded block from database:", block.height,
              block.network, block.process_status, block.inserted_at)

    # before we start, we mark the block as in-process of the shares
    with transaction.atomic():
        block.process_status = 'PS'  # Processing shares
        if not dry_run:
            block.save()

    # if we found a block, we ensure that is still ours!
    client = BiblePayRpcClient(network=network)
    subsidy = client.subsidy(lowest_pool_waiting_height)

    if settings.TASK_DEBUG:
        print("Debug | ", "Requested subsidy: ", subsidy)

    if subsidy.get('recipient') != settings.POOL_ADDRESS[network]:
        block.process_status = 'ST'  # stale
        if not dry_run:
            block.save()
        return

    # now we count the users solutions for the block
    # Important: Do not remove the ".order_by()", as it is required, or the result
    # will be wrong (seems to be a django problem)
    qs = Solution.objects.filter(network=network, block=block, ignore=False)

    if not dry_run:  # in reall live, we only want entries that where not already processed
        qs = qs.filter(processed=False)

    solution_counts = qs.values('miner_id').annotate(
        total=Count('miner_id')).order_by()

    # with that, we first calculate the total amount of relevant shares
    total_count = 0
    for solution_count in solution_counts:
        total_count += solution_count['total']

    if settings.TASK_DEBUG:
        print("Debug | ", "Solution count is ", total_count)
        print("Debug | ", "Miner/solution count is ", len(solution_counts))

    # if there are no shares, then there is nothing todo here anymore
    if total_count == 0:
        block.process_status = 'FI'  # finished
        if not dry_run:
            block.save()
        return

    # same if the block.subsidy is buggy
    if block.subsidy == 0:
        block.process_status = 'FI'  # finished
        if not dry_run:
            block.save()
        return

    # before we do the share calculation, we substract the fee from the subsidy
    miner_subsidy = block.subsidy
    if settings.POOL_FEE_PERCENT > 0:
        miner_subsidy = calculate_miner_subsidy(block.subsidy)

    if settings.TASK_DEBUG:
        print("Debug | ", "Miner subsidy is ", miner_subsidy)

    # calculcation of the amount of bbp per user based on the solutions of the block
    subsidy_per_solution = miner_subsidy / total_count

    if settings.TASK_DEBUG:
        print("Debug | ", "Subsidy per solution is ", subsidy_per_solution,
              "with a total of", (subsidy_per_solution * total_count))

    added_amount = 0

    # and now we add the transactions that add the coins to the user
    # they are not send to the user here, that will be done by a different script
    with transaction.atomic():
        for solution_count in solution_counts:
            user_subsidy = subsidy_per_solution * solution_count['total']

            inote = 'BLOCK:' + str(block.height) + '|SOLUTIONS:' + str(
                solution_count['total'])

            tx = Transaction(
                network=network,
                miner_id=solution_count['miner_id'],
                amount=user_subsidy,
                category='MS',

                # some notes for the user and some for us
                note='Share for block %s' % block.height,
                internal_note=inote,
            )
            if not dry_run:
                tx.save()

            added_amount += user_subsidy

        # last but not least, we mark the block and the solutions as processed
        if not dry_run:
            Solution.objects.filter(network=network,
                                    processed=False,
                                    block=block).update(processed=True)

        # and we mark the block as finished
        block.process_status = 'FI'  # finished
        if not dry_run:
            block.save()

    if settings.TASK_DEBUG:
        print("Debug | ", "Created transaction with a total amount of ",
              added_amount)

    # last, we update the user balances. We do this in a new step, as the step before is more important
    # and should be done as fast as possible
    # We do not load the miner from the database, we only update it. This is faster
    if not dry_run:
        update_list = []
        with transaction.atomic():
            for solution_count in solution_counts:
                value = Miner.calculate_miner_balance(
                    network, solution_count['miner_id'])
                Miner.objects.filter(pk=solution_count['miner_id']).update(
                    balance=value)
示例#8
0
def send_autopayments(network):
    """ Looks into the miner list and finds all miners that
        have a balance greater than settings.POOL_MINIMUM_AUTOSEND """

    # no autosend is done if the miner has a balance below
    # this value
    minimum = settings.POOL_MINIMUM_AUTOSEND[network]

    # miners of the network with more then the minimum balance
    miners = Miner.objects.filter(balance__gt=minimum,
                                  network=network).values('id', 'address')

    client = BiblePayRpcClient(network=network)

    # the system will only do the transactions for
    # this amount of miners before this task is closed
    # the next task will go on from then on
    max_miners = 10

    miner_pos = 0
    for miner in miners:
        # we only send payments for miners who hadn't a payment in the last 12 hours
        last_trans_dt = timezone.now() - datetime.timedelta(hours=12)
        last_trans_count = Transaction.objects.filter(
            miner_id=miner['id'],
            network=network,
            category='TX',
            inserted_at__gt=last_trans_dt).count()

        if last_trans_count > 0:
            continue

        miner_pos += 1

        with transaction.atomic():
            # for security reasons, we recalculate the balance,
            # as the miner table can be changed by the frontend
            value = Miner.calculate_miner_balance(network, miner['id'])

            # if all is fine, we send out the bbp
            if value > minimum:
                tx_id = None
                try:
                    tx_id = client.sendtoaddress(miner['address'], value)
                except Exception as e:
                    error_message = ' # '.join([str(e), str(type(e))]) + "\n"
                    error_message += str(traceback.extract_stack()) + "\n"
                    error_message += str(traceback.format_stack())

                    # we should log the error if something goes wrong
                    transerror = TransactionError(
                        network=network,
                        miner_id=miner['id'],
                        amount=(value * -1),
                        error_message=error_message,
                    )
                    transerror.save()

                # transaction failed? Then we skip this user this time
                if tx_id is None:
                    continue

                internal_note = 'TX_ID:%s' % tx_id

                tx = Transaction(
                    network=network,
                    miner_id=miner['id'],
                    category='TX',  # outgoing transaction

                    # We substract the amount here, so it must be negative (55 -> -55)
                    amount=(value * -1),

                    # biblepay transaction
                    tx=tx_id,

                    # some notes for the user and some for us
                    note='Autosend',
                    internal_note=internal_note,
                )
                tx.save()

                # no value should be on the account anymore
                # but we better calculate it, if something had changed
                value = Miner.calculate_miner_balance(network, miner['id'])

                # added to prevent a problem with to fast and many transactions
                # in a short time
                time.sleep(5)

            # we update the miner with the new balance
            # even if no bbp was send (because of wrong values in the db). We
            # at least fix the value then
            Miner.objects.filter(pk=miner['id'],
                                 network=network).update(balance=value)

        # we end after we reached our max for this task
        if miner_pos == max_miners:
            break
示例#9
0
    def test_error_on_send(self, mock_sendtoaddress):
        mock_sendtoaddress.side_effect = Exception()

        Transaction(miner=self.miner1, amount=10, network="test").save()

        send_autopayments("test")