示例#1
0
    def submit_solana_transaction(
            self,
            tx: solana.Transaction,
            invoice_list: Optional[InvoiceList] = None,
            commitment: Optional[Commitment] = Commitment.SINGLE,
            dedupe_id: Optional[bytes] = None) -> SubmitTransactionResult:
        """Submit a Solana transaction to Agora.

        :param tx: The Solana transaction.
        :param invoice_list: (optional) An :class:`InvoiceList <agora.model.invoice.InvoiceList>` to associate with the
            transaction
        :param commitment: The :class:`Commitment <agora.solana.commitment.Commitment>` to use.
        :param dedupe_id: The dedupe ID to use for the transaction submission
        :return: A :class:`SubmitTransactionResult <agora.client.internal.SubmitTransactionResult>` object.
        """

        attempt = 0
        tx_bytes = tx.marshal()

        def _submit_request():
            nonlocal attempt

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

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

            result = SubmitTransactionResult(tx_id=resp.signature.value)
            if resp.result == tx_pb.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(tx_id=resp.signature.value)
            elif resp.result == tx_pb.SubmitTransactionResponse.Result.FAILED:
                result.errors = TransactionErrors.from_solana_tx(
                    tx, resp.transaction_error, resp.signature.value)
            elif resp.result == tx_pb.SubmitTransactionResponse.Result.INVOICE_ERROR:
                result.invoice_errors = resp.invoice_errors
            elif resp.result != tx_pb.SubmitTransactionResponse.Result.OK:
                raise TransactionError(
                    f'unexpected result from agora: {resp.result}',
                    tx_id=resp.signature.value)

            return result

        return retry(self._retry_strategies, _submit_request)
示例#2
0
    def test_get_transaction(self, grpc_channel, executor, no_retry_client):
        source, dest = [key.public_key for key in generate_keys(2)]
        transaction_id = b'someid'
        future = executor.submit(no_retry_client.get_transaction,
                                 transaction_id)

        agora_memo = AgoraMemo.new(1, TransactionType.SPEND, 0, b'')
        tx = Transaction.new(PrivateKey.random().public_key, [
            memo.memo_instruction(
                base64.b64encode(agora_memo.val).decode('utf-8')),
            token.transfer(source, dest,
                           PrivateKey.random().public_key, 100),
        ])

        resp = tx_pb_v4.GetTransactionResponse(
            state=tx_pb_v4.GetTransactionResponse.State.SUCCESS,
            item=tx_pb_v4.HistoryItem(
                transaction_id=model_pb_v4.TransactionId(
                    value=transaction_id, ),
                solana_transaction=model_pb_v4.Transaction(
                    value=tx.marshal(), ),
                payments=[
                    tx_pb_v4.HistoryItem.Payment(
                        source=model_pb_v4.SolanaAccountId(value=source.raw),
                        destination=model_pb_v4.SolanaAccountId(
                            value=dest.raw),
                        amount=100,
                    )
                ],
                invoice_list=model_pb_v3.InvoiceList(invoices=[
                    model_pb_v3.Invoice(items=[
                        model_pb_v3.Invoice.LineItem(title='t1', amount=15),
                    ]),
                ])),
        )
        req = self._set_get_transaction_resp(grpc_channel, resp)
        assert req.transaction_id.value == transaction_id

        tx_data = future.result()
        assert tx_data.tx_id == transaction_id
        assert tx_data.transaction_state == TransactionState.SUCCESS
        assert len(tx_data.payments) == 1
        assert not tx_data.error

        p = tx_data.payments[0]
        assert p.sender.raw == source.raw
        assert p.destination.raw == dest.raw
        assert p.tx_type == TransactionType.SPEND
        assert p.quarks == 100
        assert p.invoice.to_proto().SerializeToString(
        ) == resp.item.invoice_list.invoices[0].SerializeToString()
        assert not p.memo
