def from_proto(
        cls, item: tx_pb_v4.HistoryItem, state: tx_pb_v4.GetTransactionResponse.State
    ) -> 'TransactionData':
        payments = []
        if item.invoice_list and item.invoice_list.invoices:
            if len(item.payments) != len(item.invoice_list.invoices):
                raise ValueError('number of invoices does not match number of payments')
            il = InvoiceList.from_proto(item.invoice_list)
        else:
            il = None

        tx_type = TransactionType.UNKNOWN
        memo = None
        tx_errors = None
        if item.solana_transaction.value:
            solana_tx = solana.Transaction.unmarshal(item.solana_transaction.value)
            program_idx = solana_tx.message.instructions[0].program_index
            if solana_tx.message.accounts[program_idx] == solana.MEMO_PROGRAM_KEY:
                decompiled_memo = solana.decompile_memo(solana_tx.message, 0)
                memo_data = decompiled_memo.data.decode('utf-8')
                try:
                    agora_memo = AgoraMemo.from_b64_string(memo_data)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    memo = memo_data
            tx_errors = TransactionErrors.from_solana_tx(solana_tx, item.transaction_error, item.transaction_id.value)
        elif item.stellar_transaction.envelope_xdr:
            env = te.TransactionEnvelope.from_xdr(base64.b64encode(item.stellar_transaction.envelope_xdr))
            tx = env.tx
            if isinstance(tx.memo, stellar_memo.HashMemo):
                try:
                    agora_memo = AgoraMemo.from_base_memo(tx.memo)
                    tx_type = agora_memo.tx_type()
                except ValueError:
                    pass
            elif isinstance(tx.memo, stellar_memo.TextMemo):
                memo = tx.memo.text.decode()

            tx_errors = TransactionErrors.from_stellar_tx(env, item.transaction_error, item.transaction_id.value)

        for idx, p in enumerate(item.payments):
            inv = il.invoices[idx] if il and il.invoices else None
            payments.append(ReadOnlyPayment(PublicKey(p.source.value), PublicKey(p.destination.value),
                                            tx_type, p.amount, invoice=inv, memo=memo))

        return cls(
            item.transaction_id.value,
            TransactionState.from_proto_v4(state),
            payments,
            error=tx_errors,
        )
Exemple #2
0
    def test_errors_from_solana_tx(self, instruction_index, exp_op_index, exp_payment_index):
        keys = [pk.public_key for pk in generate_keys(4)]
        tx = solana.Transaction.new(
            keys[0],
            [
                memo.memo_instruction('data'),
                token.transfer(keys[1], keys[2], keys[1], 100),
                token.set_authority(keys[1], keys[1], token.AuthorityType.CLOSE_ACCOUNT, keys[3])
            ]
        )
        tx_id = b'tx_sig'

        errors = TransactionErrors.from_solana_tx(tx, model_pbv4.TransactionError(
            reason=model_pbv4.TransactionError.Reason.INSUFFICIENT_FUNDS,
            instruction_index=instruction_index,
        ), tx_id)
        assert isinstance(errors.tx_error, InsufficientBalanceError)
        assert len(errors.op_errors) == 3
        for i in range(0, len(errors.op_errors)):
            if i == exp_op_index:
                assert isinstance(errors.op_errors[i], InsufficientBalanceError)
            else:
                assert not errors.op_errors[i]

        if exp_payment_index > -1:
            assert len(errors.payment_errors) == 1
            for i in range(0, len(errors.payment_errors)):
                if i == exp_payment_index:
                    assert isinstance(errors.payment_errors[i], InsufficientBalanceError)
                else:
                    assert not errors.payment_errors[i]
        else:
            assert not errors.payment_errors
