def get_or_create_destination_account(
        transaction: Transaction, ) -> Tuple[Account, bool]:
        """
        Returns:
            Account: The account found or created for the Transaction
            bool: True if trustline doesn't exist, False otherwise.

        If the account doesn't exist, Polaris must create the account using an account provided by the
        anchor. Polaris can use the distribution account of the anchored asset or a channel account if
        the asset's distribution account requires non-master signatures.

        If the transacted asset's distribution account does require non-master signatures,
        DepositIntegration.create_channel_account() will be called. See the function docstring for more
        info.

        On failure to create the destination account, a RuntimeError exception is raised.
        """
        try:
            account, json_resp = get_account_obj(
                Keypair.from_public_key(transaction.stellar_account))
            return account, is_pending_trust(transaction, json_resp)
        except RuntimeError:
            if MultiSigTransactions().requires_multisig(transaction):
                source_account_kp = MultiSigTransactions.get_channel_keypair(
                    transaction)
                source_account, _ = get_account_obj(source_account_kp)
            else:
                source_account_kp = Keypair.from_secret(
                    transaction.asset.distribution_seed)
                source_account, _ = get_account_obj(source_account_kp)

            builder = TransactionBuilder(
                source_account=source_account,
                network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
                # this transaction contains one operation so base_fee will be multiplied by 1
                base_fee=settings.MAX_TRANSACTION_FEE_STROOPS
                or settings.HORIZON_SERVER.fetch_base_fee(),
            )
            transaction_envelope = builder.append_create_account_op(
                destination=transaction.stellar_account,
                starting_balance=settings.ACCOUNT_STARTING_BALANCE,
            ).build()
            transaction_envelope.sign(source_account_kp)

            try:
                settings.HORIZON_SERVER.submit_transaction(
                    transaction_envelope)
            except BaseHorizonError as e:  # pragma: no cover
                raise RuntimeError(
                    "Horizon error when submitting create account "
                    f"to horizon: {e.message}")

            account, _ = get_account_obj(
                Keypair.from_public_key(transaction.stellar_account))
            return account, True
        except BaseHorizonError as e:
            raise RuntimeError(
                f"Horizon error when loading stellar account: {e.message}")
        except ConnectionError:
            raise RuntimeError("Failed to connect to Horizon")
Exemple #2
0
def test_get_account_obj_not_found(mock_load_account, mock_accounts_endpoint):
    mock_accounts_endpoint.return_value.account_id.return_value.call.side_effect = NotFoundError(
        Mock())
    kp = Keypair.random()
    with pytest.raises(RuntimeError,
                       match=f"account {kp.public_key} does not exist"):
        utils.get_account_obj(kp)
    mock_load_account.assert_not_called()
def check_for_multisig(transaction):
    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"]:
        # master account is not sufficient
        transaction.pending_signatures = True
        transaction.status = Transaction.STATUS.pending_anchor
        transaction.save()
        if transaction.channel_account:
            channel_kp = Keypair.from_secret(transaction.channel_seed)
        else:
            rdi.create_channel_account(transaction)
            channel_kp = Keypair.from_secret(transaction.channel_seed)
        try:
            channel_account, _ = get_account_obj(channel_kp)
        except RuntimeError as e:
            # The anchor returned a bad channel keypair for the account
            transaction.status = Transaction.STATUS.error
            transaction.status_message = str(e)
            transaction.save()
            logger.error(transaction.status_message)
        else:
            # Create the initial envelope XDR with the channel signature
            envelope = create_transaction_envelope(transaction, channel_account)
            envelope.sign(channel_kp)
            transaction.envelope_xdr = envelope.to_xdr()
            transaction.save()
        return True
    else:
        return False