示例#3
0
    def test_create_account_errors(self, grpc_channel, executor,
                                   no_retry_client, result, error_type):
        private_key = PrivateKey.random()

        no_retry_client._response_cache.clear_all()
        future = executor.submit(no_retry_client.create_solana_account,
                                 private_key)

        self._set_get_service_config_resp(grpc_channel)
        self._set_get_recent_blockhash_resp(grpc_channel)
        self._set_get_min_balance_response(grpc_channel)

        resp = account_pb_v4.CreateAccountResponse(result=result)
        req = self._set_create_account_resp(grpc_channel, resp)

        tx = Transaction.unmarshal(req.transaction.value)
        assert len(tx.signatures) == 2
        assert tx.signatures[0] == bytes(SIGNATURE_LENGTH)
        assert private_key.public_key.verify(tx.message.marshal(),
                                             tx.signatures[1])

        sys_create = decompile_create_account(tx.message, 0)
        assert sys_create.funder == _subsidizer
        assert sys_create.address == private_key.public_key
        assert sys_create.owner == _token_program
        assert sys_create.lamports == _min_balance
        assert sys_create.size == token.ACCOUNT_SIZE

        token_init = decompile_initialize_account(tx.message, 1,
                                                  _token_program)
        assert token_init.account == private_key.public_key
        assert token_init.mint == _token
        assert token_init.owner == private_key.public_key

        token_set_auth = decompile_set_authority(tx.message, 2, _token_program)
        assert token_set_auth.account == private_key.public_key
        assert token_set_auth.current_authority == private_key.public_key
        assert token_set_auth.authority_type == token.AuthorityType.CloseAccount
        assert token_set_auth.new_authority == _subsidizer

        with pytest.raises(error_type):
            future.result()
示例#4
0
    def sign_transaction(
            self,
            tx: solana.Transaction,
            invoice_list: Optional[InvoiceList] = None
    ) -> SignTransactionResult:
        """ Submits a transaction

        :param tx:
        :param invoice_list:
        :return: A :class:`SignTransactionResult <agora.client.internal.SignTransactionResult>` object.
        """
        tx_bytes = tx.marshal()

        result = SignTransactionResult()

        def _submit_request():
            req = tx_pb.SignTransactionRequest(
                transaction=model_pb.Transaction(value=tx_bytes, ),
                invoice_list=invoice_list.to_proto() if invoice_list else None,
            )
            resp = self._transaction_stub_v4.SignTransaction(
                req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS)

            if resp.signature and len(
                    resp.signature.value) == solana.SIGNATURE_LENGTH:
                result.tx_id = resp.signature.value

            if resp.result == tx_pb.SignTransactionResponse.Result.REJECTED:
                raise TransactionRejectedError()
            elif resp.result == tx_pb.SignTransactionResponse.Result.INVOICE_ERROR:
                result.invoice_errors = resp.invoice_errors
            elif resp.result != tx_pb.SignTransactionResponse.Result.OK:
                raise TransactionError(
                    f'unexpected result from agora: {resp.result}',
                    tx_id=resp.signature.value)

            return result

        return retry(self._retry_strategies, _submit_request)
    def test_from_proto_solana_text_memo(self):
        source, dest, token_program = [
            key.public_key for key in generate_keys(3)
        ]
        tx = Transaction.new(PrivateKey.random().public_key, [
            memo_instruction('somememo'),
            transfer(source, dest,
                     PrivateKey.random().public_key, 20),
        ])

        history_item = tx_pb.HistoryItem(
            transaction_id=model_pb.TransactionId(value=b'somehash'),
            cursor=tx_pb.Cursor(value=b'cursor1'),
            solana_transaction=model_pb.Transaction(value=tx.marshal(), ),
            payments=[
                tx_pb.HistoryItem.Payment(
                    source=model_pb.SolanaAccountId(value=source.raw),
                    destination=model_pb.SolanaAccountId(value=dest.raw),
                    amount=20,
                ),
            ],
        )

        data = TransactionData.from_proto(
            history_item, tx_pb.GetTransactionResponse.State.SUCCESS)
        assert data.tx_id == b'somehash'
        assert data.transaction_state == TransactionState.SUCCESS
        assert len(data.payments) == 1

        payment = data.payments[0]
        assert payment.sender.raw == source.raw
        assert payment.destination.raw == dest.raw
        assert payment.tx_type == TransactionType.UNKNOWN
        assert payment.quarks == 20
        assert not payment.invoice
        assert payment.memo == 'somememo'
