def execute_deposits(cls):
        module = sys.modules[__name__]
        try:
            ready_transactions = PendingDeposits.get_ready_deposits()
        except Exception:
            logger.exception(
                "poll_pending_deposits() threw an unexpected exception")
            return
        for i, transaction in enumerate(ready_transactions):
            if module.TERMINATE:
                still_processing_transactions = ready_transactions[i:]
                Transaction.objects.filter(
                    id__in=[t.id
                            for t in still_processing_transactions]).update(
                                pending_execution_attempt=False)
                break
            cls.execute_deposit(transaction)

        with django.db.transaction.atomic():
            multisig_transactions = list(
                Transaction.objects.filter(
                    kind=Transaction.KIND.deposit,
                    status=Transaction.STATUS.pending_anchor,
                    pending_signatures=False,
                    pending_execution_attempt=False,
                ).select_for_update())
            Transaction.objects.filter(
                id__in=[t.id for t in multisig_transactions]).update(
                    pending_execution_attempt=True)

        for i, transaction in enumerate(multisig_transactions):
            if module.TERMINATE:
                still_processing_transactions = ready_transactions[i:]
                Transaction.objects.filter(
                    id__in=[t.id
                            for t in still_processing_transactions]).update(
                                pending_execution_attempt=False)
                break

            try:
                success = PendingDeposits.submit(transaction)
            except Exception as e:
                logger.exception("submit() threw an unexpected exception")
                transaction.status_message = str(e)
                transaction.status = Transaction.STATUS.error
                transaction.pending_execution_attempt = False
                transaction.save()
                maybe_make_callback(transaction)
                continue

            if success:
                transaction.refresh_from_db()
                try:
                    rdi.after_deposit(transaction)
                except Exception:
                    logger.exception(
                        "after_deposit() threw an unexpected exception")
Beispiel #2
0
 def get_ready_deposits(cls) -> List[Transaction]:
     pending_deposits = Transaction.objects.filter(
         status__in=[
             Transaction.STATUS.pending_user_transfer_start,
             Transaction.STATUS.pending_external,
         ],
         kind=Transaction.KIND.deposit,
         pending_execution_attempt=False,
     ).select_for_update()
     with django.db.transaction.atomic():
         ready_transactions = rri.poll_pending_deposits(pending_deposits)
         Transaction.objects.filter(
             id__in=[t.id for t in ready_transactions]).update(
                 pending_execution_attempt=True)
     verified_ready_transactions = []
     for transaction in ready_transactions:
         # refresh from DB to pull pending_execution_attempt value and to ensure invalid
         # values were not assigned to the transaction in rri.poll_pending_deposits()
         transaction.refresh_from_db()
         if transaction.kind != transaction.KIND.deposit:
             cls.handle_error(
                 transaction,
                 "poll_pending_deposits() returned a non-deposit transaction",
             )
             continue
         if transaction.amount_in is None:
             cls.handle_error(
                 transaction,
                 "poll_pending_deposits() did not assign a value to the "
                 "amount_in field of a Transaction object returned",
             )
             continue
         elif transaction.amount_fee is None:
             if registered_fee_func is calculate_fee:
                 try:
                     transaction.amount_fee = calculate_fee({
                         "amount":
                         transaction.amount_in,
                         "operation":
                         settings.OPERATION_DEPOSIT,
                         "asset_code":
                         transaction.asset.code,
                     })
                 except ValueError as e:
                     cls.handle_error(transaction, str(e))
                     continue
             else:
                 transaction.amount_fee = Decimal(0)
             transaction.save()
         verified_ready_transactions.append(transaction)
     return verified_ready_transactions
Beispiel #3
0
 def create_transaction(cls, **kwargs):
     '''
     Helper for creating arbitrary transaction.
     Modifies amount exactly as in args!
     Don't do "reserve more" magic
     '''
     transaction = Transaction.objects.create(
         code=kwargs.get('code', get_random_string_for_test()),
         status=kwargs.get('status', TRANSACTION_AUTHORIZATION_STATUS))
     if kwargs.get('created_at'):
         Transaction.objects.filter(id=transaction.id).\
             update(created_at=kwargs.get('created_at'))
         transaction.refresh_from_db()
     cls._try_create_transfers(transaction, **kwargs)
     return transaction
