def _handle_valid_transaction(self, message: TACMessage, sender: Address, transaction: Transaction) -> None: """ Handle a valid transaction. That is: - update the game state - send a transaction confirmation both to the buyer and the seller. :param tx: the transaction. :return: None """ game = cast(Game, self.context.game) logger.debug("[{}]: Handling valid transaction: {}".format(self.context.agent_name, transaction.transaction_id)) game.transactions.add_confirmed(transaction) game.settle_transaction(transaction) # send the transaction confirmation. sender_tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id=transaction.transaction_id) counterparty_tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id=transaction.transaction_id) self.context.outbox.put_message(to=sender, sender=self.context.public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(sender_tac_msg)) self.context.outbox.put_message(to=cast(str, message.get("counterparty")), sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(counterparty_tac_msg)) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.context.agent_name, transaction.transaction_id)) logger.debug("[{}]: Current state:\n{}".format(self.context.agent_name, game.holdings_summary))
def handle(self, message: Message, sender: str) -> None: """ Dispatch message to relevant handler and respond. :param message: the message :param sender: the sender :return: None """ tx_message = cast(TransactionMessage, message) if TransactionMessage.Performative(tx_message.get( "performative")) == TransactionMessage.Performative.ACCEPT: logger.info( "[{}]: transaction confirmed by decision maker, sending to controller." .format(self.context.agent_name)) game = cast(Game, self.context.game) msg = TACMessage( type=TACMessage.Type.TRANSACTION, transaction_id=tx_message.get("transaction_digest"), counterparty=tx_message.get("counterparty"), amount_by_currency={ tx_message.get("currency"): tx_message.get("amount") }, sender_tx_fee=tx_message.get("sender_tx_fee"), counterparty_tx_fee=tx_message.get("counterparty_tx_fee"), quantities_by_good_pbk=tx_message.get( "quantities_by_good_pbk")) self.context.outbox.put_message( to=game.configuration.controller_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(msg)) else: logger.info("[{}]: transaction was not successful.".format( self.context.agent_name))
def _handle_invalid_transaction(self, message: TACMessage, sender: Address) -> None: """Handle an invalid transaction.""" tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.TRANSACTION_NOT_VALID, details={"transaction_id": message.get("transaction_id")}) self.context.outbox.put_message(to=sender, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg))
def _on_register(self, message: TACMessage, sender: Address) -> None: """ Handle a register message. If the public key is not registered, answer with an error message. :param message: the 'get agent state' TACMessage. :param sender: the public key of the sender :return: None """ parameters = cast(Parameters, self.context.parameters) agent_name = cast(str, message.get("agent_name")) if len(parameters.whitelist) != 0 and agent_name not in parameters.whitelist: logger.error("[{}]: Agent name not in whitelist: '{}'".format(self.context.agent_name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) self.context.outbox.put_message(to=sender, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg)) return game = cast(Game, self.context.game) if sender in game.registration.agent_pbk_to_name: logger.error("[{}]: Agent already registered: '{}'".format(self.context.agent_name, game.registration.agent_pbk_to_name[sender])) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_PBK_ALREADY_REGISTERED) self.context.outbox.put_message(to=sender, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg)) if agent_name in game.registration.agent_pbk_to_name.values(): logger.error("[{}]: Agent with this name already registered: '{}'".format(self.context.agent_name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED) self.context.outbox.put_message(to=sender, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg)) game.registration.register_agent(sender, agent_name) logger.info("[{}]: Agent registered: '{}'".format(self.context.agent_name, agent_name))
def _request_state_update(self) -> None: """ Request current agent state from TAC Controller. :return: None """ tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) tac_bytes = TACSerializer().encode(tac_msg) game = cast(Game, self.context.game) self.context.outbox.put_message(to=game.expected_controller_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)
def _cancel_tac(self): """Notify agents that the TAC is cancelled.""" game = cast(Game, self.context.game) logger.info("[{}]: Notifying agents that TAC is cancelled.".format(self.context.agent_name)) for agent_pbk in game.registration.agent_pbk_to_name.keys(): tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED) self.context.outbox.put_message(to=agent_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg))
def _rejoin_tac(self, controller_pbk: Address) -> None: """ Rejoin the TAC run by a Controller. :param controller_pbk: the public key of the controller. :return: None """ game = cast(Game, self.context.game) game.update_expected_controller_pbk(controller_pbk) game.update_game_phase(GamePhase.GAME_SETUP) tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) tac_bytes = TACSerializer().encode(tac_msg) self.context.outbox.put_message(to=controller_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)
def _register_to_tac(self, controller_pbk: Address) -> None: """ Register to active TAC Controller. :param controller_pbk: the public key of the controller. :return: None """ game = cast(Game, self.context.game) game.update_expected_controller_pbk(controller_pbk) game.update_game_phase(GamePhase.GAME_SETUP) tac_msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name=self.context.agent_name) tac_bytes = TACSerializer().encode(tac_msg) self.context.outbox.put_message(to=controller_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)
def handle_envelope(self, envelope: Envelope) -> None: """ Implement the reaction to an envelope. :param envelope: the envelope :return: None """ tac_msg = TACSerializer().decode(envelope.message) tac_msg_type = TACMessage.Type(tac_msg.get("type")) tac_msg = cast(TACMessage, tac_msg) game = cast(Game, self.context.game) logger.debug("[{}]: Handling controller response. type={}".format(self.context.agent_name, tac_msg_type)) try: if envelope.sender != game.expected_controller_pbk: raise ValueError("The sender of the message is not the controller agent we registered with.") if tac_msg_type == TACMessage.Type.TAC_ERROR: self._on_tac_error(tac_msg, envelope.sender) elif game.game_phase == GamePhase.PRE_GAME: raise ValueError("We do not expect a controller agent message in the pre game phase.") elif game.game_phase == GamePhase.GAME_SETUP: if tac_msg_type == TACMessage.Type.GAME_DATA: self._on_start(tac_msg, envelope.sender) elif tac_msg_type == TACMessage.Type.CANCELLED: self._on_cancelled() elif game.game_phase == GamePhase.GAME: if tac_msg_type == TACMessage.Type.TRANSACTION_CONFIRMATION: self._on_transaction_confirmed(tac_msg, envelope.sender) elif tac_msg_type == TACMessage.Type.CANCELLED: self._on_cancelled() elif tac_msg_type == TACMessage.Type.STATE_UPDATE: self._on_state_update(tac_msg, envelope.sender) elif game.game_phase == GamePhase.POST_GAME: raise ValueError("We do not expect a controller agent message in the post game phase.") except ValueError as e: logger.warning(str(e))
def _on_unregister(self, message: TACMessage, sender: Address) -> None: """ Handle a unregister message. If the public key is not registered, answer with an error message. :param message: the 'get agent state' TACMessage. :param sender: the public key of the sender :return: None """ game = cast(Game, self.context.game) if sender not in game.registration.agent_pbk_to_name: logger.error("[{}]: Agent not registered: '{}'".format(self.context.agent_name, sender)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) self.context.outbox.put_message(to=sender, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg)) else: logger.debug("[{}]: Agent unregistered: '{}'".format(self.context.agent_name, game.configuration.agent_pbk_to_name[sender])) game.registration.unregister_agent(sender)
def _start_tac(self): """Create a game and send the game configuration to every registered agent.""" game = cast(Game, self.context.game) game.create() logger.info("[{}]: Started competition:\n{}".format(self.context.agent_name, game.holdings_summary)) logger.info("[{}]: Computed equilibrium:\n{}".format(self.context.agent_name, game.equilibrium_summary)) for agent_public_key in game.configuration.agent_pbks: agent_state = game.current_agent_states[agent_public_key] tac_msg = TACMessage(tac_type=TACMessage.Type.GAME_DATA, amount_by_currency=agent_state.balance_by_currency, exchange_params_by_currency=agent_state.exchange_params_by_currency, quantities_by_good_pbk=agent_state.quantities_by_good_pbk, utility_params_by_good_pbk=agent_state.utility_params_by_good_pbk, tx_fee=game.configuration.tx_fee, agent_pbk_to_name=game.configuration.agent_pbk_to_name, good_pbk_to_name=game.configuration.good_pbk_to_name, version_id=game.configuration.version_id) logger.debug("[{}]: sending game data to '{}': {}" .format(self.context.agent_name, agent_public_key, str(tac_msg))) self.context.outbox.put_message(to=agent_public_key, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(tac_msg))
def test_tac_serialization(): """Test that the serialization for the tac message works.""" msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name='some_name') msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.UNREGISTER) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION, transaction_id='some_id', counterparty='some_address', amount_by_currency={'FET': 10}, sender_tx_fee=10, counterparty_tx_fee=10, quantities_by_good_pbk={ 'good_1': 0, 'good_2': 10 }) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.CANCELLED) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.GAME_DATA, amount_by_currency={'FET': 10}, exchange_params_by_currency={'FET': 10.0}, quantities_by_good_pbk={ 'good_1': 20, 'good_2': 15 }, utility_params_by_good_pbk={ 'good_1': 30.0, 'good_2': 50.0 }, tx_fee=20, agent_pbk_to_name={ 'agent_1': 'Agent one', 'agent_2': 'Agent two' }, good_pbk_to_name={ 'good_1': 'First good', 'good_2': 'Second good' }, version_id='game_version_1') msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id='some_id', amount_by_currency={'FET': 10}, quantities_by_good_pbk={ 'good_1': 20, 'good_2': 15 }) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg with pytest.raises(ValueError, match="Type not recognized."): with mock.patch('packages.protocols.tac.message.TACMessage.Type' ) as mocked_type: mocked_type.TRANSACTION_CONFIRMATION.value = "unknown" TACSerializer().encode(msg) msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.GENERIC_ERROR) msg_bytes = TACSerializer().encode(msg) actual_msg = TACSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg