def create_stellar_deposit(transaction: Transaction) -> bool: """ Performs final status and signature checks before calling submit_stellar_deposit(). Returns true on successful submission, false otherwise. `transaction` will be placed in the error status if submission fails or if it is a multisig transaction and is not signed by the channel account. """ if transaction.status not in [ Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ]: raise ValueError( f"unexpected transaction status {transaction.status} for " "create_stellar_deposit", ) elif transaction.amount_in is None or transaction.amount_fee is None: transaction.status = Transaction.STATUS.error transaction.status_message = ( "`amount_in` and `amount_fee` must be populated, skipping transaction" ) transaction.save() raise ValueError(transaction.status_message) # if the distribution account's master signer's weight is great or equal to the its # medium threshold, verify the transaction is signed by it's channel account master_signer = None if transaction.asset.distribution_account_master_signer: master_signer = transaction.asset.distribution_account_master_signer thresholds = transaction.asset.distribution_account_thresholds if not master_signer or master_signer["weight"] < thresholds[ "med_threshold"]: envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) try: _verify_te_signed_by_account_id(envelope, transaction.channel_account) except InvalidSep10ChallengeError: transaction.status = Transaction.STATUS.error transaction.status_message = gettext( "Multisig transaction's envelope was not signed by channel account" ) transaction.save() return False # otherwise, create the envelope and sign it with the distribution account's secret else: distribution_acc, _ = get_account_obj( Keypair.from_public_key(transaction.asset.distribution_account)) envelope = create_transaction_envelope(transaction, distribution_acc) envelope.sign(transaction.asset.distribution_seed) try: submit_stellar_deposit(transaction, envelope) except (RuntimeError, BaseHorizonError) as e: transaction.status_message = f"{e.__class__.__name__}: {e.message}" transaction.status = Transaction.STATUS.error transaction.save() logger.error(transaction.status_message) return False else: return True
def update_transaction(response: Dict, transaction: Transaction, error_msg: str = None): """ Updates the transaction depending on whether or not the transaction was successfully executed on the Stellar network and `process_withdrawal` completed without raising an exception. If the Horizon response indicates the response was not successful or an exception was raised while processing the withdrawal, we mark the status as `error`. If the Stellar transaction succeeded, we mark it as `completed`. :param error_msg: a description of the error that has occurred. :param response: a response body returned from Horizon for the transaction :param transaction: a database model object representing the transaction """ if error_msg or not response["successful"]: transaction.status = Transaction.STATUS.error transaction.status_message = error_msg else: transaction.completed_at = now() transaction.status = Transaction.STATUS.completed transaction.status_eta = 0 transaction.amount_out = transaction.amount_in - transaction.amount_fee transaction.stellar_transaction_id = response["id"] transaction.save()
def create_stellar_deposit(transaction: Transaction, destination_exists: bool = False) -> bool: """ Create and submit the Stellar transaction for the deposit. The Transaction can be either `pending_anchor` if the task is called from `poll_pending_deposits()` or `pending_trust` if called from the `check_trustlines()`. """ if transaction.status not in [ Transaction.STATUS.pending_anchor, Transaction.STATUS.pending_trust, ]: raise ValueError( f"unexpected transaction status {transaction.status} for " "create_stellar_deposit", ) elif transaction.amount_in is None or transaction.amount_fee is None: transaction.status = Transaction.STATUS.error transaction.status_message = ( "`amount_in` and `amount_fee` must be populated, skipping transaction" ) transaction.save() raise ValueError(transaction.status_message) # if we don't know if the destination account exists if not destination_exists: try: _, created, pending_trust = get_or_create_transaction_destination_account( transaction) except RuntimeError as e: transaction.status = Transaction.STATUS.error transaction.status_message = str(e) transaction.save() logger.error(transaction.status_message) return False if created or pending_trust: # the account is pending_trust for the asset to be received if pending_trust and transaction.status != Transaction.STATUS.pending_trust: transaction.status = Transaction.STATUS.pending_trust transaction.save() return False # if the distribution account's master signer's weight is great or equal to the its # medium threshold, verify the transaction is signed by it's channel account master_signer = None if transaction.asset.distribution_account_master_signer: master_signer = transaction.asset.distribution_account_master_signer thresholds = transaction.asset.distribution_account_thresholds if not (master_signer and master_signer["weight"] >= thresholds["med_threshold"]): multisig = True envelope = TransactionEnvelope.from_xdr( transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE) try: _verify_te_signed_by_account_id(envelope, transaction.channel_account) except InvalidSep10ChallengeError: transaction.status = Transaction.STATUS.error transaction.status_message = gettext( "Multisig transaction's envelope was not signed by channel account" ) transaction.save() return False # otherwise, create the envelope and sign it with the distribution account's secret else: multisig = False distribution_acc, _ = get_account_obj( Keypair.from_public_key(transaction.asset.distribution_account)) envelope = create_transaction_envelope(transaction, distribution_acc) envelope.sign(transaction.asset.distribution_seed) transaction.envelope_xdr = envelope.to_xdr() try: return submit_stellar_deposit(transaction, multisig=multisig) except RuntimeError as e: transaction.status_message = str(e) transaction.status = Transaction.STATUS.error transaction.save() logger.error(transaction.status_message) return False