예제 #1
0
    def test_payments_from_transaction_with_invoice_list(self):
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=10),
                ]
            ),
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=15),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(5)]
        token_program = keys[4]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
                solana.transfer(
                    keys[2],
                    keys[3],
                    keys[1],
                    40,
                    token_program,
                ),
            ]
        )

        payments = ReadOnlyPayment.payments_from_transaction(tx, il)

        assert len(payments) == 2

        assert payments[0].sender == keys[1]
        assert payments[0].destination == keys[2]
        assert payments[0].tx_type == TransactionType.P2P
        assert payments[0].quarks == 20
        assert payments[0].invoice == Invoice.from_proto(il.invoices[0])
        assert not payments[0].memo

        assert payments[1].sender == keys[2]
        assert payments[1].destination == keys[3]
        assert payments[1].tx_type == TransactionType.P2P
        assert payments[1].quarks == 40
        assert payments[1].invoice == Invoice.from_proto(il.invoices[1])
        assert not payments[1].memo
def _generate_tx(with_il: Optional[bool] = False) -> Tuple[solana.Transaction, Optional[model_pb2.InvoiceList]]:
    il = None
    instructions = []

    if with_il:
        il = model_pb2.InvoiceList(
            invoices=[
                model_pb2.Invoice(
                    items=[
                        model_pb2.Invoice.LineItem(title='title1', description='desc1', amount=50, sku=b'somesku')
                    ]
                )
            ]
        )

        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)
        instructions.append(solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')))

    keys = [key.public_key for key in generate_keys(3)]
    instructions.append(solana.transfer(
        keys[0],
        keys[1],
        keys[2],
        20,
    ), )

    return solana.Transaction.new(
        _SIGNING_KEY.public_key,
        instructions
    ), il
예제 #3
0
    def test_from_json(self):
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, b'somefk')
        keys = [key.public_key for key in generate_keys(4)]
        token_program = keys[3]
        tx = solana.Transaction.new(keys[0], [
            solana.memo_instruction(
                base64.b64encode(memo.val).decode('utf-8')),
            solana.transfer(
                keys[1],
                keys[2],
                keys[3],
                20,
                token_program,
            ),
        ])

        data = {
            'transaction': base64.b64encode(tx.marshal()).decode('utf-8'),
            'transaction_error': 'unauthorized',
            'transaction_error_raw': 'raw_error'
        }

        solana_event = SolanaEvent.from_json(data)
        assert solana_event.transaction == tx
        assert isinstance(solana_event.tx_error, InvalidSignatureError)
        assert solana_event.tx_error_raw == 'raw_error'
예제 #4
0
    def test_payments_from_transaction(self):
        keys = [key.public_key for key in generate_keys(5)]
        token_program = keys[4]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction('somememo'),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
                solana.transfer(
                    keys[2],
                    keys[3],
                    keys[1],
                    40,
                    token_program,
                ),
            ]
        )

        payments = ReadOnlyPayment.payments_from_transaction(tx)

        assert len(payments) == 2

        assert payments[0].sender == keys[1]
        assert payments[0].destination == keys[2]
        assert payments[0].tx_type == TransactionType.UNKNOWN
        assert payments[0].quarks == 20
        assert not payments[0].invoice
        assert payments[0].memo == 'somememo'

        assert payments[1].sender == keys[2]
        assert payments[1].destination == keys[3]
        assert payments[1].tx_type == TransactionType.UNKNOWN
        assert payments[1].quarks == 40
        assert not payments[1].invoice
        assert payments[1].memo == 'somememo'