Exemple #4
0
def test_get_account_obj(mock_load_account, mock_accounts_endpoint):
    kp = Keypair.random()
    mock_accounts_endpoint.return_value.account_id.return_value.call.return_value = {
        "sequence":
        1,
        "account_id":
        kp.public_key,
        "signers": [{
            "key": kp.public_key,
            "weight": 1,
            "type": "ed25519_public_key"
        }],
        "thresholds": {
            "low_threshold": 0,
            "med_threshold": 1,
            "high_threshold": 2
        },
    }
    account, mock_json = utils.get_account_obj(kp)
    mock_accounts_endpoint.return_value.account_id.assert_called_once_with(
        account_id=kp.public_key)
    mock_accounts_endpoint.return_value.account_id.return_value.call.assert_called_once(
    )
    assert (mock_json == mock_accounts_endpoint.return_value.account_id.
            return_value.call.return_value)
    mock_load_account.assert_called_once_with(mock_json)
 def save_as_pending_signatures(cls, transaction):
     channel_kp = cls.get_channel_keypair(transaction)
     try:
         channel_account, _ = get_account_obj(channel_kp)
     except RuntimeError as e:
         transaction.status = Transaction.STATUS.error
         transaction.status_message = str(e)
         logger.error(transaction.status_message)
     else:
         # Create the initial envelope XDR with the channel signature
         envelope = PendingDeposits.create_deposit_envelope(
             transaction, channel_account)
         envelope.sign(channel_kp)
         transaction.envelope_xdr = envelope.to_xdr()
         transaction.pending_signatures = True
         transaction.status = Transaction.STATUS.pending_anchor
     transaction.pending_execution_attempt = False
     transaction.save()
     maybe_make_callback(transaction)
 def create_deposit_envelope(transaction,
                             source_account) -> TransactionEnvelope:
     payment_amount = round(
         Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
         transaction.asset.significant_decimals,
     )
     builder = TransactionBuilder(
         source_account=source_account,
         network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
         # only one operation, so base_fee will be multipled by 1
         base_fee=settings.MAX_TRANSACTION_FEE_STROOPS
         or settings.HORIZON_SERVER.fetch_base_fee(),
     )
     _, json_resp = get_account_obj(
         Keypair.from_public_key(transaction.stellar_account))
     if transaction.claimable_balance_supported and is_pending_trust(
             transaction, json_resp):
         logger.debug(
             f"Crafting claimable balance operation for Transaction {transaction.id}"
         )
         claimant = Claimant(destination=transaction.stellar_account)
         asset = Asset(code=transaction.asset.code,
                       issuer=transaction.asset.issuer)
         builder.append_create_claimable_balance_op(
             claimants=[claimant],
             asset=asset,
             amount=str(payment_amount),
             source=transaction.asset.distribution_account,
         )
     else:
         builder.append_payment_op(
             destination=transaction.stellar_account,
             asset_code=transaction.asset.code,
             asset_issuer=transaction.asset.issuer,
             amount=str(payment_amount),
             source=transaction.asset.distribution_account,
         )
     if transaction.memo:
         builder.add_memo(make_memo(transaction.memo,
                                    transaction.memo_type))
     return builder.build()