Exemple #3
0
    def test_errors_from_stellar_tx(self, instruction_index, exp_op_index, exp_payment_index):
        acc1 = gen_account_id()
        acc2 = gen_account_id()
        operations = [
            gen_create_op(acc1, acc2),
            gen_payment_op(acc2, amount=15),
            gen_payment_op(acc1, amount=15),
            gen_create_op(acc1, acc2),
        ]
        memo = AgoraMemo.new(1, TransactionType.EARN, 1, b'')
        hash_memo = gen_hash_memo(memo.val)
        envelope_xdr = gen_tx_envelope_xdr(acc1, 1, operations, hash_memo)
        env = te.TransactionEnvelope.from_xdr(base64.b64encode(envelope_xdr))

        errors = TransactionErrors.from_stellar_tx(env, model_pbv4.TransactionError(
            reason=model_pbv4.TransactionError.Reason.INSUFFICIENT_FUNDS,
            instruction_index=instruction_index,
        ), b'tx_hash')
        assert isinstance(errors.tx_error, InsufficientBalanceError)
        assert len(errors.op_errors) == 4
        for i in range(0, len(errors.op_errors)):
            if i == exp_op_index:
                assert isinstance(errors.op_errors[i], InsufficientBalanceError)
            else:
                assert not errors.op_errors[i]

        if exp_payment_index > -1:
            assert len(errors.payment_errors) == 2
            for i in range(0, len(errors.payment_errors)):
                if i == exp_payment_index:
                    assert isinstance(errors.payment_errors[i], InsufficientBalanceError)
                else:
                    assert not errors.payment_errors[i]
        else:
            assert not errors.payment_errors
Exemple #4
0
        def _submit():
            nonlocal attempt

            attempt += 1
            req = tx_pb_v4.SubmitTransactionRequest(
                transaction=model_pb_v4.Transaction(value=tx_bytes, ),
                invoice_list=invoice_list.to_proto() if invoice_list else None,
                commitment=commitment.to_proto(),
            )
            resp = self._transaction_stub_v4.SubmitTransaction(
                req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS)

            if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.REJECTED:
                raise TransactionRejectedError()
            if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.PAYER_REQUIRED:
                raise PayerRequiredError()

            result = SubmitTransactionResult(tx_id=resp.signature.value)
            if resp.result == tx_pb_v4.SubmitTransactionResponse.Result.ALREADY_SUBMITTED:
                # If this occurs on the first attempt, it's likely due to the submission of two identical transactions
                # in quick succession and we should raise the error to the caller. Otherwise, it's likely that the
                # transaction completed successfully on a previous attempt that failed due to a transient error.
                if attempt == 1:
                    raise AlreadySubmittedError()
            elif resp.result == tx_pb_v4.SubmitTransactionResponse.Result.FAILED:
                result.tx_error = TransactionErrors.from_proto_error(
                    resp.transaction_error)
            elif resp.result == tx_pb_v4.SubmitTransactionResponse.Result.INVOICE_ERROR:
                result.invoice_errors = resp.invoice_errors
            elif resp.result != tx_pb_v4.SubmitTransactionResponse.Result.OK:
                raise Error(f'unexpected result from agora: {resp.result}')

            return result
Exemple #5
0
        def _submit():
            req = tx_pb_v3.SubmitTransactionRequest(
                envelope_xdr=tx_bytes,
                invoice_list=invoice_list.to_proto() if invoice_list else None,
            )
            try:
                resp = self._transaction_stub_v3.SubmitTransaction(
                    req,
                    metadata=self._metadata,
                    timeout=_GRPC_TIMEOUT_SECONDS)
            except grpc.RpcError as e:
                raise BlockchainVersionError() if self._is_migration_error(
                    e) else e

            result = SubmitTransactionResult(tx_id=resp.hash.value)
            if resp.result == tx_pb_v3.SubmitTransactionResponse.Result.REJECTED:
                raise TransactionRejectedError()
            elif resp.result == tx_pb_v3.SubmitTransactionResponse.Result.INVOICE_ERROR:
                result.invoice_errors = resp.invoice_errors
            elif resp.result == tx_pb_v3.SubmitTransactionResponse.Result.FAILED:
                result.tx_error = TransactionErrors.from_result(
                    resp.result_xdr)
            elif resp.result != tx_pb_v3.SubmitTransactionResponse.Result.OK:
                raise Error(f'unexpected result from agora: {resp.result}')

            return result