예제 #5
0
    def test_from_json_full_kin_4(self):
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, b'somefk')
        keys = [key.public_key for key in generate_keys(4)]
        token_program = keys[3]
        tx = solana.Transaction.new(keys[0], [
            solana.memo_instruction(
                base64.b64encode(memo.val).decode('utf-8')),
            solana.transfer(
                keys[1],
                keys[2],
                keys[3],
                20,
                token_program,
            ),
        ])

        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(items=[
                model_pb2.Invoice.LineItem(title='title1',
                                           description='desc1',
                                           amount=50,
                                           sku=b'somesku')
            ])
        ])

        data = {
            'kin_version': 4,
            'tx_id': base64.b64encode(b'txsig'),
            'invoice_list': il.SerializeToString(),
            'solana_event': {
                'transaction': base64.b64encode(tx.marshal()).decode('utf-8'),
                'transaction_error': 'bad_nonce',
                'transaction_error_raw': 'raw_error',
            }
        }

        event = TransactionEvent.from_json(data)
        assert event.kin_version == 4
        assert event.tx_id == b'txsig'
        assert len(event.invoice_list.invoices) == 1
        assert len(event.invoice_list.invoices[0].items) == 1

        line_item = event.invoice_list.invoices[0].items[0]
        assert line_item.title == 'title1'
        assert line_item.description == 'desc1'
        assert line_item.amount == 50
        assert line_item.sku == b'somesku'

        assert not event.stellar_event

        assert event.solana_event.transaction == tx
        assert isinstance(event.solana_event.tx_error, BadNonceError)
        assert event.solana_event.tx_error_raw == 'raw_error'
예제 #6
0
    def test_payments_from_transaction_invalid(self):
        il = model_pb2.InvoiceList(invoices=[
            model_pb2.Invoice(
                items=[
                    model_pb2.Invoice.LineItem(title='t1', amount=10),
                ]
            ),
        ])
        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(5)]
        token_program = keys[4]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
                solana.transfer(
                    keys[2],
                    keys[3],
                    keys[1],
                    40,
                    token_program,
                ),
            ]
        )

        # mismatching number of invoices and instructions
        with pytest.raises(ValueError):
            ReadOnlyPayment.payments_from_transaction(tx, il)
예제 #7
0
    def test_from_json_kin_4(self):
        il = model_pb2.InvoiceList(
            invoices=[
                model_pb2.Invoice(
                    items=[
                        model_pb2.Invoice.LineItem(title='title1', description='desc1', amount=50, sku=b'somesku')
                    ]
                )
            ]
        )

        fk = InvoiceList.from_proto(il).get_sha_224_hash()
        memo = AgoraMemo.new(1, TransactionType.P2P, 0, fk)

        keys = [key.public_key for key in generate_keys(4)]
        token_program = keys[3]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.memo_instruction(base64.b64encode(memo.val).decode('utf-8')),
                solana.transfer(
                    keys[1],
                    keys[2],
                    keys[3],
                    20,
                    token_program,
                ),
            ]
        )

        data = {
            'kin_version': 4,
            'solana_transaction': base64.b64encode(tx.marshal()),
            'invoice_list': base64.b64encode(il.SerializeToString()),
        }

        req = SignTransactionRequest.from_json(data, Environment.TEST)
        assert len(req.payments) == 1
        assert req.payments[0].invoice == Invoice.from_proto(il.invoices[0])

        assert req.kin_version == data['kin_version']
        assert req.transaction == tx
예제 #8
0
    def _submit_solana_earn_batch_tx(
        self,
        batch: EarnBatch,
        service_config: tx_pb.GetServiceConfigResponse,
        commitment: Commitment,
        transfer_sender: Optional[PublicKey] = None,
    ) -> SubmitTransactionResult:
        token_program = PublicKey(service_config.token_program.value)
        subsidizer_id = (batch.subsidizer.public_key if batch.subsidizer else
                         PublicKey(service_config.subsidizer_account.value))

        transfer_sender = transfer_sender if transfer_sender else batch.sender.public_key
        instructions = [
            transfer(transfer_sender, earn.destination,
                     batch.sender.public_key, earn.quarks, token_program)
            for earn in batch.earns
        ]

        invoices = [earn.invoice for earn in batch.earns if earn.invoice]
        invoice_list = InvoiceList(invoices) if invoices else None
        if batch.memo:
            instructions = [memo_instruction(batch.memo)] + instructions
        elif self.app_index > 0:
            fk = invoice_list.get_sha_224_hash() if invoice_list else b''
            agora_memo = AgoraMemo.new(1, TransactionType.EARN, self.app_index,
                                       fk)
            instructions = [
                memo_instruction(
                    base64.b64encode(agora_memo.val).decode('utf-8'))
            ] + instructions

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if batch.subsidizer:
            signers = [batch.subsidizer, batch.sender]
        else:
            signers = [batch.sender]

        return self._sign_and_submit_solana_tx(signers,
                                               tx,
                                               commitment,
                                               invoice_list=invoice_list)