def get_or_create_transaction_destination_account(
    transaction: Transaction,
) -> Tuple[Optional[Account], bool, bool]:
    """
    Returns:
        Tuple[Optional[Account]: The account(s) found or created for the Transaction
        bool: boolean, True if created, False otherwise.
        bool: boolean, True if trustline doesn't exist, False otherwise.

    If the account doesn't exist, Polaris must create the account using an account provided by the
    anchor. Polaris can use the distribution account of the anchored asset or a channel account if
    the asset's distribution account requires non-master signatures.

    If the transacted asset's distribution account does not require non-master signatures, Polaris
    can create the destination account using the distribution account.

    If the transacted asset's distribution account does require non-master signatures, the anchor
    should save a keypair of a pre-existing Stellar account to use as the channel account via
    DepositIntegration.create_channel_account(). See the function docstring for more info.

    On failure to create the destination account, a RuntimeError exception is raised.
    """
    try:
        account, json_resp = get_account_obj(
            Keypair.from_public_key(transaction.stellar_account)
        )
        return account, False, is_pending_trust(transaction, json_resp)
    except RuntimeError:
        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 master_signer and master_signer["weight"] >= thresholds["med_threshold"]:
            source_account_kp = Keypair.from_secret(transaction.asset.distribution_seed)
            source_account, _ = get_account_obj(source_account_kp)
        else:
            from polaris.integrations import registered_deposit_integration as rdi

            rdi.create_channel_account(transaction)
            source_account_kp = Keypair.from_secret(transaction.channel_seed)
            source_account, _ = get_account_obj(source_account_kp)

        builder = TransactionBuilder(
            source_account=source_account,
            network_passphrase=settings.STELLAR_NETWORK_PASSPHRASE,
            # this transaction contains one operation so base_fee will be multiplied by 1
            base_fee=settings.MAX_TRANSACTION_FEE_STROOPS
            or settings.HORIZON_SERVER.fetch_base_fee(),
        )
        transaction_envelope = builder.append_create_account_op(
            destination=transaction.stellar_account,
            starting_balance=settings.ACCOUNT_STARTING_BALANCE,
        ).build()
        transaction_envelope.sign(source_account_kp)

        try:
            settings.HORIZON_SERVER.submit_transaction(transaction_envelope)
        except BaseHorizonError as submit_exc:  # pragma: no cover
            raise RuntimeError(
                "Horizon error when submitting create account to horizon: "
                f"{submit_exc.message}"
            )

        transaction.status = Transaction.STATUS.pending_trust
        transaction.save()
        logger.info(
            f"Transaction {transaction.id} is now pending_trust of destination account"
        )
        account, _ = get_account_obj(
            Keypair.from_public_key(transaction.stellar_account)
        )
        return account, True, True
    except BaseHorizonError as e:
        raise RuntimeError(f"Horizon error when loading stellar account: {e.message}")
    def submit(cls, transaction: Transaction) -> bool:
        valid_statuses = [
            Transaction.STATUS.pending_user_transfer_start,
            Transaction.STATUS.pending_external,
            Transaction.STATUS.pending_anchor,
            Transaction.STATUS.pending_trust,
        ]
        if transaction.status not in valid_statuses:
            raise ValueError(
                f"Unexpected transaction status: {transaction.status}, expecting "
                f"{' or '.join(valid_statuses)}.")

        transaction.status = Transaction.STATUS.pending_anchor
        transaction.save()
        logger.info(f"Initiating Stellar deposit for {transaction.id}")
        maybe_make_callback(transaction)

        if transaction.envelope_xdr:
            try:
                envelope = TransactionEnvelope.from_xdr(
                    transaction.envelope_xdr,
                    settings.STELLAR_NETWORK_PASSPHRASE)
            except Exception:
                cls._handle_error(transaction,
                                  "Failed to decode transaction envelope")
                return False
        else:
            distribution_acc, _ = get_account_obj(
                Keypair.from_public_key(
                    transaction.asset.distribution_account))
            envelope = cls.create_deposit_envelope(transaction,
                                                   distribution_acc)
            envelope.sign(transaction.asset.distribution_seed)

        transaction.status = Transaction.STATUS.pending_stellar
        transaction.save()
        logger.info(f"Transaction {transaction.id} now pending_stellar")
        maybe_make_callback(transaction)

        try:
            response = settings.HORIZON_SERVER.submit_transaction(envelope)
        except BaseHorizonError as e:
            cls._handle_error(transaction,
                              f"{e.__class__.__name__}: {e.message}")
            return False

        if not response.get("successful"):
            cls._handle_error(
                transaction,
                f"Stellar transaction failed when submitted to horizon: {response['result_xdr']}",
            )
            return False
        elif transaction.claimable_balance_supported:
            transaction.claimable_balance_id = cls.get_balance_id(response)

        transaction.envelope_xdr = response["envelope_xdr"]
        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.amount_out = round(
            Decimal(transaction.amount_in) - Decimal(transaction.amount_fee),
            transaction.asset.significant_decimals,
        )
        transaction.save()
        logger.info(f"Transaction {transaction.id} completed.")
        maybe_make_callback(transaction)
        return True