Beispiel #4
0
    def handle_submit(cls, transaction: Transaction):
        try:
            success = PendingDeposits.submit(transaction)
        except Exception as e:
            logger.exception("submit() threw an unexpected exception")
            cls.handle_error(transaction, str(e))
            return

        if success:
            transaction.refresh_from_db()
            try:
                rdi.after_deposit(transaction)
            except Exception:
                logger.exception(
                    "after_deposit() threw an unexpected exception")
Beispiel #5
0
    def atomic_transfer(txns):
        if len(txns) == 0:
            raise Exception({
                "transfer":
                "Atomic transfer has to have at least 1 transfer"
            })

        utils.atomic_transfer(txns)

        last = None
        for txn, transaction in txns:
            transaction.atomic = True
            transaction.atomic_prev = last
            transaction.save()
            transaction.refresh_from_db()
            last = transaction

        return [transaction.txid for _, transaction in txns]
    def execute_deposit(cls, transaction):
        try:
            _, pending_trust = PendingDeposits.get_or_create_destination_account(
                transaction)
        except RuntimeError as e:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = str(e)
            transaction.save()
            logger.error(transaction.status_message)
            maybe_make_callback(transaction)
            return

        if pending_trust and not transaction.claimable_balance_supported:
            logger.info(
                f"destination account is pending_trust for transaction {transaction.id}"
            )
            transaction.status = Transaction.STATUS.pending_trust
            transaction.save()
            maybe_make_callback(transaction)
            return
        if MultiSigTransactions.requires_multisig(transaction):
            MultiSigTransactions.save_as_pending_signatures(transaction)
            return

        try:
            success = PendingDeposits.submit(transaction)
        except Exception as e:
            logger.exception("submit() threw an unexpected exception")
            transaction.status_message = str(e)
            transaction.status = Transaction.STATUS.error
            transaction.pending_execution_attempt = False
            transaction.save()
            maybe_make_callback(transaction)
            return

        if success:
            transaction.refresh_from_db()
            try:
                rdi.after_deposit(transaction)
            except Exception:
                logger.exception(
                    "after_deposit() threw an unexpected exception")
Beispiel #7
0
    def check_trustlines():
        """
        Create Stellar transaction for deposit transactions marked as pending
        trust, if a trustline has been created.
        """
        module = sys.modules[__name__]
        with django.db.transaction.atomic():
            transactions = list(
                Transaction.objects.filter(
                    kind=Transaction.KIND.deposit,
                    status=Transaction.STATUS.pending_trust,
                    pending_execution_attempt=False,
                ).select_for_update()
            )
            ids = []
            for t in transactions:
                t.pending_execution_attempt = True
                ids.append(t.id)
            Transaction.objects.filter(id__in=ids).update(
                pending_execution_attempt=True
            )
        server = settings.HORIZON_SERVER
        accounts = {}
        for i, transaction in enumerate(transactions):
            if module.TERMINATE:
                still_process_transactions = transactions[i:]
                Transaction.objects.filter(
                    id__in=[t.id for t in still_process_transactions]
                ).update(pending_execution_attempt=False)
                break
            if accounts.get(transaction.stellar_account):
                account = accounts[transaction.stellar_account]
            else:
                try:
                    account = (
                        server.accounts().account_id(transaction.stellar_account).call()
                    )
                    accounts[transaction.stellar_account] = account
                except BaseRequestError:
                    logger.exception(
                        f"Failed to load account {transaction.stellar_account}"
                    )
                    transaction.pending_execution_attempt = False
                    transaction.save()
                    continue
            for balance in account["balances"]:
                if balance.get("asset_type") == "native":
                    continue
                if (
                    balance["asset_code"] == transaction.asset.code
                    and balance["asset_issuer"] == transaction.asset.issuer
                ):
                    logger.info(
                        f"Account {account['id']} has established a trustline for "
                        f"{balance['asset_code']}:{balance['asset_issuer']}"
                    )
                    try:
                        requires_multisig = PendingDeposits.requires_multisig(
                            transaction
                        )
                    except NotFoundError:
                        PendingDeposits.handle_error(
                            transaction,
                            f"{transaction.asset.code} distribution account "
                            f"{transaction.asset.distribution_account} does not exist",
                        )
                        break
                    except ConnectionError:
                        logger.error("Failed to connect to Horizon")
                        transaction.pending_execution_attempt = False
                        transaction.save()
                        break
                    if requires_multisig:
                        PendingDeposits.save_as_pending_signatures(transaction)
                        break

                    try:
                        success = PendingDeposits.submit(transaction)
                    except Exception as e:
                        logger.exception("submit() threw an unexpected exception")
                        PendingDeposits.handle_error(
                            transaction, f"{e.__class__.__name__}: {str(e)}"
                        )
                        break

                    if success:
                        transaction.refresh_from_db()
                        try:
                            rdi.after_deposit(transaction)
                        except Exception:
                            logger.exception(
                                "after_deposit() threw an unexpected exception"
                            )
            if transaction.pending_execution_attempt:
                transaction.pending_execution_attempt = False
                transaction.save()