예제 #9
0
    def test_get_tx_id(self):
        envelope = _generate_envelope()
        data = {
            'kin_version': 3,
            'envelope_xdr': envelope.xdr(),
        }

        req = SignTransactionRequest.from_json(data, Environment.TEST)
        assert req.get_tx_id() == envelope.hash_meta()

        envelope = _generate_kin_2_envelope()
        data = {
            'kin_version': 2,
            'envelope_xdr': envelope.xdr(),
        }

        req = SignTransactionRequest.from_json(data, Environment.TEST)
        assert req.get_tx_id() == envelope.hash_meta()

        keys = generate_keys(4)
        public_keys = [key.public_key for key in keys]
        token_program = public_keys[3]

        tx = solana.Transaction.new(
            public_keys[0],
            [
                solana.transfer(
                    public_keys[1],
                    public_keys[2],
                    public_keys[3],
                    20,
                    token_program,
                ),
            ]
        )
        tx.sign([keys[0]])

        req = SignTransactionRequest.from_json(data, Environment.TEST)
        assert req.get_tx_hash() == envelope.hash_meta()
예제 #10
0
    def _submit_solana_payment_tx(
        self,
        payment: Payment,
        service_config: tx_pb.GetServiceConfigResponse,
        commitment: Commitment,
        transfer_sender: Optional[PublicKey] = None
    ) -> SubmitTransactionResult:
        token_program = PublicKey(service_config.token_program.value)
        subsidizer_id = (payment.subsidizer.public_key
                         if payment.subsidizer else PublicKey(
                             service_config.subsidizer_account.value))

        instructions = []
        invoice_list = None
        if payment.memo:
            instructions = [memo_instruction(payment.memo)]
        elif self.app_index > 0:
            if payment.invoice:
                invoice_list = InvoiceList(invoices=[payment.invoice])
            fk = invoice_list.get_sha_224_hash() if payment.invoice else b''
            memo = AgoraMemo.new(1, payment.tx_type, self.app_index, fk)
            instructions = [
                memo_instruction(base64.b64encode(memo.val).decode('utf-8'))
            ]

        sender = transfer_sender if transfer_sender else payment.sender.public_key
        instructions.append(
            transfer(sender, payment.destination, payment.sender.public_key,
                     payment.quarks, token_program))

        tx = solana.Transaction.new(subsidizer_id, instructions)
        if payment.subsidizer:
            signers = [payment.subsidizer, payment.sender]
        else:
            signers = [payment.sender]

        return self._sign_and_submit_solana_tx(signers, tx, commitment,
                                               invoice_list)
예제 #11
0
    def test_from_json_with_tx_event(self):
        keys = [key.public_key for key in generate_keys(3)]
        tx = solana.Transaction.new(
            keys[0],
            [
                solana.transfer(
                    keys[1],
                    keys[1],
                    keys[2],
                    20,
                ),
            ]
        )

        event = Event.from_json({
            'transaction_event': {
                'tx_id': base64.b64encode(b'txsig'),
                'solana_event': {
                    'transaction': base64.b64encode(tx.marshal()).decode('utf-8'),
                }
            }
        })
        assert event.transaction_event.tx_id == b'txsig'
        assert event.transaction_event.solana_event
예제 #12
0
    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'
