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
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'
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'
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'
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)
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
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)
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()
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)
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
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'
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
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']))
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