Beispiel #8
0
    def execute_outgoing_transactions():
        """
        Execute pending withdrawals.
        """
        module = sys.modules[__name__]
        sep31_qparams = Q(
            protocol=Transaction.PROTOCOL.sep31,
            status=Transaction.STATUS.pending_receiver,
            kind=Transaction.KIND.send,
        )
        sep6_24_qparams = Q(
            protocol__in=[
                Transaction.PROTOCOL.sep24, Transaction.PROTOCOL.sep6
            ],
            status=Transaction.STATUS.pending_anchor,
            kind=Transaction.KIND.withdrawal,
        )
        with django.db.transaction.atomic():
            transactions = list(
                Transaction.objects.filter(
                    sep6_24_qparams | sep31_qparams,
                    pending_execution_attempt=False).select_for_update())
            ids = []
            for t in transactions:
                t.pending_execution_attempt = True
                ids.append(t.id)
            Transaction.objects.filter(id__in=ids).update(
                pending_execution_attempt=True)

        if transactions:
            logger.info(f"Executing {len(transactions)} outgoing transactions")
        num_completed = 0
        for i, transaction in enumerate(transactions):
            if module.TERMINATE:
                still_processing_transactions = transactions[i:]
                Transaction.objects.filter(
                    id__in=[t.id
                            for t in still_processing_transactions]).update(
                                pending_execution_attempt=False)
                break

            logger.info(
                f"Calling execute_outgoing_transaction() for {transaction.id}")
            try:
                rri.execute_outgoing_transaction(transaction)
            except Exception:
                transaction.pending_execution_attempt = False
                transaction.save()
                logger.exception(
                    "execute_outgoing_transactions() threw an unexpected exception"
                )
                continue

            transaction.refresh_from_db()
            if (transaction.protocol == Transaction.PROTOCOL.sep31 and
                    transaction.status == Transaction.STATUS.pending_receiver
                ) or (transaction.protocol in [
                    Transaction.PROTOCOL.sep24, Transaction.PROTOCOL.sep6
                ] and transaction.status == transaction.STATUS.pending_anchor):
                transaction.pending_execution_attempt = False
                transaction.save()
                logger.error(
                    f"Transaction {transaction.id} status must be "
                    f"updated after call to execute_outgoing_transaction()")
                continue
            elif transaction.status in [
                    Transaction.STATUS.pending_external,
                    Transaction.STATUS.completed,
            ]:
                if transaction.amount_fee is None:
                    if registered_fee_func is calculate_fee:
                        op = {
                            Transaction.KIND.withdrawal:
                            settings.OPERATION_WITHDRAWAL,
                            Transaction.KIND.send: Transaction.KIND.send,
                        }[transaction.kind]
                        try:
                            transaction.amount_fee = calculate_fee({
                                "amount":
                                transaction.amount_in,
                                "operation":
                                op,
                                "asset_code":
                                transaction.asset.code,
                            })
                        except ValueError:
                            transaction.pending_execution_attempt = False
                            transaction.save()
                            logger.exception("Unable to calculate fee")
                            continue
                    else:
                        transaction.amount_fee = Decimal(0)
                transaction.amount_out = transaction.amount_in - transaction.amount_fee
                # Anchors can mark transactions as pending_external if the transfer
                # cannot be completed immediately due to external processing.
                # poll_outgoing_transactions will check on these transfers and mark them
                # as complete when the funds have been received by the user.
                if transaction.status == Transaction.STATUS.completed:
                    num_completed += 1
                    transaction.completed_at = datetime.now(timezone.utc)
            elif transaction.status not in [
                    Transaction.STATUS.error,
                    Transaction.STATUS.pending_transaction_info_update,
                    Transaction.STATUS.pending_customer_info_update,
            ]:
                transaction.pending_execution_attempt = False
                transaction.save()
                logger.error(
                    f"Transaction {transaction.id} was moved to invalid status"
                    f" {transaction.status}")
                continue

            transaction.pending_execution_attempt = False
            transaction.save()
            maybe_make_callback(transaction)

        if num_completed:
            logger.info(f"{num_completed} transfers have been completed")