예제 #13
0
    def test_handle_event(self):
        secret = 'secret'
        handler = WebhookHandler(Environment.TEST, secret=secret)

        keys = [key.public_key for key in generate_keys(4)]
        tx = solana.Transaction.new(keys[0], [
            solana.transfer(
                keys[1],
                keys[1],
                keys[2],
                20,
            ),
        ])
        data = [{
            'transaction_event': {
                'tx_id': base64.b64encode(b'txsig').decode('utf-8'),
                'solana_event': {
                    'transaction':
                    base64.b64encode(tx.marshal()).decode('utf-8'),
                }
            }
        }]
        req_body = json.dumps(data)
        sig = base64.b64encode(
            hmac.new(secret.encode(), req_body.encode(),
                     hashlib.sha256).digest())
        text_sig = base64.b64encode(
            hmac.new(secret.encode(), b'someotherdata',
                     hashlib.sha256).digest())

        # invalid signature
        status_code, resp_body = handler.handle_events(self._event_return_none,
                                                       text_sig, req_body)
        assert status_code == 401
        assert resp_body == ''

        # invalid req body
        status_code, resp_body = handler.handle_events(self._event_return_none,
                                                       text_sig,
                                                       'someotherdata')
        assert status_code == 400
        assert resp_body == 'invalid request body'

        # webhook request error
        status_code, resp_body = handler.handle_events(
            self._event_raise_webhook_request_error, sig, req_body)
        assert status_code == 400
        assert resp_body == 'some error'

        # other error
        status_code, resp_body = handler.handle_events(
            self._event_raise_other_error, sig, req_body)
        assert status_code == 500
        assert resp_body == 'bad stuff'

        # successful
        status_code, resp_body = handler.handle_events(self._event_return_none,
                                                       sig, req_body)
        assert status_code == 200

        # fake signature with no webhook secret should result in a successful response
        handler = WebhookHandler(Environment.TEST)
        status_code, resp_body = handler.handle_events(self._event_return_none,
                                                       "fakesig", req_body)
        assert status_code == 200
예제 #14
0
    def test_handle_sign_tx(self):
        secret = 'secret'
        handler = WebhookHandler(Environment.TEST, secret=secret)

        keys = [key.public_key for key in generate_keys(3)]
        tx = solana.Transaction.new(_TEST_PRIVATE_KEY.public_key, [
            solana.transfer(
                keys[0],
                keys[1],
                keys[2],
                20,
            ),
        ])

        data = {
            'kin_version': 4,
            'solana_transaction':
            base64.b64encode(tx.marshal()).decode('utf-8'),
        }

        req_body = json.dumps(data)
        sig = base64.b64encode(
            hmac.new(secret.encode(), req_body.encode(),
                     hashlib.sha256).digest())
        text_sig = base64.b64encode(
            hmac.new(secret.encode(), b'someotherdata',
                     hashlib.sha256).digest())

        # invalid signature
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_success, text_sig, req_body)
        assert status_code == 401
        assert resp_body == ''

        # invalid req body
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_success, text_sig, 'someotherdata')
        assert status_code == 400
        assert resp_body == 'invalid json request body'

        # webhook request error
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_raise_webhook_request_error, sig, req_body)
        assert status_code == 400
        assert resp_body == 'some error'

        # other error
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_raise_other_error, sig, req_body)
        assert status_code == 500
        assert resp_body == 'bad stuff'

        # rejected
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_return_rejected, sig, req_body)
        assert status_code == 403
        assert json.loads(resp_body) == {
            'invoice_errors': [{
                'operation_index':
                0,
                'reason':
                InvoiceErrorReason.UNKNOWN.to_lowercase()
            }]
        }

        # successful
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_success, sig, req_body)
        assert status_code == 200

        body = json.loads(resp_body)
        _TEST_PRIVATE_KEY.public_key.verify(
            tx.message.marshal(), base64.b64decode(body['signature']))

        # fake signature with no webhook secret should result in a successful response
        handler = WebhookHandler(Environment.TEST)
        status_code, resp_body = handler.handle_sign_transaction(
            self._sign_tx_success, "fakesig", req_body)
        assert status_code == 200

        body = json.loads(resp_body)
        _TEST_PRIVATE_KEY.public_key.verify(
            tx.message.marshal(), base64.b64decode(body['signature']))
예제 #15
0
    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