Exemple #6
0
    def test_error_from_result_other_op(self):
        op_result = gen_merge_op_result(xdr_const.ACCOUNT_MERGE_MALFORMED)
        result_xdr = gen_result_xdr(xdr_const.txFAILED, [op_result])

        te = TransactionErrors.from_result(result_xdr)
        assert isinstance(te.tx_error, Error)
        assert isinstance(te.op_errors[0], Error)
Exemple #7
0
    def from_proto(cls, item: tx_pb.HistoryItem) -> 'TransactionData':
        data = cls(
            item.hash.value,
            error=TransactionErrors.from_result(item.result_xdr),
        )
        if item.envelope_xdr:
            env = te.TransactionEnvelope.from_xdr(
                base64.b64encode(item.envelope_xdr))
            data.payments = ReadOnlyPayment.payments_from_envelope(
                env, item.invoice_list)

        return data
Exemple #8
0
    def test_error_from_result_multi_op(self):
        op_results = [
            gen_create_op_result(xdr_const.CREATE_ACCOUNT_SUCCESS),
            gen_create_op_result(xdr_const.CREATE_ACCOUNT_MALFORMED),
            gen_payment_op_result(xdr_const.PAYMENT_UNDERFUNDED),
        ]

        result_xdr = gen_result_xdr(xdr_const.txFAILED, op_results)

        te = TransactionErrors.from_result(result_xdr)
        assert isinstance(te.tx_error, Error)
        assert not te.op_errors[0]
        assert isinstance(te.op_errors[1], TransactionMalformedError)
        assert isinstance(te.op_errors[2], InsufficientBalanceError)
Exemple #9
0
    def test_from_result_tx_failed(self, tx_result_code: int, op_type: int,
                                   op_result_code: int, op_error_type: type):
        """ Tests conversion of error types with transaction results containing only one operation result.
        """
        if op_type == xdr_const.CREATE_ACCOUNT:
            op_result = gen_create_op_result(op_result_code)
        elif op_type == xdr_const.PAYMENT:
            op_result = gen_payment_op_result(op_result_code)
        else:
            raise ValueError('invalid op_type')

        result_xdr = gen_result_xdr(tx_result_code,
                                    [op_result] if op_result else [])

        te = TransactionErrors.from_result(result_xdr)
        assert isinstance(te.tx_error, Error)
        assert isinstance(te.op_errors[0], op_error_type)
Exemple #10
0
        def _submit():
            req = tx_pb.SubmitTransactionRequest(
                envelope_xdr=tx_bytes,
                invoice_list=invoice_list.to_proto() if invoice_list else None,
            )
            resp = self.transaction_stub.SubmitTransaction(
                req, timeout=_GRPC_TIMEOUT_SECONDS)

            result = SubmitStellarTransactionResult(tx_hash=resp.hash.value)
            if resp.result == tx_pb.SubmitTransactionResponse.Result.REJECTED:
                raise TransactionRejectedError()
            elif resp.result == tx_pb.SubmitTransactionResponse.Result.INVOICE_ERROR:
                result.invoice_errors = resp.invoice_errors
            elif resp.result == tx_pb.SubmitTransactionResponse.Result.FAILED:
                result.tx_error = TransactionErrors.from_result(
                    resp.result_xdr)
            elif resp.result != tx_pb.SubmitTransactionResponse.Result.OK:
                raise Error("unexpected result from agora: {}".format(
                    resp.result))

            return result
Exemple #11
0
 def test_error_from_result_no_op(self, tx_result_code: int,
                                  exception_type: type):
     result_xdr = gen_result_xdr(tx_result_code, [])
     assert isinstance(
         TransactionErrors.from_result(result_xdr).tx_error, exception_type)
Exemple #12
0
 def test_from_result_success(self):
     op_result = gen_create_op_result(xdr_const.CREATE_ACCOUNT_SUCCESS)
     result_xdr = gen_result_xdr(xdr_const.txSUCCESS,
                                 [op_result] if op_result else [])
     assert not TransactionErrors.from_result(result_xdr)