Exemplo n.º 1
0
def submit_stellar_deposit(transaction, multisig=False) -> bool:
    transaction.status = Transaction.STATUS.pending_stellar
    transaction.save()
    logger.info(f"Transaction {transaction.id} now pending_stellar")
    envelope = TransactionEnvelope.from_xdr(
        transaction.envelope_xdr, settings.STELLAR_NETWORK_PASSPHRASE)
    try:
        response = settings.HORIZON_SERVER.submit_transaction(envelope)
    except BaseHorizonError as e:
        logger.info(e.__class__.__name__)
        tx_result = TransactionResult.from_xdr(e.result_xdr)
        op_results = tx_result.result.results
        if isinstance(e, BadRequestError):
            if op_results[0].code == -1:  # Bad Auth
                handle_bad_signatures_error(e, transaction, multisig=multisig)
                return False  # handle_bad_signatures_error() saves transaction
            else:
                transaction.status = Transaction.STATUS.error
                transaction.status_message = (
                    f"tx failed with codes: {op_results}. Result XDR: {e.result_xdr}"
                )
        elif op_results[0].tr.paymentResult.code == const.PAYMENT_NO_TRUST:
            transaction.status = Transaction.STATUS.pending_trust
            transaction.status_message = (
                "trustline error when submitting transaction to horizon")
        else:
            raise RuntimeError("Unable to submit payment to horizon, "
                               f"non-trustline failure: {e.message}")
        transaction.save()
        logger.error(transaction.status_message)
        return False

    if not response.get("successful"):
        transaction_result = TransactionResult.from_xdr(response["result_xdr"])
        raise RuntimeError(
            "Stellar transaction failed when submitted to horizon: "
            f"{transaction_result.result.results}")

    transaction.paging_token = response["paging_token"]
    transaction.stellar_transaction_id = response["id"]
    transaction.status = Transaction.STATUS.completed
    transaction.completed_at = datetime.datetime.now(datetime.timezone.utc)
    transaction.status_eta = 0
    transaction.amount_out = round(
        Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
        transaction.asset.significant_decimals,
    )
    transaction.save()
    logger.info(f"Transaction {transaction.id} completed.")
    return True
    def get_balance_id(response: dict) -> Optional[str]:
        """
        Pulls claimable balance ID from horizon responses if present

        When called we decode and read the result_xdr from the horizon response.
        If any of the operations is a createClaimableBalanceResult we
        decode the Base64 representation of the balanceID xdr.
        After the fact we encode the result to hex.

        The hex representation of the balanceID is important because its the
        representation required to query and claim claimableBalances.

        :param
            response: the response from horizon

        :return
            hex representation of the balanceID
            or
            None (if no createClaimableBalanceResult operation is found)
        """
        result_xdr = response["result_xdr"]
        balance_id_hex = None
        for op_result in TransactionResult.from_xdr(result_xdr).result.results:
            if hasattr(op_result.tr, "createClaimableBalanceResult"):
                balance_id_hex = base64.b64decode(
                    op_result.tr.createClaimableBalanceResult.balanceID.to_xdr(
                    )).hex()
        return balance_id_hex