示例#6
0
        def _create():
            nonlocal subsidizer

            service_config_resp = self.get_service_config()
            if not service_config_resp.subsidizer_account.value and not subsidizer:
                raise NoSubsidizerError()

            subsidizer_id = (subsidizer.public_key
                             if subsidizer else PublicKey(
                                 service_config_resp.subsidizer_account.value))

            recent_blockhash_future = self._transaction_stub_v4.GetRecentBlockhash.future(
                tx_pb_v4.GetRecentBlockhashRequest(),
                metadata=self._metadata,
                timeout=_GRPC_TIMEOUT_SECONDS)
            min_balance_future = self._transaction_stub_v4.GetMinimumBalanceForRentExemption.future(
                tx_pb_v4.GetMinimumBalanceForRentExemptionRequest(
                    size=token.ACCOUNT_SIZE),
                metadata=self._metadata,
                timeout=_GRPC_TIMEOUT_SECONDS)
            recent_blockhash_resp = recent_blockhash_future.result()
            min_balance_resp = min_balance_future.result()

            token_program = PublicKey(service_config_resp.token_program.value)
            transaction = Transaction.new(subsidizer_id, [
                system.create_account(
                    subsidizer_id,
                    private_key.public_key,
                    token_program,
                    min_balance_resp.lamports,
                    token.ACCOUNT_SIZE,
                ),
                token.initialize_account(
                    private_key.public_key,
                    PublicKey(service_config_resp.token.value),
                    private_key.public_key,
                    token_program,
                ),
                token.set_authority(
                    private_key.public_key,
                    private_key.public_key,
                    token.AuthorityType.CloseAccount,
                    token_program,
                    new_authority=subsidizer_id,
                )
            ])
            transaction.set_blockhash(recent_blockhash_resp.blockhash.value)
            transaction.sign([private_key])
            if subsidizer:
                transaction.sign([subsidizer])

            req = account_pb_v4.CreateAccountRequest(
                transaction=model_pb_v4.Transaction(
                    value=transaction.marshal()),
                commitment=commitment.to_proto(),
            )
            resp = self._account_stub_v4.CreateAccount(
                req, metadata=self._metadata, timeout=_GRPC_TIMEOUT_SECONDS)

            if resp.result == account_pb_v4.CreateAccountResponse.Result.EXISTS:
                raise AccountExistsError()
            if resp.result == account_pb_v4.CreateAccountResponse.Result.PAYER_REQUIRED:
                raise PayerRequiredError()
            if resp.result == account_pb_v4.CreateAccountResponse.Result.BAD_NONCE:
                raise BadNonceError()
            if resp.result != account_pb_v4.CreateAccountResponse.Result.OK:
                raise Error(f'unexpected result from agora: {resp.result}')
