def test_revert_transaction_conversion(self): expected_result = Transaction( tx="79283647", receivers=[ TransactionReceiver(address="9782364", amount=0.40), TransactionReceiver(address="9782364", amount=0.30) ]) transaction = Transaction(tx="79283647", receivers=[ TransactionReceiver(address="9782364", amount=40), TransactionReceiver(address="9782364", amount=30) ]) with patch.object(self._integer_converter, 'revert_amount_conversion'): def stub(amount: float): return float(amount / 100) self._integer_converter.revert_amount_conversion.side_effect = stub actual_result = self._integer_converter.revert_transaction_conversion( transaction) self.assertEqual(actual_result, expected_result)
def test_iteration_filter_one_transaction(self): """Two transactions were given, one should be filtered and the other one not.""" height_of_highest_block = 30 last_checked_coin_block_height = height_of_highest_block - self._last_block_distance - 1 not_filtered_transaction = Transaction(tx="tx", receivers=[self._gateway_managed_address_receiver]) filtered_transaction = Transaction(tx="khj", receivers=[TransactionReceiver(address="92634867", amount=3425)]) self._block_height_storage.get_last_checked_block_height.return_value = last_checked_coin_block_height self._chain_query_service.get_height_of_highest_block.return_value = height_of_highest_block self._polling_state_storage.get_polling_state.return_value = None def _get_transactions_of_block_at_height(height: int): if height == (last_checked_coin_block_height + 1): return [not_filtered_transaction, filtered_transaction] else: return [] self._chain_query_service.get_transactions_of_block_at_height.side_effect = _get_transactions_of_block_at_height self._transaction_polling_service._iteration() self._transaction_consumer.filter_transaction.assert_any_call(not_filtered_transaction) self._transaction_consumer.filter_transaction.assert_any_call(filtered_transaction) self._transaction_consumer.handle_transaction.assert_called_once_with(not_filtered_transaction) self._chain_query_service.get_transactions_of_block_at_height.assert_called_once_with( last_checked_coin_block_height + 1) self._block_height_storage.set_last_checked_block_height.assert_called_once_with( last_checked_coin_block_height + 1)
def test_convert_transaction_to_int(self): transaction = Transaction(tx="79283647", receivers=[ TransactionReceiver(address="9782364", amount=0.40), TransactionReceiver(address="9782364", amount=0.30) ]) expected_result = Transaction( tx="79283647", receivers=[ TransactionReceiver(address="9782364", amount=40), TransactionReceiver(address="9782364", amount=30) ]) with patch.object(self._integer_converter, 'safely_convert_to_int'): def stub(amount: float): return int(amount * 100) self._integer_converter.safely_convert_to_int.side_effect = stub actual_result = self._integer_converter.convert_transaction_to_int( transaction) self.assertEqual(actual_result, expected_result) self.assertEqual( self._integer_converter.safely_convert_to_int.call_count, 2)
def test_iteration_handle_one_transaction(self): """The TransactionPollingService should process a single transaction that is not filtered.""" height_of_highest_block = 30 last_checked_coin_block_height = height_of_highest_block - self._last_block_distance - 1 transaction = Transaction(tx="tx", receivers=[self._gateway_managed_address_receiver]) self._block_height_storage.get_last_checked_block_height.return_value = last_checked_coin_block_height self._chain_query_service.get_height_of_highest_block.return_value = height_of_highest_block self._polling_state_storage.get_polling_state.return_value = None def _get_transactions_of_block_at_height(height: int): if height == (last_checked_coin_block_height + 1): return [transaction] else: return [] self._chain_query_service.get_transactions_of_block_at_height.side_effect = _get_transactions_of_block_at_height self._transaction_polling_service._iteration() self._transaction_consumer.filter_transaction.assert_called_once_with(transaction) self._transaction_consumer.handle_transaction.assert_called_once_with(transaction) self._chain_query_service.get_transactions_of_block_at_height.assert_called_once_with( last_checked_coin_block_height + 1) self._block_height_storage.set_last_checked_block_height.assert_called_once_with( last_checked_coin_block_height + 1)
def test_skip_transaction_after_too_many_tries(self): self._mock_real_storage() height_of_highest_block = 30 last_checked_coin_block_height = height_of_highest_block - self._last_block_distance - 1 transaction = Transaction(tx="tx", receivers=[self._gateway_managed_address_receiver]) def _get_transactions_of_block_at_height(height: int): if height == (last_checked_coin_block_height + 1): return [transaction] else: return [] def mock_handle_transaction(tr: Transaction): raise KeyError('failed') self._block_height_storage.get_last_checked_block_height.return_value = last_checked_coin_block_height self._chain_query_service.get_height_of_highest_block.return_value = height_of_highest_block self._transaction_consumer.handle_transaction.side_effect = mock_handle_transaction self._chain_query_service.get_transactions_of_block_at_height.side_effect = _get_transactions_of_block_at_height with self.assertRaises(KeyError): self._transaction_polling_service._iteration() self._transaction_polling_service._iteration() self._transaction_consumer.handle_transaction.assert_called_once_with(transaction) self.assertEqual(self._transaction_consumer.filter_transaction.call_count, 2)
def test_handle_transaction_multi_receiver(self, mock_datetime: MagicMock): now = MagicMock() mock_datetime.utcnow.return_value = now gateway_coin_address = self._gateway_managed_receiver.address dst_waves_address = '039769' incoming_transaction = Transaction( tx='723968', receivers=[self._gateway_managed_receiver]) amount_after_fees = self._gateway_managed_receiver.amount - self._coin_standard_fee - self._gateway_fee attempts = [ TransactionAttempt(sender=gateway_coin_address, currency="coin", fee=self._coin_standard_fee, receivers=[ TransactionAttemptReceiver( address=self._gateway_address, amount=amount_after_fees), TransactionAttemptReceiver( address=self._gateway_owner_address, amount=self._gateway_fee) ]), TransactionAttempt(sender=self._gateway_waves_address, fee=self._waves_standard_fee, currency="waves", receivers=[ TransactionAttemptReceiver( address=dst_waves_address, amount=amount_after_fees) ]) ] trigger = AttemptListTrigger(tx=incoming_transaction.tx, currency="coin", receiver=0) attempt_list = TransactionAttemptList(trigger=trigger, attempts=attempts, created_at=now, last_modified=now) self._attempt_list_storage.find_by_trigger.return_value = None self._map_storage.get_waves_address_by_coin_address.return_value = dst_waves_address self._coin_transaction_consumer_impl.handle_transaction( incoming_transaction) self._attempt_list_storage.find_by_trigger.assert_any_call( AttemptListTrigger(tx=incoming_transaction.tx, receiver=0, currency="coin")) self._map_storage.get_waves_address_by_coin_address.assert_called_once_with( gateway_coin_address) self._attempt_service.continue_transaction_attempt_list.assert_not_called( ) self._attempt_list_storage.safely_save_attempt_list.assert_called_once_with( attempt_list)
def test_filter_transaction_already_exists(self): """ Should not pass a transaction that is existing in one of the attempt lists """ transaction = Transaction( tx="78265", receivers=[TransactionReceiver('28734', 7823)]) self._attempt_list_storage.gateway_transaction_exists.return_value = True res = self._waves_transaction_consumer_impl.filter_transaction( transaction) self.assertFalse(res)
def test_filter_transaction_by_receiver_failure(self): """ Should not pass a transaction with only one receiver which does not equal the gateway address """ transaction = Transaction( tx="78265", receivers=[TransactionReceiver('28734', 7823)]) self._attempt_list_storage.gateway_transaction_exists.return_value = False res = self._waves_transaction_consumer_impl.filter_transaction( transaction) self._attempt_list_storage.find_by_trigger.assert_not_called() self.assertFalse(res)
def test_get_transactions_of_block_at_height(self, mock_requests_get: MagicMock): mock_get_result = MagicMock() mock_get_result.ok = True transactions = [{ 'signature': '5Kg1u9AUix8H3xkPGpHBCuFptCJJwMGkYcdRitsCuSd' 'Zidcf19DoMNMJUKvRrRw2SjF5fGgedZJiJWwZ5DRENM64', 'id': 'HR96p2fuceFm2P1wbs8FtxyeoPmGooKjHmV1gHUhZ5U2', 'assetId': self._asset_id, 'amount': 110000, 'feeAsset': None, 'fee': 100000, 'recipient': 'address:3Mxjf6da1ixZD5UzM7H9rLXLWzfv5YXZvVn', 'attachment': 'B4GdmuG3Jbxg7DLNWu3YrXiFbdQzPiCYBGqqunsG3', 'senderPublicKey': '3bwjGNumMn4K5Yzt154PFegJN4StPvdQ6HAhzbDnCP1m', 'timestamp': 1502026364974, 'type': 4, 'sender': '3Mxjf6da1ixZD5UzM7H9rLXLWzfv5YXZvVn' }] mock_get_result.text = json.dumps({'transactions': transactions}) mock_requests_get.return_value = mock_get_result # self._chain_query_service._get_response_from_node.return_value = {'transactions': transactions} transactions = self._chain_query_service.get_transactions_of_block_at_height( 20) expected_transaction = Transaction( tx='HR96p2fuceFm2P1wbs8FtxyeoPmGooKjHmV1gHUhZ5U2', receivers=[ TransactionReceiver( address='3Mxjf6da1ixZD5UzM7H9rLXLWzfv5YXZvVn', amount=110000) ]) self.assertEqual(len(transactions), 1) self.assertEqual(transactions[0], expected_transaction)
def test_handle_transaction(self): _filter_receivers = MagicMock(return_value=[0]) _handle_receiver = MagicMock() transaction = Transaction(tx="78265", receivers=[self._gateway_waves_receiver]) with patch.multiple(self._waves_transaction_consumer_impl, _filter_receivers=_filter_receivers, _handle_receiver=_handle_receiver): self._waves_transaction_consumer_impl.handle_transaction( transaction) _filter_receivers.assert_called_once_with(transaction) _handle_receiver.assert_called_once_with( transaction.tx, self._gateway_waves_receiver, 0, None)
def test_filter_transaction_by_receivers_failure(self): """ The filter should ignore transactions with no gateway managed receivers """ self._attempt_list_storage.gateway_transaction_exists.return_value = False self._map_storage.coin_address_exists.return_value = False transaction = Transaction( tx='723968', receivers=[self._not_gateway_managed_receiver]) res = self._coin_transaction_consumer_impl.filter_transaction( transaction) self.assertFalse(res) self._map_storage.coin_address_exists.assert_called_once_with( self._not_gateway_managed_receiver.address) self._attempt_list_storage.find_by_trigger.assert_not_called()
def test_filter_transaction_by_receiver_success(self): """ Should pass a transaction with a receiver that is addressed to the main gateway waves address """ transaction = Transaction(tx="78265", receivers=[self._gateway_waves_receiver]) self._attempt_list_storage.gateway_transaction_exists.return_value = False self._attempt_list_storage.find_by_trigger.return_value = None res = self._waves_transaction_consumer_impl.filter_transaction( transaction) self._attempt_list_storage.gateway_transaction_exists.assert_called_once_with( transaction.tx) self.assertTrue(res)
def test_filter_transaction_exists(self): """ The filter should not allow transactions to pass which were already processed """ with patch.object(self._coin_transaction_consumer_impl, "_filter_receivers"): self._attempt_service.gateway_transaction_exists.return_value = True transaction = Transaction( tx='723968', receivers=[self._gateway_managed_receiver]) res = self._coin_transaction_consumer_impl.filter_transaction( transaction) self.assertFalse(res) cast(MagicMock, self._coin_transaction_consumer_impl. _filter_receivers).assert_not_called()
def test_filter_transaction_block_coin_holder(self): """ The filter should not allow transactions that are addressed to the gateway coin holder """ self._attempt_list_storage.gateway_transaction_exists.return_value = False self._map_storage.coin_address_exists.return_value = True transaction = Transaction( tx='723968', receivers=[self._gateway_coin_holder_receiver]) res = self._coin_transaction_consumer_impl.filter_transaction( transaction) self.assertFalse(res) self._map_storage.coin_address_exists.assert_called_once_with( self._gateway_coin_holder_receiver.address) self._attempt_list_storage.gateway_transaction_exists.assert_called_once_with( transaction.tx)
def send_coin(self, attempt: TransactionAttempt, secret: Optional[str]) -> Transaction: if attempt.sender != self._gateway_pywaves_address.address: raise PyWavesError('Missing secret for sender ' + attempt.sender) transaction = self._gateway_pywaves_address.sendAsset( recipient=pw.Address(attempt.receivers[0].address), asset=self._w_ltc, amount=attempt.receivers[0].amount, txFee=attempt.fee) if transaction is None: raise PyWavesError("Encountered an unknown exception while trying to perform waves transaction.") return Transaction( tx=transaction['id'], receivers=[TransactionReceiver(transaction['recipient'], attempt.receivers[0].amount)])
def test_iteration_do_nothing(self): """In the case of a fresh start, the handle_transaction method should not be called.""" height_of_highest_block = 30 transaction = Transaction(tx="tx", receivers=[TransactionReceiver("receiver", 6587)]) self._block_height_storage.get_last_checked_block_height.return_value = None self._chain_query_service.get_height_of_highest_block.return_value = height_of_highest_block self._chain_query_service.get_transactions_of_block_at_height.return_value = [transaction] self._transaction_consumer.filter_transaction.return_value = False self._polling_state_storage.get_polling_state.return_value = None self._transaction_polling_service._iteration() self._transaction_consumer.filter_transaction.assert_not_called() self._transaction_consumer.handle_transaction.assert_not_called() self._chain_query_service.get_transactions_of_block_at_height.assert_not_called() self._block_height_storage.set_last_checked_block_height.assert_called_once_with(height_of_highest_block)
def test_filter_transaction_by_receivers_success(self): """ The filter should pass a transaction that has not yet been processed and contains a gateway managed address """ self._attempt_list_storage.gateway_transaction_exists.return_value = False self._map_storage.coin_address_exists.return_value = True self._attempt_list_storage.find_by_trigger.return_value = None transaction = Transaction(tx='723968', receivers=[self._gateway_managed_receiver]) res = self._coin_transaction_consumer_impl.filter_transaction( transaction) self.assertTrue(res) self._map_storage.coin_address_exists.assert_called_once_with( self._gateway_managed_receiver.address) self._attempt_list_storage.find_by_trigger.assert_called_once_with( AttemptListTrigger(tx=transaction.tx, receiver=0, currency="coin"))
def _convert_node_response_to_transaction(self, res: dict) -> Optional[Transaction]: if 'assetId' not in res or res['assetId'] != self._asset_id: return None if res['type'] != 4: return None try: address = res['recipient'].split(':')[1] except IndexError: address = res['recipient'] try: sender = res['sender'].split(':')[1] except IndexError: sender = res['sender'] return Transaction( tx=res['id'], receivers=[TransactionReceiver(address=address, amount=res['amount'])], senders=[TransactionSender(address=sender)])