def decode(self, obj: bytes) -> Message: """Decode bytes into a FIPA message.""" fipa_pb = fipa_pb2.FIPAMessage() fipa_pb.ParseFromString(obj) message_id = fipa_pb.message_id dialogue_reference = ( fipa_pb.dialogue_starter_reference, fipa_pb.dialogue_responder_reference, ) target = fipa_pb.target performative = fipa_pb.WhichOneof("performative") performative_id = FIPAMessage.Performative(str(performative)) performative_content = dict() if performative_id == FIPAMessage.Performative.CFP: query_type = fipa_pb.cfp.WhichOneof("query") if query_type == "nothing": query = None elif query_type == "query_bytes": query = pickle.loads(fipa_pb.cfp.query_bytes) # nosec elif query_type == "bytes": query = fipa_pb.cfp.bytes else: raise ValueError("Query type not recognized.") # pragma: no cover performative_content["query"] = query elif performative_id == FIPAMessage.Performative.PROPOSE: descriptions = [] for p_bytes in fipa_pb.propose.proposal: p = pickle.loads(p_bytes) # nosec p = cast(Description, p) descriptions.append(p) performative_content["proposal"] = descriptions elif performative_id == FIPAMessage.Performative.ACCEPT: pass elif performative_id == FIPAMessage.Performative.MATCH_ACCEPT: pass elif performative_id == FIPAMessage.Performative.ACCEPT_W_INFORM: info = pickle.loads(fipa_pb.accept_w_inform.bytes) # nosec performative_content["info"] = info elif performative_id == FIPAMessage.Performative.MATCH_ACCEPT_W_INFORM: info = pickle.loads(fipa_pb.match_accept_w_inform.bytes) # nosec performative_content["info"] = info elif performative_id == FIPAMessage.Performative.DECLINE: pass elif performative_id == FIPAMessage.Performative.INFORM: info = pickle.loads(fipa_pb.inform.bytes) # nosec performative_content["info"] = info else: raise ValueError("Performative not valid: {}.".format(performative)) return FIPAMessage( message_id=message_id, dialogue_reference=dialogue_reference, target=target, performative=performative, **performative_content )
def _handle_cfp(self, msg: FIPAMessage, dialogue: Dialogue) -> None: """ Handle the CFP. If the CFP matches the supplied services then send a PROPOSE, otherwise send a DECLINE. :param msg: the message :param dialogue: the dialogue object :return: None """ new_message_id = msg.message_id + 1 new_target = msg.message_id self.context.logger.info("[{}]: received CFP from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) query = cast(Query, msg.query) strategy = cast(Strategy, self.context.strategy) if strategy.is_matching_supply(query): proposal, weather_data = strategy.generate_proposal_and_data( query, msg.counterparty) dialogue.weather_data = weather_data dialogue.proposal = proposal self.context.logger.info( "[{}]: sending sender={} a PROPOSE with proposal={}".format( self.context.agent_name, msg.counterparty[-5:], proposal.values)) proposal_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FIPAMessage.Performative.PROPOSE, proposal=[proposal], ) dialogue.outgoing_extend(proposal_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(proposal_msg), ) else: self.context.logger.info( "[{}]: declined the CFP from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) decline_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FIPAMessage.Performative.DECLINE, ) dialogue.outgoing_extend(decline_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(decline_msg), )
def test_error_handler_handle(self): """Test the handle function.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.ACCEPT, ) msg.counterparty = "a_counterparty" self.my_error_handler.handle(message=msg)
def test_unknown_performative(): """Test that we raise an exception when the performative is unknown during check_consistency.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=1, performative=FIPAMessage.Performative.ACCEPT, ) with mock.patch.object(FIPAMessage.Performative, "__eq__", return_value=False): assert not msg._is_consistent()
def handle(self, message: Message) -> None: """ Implement the reaction to a message. :param message: the message :return: None """ # convenience representations fipa_msg = cast(FIPAMessage, message) msg_performative = FIPAMessage.Performative( message.get("performative")) # recover dialogue dialogues = cast(Dialogues, self.context.dialogues) if dialogues.is_belonging_to_registered_dialogue( fipa_msg, self.context.agent_address): dialogue = cast( Dialogue, dialogues.get_dialogue(fipa_msg, self.context.agent_address)) dialogue.incoming_extend(fipa_msg) else: self._handle_unidentified_dialogue(fipa_msg) return # handle message if msg_performative == FIPAMessage.Performative.PROPOSE: self._handle_propose(fipa_msg, dialogue) elif msg_performative == FIPAMessage.Performative.DECLINE: self._handle_decline(fipa_msg, dialogue) elif msg_performative == FIPAMessage.Performative.MATCH_ACCEPT_W_INFORM: self._handle_match_accept(fipa_msg, dialogue) elif msg_performative == FIPAMessage.Performative.INFORM: self._handle_inform(fipa_msg, dialogue)
def _handle_accept(self, msg: FIPAMessage, dialogue: Dialogue) -> None: """ Handle the ACCEPT. Respond with a MATCH_ACCEPT_W_INFORM which contains the address to send the funds to. :param msg: the message :param dialogue: the dialogue object :return: None """ new_message_id = msg.message_id + 1 new_target = msg.message_id self.context.logger.info("[{}]: received ACCEPT from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) self.context.logger.info( "[{}]: sending MATCH_ACCEPT_W_INFORM to sender={}".format( self.context.agent_name, msg.counterparty[-5:])) proposal = cast(Description, dialogue.proposal) identifier = cast(str, proposal.values.get("ledger_id")) match_accept_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FIPAMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"address": self.context.agent_addresses[identifier]}, ) dialogue.outgoing_extend(match_accept_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(match_accept_msg), )
def test_fipa_propose_serialization(): """Test that the serialization for the 'fipa' protocol works.""" proposal = [ Description({"foo1": 1, "bar1": 2}), Description({"foo2": 1, "bar2": 2}), ] msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=proposal, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope == actual_envelope actual_msg = FIPASerializer().decode(actual_envelope.message) expected_msg = msg p1 = actual_msg.get("proposal") p2 = expected_msg.get("proposal") assert p1[0].values == p2[0].values assert p1[1].values == p2[1].values
def _handle_match_accept(self, msg: FIPAMessage, dialogue: Dialogue) -> None: """ Handle the match accept. :param msg: the message :param dialogue: the dialogue object :return: None """ strategy = cast(Strategy, self.context.strategy) if strategy.is_ledger_tx: self.context.logger.info( "[{}]: received MATCH_ACCEPT_W_INFORM from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) info = msg.info address = cast(str, info.get("address")) proposal = cast(Description, dialogue.proposal) strategy = cast(Strategy, self.context.strategy) tx_msg = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[ PublicId("fetchai", "carpark_client", "0.1.0") ], tx_id="transaction0", tx_sender_addr=self.context.agent_addresses["fetchai"], tx_counterparty_addr=address, tx_amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, tx_sender_fee=strategy.max_buyer_tx_fee, tx_counterparty_fee=proposal.values["seller_tx_fee"], tx_quantities_by_good_id={}, ledger_id=proposal.values["ledger_id"], info={"dialogue_label": dialogue.dialogue_label.json}, ) 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: new_message_id = msg.message_id + 1 new_target = msg.message_id inform_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FIPAMessage.Performative.INFORM, info={"Done": "Sending payment via bank transfer"}, ) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform_msg), ) self.context.logger.info( "[{}]: informing counterparty={} of payment.".format( self.context.agent_name, msg.counterparty[-5:]))
def test_decline(self): """Test that a Decline can be sent correctly.""" decline = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.DECLINE, ) decline.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(decline), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_decline = FIPASerializer().decode(envelope.message) expected_decline.counterparty = self.crypto2.address assert expected_decline == decline
def test_accept(self): """Test that an Accept can be sent correctly.""" accept = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.ACCEPT, ) accept.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(accept), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_accept = FIPASerializer().decode(envelope.message) expected_accept.counterparty = self.crypto2.address assert expected_accept == accept
def test_fipa_encoding_unknown_performative(): """Test that we raise an exception when the performative is unknown during encoding.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=1, performative=FIPAMessage.Performative.ACCEPT, ) with pytest.raises(ValueError, match="Performative not valid:"): with mock.patch.object(FIPAMessage.Performative, "__eq__", return_value=False): FIPASerializer().encode(msg)
def test_accept_w_inform(self): """Test that an accept with address can be sent correctly.""" accept_w_inform = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.ACCEPT_W_INFORM, info={"address": "my_address"}, ) accept_w_inform.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(accept_w_inform), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) returned_accept_w_inform = FIPASerializer().decode(envelope.message) returned_accept_w_inform.counterparty = self.crypto2.address assert returned_accept_w_inform == accept_w_inform
def test_match_accept(self): """Test that a match accept can be sent correctly.""" # NOTE since the OEF SDK doesn't support the match accept, we have to use a fixed message id! match_accept = FIPAMessage( message_id=4, dialogue_reference=(str(0), ""), target=3, performative=FIPAMessage.Performative.MATCH_ACCEPT, ) match_accept.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(match_accept), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_match_accept = FIPASerializer().decode(envelope.message) expected_match_accept.counterparty = self.crypto2.address assert expected_match_accept == match_accept
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 is not None and tx_msg_response.performative == TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT ): self.context.logger.info( "[{}]: transaction was successful.".format(self.context.agent_name) ) json_data = {"transaction_digest": tx_msg_response.tx_digest} info = cast(Dict[str, Any], tx_msg_response.info) dialogue_label = DialogueLabel.from_json( cast(Dict[str, str], info.get("dialogue_label")) ) dialogues = cast(Dialogues, self.context.dialogues) dialogue = dialogues.dialogues[dialogue_label] fipa_msg = cast(FIPAMessage, dialogue.last_incoming_message) new_message_id = fipa_msg.message_id + 1 new_target_id = fipa_msg.message_id counterparty_addr = dialogue.dialogue_label.dialogue_opponent_addr inform_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target_id, performative=FIPAMessage.Performative.INFORM, info=json_data, ) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message( to=counterparty_addr, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform_msg), ) self.context.logger.info( "[{}]: informing counterparty={} of transaction digest.".format( self.context.agent_name, counterparty_addr[-5:] ) ) else: self.context.logger.info( "[{}]: transaction was not successful.".format(self.context.agent_name) )
def test_inform(self): """Test that an inform can be sent correctly.""" payload = {"foo": "bar"} inform = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.INFORM, info=payload, ) inform.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) returned_inform = FIPASerializer().decode(envelope.message) returned_inform.counterparty = self.crypto2.address assert returned_inform == inform
def on_cfp( self, msg_id: int, dialogue_id: int, origin: Address, target: int, query: CFP_TYPES, ) -> None: """ On cfp event handler. :param msg_id: the message id. :param dialogue_id: the dialogue id. :param origin: the address of the sender. :param target: the message target. :param query: the query. :return: None """ assert self.in_queue is not None assert self.loop is not None logger.warning( "Accepting on_cfp from deprecated API: msg_id={}, dialogue_id={}, origin={}, target={}. Continuing dialogue via envelopes!".format( msg_id, dialogue_id, origin, target ) ) try: query = pickle.loads(query) # nosec except Exception as e: logger.debug( "When trying to unpickle the query the following exception occured: {}".format( e ) ) msg = FIPAMessage( message_id=msg_id, dialogue_reference=(str(dialogue_id), ""), target=target, performative=FIPAMessage.Performative.CFP, query=query if query != b"" else None, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to=self.address, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) asyncio.run_coroutine_threadsafe( self.in_queue.put(envelope), self.loop ).result()
def test_performative_match_accept(): """Test the serialization - deserialization of the match_accept performative.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=1, performative=FIPAMessage.Performative.MATCH_ACCEPT, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) msg.counterparty = "sender" envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope == actual_envelope deserialised_msg = FIPASerializer().decode(envelope.message) assert msg.get("performative") == deserialised_msg.get("performative")
def test_performative_inform(): """Test the serialization-deserialization of the inform performative.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=1, performative=FIPAMessage.Performative.INFORM, info={"foo": "bar"}, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope == actual_envelope deserialised_msg = FIPASerializer().decode(envelope.message) assert msg.get("performative") == deserialised_msg.get("performative")
def test_performative_match_accept_with_inform(): """Test the serialization - deserialization of the match_accept_with_address performative.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=1, performative=FIPAMessage.Performative.MATCH_ACCEPT_W_INFORM, info={"address": "dummy_address", "signature": "my_signature"}, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) envelope_bytes = envelope.encode() actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope assert expected_envelope == actual_envelope deserialised_msg = FIPASerializer().decode(envelope.message) assert msg.get("performative") == deserialised_msg.get("performative")
async def test_messages(self): """Test that at the beginning, the search request returns an empty search result.""" msg = FIPAMessage((str(0), ""), 0, 0, FIPAMessage.Performative.CFP, query=None) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to=DEFAULT_OEF, sender=self.address_1, protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) with pytest.raises(AEAConnectionError): await OEFLocalConnection( self.address_1, self.node, connection_id=PublicId("fetchai", "local", "0.1.0"), ).send(envelope) self.multiplexer1.connect() msg = FIPAMessage( (str(0), str(1)), 0, 0, FIPAMessage.Performative.CFP, query=None ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="this_address_does_not_exist", sender=self.address_1, protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert response_envelope.protocol_id == OEFMessage.protocol_id assert response_envelope.sender == DEFAULT_OEF result = OEFSerializer().decode(response_envelope.message) assert result.get("type") == OEFMessage.Type.DIALOGUE_ERROR
def _handle_search(self, agents: List[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 agents={}, stopping search.".format( self.context.agent_name, list(map(lambda x: x[-5:], agents)) ) ) strategy = cast(Strategy, self.context.strategy) # stopping search strategy.is_searching = False # pick first agent found opponent_addr = agents[0] dialogues = cast(Dialogues, self.context.dialogues) dialogue = dialogues.create_self_initiated( opponent_addr, self.context.agent_address, is_seller=False ) query = strategy.get_service_query() self.context.logger.info( "[{}]: sending CFP to agent={}".format( self.context.agent_name, opponent_addr[-5:] ) ) cfp_msg = FIPAMessage( message_id=FIPAMessage.STARTING_MESSAGE_ID, dialogue_reference=dialogue.dialogue_label.dialogue_reference, performative=FIPAMessage.Performative.CFP, target=FIPAMessage.STARTING_TARGET, query=query, ) dialogue.outgoing_extend(cfp_msg) self.context.outbox.put_message( to=opponent_addr, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(cfp_msg), ) else: self.context.logger.info( "[{}]: found no agents, continue searching.".format( self.context.agent_name ) )
def _handle_decline(self, msg: FIPAMessage, dialogue: Dialogue) -> None: """ Handle the decline. :param msg: the message :param dialogue: the dialogue object :return: None """ self.context.logger.info( "[{}]: received DECLINE from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) target = msg.get("target") dialogues = cast(Dialogues, self.context.dialogues) if target == 1: dialogues.dialogue_stats.add_dialogue_endstate( Dialogue.EndState.DECLINED_CFP, dialogue.is_self_initiated) elif target == 3: dialogues.dialogue_stats.add_dialogue_endstate( Dialogue.EndState.DECLINED_ACCEPT, dialogue.is_self_initiated)
def test_propose(self): """Test that a Propose can be sent correctly.""" propose_empty = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=[], ) propose_empty.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(propose_empty), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_propose_empty = FIPASerializer().decode(envelope.message) expected_propose_empty.counterparty = self.crypto2.address assert expected_propose_empty == propose_empty propose_descriptions = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=[ Description( {"foo": "bar"}, DataModel("foobar", [Attribute("foo", str, True)]) ) ], ) propose_descriptions.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(propose_descriptions), ) ) envelope = self.multiplexer2.get(block=True, timeout=2.0) expected_propose_descriptions = FIPASerializer().decode(envelope.message) expected_propose_descriptions.counterparty = self.crypto2.address assert expected_propose_descriptions == propose_descriptions
def test_error_invalid_message(self): """Test the invalid message.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.ACCEPT, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to=self.address, sender=self.address, protocol_id=OEFMessage.protocol_id, message=msg_bytes, ) self.my_error_handler.send_invalid_message(envelope) envelope = self.my_aea.inbox.get(block=True, timeout=1.0) msg = DefaultSerializer().decode(envelope.message) assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.INVALID_MESSAGE
def test_error_skill_unsupported_protocol(self): """Test the unsupported error message.""" msg = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.ACCEPT, ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to=self.address, sender=self.address, protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) self.my_error_handler.send_unsupported_protocol(envelope) envelope = self.my_aea.inbox.get(block=True, timeout=1.0) msg = DefaultSerializer().decode(envelope.message) assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.UNSUPPORTED_PROTOCOL
def test_cfp(self): """Test that a CFP can be sent correctly.""" cfp_message = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.CFP, query=Query([Constraint("something", ConstraintType(">", 1))]), ) cfp_message.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(cfp_message), ) ) envelope = self.multiplexer2.get(block=True, timeout=5.0) expected_cfp_message = FIPASerializer().decode(envelope.message) expected_cfp_message.counterparty = self.crypto2.address assert expected_cfp_message == cfp_message cfp_none = FIPAMessage( message_id=0, dialogue_reference=(str(0), ""), target=0, performative=FIPAMessage.Performative.CFP, query=None, ) cfp_none.counterparty = self.crypto2.address self.multiplexer1.put( Envelope( to=self.crypto2.address, sender=self.crypto1.address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(cfp_none), ) ) envelope = self.multiplexer2.get(block=True, timeout=5.0) expected_cfp_none = FIPASerializer().decode(envelope.message) expected_cfp_none.counterparty = self.crypto2.address assert expected_cfp_none == cfp_none
def test_dialogues_self_initiated_is_seller(self): """Test an end to end scenario of seller-client dialogue.""" # Initialise a dialogue seller_dialogue = self.seller_dialogues.create_self_initiated( dialogue_opponent_addr=self.client_addr, dialogue_starter_addr=self.seller_addr, is_seller=True, ) # Register the dialogue to the dictionary of dialogues. self.seller_dialogues.dialogues[seller_dialogue.dialogue_label] = cast( FIPADialogue, seller_dialogue ) # Send a message to the client. cfp_msg = FIPAMessage( message_id=1, dialogue_reference=seller_dialogue.dialogue_label.dialogue_reference, target=0, performative=FIPAMessage.Performative.CFP, query=None, ) cfp_msg.counterparty = self.seller_addr seller_dialogue.outgoing_extend(cfp_msg) # Creates a new dialogue for the client side based on the income message. client_dialogue = self.client_dialogues.create_opponent_initiated( dialogue_opponent_addr=cfp_msg.counterparty, dialogue_reference=cfp_msg.dialogue_reference, is_seller=False, ) # Register the dialogue to the dictionary of dialogues. self.client_dialogues.dialogues[client_dialogue.dialogue_label] = cast( FIPADialogue, client_dialogue ) # Extend the incoming list of messages. client_dialogue.incoming_extend(cfp_msg) # Checks if the message we received is permitted for a new dialogue or if it is a registered dialogue. assert self.client_dialogues.is_permitted_for_new_dialogue( client_dialogue.last_incoming_message ), "Should be permitted since the first incoming msg is CFP" # Generate a proposal message to send to the seller. proposal = [ Description({"foo1": 1, "bar1": 2}), Description({"foo2": 1, "bar2": 2}), ] message_id = cfp_msg.message_id + 1 target = cfp_msg.message_id proposal_msg = FIPAMessage( message_id=message_id, dialogue_reference=client_dialogue.dialogue_label.dialogue_reference, target=target, performative=FIPAMessage.Performative.PROPOSE, proposal=proposal, ) proposal_msg.counterparty = self.client_addr # Extends the outgoing list of messages. client_dialogue.outgoing_extend(proposal_msg) # Seller received the message and we extend the incoming messages list. seller_dialogue.incoming_extend(proposal_msg) assert not self.seller_dialogues.is_permitted_for_new_dialogue( seller_dialogue.last_incoming_message ), "Should not be permitted since we registered the cfp message." response = self.seller_dialogues.is_belonging_to_registered_dialogue( proposal_msg, agent_addr=self.seller_addr ) assert response, "We expect the response from the function to be true." # Test the self_initiated_dialogue explicitly message_id = proposal_msg.message_id + 1 target = proposal_msg.message_id accept_msg = FIPAMessage( message_id=message_id, dialogue_reference=seller_dialogue.dialogue_label.dialogue_reference, target=target, performative=FIPAMessage.Performative.ACCEPT_W_INFORM, info={"address": "dummy_address"}, ) accept_msg.counterparty = self.client_addr # Adds the message to the client outgoing list. seller_dialogue.outgoing_extend(accept_msg) # Adds the message to the seller incoming message list. client_dialogue.incoming_extend(accept_msg) # Check if this message is registered to a dialogue. response = self.seller_dialogues.is_belonging_to_registered_dialogue( accept_msg, agent_addr=self.seller_addr ) assert not response, "We expect the response from the function to be true."
def test_dialogues_self_initiated_no_seller(self): """Test an end to end scenario of client-seller dialogue.""" # Initialise a dialogue client_dialogue = self.client_dialogues.create_self_initiated( dialogue_opponent_addr=self.seller_addr, dialogue_starter_addr=self.client_addr, is_seller=False, ) # Register the dialogue to the dictionary of dialogues. self.client_dialogues.dialogues[client_dialogue.dialogue_label] = cast( FIPADialogue, client_dialogue ) # Send a message to the seller. cfp_msg = FIPAMessage( message_id=1, dialogue_reference=client_dialogue.dialogue_label.dialogue_reference, target=0, performative=FIPAMessage.Performative.CFP, query=None, ) cfp_msg.counterparty = self.client_addr # Checking that I cannot retrieve the dialogue. retrieved_dialogue = self.client_dialogues.is_belonging_to_registered_dialogue( cfp_msg, "client" ) assert not retrieved_dialogue, "Should not belong to registered dialogue" # Checking the value error when we are trying to retrieve an un-existing dialogue. with pytest.raises(ValueError, match="Should have found dialogue."): self.client_dialogues.get_dialogue(cfp_msg, self.client_addr) # Extends the outgoing list of messages. client_dialogue.outgoing_extend(cfp_msg) # Creates a new dialogue for the seller side based on the income message. seller_dialogue = self.seller_dialogues.create_opponent_initiated( dialogue_opponent_addr=cfp_msg.counterparty, dialogue_reference=cfp_msg.dialogue_reference, is_seller=True, ) # Register the dialogue to the dictionary of dialogues. self.seller_dialogues.dialogues[seller_dialogue.dialogue_label] = cast( FIPADialogue, seller_dialogue ) # Extend the incoming list of messages. seller_dialogue.incoming_extend(cfp_msg) # Check that both fields in the dialogue_reference are set. last_msg = seller_dialogue.last_incoming_message assert last_msg == cfp_msg, "The messages must be equal" dialogue_reference = cast( Tuple[str, str], last_msg.body.get("dialogue_reference") ) assert ( dialogue_reference[0] != "" and dialogue_reference[1] == "" ), "The dialogue_reference is not set correctly." # Checks if the message we received is permitted for a new dialogue or if it is a registered dialogue. assert self.seller_dialogues.is_permitted_for_new_dialogue( seller_dialogue.last_incoming_message ), "Should be permitted since the first incoming msg is CFP" # Generate a proposal message to send to the client. proposal = [ Description({"foo1": 1, "bar1": 2}), Description({"foo2": 1, "bar2": 2}), ] message_id = cfp_msg.message_id + 1 target = cfp_msg.message_id proposal_msg = FIPAMessage( message_id=message_id, dialogue_reference=seller_dialogue.dialogue_label.dialogue_reference, target=target, performative=FIPAMessage.Performative.PROPOSE, proposal=proposal, ) proposal_msg.counterparty = self.seller_addr # Extends the outgoing list of messages. seller_dialogue.outgoing_extend(proposal_msg) # Client received the message and we extend the incoming messages list. client_dialogue.incoming_extend(proposal_msg) # Check that both fields in the dialogue_reference are set. last_msg = client_dialogue.last_incoming_message assert last_msg == proposal_msg, "The two messages must be equal." dialogue_reference = cast( Tuple[str, str], last_msg.body.get("dialogue_reference") ) assert ( dialogue_reference[0] != "" and dialogue_reference[1] != "" ), "The dialogue_reference is not setup properly." assert not self.client_dialogues.is_permitted_for_new_dialogue( client_dialogue.last_incoming_message ), "Should not be permitted since we registered the cfp message." response = self.client_dialogues.is_belonging_to_registered_dialogue( proposal_msg, agent_addr=self.client_addr ) assert response, "We expect the response from the function to be true." # Retrieve the dialogue based on the received message. retrieved_dialogue = self.client_dialogues.get_dialogue( proposal_msg, self.client_addr ) assert retrieved_dialogue.dialogue_label is not None # Create an accept_w_inform message to send seller. message_id = proposal_msg.message_id + 1 target = proposal_msg.message_id accept_msg = FIPAMessage( message_id=message_id, dialogue_reference=client_dialogue.dialogue_label.dialogue_reference, target=target, performative=FIPAMessage.Performative.ACCEPT_W_INFORM, info={"address": "dummy_address"}, ) accept_msg.counterparty = self.client_addr # Adds the message to the client outgoing list. client_dialogue.outgoing_extend(accept_msg) # Adds the message to the seller incoming message list. seller_dialogue.incoming_extend(accept_msg) # Check if this message is registered to a dialogue. response = self.seller_dialogues.is_belonging_to_registered_dialogue( accept_msg, agent_addr=self.seller_addr ) assert response, "We expect the response from the function to be true." retrieved_dialogue = self.seller_dialogues.get_dialogue( accept_msg, self.seller_addr ) assert retrieved_dialogue.dialogue_label in self.seller_dialogues.dialogues
def test_communication(): """Test that two multiplexer can communicate through the node.""" with LocalNode() as node: local_public_id = PublicId("fetchai", "local", "0.1.0") multiplexer1 = Multiplexer([ OEFLocalConnection("multiplexer1", node, connection_id=local_public_id) ]) multiplexer2 = Multiplexer([ OEFLocalConnection("multiplexer2", node, connection_id=local_public_id) ]) multiplexer1.connect() multiplexer2.connect() msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"hello", ) msg_bytes = DefaultSerializer().encode(msg) envelope = Envelope( to="multiplexer2", sender="multiplexer1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes, ) multiplexer1.put(envelope) msg = FIPAMessage((str(0), ""), 0, 0, FIPAMessage.Performative.CFP, query=None) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="multiplexer2", sender="multiplexer1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) multiplexer1.put(envelope) msg = FIPAMessage( (str(0), str(1)), 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[], ) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="multiplexer2", sender="multiplexer1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) multiplexer1.put(envelope) msg = FIPAMessage((str(0), str(1)), 0, 0, FIPAMessage.Performative.ACCEPT) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="multiplexer2", sender="multiplexer1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) multiplexer1.put(envelope) msg = FIPAMessage((str(0), str(1)), 0, 0, FIPAMessage.Performative.DECLINE) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope( to="multiplexer2", sender="multiplexer1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes, ) multiplexer1.put(envelope) envelope = multiplexer2.get(block=True, timeout=1.0) msg = DefaultSerializer().decode(envelope.message) assert envelope.protocol_id == DefaultMessage.protocol_id assert msg.get("content") == b"hello" envelope = multiplexer2.get(block=True, timeout=1.0) msg = FIPASerializer().decode(envelope.message) assert envelope.protocol_id == FIPAMessage.protocol_id assert msg.get("performative") == FIPAMessage.Performative.CFP envelope = multiplexer2.get(block=True, timeout=1.0) msg = FIPASerializer().decode(envelope.message) assert envelope.protocol_id == FIPAMessage.protocol_id assert msg.get("performative") == FIPAMessage.Performative.PROPOSE envelope = multiplexer2.get(block=True, timeout=1.0) msg = FIPASerializer().decode(envelope.message) assert envelope.protocol_id == FIPAMessage.protocol_id assert msg.get("performative") == FIPAMessage.Performative.ACCEPT envelope = multiplexer2.get(block=True, timeout=1.0) msg = FIPASerializer().decode(envelope.message) assert envelope.protocol_id == FIPAMessage.protocol_id assert msg.get("performative") == FIPAMessage.Performative.DECLINE multiplexer1.disconnect() multiplexer2.disconnect()
def _handle_inform(self, msg: FIPAMessage, dialogue: Dialogue) -> None: """ Handle the INFORM. If the INFORM message contains the transaction_digest then verify that it is settled, otherwise do nothing. If the transaction is settled, send the data, otherwise do nothing. :param msg: the message :param dialogue: the dialogue object :return: None """ new_message_id = msg.message_id + 1 new_target = msg.message_id self.context.logger.info("[{}]: received INFORM from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) strategy = cast(Strategy, self.context.strategy) if strategy.is_ledger_tx and ("transaction_digest" in msg.info.keys()): tx_digest = msg.info["transaction_digest"] self.context.logger.info( "[{}]: checking whether transaction={} has been received ...". format(self.context.agent_name, tx_digest)) proposal = cast(Description, dialogue.proposal) ledger_id = cast(str, proposal.values.get("ledger_id")) is_valid = self.context.ledger_apis.is_tx_valid( ledger_id, tx_digest, self.context.agent_addresses[ledger_id], msg.counterparty, cast(str, proposal.values.get("tx_nonce")), cast(int, proposal.values.get("price")), ) # TODO: check the tx_digest references a transaction with the correct terms if is_valid: token_balance = self.context.ledger_apis.token_balance( ledger_id, cast(str, self.context.agent_addresses.get(ledger_id))) self.context.logger.info( "[{}]: transaction={} settled, new balance={}. Sending data to sender={}" .format( self.context.agent_name, tx_digest, token_balance, msg.counterparty[-5:], )) inform_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label. dialogue_reference, target=new_target, performative=FIPAMessage.Performative.INFORM, info=dialogue.data_for_sale, ) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform_msg), ) dialogues = cast(Dialogues, self.context.dialogues) dialogues.dialogue_stats.add_dialogue_endstate( Dialogue.EndState.SUCCESSFUL, dialogue.is_self_initiated) else: self.context.logger.info( "[{}]: transaction={} not settled, aborting".format( self.context.agent_name, tx_digest)) elif "Done" in msg.info.keys(): inform_msg = FIPAMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FIPAMessage.Performative.INFORM, info=dialogue.data_for_sale, ) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform_msg), ) dialogues = cast(Dialogues, self.context.dialogues) dialogues.dialogue_stats.add_dialogue_endstate( Dialogue.EndState.SUCCESSFUL, dialogue.is_self_initiated) else: self.context.logger.warning( "[{}]: did not receive transaction digest from sender={}.". format(self.context.agent_name, msg.counterparty[-5:]))