示例#7
0
    def test_create_account_no_service_subsidizer(self, grpc_channel, executor,
                                                  no_retry_client):
        private_key = PrivateKey.random()

        no_retry_client._response_cache.clear_all()
        future = executor.submit(no_retry_client.create_solana_account,
                                 private_key)

        md, request, rpc = grpc_channel.take_unary_unary(
            tx_pb_v4.DESCRIPTOR.services_by_name['Transaction'].
            methods_by_name['GetServiceConfig'])
        rpc.terminate(
            tx_pb_v4.GetServiceConfigResponse(
                token=model_pb_v4.SolanaAccountId(value=_token.raw),
                token_program=model_pb_v4.SolanaAccountId(
                    value=_token_program.raw),
            ), (), grpc.StatusCode.OK, '')

        TestInternalClientV4._assert_metadata(md)

        with pytest.raises(NoSubsidizerError):
            future.result()

        subsidizer = PrivateKey.random()
        future = executor.submit(no_retry_client.create_solana_account,
                                 private_key,
                                 subsidizer=subsidizer)

        self._set_get_recent_blockhash_resp(grpc_channel)
        self._set_get_min_balance_response(grpc_channel)

        req = self._set_create_account_resp(
            grpc_channel, account_pb_v4.CreateAccountResponse())

        tx = Transaction.unmarshal(req.transaction.value)
        assert len(tx.signatures) == 2
        assert subsidizer.public_key.verify(tx.message.marshal(),
                                            tx.signatures[0])
        assert private_key.public_key.verify(tx.message.marshal(),
                                             tx.signatures[1])

        sys_create = decompile_create_account(tx.message, 0)
        assert sys_create.funder == subsidizer.public_key
        assert sys_create.address == private_key.public_key
        assert sys_create.owner == _token_program
        assert sys_create.lamports == _min_balance
        assert sys_create.size == token.ACCOUNT_SIZE

        token_init = decompile_initialize_account(tx.message, 1,
                                                  _token_program)
        assert token_init.account == private_key.public_key
        assert token_init.mint == _token
        assert token_init.owner == private_key.public_key

        token_set_auth = decompile_set_authority(tx.message, 2, _token_program)
        assert token_set_auth.account == private_key.public_key
        assert token_set_auth.current_authority == private_key.public_key
        assert token_set_auth.authority_type == token.AuthorityType.CloseAccount
        assert token_set_auth.new_authority == subsidizer.public_key

        assert not future.result()
    def test_from_proto_solana_agora_memo(self):
        acc1, acc2, token_program = [
            key.public_key for key in generate_keys(3)
        ]
        il = model_pb_v3.InvoiceList(invoices=[
            model_pb_v3.Invoice(items=[
                model_pb_v3.Invoice.LineItem(title='t1', amount=10),
            ]),
            model_pb_v3.Invoice(items=[
                model_pb_v3.Invoice.LineItem(title='t1', amount=15),
            ]),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        agora_memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        tx = Transaction.new(PrivateKey.random().public_key, [
            memo_instruction(base64.b64encode(agora_memo.val).decode('utf-8')),
            transfer(acc1, acc2,
                     PrivateKey.random().public_key, 10),
            transfer(acc2, acc1,
                     PrivateKey.random().public_key, 15),
        ])

        history_item = tx_pb.HistoryItem(
            transaction_id=model_pb.TransactionId(value=b'somehash'),
            cursor=tx_pb.Cursor(value=b'cursor1'),
            solana_transaction=model_pb.Transaction(value=tx.marshal(), ),
            payments=[
                tx_pb.HistoryItem.Payment(
                    source=model_pb.SolanaAccountId(value=acc1.raw),
                    destination=model_pb.SolanaAccountId(value=acc2.raw),
                    amount=10,
                ),
                tx_pb.HistoryItem.Payment(
                    source=model_pb.SolanaAccountId(value=acc2.raw),
                    destination=model_pb.SolanaAccountId(value=acc1.raw),
                    amount=15,
                ),
            ],
            invoice_list=il,
        )

        data = TransactionData.from_proto(
            history_item, tx_pb.GetTransactionResponse.State.SUCCESS)
        assert data.tx_id == b'somehash'
        assert data.transaction_state == TransactionState.SUCCESS
        assert len(data.payments) == 2

        payment1 = data.payments[0]
        assert payment1.sender.raw == acc1.raw
        assert payment1.destination.raw == acc2.raw
        assert payment1.tx_type == TransactionType.P2P
        assert payment1.quarks == 10
        assert (payment1.invoice.to_proto().SerializeToString() ==
                il.invoices[0].SerializeToString())
        assert not payment1.memo

        payment2 = data.payments[1]
        assert payment2.sender.raw == acc2.raw
        assert payment2.destination.raw == acc1.raw
        assert payment2.tx_type == TransactionType.P2P
        assert payment2.quarks == 15
        assert (payment2.invoice.to_proto().SerializeToString() ==
                il.invoices[1].SerializeToString())
        assert not payment2.memo