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")
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
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
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")
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")
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()
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")