def _handle_cft(self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue) -> None: """ Handle call for terms. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object :return: None """ query = ml_trade_msg.query self.context.logger.info("got a Call for Terms from {}.".format( ml_trade_msg.counterparty[-5:])) strategy = cast(Strategy, self.context.strategy) if not strategy.is_matching_supply(query): self.context.logger.info("query does not match supply.") return terms = strategy.generate_terms() self.context.logger.info( "sending to the address={} a Terms message: {}".format( ml_trade_msg.counterparty[-5:], terms.values)) terms_msg = MlTradeMessage( performative=MlTradeMessage.Performative.TERMS, dialogue_reference=ml_trade_dialogue.dialogue_label. dialogue_reference, message_id=ml_trade_msg.message_id + 1, target=ml_trade_msg.message_id, terms=terms, ) terms_msg.counterparty = ml_trade_msg.counterparty ml_trade_dialogue.update(terms_msg) self.context.outbox.put_message(message=terms_msg)
def _handle_accept(self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue) -> None: """ Handle accept. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object :return: None """ terms = ml_trade_msg.terms self.context.logger.info("got an Accept from {}: {}".format( ml_trade_msg.counterparty[-5:], terms.values)) strategy = cast(Strategy, self.context.strategy) if not strategy.is_valid_terms(terms): self.context.logger.info("terms are not valid.") return data = strategy.sample_data(terms.values["batch_size"]) self.context.logger.info( "sending to address={} a Data message: shape={}".format( ml_trade_msg.counterparty[-5:], data[0].shape)) payload = pickle.dumps(data) # nosec data_msg = MlTradeMessage( performative=MlTradeMessage.Performative.DATA, dialogue_reference=ml_trade_dialogue.dialogue_label. dialogue_reference, message_id=ml_trade_msg.message_id + 1, target=ml_trade_msg.message_id, terms=terms, payload=payload, ) data_msg.counterparty = ml_trade_msg.counterparty ml_trade_dialogue.update(data_msg) self.context.outbox.put_message(message=data_msg)
def test_terms_serialization(): """Test the serialization for 'terms' speech-act works.""" msg = MlTradeMessage( message_id=2, target=1, performative=MlTradeMessage.Performative.TERMS, terms=Description({ "foo1": 1, "bar1": 2 }), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert (expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg
def test_data_serialization(): """Test the serialization for 'data' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.DATA, terms=Description({ "foo1": 1, "bar1": 2 }), payload=b"some_payload", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", protocol_id=MlTradeMessage.protocol_id, message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert expected_envelope.protocol_id == actual_envelope.protocol_id assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg
def test_accept_serialization(): """Test the serialization for 'accept' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, terms=Description({ "foo1": 1, "bar1": 2 }), tx_digest="some_tx_digest", ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert (expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg
def test_ml_messge_consistency(): """Test the consistency of the message.""" dm = DataModel("ml_datamodel", [Attribute("dataset_id", str, True)]) query = Query([Constraint("dataset_id", ConstraintType("==", "fmnist"))], model=dm) msg = MlTradeMessage(performative=MlTradeMessage.Performative.CFP, query=query) with mock.patch.object(MlTradeMessage.Performative, "__eq__", return_value=False): assert not msg._is_consistent()
def _handle_transaction_digest( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue) -> None: """ Handle a message of transaction_digest performative. :param ledger_api_message: the ledger api message :param ledger_api_dialogue: the ledger api dialogue """ ml_trade_dialogue = ledger_api_dialogue.associated_ml_trade_dialogue self.context.logger.info( "transaction was successfully submitted. Transaction digest={}". format(ledger_api_msg.transaction_digest)) ml_trade_msg = cast(Optional[MlTradeMessage], ml_trade_dialogue.last_incoming_message) assert ml_trade_msg is not None, "Could not retrieve ml_trade message" ml_accept = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, message_id=ml_trade_msg.message_id + 1, dialogue_reference=ml_trade_dialogue.dialogue_label. dialogue_reference, target=ml_trade_msg.message_id, tx_digest=ledger_api_msg.transaction_digest.body, terms=ml_trade_msg.terms, ) ml_accept.counterparty = ml_trade_msg.counterparty ml_trade_dialogue.update(ml_accept) self.context.outbox.put_message(message=ml_accept) self.context.logger.info( "informing counterparty={} of transaction digest={}.".format( ml_trade_msg.counterparty[-5:], ledger_api_msg.transaction_digest, ))
def _handle_search(self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue) -> None: """ Handle the search response. :param agents: the agents returned by the search :return: None """ if len(oef_search_msg.agents) == 0: self.context.logger.info("found no agents, continue searching.") return self.context.logger.info("found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), )) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False query = strategy.get_service_query() ml_trade_dialogues = cast(MlTradeDialogues, self.context.ml_trade_dialogues) for idx, opponent_address in enumerate(oef_search_msg.agents): if idx >= strategy.max_negotiations: continue self.context.logger.info("sending CFT to agent={}".format( opponent_address[-5:])) cft_msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, dialogue_reference=ml_trade_dialogues. new_self_initiated_dialogue_reference(), query=query, ) cft_msg.counterparty = opponent_address ml_trade_dialogues.update(cft_msg) self.context.outbox.put_message(message=cft_msg)
def test_cfp_serialization(): """Test the serialization for 'cfp' speech-act works.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) msg.to = "receiver" envelope = Envelope( to=msg.to, sender="sender", message=msg, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope.to == actual_envelope.to assert expected_envelope.sender == actual_envelope.sender assert (expected_envelope.protocol_specification_id == actual_envelope.protocol_specification_id) assert expected_envelope.message != actual_envelope.message actual_msg = MlTradeMessage.serializer.decode(actual_envelope.message) actual_msg.to = actual_envelope.to actual_msg.sender = actual_envelope.sender expected_msg = msg assert expected_msg == actual_msg
def decode(obj: bytes) -> Message: """ Decode bytes into a 'MlTrade' message. :param obj: the bytes object. :return: the 'MlTrade' message. """ message_pb = ProtobufMessage() ml_trade_pb = ml_trade_pb2.MlTradeMessage() message_pb.ParseFromString(obj) message_id = message_pb.dialogue_message.message_id dialogue_reference = ( message_pb.dialogue_message.dialogue_starter_reference, message_pb.dialogue_message.dialogue_responder_reference, ) target = message_pb.dialogue_message.target ml_trade_pb.ParseFromString(message_pb.dialogue_message.content) performative = ml_trade_pb.WhichOneof("performative") performative_id = MlTradeMessage.Performative(str(performative)) performative_content = dict() # type: Dict[str, Any] if performative_id == MlTradeMessage.Performative.CFP: pb2_query = ml_trade_pb.cfp.query query = Query.decode(pb2_query) performative_content["query"] = query elif performative_id == MlTradeMessage.Performative.TERMS: pb2_terms = ml_trade_pb.terms.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms elif performative_id == MlTradeMessage.Performative.ACCEPT: pb2_terms = ml_trade_pb.accept.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms tx_digest = ml_trade_pb.accept.tx_digest performative_content["tx_digest"] = tx_digest elif performative_id == MlTradeMessage.Performative.DATA: pb2_terms = ml_trade_pb.data.terms terms = Description.decode(pb2_terms) performative_content["terms"] = terms payload = ml_trade_pb.data.payload performative_content["payload"] = payload else: raise ValueError( "Performative not valid: {}.".format(performative_id)) return MlTradeMessage(message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content)
def _handle_accept(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle accept. :param ml_trade_msg: the ml trade message :return: None """ terms = ml_trade_msg.terms self.context.logger.info("Got an Accept from {}: {}".format( ml_trade_msg.counterparty[-5:], terms.values)) strategy = cast(Strategy, self.context.strategy) if not strategy.is_valid_terms(terms): return batch_size = terms.values["batch_size"] data = strategy.sample_data(batch_size) self.context.logger.info( "[{}]: sending to address={} a Data message: shape={}".format( self.context.agent_name, ml_trade_msg.counterparty[-5:], data[0].shape)) payload = pickle.dumps(data) # nosec data_msg = MlTradeMessage( performative=MlTradeMessage.Performative.DATA, terms=terms, payload=payload) self.context.outbox.put_message( to=ml_trade_msg.counterparty, sender=self.context.agent_address, protocol_id=MlTradeMessage.protocol_id, message=MlTradeSerializer().encode(data_msg), )
def _handle_cft(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle call for terms. :param ml_trade_msg: the ml trade message :return: None """ query = ml_trade_msg.query self.context.logger.info( "Got a Call for Terms from {}: query={}".format( ml_trade_msg.counterparty[-5:], query)) strategy = cast(Strategy, self.context.strategy) if not strategy.is_matching_supply(query): return terms = strategy.generate_terms() self.context.logger.info( "[{}]: sending to the address={} a Terms message: {}".format( self.context.agent_name, ml_trade_msg.counterparty[-5:], terms.values)) terms_msg = MlTradeMessage( performative=MlTradeMessage.Performative.TERMS, terms=terms) self.context.outbox.put_message( to=ml_trade_msg.counterparty, sender=self.context.agent_address, protocol_id=MlTradeMessage.protocol_id, message=MlTradeSerializer().encode(terms_msg), )
def _handle_search(self, agents: Tuple[str, ...]) -> None: """ Handle the search response. :param agents: the agents returned by the search :return: None """ if len(agents) == 0: self.context.logger.info( "[{}]: found no agents, continue searching.".format( self.context.agent_name)) return self.context.logger.info( "[{}]: found agents={}, stopping search.".format( self.context.agent_name, list(map(lambda x: x[-5:], agents)))) strategy = cast(Strategy, self.context.strategy) strategy.is_searching = False query = strategy.get_service_query() for opponent_address in agents: self.context.logger.info("[{}]: sending CFT to agent={}".format( self.context.agent_name, opponent_address[-5:])) cft_msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=query) self.context.outbox.put_message( to=opponent_address, sender=self.context.agent_address, protocol_id=MlTradeMessage.protocol_id, message=MlTradeSerializer().encode(cft_msg), )
def test_ml_message_creation(): """Test the creation of a ml message.""" dm = DataModel("ml_datamodel", [Attribute("dataset_id", str, True)]) query = Query([Constraint("dataset_id", ConstraintType("==", "fmnist"))], model=dm) msg = MlTradeMessage(performative=MlTradeMessage.Performative.CFP, query=query) msg_bytes = MlTradeSerializer().encode(msg) recovered_msg = MlTradeSerializer().decode(msg_bytes) assert recovered_msg == msg terms = Description({ "batch_size": 5, "price": 10, "seller_tx_fee": 5, "buyer_tx_fee": 2, "currency_id": "FET", "ledger_id": "fetch", "address": "agent1", }) msg = MlTradeMessage(performative=MlTradeMessage.Performative.TERMS, terms=terms) msg_bytes = MlTradeSerializer().encode(msg) recovered_msg = MlTradeSerializer().decode(msg_bytes) assert recovered_msg == msg tx_digest = "This is the transaction digest." msg = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, terms=terms, tx_digest=tx_digest, ) msg_bytes = MlTradeSerializer().encode(msg) recovered_msg = MlTradeSerializer().decode(msg_bytes) assert recovered_msg == msg data = np.zeros((5, 2)), np.zeros((5, 2)) payload = pickle.dumps(data) # nosec msg = MlTradeMessage(performative=MlTradeMessage.Performative.DATA, terms=terms, payload=payload) msg_bytes = MlTradeSerializer().encode(msg) recovered_msg = MlTradeSerializer().decode(msg_bytes) assert recovered_msg == msg recovered_data = pickle.loads(recovered_msg.payload) # nosec assert np.array_equal(recovered_data, data)
def test_fipa_incorrect_message(mocked_enforce): """Test that we raise an exception when the fipa message is incorrect.""" with mock.patch.object(ml_trade_message_logger, "error") as mock_logger: MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) mock_logger.assert_any_call("some error")
def test_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = MlTradeMessage( performative=MlTradeMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(MlTradeMessage.Performative, "__eq__", return_value=False): MlTradeMessage.serializer.encode(msg)
def _handle_unidentified_dialogue(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "received invalid ml_trade message={}, unidentified dialogue.". format(ml_trade_msg)) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg, _ = default_dialogues.create( counterparty=ml_trade_msg.sender, performative=DefaultMessage.Performative.ERROR, error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": ml_trade_msg.encode()}, ) self.context.outbox.put_message(message=default_msg)
def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ tx_msg_response = cast(TransactionMessage, message) if ( tx_msg_response.performative == TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT ): self.context.logger.info( "[{}]: transaction was successful.".format(self.context.agent_name) ) info = tx_msg_response.info terms = cast(Description, info.get("terms")) ml_accept = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, tx_digest=tx_msg_response.tx_digest, terms=terms, ) self.context.outbox.put_message( to=tx_msg_response.tx_counterparty_addr, sender=self.context.agent_address, protocol_id=MlTradeMessage.protocol_id, message=MlTradeSerializer().encode(ml_accept), ) self.context.logger.info( "[{}]: Sending accept to counterparty={} with transaction digest={} and terms={}.".format( self.context.agent_name, tx_msg_response.tx_counterparty_addr[-5:], tx_msg_response.tx_digest, terms.values, ) ) else: self.context.logger.info( "[{}]: transaction was not successful.".format(self.context.agent_name) )
def _handle_unidentified_dialogue(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle an unidentified dialogue. :param fipa_msg: the message """ self.context.logger.info( "[{}]: received invalid ml_trade message={}, unidentified dialogue." .format(self.context.agent_name, ml_trade_msg)) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg = DefaultMessage( performative=DefaultMessage.Performative.ERROR, dialogue_reference=default_dialogues. new_self_initiated_dialogue_reference(), error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", error_data={"ml_trade_message": ml_trade_msg.encode()}, ) default_msg.counterparty = ml_trade_msg.counterparty default_dialogues.update(default_msg) self.context.outbox.put_message(message=default_msg)
def test_ml_wrong_message_creation(): """Test the creation of a ml message.""" msg = MlTradeMessage(performative=MlTradeMessage.Performative.CFP, query="") assert not msg._is_consistent()
def _handle_terms(self, ml_trade_msg: MlTradeMessage) -> None: """ Handle the terms of the request. :param ml_trade_msg: the ml trade message :return: None """ terms = ml_trade_msg.terms self.context.logger.info( "Received terms message from {}: terms={}".format( ml_trade_msg.counterparty[-5:], terms.values ) ) strategy = cast(Strategy, self.context.strategy) acceptable = strategy.is_acceptable_terms(terms) affordable = strategy.is_affordable_terms(terms) if not acceptable and affordable: self.context.logger.info( "[{}]: rejecting, terms are not acceptable and/or affordable".format( self.context.agent_name ) ) return if strategy.is_ledger_tx: # propose the transaction to the decision maker for settlement on the ledger tx_msg = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId("fetchai", "ml_train", "0.1.0")], tx_id=strategy.get_next_transition_id(), tx_sender_addr=self.context.agent_addresses[terms.values["ledger_id"]], tx_counterparty_addr=terms.values["address"], tx_amount_by_currency_id={ terms.values["currency_id"]: -terms.values["price"] }, tx_sender_fee=terms.values["buyer_tx_fee"], tx_counterparty_fee=terms.values["seller_tx_fee"], tx_quantities_by_good_id={}, ledger_id=terms.values["ledger_id"], info={"terms": terms, "counterparty_addr": ml_trade_msg.counterparty}, ) # this is used to send the terms later - because the seller is stateless and must know what terms have been accepted self.context.decision_maker_message_queue.put_nowait(tx_msg) self.context.logger.info( "[{}]: proposing the transaction to the decision maker. Waiting for confirmation ...".format( self.context.agent_name ) ) else: # accept directly with a dummy transaction digest, no settlement ml_accept = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, tx_digest=DUMMY_DIGEST, terms=terms, ) self.context.outbox.put_message( to=ml_trade_msg.counterparty, sender=self.context.agent_address, protocol_id=MlTradeMessage.protocol_id, message=MlTradeSerializer().encode(ml_accept), ) self.context.logger.info( "[{}]: sending dummy transaction digest ...".format( self.context.agent_name ) )
def _handle_terms(self, ml_trade_msg: MlTradeMessage, ml_trade_dialogue: MlTradeDialogue) -> None: """ Handle the terms of the request. :param ml_trade_msg: the ml trade message :param ml_trade_dialogue: the dialogue object :return: None """ terms = ml_trade_msg.terms self.context.logger.info( "received terms message from {}: terms={}".format( ml_trade_msg.counterparty[-5:], terms.values)) strategy = cast(Strategy, self.context.strategy) acceptable = strategy.is_acceptable_terms(terms) affordable = strategy.is_affordable_terms(terms) if not acceptable and affordable: self.context.logger.info( "rejecting, terms are not acceptable and/or affordable") return if strategy.is_ledger_tx: # construct a tx for settlement on the ledger ledger_api_dialogues = cast(LedgerApiDialogues, self.context.ledger_api_dialogues) ledger_api_msg = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=ledger_api_dialogues. new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=terms.values["ledger_id"], sender_address=self.context.agent_addresses[ terms.values["ledger_id"]], counterparty_address=terms.values["address"], amount_by_currency_id={ terms.values["currency_id"]: -terms.values["price"] }, is_sender_payable_tx_fee=True, quantities_by_good_id={"ml_training_data": 1}, nonce=uuid.uuid4().hex, fee_by_currency_id={terms.values["currency_id"]: 1}, ), ) ledger_api_msg.counterparty = LEDGER_API_ADDRESS ledger_api_dialogue = cast( Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg)) assert (ledger_api_dialogue is not None), "Error when creating ledger api dialogue." ledger_api_dialogue.associated_ml_trade_dialogue = ml_trade_dialogue self.context.outbox.put_message(message=ledger_api_msg) self.context.logger.info( "requesting transfer transaction from ledger api...") else: # accept directly with a dummy transaction digest, no settlement ml_accept = MlTradeMessage( performative=MlTradeMessage.Performative.ACCEPT, dialogue_reference=ml_trade_dialogue.dialogue_label. dialogue_reference, message_id=ml_trade_msg.message_id + 1, target=ml_trade_msg.message_id, tx_digest=DUMMY_DIGEST, terms=terms, ) ml_accept.counterparty = ml_trade_msg.counterparty ml_trade_dialogue.update(ml_accept) self.context.outbox.put_message(message=ml_accept) self.context.logger.info("sending dummy transaction digest ...")