Exemplo n.º 3
0
def create_stellar_deposit(transaction_id: str) -> bool:
    """
    Create and submit the Stellar transaction for the deposit.

    :returns a boolean indicating whether or not the deposit was successfully
        completed. One reason a transaction may not be completed is if a
        trustline must be established. The transaction's status will be set as
        ``pending_stellar`` and the status_message will be populated
        with a description of the problem encountered.
    :raises ValueError: the transaction has an unexpected status
    """
    transaction = Transaction.objects.get(id=transaction_id)

    # We check the Transaction status to avoid double submission of a Stellar
    # transaction. 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)
    transaction.status = Transaction.STATUS.pending_stellar
    transaction.save()
    logger.info(f"Transaction {transaction_id} now pending_stellar")

    # We can assume transaction has valid stellar_account, amount_in, and asset
    # because this task is only called after those parameters are validated.
    stellar_account = transaction.stellar_account
    payment_amount = round(
        transaction.amount_in - transaction.amount_fee,
        transaction.asset.significant_decimals,
    )
    asset = transaction.asset
    memo = make_memo(transaction.memo, transaction.memo_type)

    # If the given Stellar account does not exist, create
    # the account with at least enough XLM for the minimum
    # reserve and a trust line (recommended 2.01 XLM), update
    # the transaction in our internal database, and return.

    server = settings.HORIZON_SERVER
    starting_balance = settings.ACCOUNT_STARTING_BALANCE
    server_account = server.load_account(asset.distribution_account)
    base_fee = server.fetch_base_fee()
    builder = TransactionBuilder(
        source_account=server_account,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        base_fee=base_fee,
    )
    try:
        server.load_account(stellar_account)
    except BaseHorizonError as address_exc:
        # 404 code corresponds to Resource Missing.
        if address_exc.status != 404:  # pragma: no cover
            msg = (
                "Horizon error when loading stellar account: " f"{address_exc.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False

        logger.info(f"Stellar account {stellar_account} does not exist. Creating.")
        transaction_envelope = builder.append_create_account_op(
            destination=stellar_account,
            starting_balance=starting_balance,
            source=asset.distribution_account,
        ).build()
        transaction_envelope.sign(asset.distribution_seed)
        try:
            server.submit_transaction(transaction_envelope)
        except BaseHorizonError as submit_exc:  # pragma: no cover
            msg = (
                "Horizon error when submitting create account to horizon: "
                f"{submit_exc.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False

        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        logger.info(f"Transaction for account {stellar_account} now pending_trust.")
        return False

    # If the account does exist, deposit the desired amount of the given
    # asset via a Stellar payment. If that payment succeeds, we update the
    # transaction to completed at the current time. If it fails due to a
    # trustline error, we update the database accordingly. Else, we do not update.

    builder.append_payment_op(
        destination=stellar_account,
        asset_code=asset.code,
        asset_issuer=asset.issuer,
        amount=str(payment_amount),
    )
    if memo:
        builder.add_memo(memo)
    transaction_envelope = builder.build()
    transaction_envelope.sign(asset.distribution_seed)
    try:
        response = server.submit_transaction(transaction_envelope)
    # Functional errors at this stage are Horizon errors.
    except BaseHorizonError as exception:
        tx_result = TransactionResult.from_xdr(exception.result_xdr)
        op_result = tx_result.result.results[0]
        if op_result.tr.paymentResult.code != const.PAYMENT_NO_TRUST:
            msg = (
                "Unable to submit payment to horizon, "
                f"non-trustline failure: {exception.message}"
            )
            logger.error(msg)
            transaction.status_message = msg
            transaction.status = Transaction.STATUS.error
            transaction.save()
            return False
        msg = "trustline error when submitting transaction to horizon"
        logger.error(msg)
        transaction.status_message = msg
        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        return False

    if not response.get("successful"):
        transaction_result = TransactionResult.from_xdr(response["result_xdr"])
        msg = (
            "Stellar transaction failed when submitted to horizon: "
            f"{transaction_result.result.results}"
        )
        logger.error(msg)
        transaction.status_message = msg
        transaction.status = Transaction.STATUS.error
        transaction.save()
        return False

    transaction.paging_token = response["paging_token"]
    transaction.stellar_transaction_id = response["id"]
    transaction.status = Transaction.STATUS.completed
    transaction.completed_at = datetime.datetime.now(datetime.timezone.utc)
    transaction.status_eta = 0  # No more status change.
    transaction.amount_out = payment_amount
    transaction.save()
    logger.info(f"Transaction {transaction.id} completed.")
    return True
Exemplo n.º 4
0
def create_stellar_deposit(transaction_id: str) -> bool:
    """
    Create and submit the Stellar transaction for the deposit.

    :returns a boolean indicating whether or not the deposit was successfully
        completed. One reason a transaction may not be completed is if a
        trustline must be established. The transaction's status will be set as
        ``pending_stellar`` and the status_message will be populated
        with a description of the problem encountered.
    :raises ValueError: the transaction has an unexpected status
    """
    transaction = Transaction.objects.get(id=transaction_id)

    # We check the Transaction status to avoid double submission of a Stellar
    # transaction. 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", )
    transaction.status = Transaction.STATUS.pending_stellar
    transaction.save()

    # We can assume transaction has valid stellar_account, amount_in, and asset
    # because this task is only called after those parameters are validated.
    stellar_account = transaction.stellar_account
    payment_amount = round(transaction.amount_in - transaction.amount_fee, 7)
    asset = transaction.asset.code

    # If the given Stellar account does not exist, create
    # the account with at least enough XLM for the minimum
    # reserve and a trust line (recommended 2.01 XLM), update
    # the transaction in our internal database, and return.

    server = settings.HORIZON_SERVER
    starting_balance = settings.ACCOUNT_STARTING_BALANCE
    server_account = server.load_account(
        settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS)
    base_fee = server.fetch_base_fee()
    builder = TransactionBuilder(
        source_account=server_account,
        network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
        base_fee=base_fee,
    )
    try:
        server.load_account(stellar_account)
    except BaseHorizonError as address_exc:
        # 404 code corresponds to Resource Missing.
        if address_exc.status != 404:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = (
                "Horizon error when loading stellar account: "
                f"{address_exc.message}")
            transaction.save()
            return False

        transaction_envelope = builder.append_create_account_op(
            destination=stellar_account,
            starting_balance=starting_balance,
            source=settings.STELLAR_DISTRIBUTION_ACCOUNT_ADDRESS,
        ).build()
        transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED)
        try:
            server.submit_transaction(transaction_envelope)
        except BaseHorizonError as submit_exc:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = (
                "Horizon error when submitting create account to horizon: "
                f"{submit_exc.message}")
            transaction.save()
            return False

        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        return False

    # If the account does exist, deposit the desired amount of the given
    # asset via a Stellar payment. If that payment succeeds, we update the
    # transaction to completed at the current time. If it fails due to a
    # trustline error, we update the database accordingly. Else, we do not update.

    transaction_envelope = builder.append_payment_op(
        destination=stellar_account,
        asset_code=asset,
        asset_issuer=settings.STELLAR_ISSUER_ACCOUNT_ADDRESS,
        amount=str(payment_amount),
    ).build()
    transaction_envelope.sign(settings.STELLAR_DISTRIBUTION_ACCOUNT_SEED)
    try:
        response = server.submit_transaction(transaction_envelope)
    # Functional errors at this stage are Horizon errors.
    except BaseHorizonError as exception:
        if TRUSTLINE_FAILURE_XDR not in exception.result_xdr:
            transaction.status = Transaction.STATUS.error
            transaction.status_message = (
                "Unable to submit payment to horizon, "
                f"non-trustline failure: {exception.message}")
            transaction.save()
            return False
        transaction.status_message = (
            "trustline error when submitting transaction to horizon")
        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        return False

    if response["result_xdr"] != SUCCESS_XDR:
        transaction_result = TransactionResult.from_xdr(response["result_xdr"])
        transaction.status_message = (
            "Stellar transaction failed when submitted to horizon: "
            f"{transaction_result.result}")
        transaction.save()
        return False

    transaction.stellar_transaction_id = response["hash"]
    transaction.status = Transaction.STATUS.completed
    transaction.completed_at = now()
    transaction.status_eta = 0  # No more status change.
    transaction.amount_out = payment_amount
    transaction.save()
    return True