def verify_owners(self, provider_public_key=None, requestor_public_key=None, concent_public_key=None): """ verifies both that the whole message chain is consistent with respect to the expected message ownership and that the roles in the message chain match the expected provider/requestor keys if provided, `provider_public_key` / `requestor_public_key` will be verified against the roles extracted from the included message chain. :param provider_public_key: :param requestor_public_key: :param concent_public_key: must be provided if any of the child messages is expected to be signed by the Concent :return: """ def assert_role(role, expected, actual): if expected != actual: raise exceptions.OwnershipMismatch( "%s: Task %s mismatch - expected: %s, actual: %s" % (self.__class__.__name__, role, expected, actual)) if provider_public_key: assert_role('provider', provider_public_key, decode_hex(self.task_to_compute.provider_public_key)) if requestor_public_key: assert_role('requestor', requestor_public_key, decode_hex(self.task_to_compute.requestor_public_key)) self.validate_ownership_chain(concent_public_key=concent_public_key) return True
def validate_ownership(self, concent_public_key=None): """ validates that the message is signed by one of the expected parties requires `concent_public_key` if the Concent is one of the possible owners """ owner_map = { TaskMessage.OWNER_CHOICES.provider: decode_hex(self.task_to_compute.provider_public_key), TaskMessage.OWNER_CHOICES.requestor: decode_hex(self.task_to_compute.requestor_public_key), TaskMessage.OWNER_CHOICES.concent: concent_public_key, } for owner in self.EXPECTED_OWNERS: try: if self.verify_signature(public_key=owner_map.get(owner)): return True except exceptions.InvalidSignature: pass exc = exceptions.InvalidSignature( '%s is not signed by the %s' % (self.__class__.__name__, ' or '.join([ '%s: %s' % (o.value, owner_map.get(o)) for o in self.EXPECTED_OWNERS ]))) exc.message = self raise exc
def hex_to_bytes_convert(client_public_key: str): if not isinstance(client_public_key, str): raise Http400("Client public key must be string", error_code=ErrorCode.MESSAGE_VALUE_NOT_STRING) if not len(client_public_key) == GOLEM_PUBLIC_KEY_HEX_LENGTH: raise Http400("Client public key must be length of 128 characters", error_code=ErrorCode.MESSAGE_VALUE_WRONG_LENGTH) key_bytes = decode_hex(client_public_key) assert len(key_bytes) == GOLEM_PUBLIC_KEY_LENGTH return key_bytes
def ethereum_public_key_to_address(ethereum_public_key: str) -> str: return to_checksum_address( sha3(decode_hex(ethereum_public_key))[12:].hex())
def generate_ethereum_address_from_ethereum_public_key_bytes( ethereum_public_key: str) -> bytes: assert isinstance(ethereum_public_key, str) assert len(ethereum_public_key) == ETHEREUM_PUBLIC_KEY_LENGTH return sha3(decode_hex(ethereum_public_key))[12:]
def test_sum_of_payments_when_lists_of_transactions_from_payment_api_are_empty( self): """ Expected message exchange: Provider -> Concent: ForcePayment Concent -> Provider: ForcePaymentCommitted Concent -> Requestor: ForcePaymentCommitted """ task_to_compute = self._get_deserialized_task_to_compute( timestamp="2018-02-05 10:00:00", deadline="2018-02-05 10:00:10", subtask_id=self._get_uuid('1'), price=20000, ) subtask_results_accepted_list = [ self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 10:00:15", payment_ts="2018-02-05 12:00:00", report_computed_task=self. _get_deserialized_report_computed_task( timestamp="2018-02-05 10:00:05", task_to_compute=task_to_compute)), self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 9:00:15", payment_ts="2018-02-05 11:00:00", report_computed_task=self. _get_deserialized_report_computed_task( timestamp="2018-02-05 10:00:05", task_to_compute=self._get_deserialized_task_to_compute( timestamp="2018-02-05 9:00:00", deadline="2018-02-05 9:00:10", subtask_id=self._get_uuid('2'), price=5000, ))) ] serialized_force_payment = self._get_serialized_force_payment( timestamp="2018-02-05 12:00:20", subtask_results_accepted_list=subtask_results_accepted_list) with freeze_time("2018-02-05 12:00:20"): with mock.patch( 'core.message_handlers.bankster.settle_overdue_acceptances', side_effect=self.settle_overdue_acceptances_mock ) as settle_overdue_acceptances: response_1 = self.send_request( url='core:send', data=serialized_force_payment, ) settle_overdue_acceptances.assert_called_with( requestor_ethereum_address=task_to_compute. requestor_ethereum_address, provider_ethereum_address=task_to_compute. provider_ethereum_address, acceptances=subtask_results_accepted_list, requestor_public_key=hex_to_bytes_convert( task_to_compute.requestor_public_key), ) self._test_response( response_1, status=200, key=self.PROVIDER_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Provider, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:20"), 'amount_pending': self.amount_pending, 'amount_paid': self.amount_paid, }) self._assert_stored_message_counter_not_increased() with freeze_time("2018-02-05 12:00:21"): response_2 = self.send_request( url='core:receive', data=self._create_requestor_auth_message(), ) self._test_response( response_2, status=200, key=self.REQUESTOR_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Requestor, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:21"), 'amount_pending': self.amount_pending, 'amount_paid': self.amount_paid, 'task_owner_key': decode_hex(task_to_compute.requestor_ethereum_public_key), }) self._assert_stored_message_counter_not_increased()
def test_provider_send_correct_force_payment_concent_should_accept(self): """ Expected message exchange: Provider -> Concent: ForcePayment Concent -> Provider: ForcePaymentCommitted Concent -> Requestor: ForcePaymentCommitted """ task_to_compute = self._get_deserialized_task_to_compute( timestamp="2018-02-05 10:00:00", deadline="2018-02-05 10:00:10", subtask_id=self._get_uuid('1'), price=15000, ) subtask_results_accepted_list = [ self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 10:00:15", payment_ts="2018-02-05 11:55:00", report_computed_task=self. _get_deserialized_report_computed_task( timestamp="2018-02-05 10:00:05", task_to_compute=task_to_compute, )), self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 9:00:15", payment_ts="2018-02-05 11:55:00", report_computed_task=self. _get_deserialized_report_computed_task( timestamp="2018-02-05 9:00:05", task_to_compute=self._get_deserialized_task_to_compute( timestamp="2018-02-05 9:00:00", deadline="2018-02-05 9:00:10", subtask_id=self._get_uuid('2'), price=7000, ))) ] serialized_force_payment = self._get_serialized_force_payment( timestamp="2018-02-05 12:00:20", subtask_results_accepted_list=subtask_results_accepted_list) with freeze_time("2018-02-05 12:00:20"): with mock.patch( 'core.message_handlers.bankster.settle_overdue_acceptances', side_effect=self.settle_overdue_acceptances_mock ) as settle_overdue_acceptances: response_1 = self.send_request( url='core:send', data=serialized_force_payment, ) settle_overdue_acceptances.assert_called_with( requestor_ethereum_address=task_to_compute. requestor_ethereum_address, provider_ethereum_address=task_to_compute. provider_ethereum_address, acceptances=subtask_results_accepted_list, requestor_public_key=hex_to_bytes_convert( task_to_compute.requestor_public_key), ) self._test_response( response_1, status=200, key=self.PROVIDER_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Provider, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:20"), 'amount_pending': self.amount_pending, 'amount_paid': self.amount_paid, }) self._assert_stored_message_counter_not_increased() last_pending_message = PendingResponse.objects.filter( delivered=False).order_by('created_at').last() self.assertEqual( last_pending_message.response_type, PendingResponse.ResponseType.ForcePaymentCommitted.name) # pylint: disable=no-member self.assertEqual(last_pending_message.client.public_key_bytes, self.REQUESTOR_PUBLIC_KEY) with freeze_time("2018-02-05 12:00:21"): response_2 = self.send_request( url='core:receive', data=self._create_requestor_auth_message(), ) self._test_response( response_2, status=200, key=self.REQUESTOR_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Requestor, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:21"), 'amount_pending': self.amount_pending, 'amount_paid': self.amount_paid, 'task_owner_key': decode_hex(task_to_compute.requestor_ethereum_public_key), }) self._assert_stored_message_counter_not_increased() last_pending_message = PendingResponse.objects.filter( delivered=False).last() self.assertIsNone(last_pending_message)
def test_sum_of_payments_when_lists_of_transactions_from_payment_api_are_empty( self): """ Expected message exchange: Provider -> Concent: ForcePayment Concent -> Provider: ForcePaymentCommitted Concent -> Requestor: ForcePaymentCommitted """ task_to_compute = self._get_deserialized_task_to_compute( timestamp="2018-02-05 10:00:00", deadline="2018-02-05 10:00:10", subtask_id='2', price=20000, ) subtask_results_accepted_list = [ self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 10:00:15", payment_ts="2018-02-05 12:00:00", task_to_compute=task_to_compute), self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 9:00:15", payment_ts="2018-02-05 11:00:00", task_to_compute=self._get_deserialized_task_to_compute( timestamp="2018-02-05 9:00:00", deadline="2018-02-05 9:00:10", subtask_id='3', price=5000, )) ] serialized_force_payment = self._get_serialized_force_payment( timestamp="2018-02-05 12:00:20", subtask_results_accepted_list=subtask_results_accepted_list) with freeze_time("2018-02-05 12:00:20"): with mock.patch( 'core.message_handlers.payments_service.get_list_of_payments', side_effect=self._get_empty_list_of_transactions ) as get_list_of_payments_mock_function,\ mock.patch( 'core.message_handlers.payments_service.make_force_payment_to_provider', side_effect=self._make_force_payment_to_provider ) as make_force_payment_to_provider_mock_function: response_1 = self.client.post( reverse('core:send'), data=serialized_force_payment, content_type='application/octet-stream', ) make_force_payment_to_provider_mock_function.assert_called_with( requestor_eth_address=task_to_compute.requestor_ethereum_address, provider_eth_address=task_to_compute.provider_ethereum_address, value=25000, payment_ts=parse_iso_date_to_timestamp("2018-02-05 12:00:20"), ) get_list_of_payments_mock_function.assert_called_with( requestor_eth_address=task_to_compute.requestor_ethereum_address, provider_eth_address=task_to_compute.provider_ethereum_address, payment_ts=parse_iso_date_to_timestamp("2018-02-05 11:00:10"), current_time=parse_iso_date_to_timestamp("2018-02-05 12:00:20"), transaction_type=TransactionType.FORCE, ) self._test_response( response_1, status=200, key=self.PROVIDER_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Provider, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:20"), 'amount_pending': 25000, 'amount_paid': 0, }) self._assert_stored_message_counter_not_increased() with freeze_time("2018-02-05 12:00:21"): response_2 = self.client.post( reverse('core:receive_out_of_band'), data=self._create_requestor_auth_message(), content_type='application/octet-stream', ) self._test_response( response_2, status=200, key=self.REQUESTOR_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Requestor, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:21"), 'amount_pending': 25000, 'amount_paid': 0, 'task_owner_key': decode_hex(task_to_compute.requestor_ethereum_public_key), }) self._assert_stored_message_counter_not_increased()
def test_provider_send_correct_force_payment_concent_should_accept(self): """ Expected message exchange: Provider -> Concent: ForcePayment Concent -> Provider: ForcePaymentCommitted Concent -> Requestor: ForcePaymentCommitted """ task_to_compute = self._get_deserialized_task_to_compute( timestamp="2018-02-05 10:00:00", deadline="2018-02-05 10:00:10", subtask_id='2', price=15000, ) subtask_results_accepted_list = [ self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 10:00:15", payment_ts="2018-02-05 11:55:00", task_to_compute=task_to_compute), self._get_deserialized_subtask_results_accepted( timestamp="2018-02-05 9:00:15", payment_ts="2018-02-05 11:55:00", task_to_compute=self._get_deserialized_task_to_compute( timestamp="2018-02-05 9:00:00", deadline="2018-02-05 9:00:10", subtask_id='3', price=7000, )) ] serialized_force_payment = self._get_serialized_force_payment( timestamp="2018-02-05 12:00:20", subtask_results_accepted_list=subtask_results_accepted_list) with freeze_time("2018-02-05 12:00:20"): fake_responses = [ self._get_list_of_batch_transactions(), self._get_list_of_force_transactions() ] with mock.patch( 'core.message_handlers.payments_service.make_force_payment_to_provider', side_effect=self._make_force_payment_to_provider ) as make_force_payment_to_provider_mock_function,\ mock.patch( 'core.message_handlers.payments_service.get_list_of_payments', side_effect=fake_responses ) as get_list_of_payments_mock_function: response_1 = self.client.post( reverse('core:send'), data=serialized_force_payment, content_type='application/octet-stream', ) make_force_payment_to_provider_mock_function.assert_called_with( requestor_eth_address=task_to_compute.requestor_ethereum_address, provider_eth_address=task_to_compute.provider_ethereum_address, value=9000, payment_ts=parse_iso_date_to_timestamp("2018-02-05 12:00:20"), ) get_list_of_payments_mock_function.assert_called_with( requestor_eth_address=task_to_compute.requestor_ethereum_address, provider_eth_address=task_to_compute.provider_ethereum_address, payment_ts=parse_iso_date_to_timestamp("2018-02-05 11:55:10"), current_time=parse_iso_date_to_timestamp("2018-02-05 12:00:20"), transaction_type=TransactionType.FORCE, ) # Sum of prices from force and batch lists of transactions which have been paid amount_paid = 10000 + 3000 # Sum of price in all TaskToCompute messages minus amount_paid amount_pending = 15000 + 7000 - amount_paid self._test_response( response_1, status=200, key=self.PROVIDER_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Provider, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:20"), 'amount_pending': amount_pending, 'amount_paid': amount_paid, }) self._assert_stored_message_counter_not_increased() last_pending_message = PendingResponse.objects.filter( delivered=False).order_by('created_at').last() self.assertEqual( last_pending_message.response_type, PendingResponse.ResponseType.ForcePaymentCommitted.name) # pylint: disable=no-member self.assertEqual(last_pending_message.client.public_key_bytes, self.REQUESTOR_PUBLIC_KEY) with freeze_time("2018-02-05 12:00:21"): response_2 = self.client.post( reverse('core:receive_out_of_band'), data=self._create_requestor_auth_message(), content_type='application/octet-stream', ) self._test_response( response_2, status=200, key=self.REQUESTOR_PRIVATE_KEY, message_type=message.concents.ForcePaymentCommitted, fields={ 'recipient_type': message.concents.ForcePaymentCommitted.Actor.Requestor, 'timestamp': parse_iso_date_to_timestamp("2018-02-05 12:00:21"), 'amount_pending': amount_pending, 'amount_paid': amount_paid, 'task_owner_key': decode_hex(task_to_compute.requestor_ethereum_public_key), }) self._assert_stored_message_counter_not_increased() last_pending_message = PendingResponse.objects.filter( delivered=False).last() self.assertIsNone(last_pending_message)