From bc47160787edf3eae79c58ab5e58291aea760152 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 12 Aug 2019 12:59:49 +0200 Subject: [PATCH 001/107] add agent state. Add a property to the 'Agent' object that tracks the execution state of the agent. In particular, the agent can be in the following states: - AgentState.INITIATED: when the Agent object has been created. - AgentState.CONNECTED: when the agent is connected. - AgentState.RUNNING: when the agent is running. --- tac/agents/v1/agent.py | 34 ++++++++++++++++++-- tac/agents/v1/mail.py | 14 +++++--- tests/conftest.py | 2 +- tests/test_agent/__init__.py | 1 + tests/test_agent/test_agent_state.py | 48 ++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 tests/test_agent/__init__.py create mode 100644 tests/test_agent/test_agent_state.py diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index ffbece72..319cdc93 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -24,6 +24,7 @@ import time from abc import abstractmethod +from enum import Enum from typing import Optional from tac.agents.v1.mail import MailBox, InBox, OutBox @@ -32,6 +33,13 @@ logger = logging.getLogger(__name__) +class AgentState(Enum): + """Enumeration for an agent state.""" + INITIATED = "initiated" + CONNECTED = "connected" + RUNNING = "running" + + class Liveness: """Determines the liveness of the agent.""" @@ -84,6 +92,28 @@ def liveness(self) -> Liveness: """Get the liveness.""" return self._liveness + @property + def agent_state(self) -> AgentState: + """ + The state of the agent. + + In particular, it can be one of the following states: + - AgentState.INITIATED: when the Agent object has been created. + - AgentState.CONNECTED: when the agent is connected. + - AgentState.RUNNING: when the agent is running. + + :return the agent state. + :raises ValueError: if the state does not satisfy any of the foreseen conditions. + """ + if self.mail_box is None or not self.mail_box.is_connected: + return AgentState.INITIATED + elif self.mail_box.is_connected and self.liveness.is_stopped: + return AgentState.CONNECTED + elif self.mail_box.is_connected and not self.liveness.is_stopped: + return AgentState.RUNNING + else: + raise ValueError("Agent state not recognized.") + def start(self) -> None: """ Start the agent. @@ -92,9 +122,9 @@ def start(self) -> None: """ self.mail_box.start() self.liveness._is_stopped = False - self.run_main_loop() + self._run_main_loop() - def run_main_loop(self) -> None: + def _run_main_loop(self) -> None: """ Run the main loop of the agent. diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index a3e08a93..08d25774 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -122,6 +122,16 @@ def mail_stats(self) -> MailStats: """Get the mail stats.""" return self._mail_stats + @property + def is_running(self) -> bool: + """Check whether the mailbox is running.""" + return self._mail_box_thread is None + + @property + def is_connected(self) -> bool: + """Check whether the mailbox is connected to an OEF node.""" + return self._oef_proxy.is_connected() + def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: """ Handle a message. @@ -170,10 +180,6 @@ def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> No """ self.in_queue.put(DialogueErrorMessage(answer_id, dialogue_id, origin)) - def is_running(self) -> bool: - """Check whether the mailbox is running.""" - return self._mail_box_thread is None - def connect(self) -> bool: """ Connect to the OEF Node. If it fails, then sleep for 3 seconds and try to reconnect again. diff --git a/tests/conftest.py b/tests/conftest.py index 6296ba77..a48134e7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) CUR_PATH = inspect.getfile(inspect.currentframe()) -ROOT_DIR = os.path.dirname(CUR_PATH) + "/.." +ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") def pytest_addoption(parser): diff --git a/tests/test_agent/__init__.py b/tests/test_agent/__init__.py new file mode 100644 index 00000000..7c68785e --- /dev/null +++ b/tests/test_agent/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py new file mode 100644 index 00000000..7455edc4 --- /dev/null +++ b/tests/test_agent/test_agent_state.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +import time +from threading import Thread + +from tac.agents.v1.agent import Agent, AgentState +from tac.agents.v1.mail import FIPAMailBox + + +class TestAgent(Agent): + """A class to implement an agent for testing.""" + + def __init__(self): + super().__init__("test_agent", "127.0.0.1", 10000) + self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000) + + def act(self) -> None: + """Perform actions.""" + + def react(self) -> None: + """React to incoming events.""" + + def update(self) -> None: + """Update the current state of the agent.""" + + +def test_agent_initiated(): + """Test that when the agent is initiated, her state is AgentState.INITIATED""" + test_agent = Agent("test_agent", "127.0.0.1", 10000) + assert test_agent.agent_state == AgentState.INITIATED + + +def test_agent_connected(network_node): + """Test that when the agent is connected, her state is AgentState.CONNECTED""" + test_agent = TestAgent() + assert test_agent.agent_state == AgentState.CONNECTED + test_agent.stop() + + +def test_agent_running(network_node): + """Test that when the agent is running, her state is AgentState.RUNNING""" + test_agent = TestAgent() + job = Thread(target=test_agent.start) + job.start() + time.sleep(1.0) + assert test_agent.agent_state == AgentState.RUNNING + test_agent.stop() + job.join() + From fb0df0f60e9e68ec85afa8a48dee002ee6ad9d39 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 12 Aug 2019 14:11:56 +0200 Subject: [PATCH 002/107] fix stylecheck. --- tac/agents/v1/agent.py | 3 ++- tests/test_agent/__init__.py | 22 ++++++++++++++++++++- tests/test_agent/test_agent_state.py | 29 ++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index 319cdc93..daa2c7af 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -35,6 +35,7 @@ class AgentState(Enum): """Enumeration for an agent state.""" + INITIATED = "initiated" CONNECTED = "connected" RUNNING = "running" @@ -95,7 +96,7 @@ def liveness(self) -> Liveness: @property def agent_state(self) -> AgentState: """ - The state of the agent. + Get the state of the agent. In particular, it can be one of the following states: - AgentState.INITIATED: when the Agent object has been created. diff --git a/tests/test_agent/__init__.py b/tests/test_agent/__init__.py index 7c68785e..26208ebe 100644 --- a/tests/test_agent/__init__.py +++ b/tests/test_agent/__init__.py @@ -1 +1,21 @@ -# -*- coding: utf-8 -*- \ No newline at end of file +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""The tests module contains the tests for the agent module.""" diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 7455edc4..5434e180 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -1,4 +1,25 @@ # -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test the agent state.""" + import time from threading import Thread @@ -10,6 +31,7 @@ class TestAgent(Agent): """A class to implement an agent for testing.""" def __init__(self): + """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000) self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000) @@ -24,20 +46,20 @@ def update(self) -> None: def test_agent_initiated(): - """Test that when the agent is initiated, her state is AgentState.INITIATED""" + """Test that when the agent is initiated, her state is AgentState.INITIATED.""" test_agent = Agent("test_agent", "127.0.0.1", 10000) assert test_agent.agent_state == AgentState.INITIATED def test_agent_connected(network_node): - """Test that when the agent is connected, her state is AgentState.CONNECTED""" + """Test that when the agent is connected, her state is AgentState.CONNECTED.""" test_agent = TestAgent() assert test_agent.agent_state == AgentState.CONNECTED test_agent.stop() def test_agent_running(network_node): - """Test that when the agent is running, her state is AgentState.RUNNING""" + """Test that when the agent is running, her state is AgentState.RUNNING.""" test_agent = TestAgent() job = Thread(target=test_agent.start) job.start() @@ -45,4 +67,3 @@ def test_agent_running(network_node): assert test_agent.agent_state == AgentState.RUNNING test_agent.stop() job.join() - From d1c96dc806817d2d9d4d7a4c7ec81843e75d3808 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 12 Aug 2019 14:36:40 +0200 Subject: [PATCH 003/107] add 'setup' and 'teardown' method. --- tac/agents/v1/agent.py | 22 ++++++++++++++++++++++ tac/agents/v1/base/participant_agent.py | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index ffbece72..95f5aeef 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -100,6 +100,9 @@ def run_main_loop(self) -> None: :return: None """ + logger.debug("[{}]: Calling setup method...".format(self.name)) + self.setup() + logger.debug("[{}]: Start processing messages...".format(self.name)) while not self.liveness.is_stopped: self.act() @@ -107,6 +110,9 @@ def run_main_loop(self) -> None: self.react() self.update() + logger.debug("[{}]: Calling teardown method...".format(self.name)) + self.teardown() + self.stop() logger.debug("[{}]: Exiting main loop...".format(self.name)) @@ -120,6 +126,14 @@ def stop(self) -> None: self.liveness._is_stopped = True self.mail_box.stop() + @abstractmethod + def setup(self) -> None: + """ + Set up the agent. + + :return: None + """ + @abstractmethod def act(self) -> None: """ @@ -142,3 +156,11 @@ def update(self) -> None: :return None """ + + @abstractmethod + def teardown(self) -> None: + """ + Tear down the agent. + + :return: None + """ diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index 943c4f56..35067772 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -163,3 +163,9 @@ def start(self, rejoin: bool = False) -> None: logger.debug("Trying to rejoin in 5 seconds...") time.sleep(5.0) self.start(rejoin=True) + + def setup(self) -> None: + """Set up the agent.""" + + def teardown(self) -> None: + """Tear down the agent.""" From 1bf6c64f2525ff26557c9e6fed821f5d3e522ecd Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 12 Aug 2019 16:19:09 +0200 Subject: [PATCH 004/107] add documentation related to the 'setup' and the 'teardown' method. --- docs/sections/baseline_agent.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sections/baseline_agent.rst b/docs/sections/baseline_agent.rst index 672597d6..fa2310cc 100644 --- a/docs/sections/baseline_agent.rst +++ b/docs/sections/baseline_agent.rst @@ -9,10 +9,15 @@ In this section, we describe the baseline agent v1 :class:`~tac.agents.v1.exampl Main Loop and Event Loop ------------------------ -A generic :class:`~tac.agents.v1.agent.Agent` can be started via :meth:`~tac.agents.v1.agent.Agent.start`. This starts the :class:`~tac.agents.v1.mail.MailBox` and a main loop implemented in :meth:`~tac.agents.v1.agent.Agent.run_main_loop`. +A generic :class:`~tac.agents.v1.agent.Agent` can be started via :meth:`~tac.agents.v1.agent.Agent.start`. This starts the :class:`~tac.agents.v1.mail.MailBox` and a main loop implemented in :meth:`~tac.agents.v1.agent.Agent._run_main_loop`. The mailbox is responsible for handling incoming and outgoing messages. The :class:`~tac.agents.v1.mail.InBox` enqueues incoming messages on an :attr:`~tac.agents.v1.mail.MailBox.in_queue` for later processing, the :class:`~tac.agents.v1.mail.OutBox` picks messages from the :attr:`~tac.agents.v1.mail.MailBox.out_queue` and sends them to the OEF. +Before the execution of the main loop, the framework will call the user's implementation of the +:meth:`~tac.agents.v1.agent.Agent.setup` method, to let the initialization of the resources needed to the agent. +Upon exit, the framework will call the user's implementation of the +:meth:`~tac.agents.v1.agent.Agent.teardown` method, to let the release of the initialized resources. + The main loop deals with processing enqueued events/messages. It has the methods :meth:`~tac.agents.v1.agent.Agent.act` and :meth:`~tac.agents.v1.agent.Agent.react` which handle the active and reactive agent behaviours. From af00b72392ea6b189d67978b89da2c2a255faf94 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 12 Aug 2019 16:21:20 +0200 Subject: [PATCH 005/107] add documentation related to the agent state. --- docs/sections/baseline_agent.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sections/baseline_agent.rst b/docs/sections/baseline_agent.rst index 672597d6..95278fcf 100644 --- a/docs/sections/baseline_agent.rst +++ b/docs/sections/baseline_agent.rst @@ -13,6 +13,9 @@ A generic :class:`~tac.agents.v1.agent.Agent` can be started via :meth:`~tac.age The mailbox is responsible for handling incoming and outgoing messages. The :class:`~tac.agents.v1.mail.InBox` enqueues incoming messages on an :attr:`~tac.agents.v1.mail.MailBox.in_queue` for later processing, the :class:`~tac.agents.v1.mail.OutBox` picks messages from the :attr:`~tac.agents.v1.mail.MailBox.out_queue` and sends them to the OEF. +At any moment, the execution state of the agent can be inspected by reading the +:meth:`~tac.agents.v1.agent.Agent.agent_state` property. + The main loop deals with processing enqueued events/messages. It has the methods :meth:`~tac.agents.v1.agent.Agent.act` and :meth:`~tac.agents.v1.agent.Agent.react` which handle the active and reactive agent behaviours. From 2c2b20a94cfb6832ff19564883fb50aedb1d4791 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 13 Aug 2019 07:23:38 +0200 Subject: [PATCH 006/107] add 'debug' flag to 'Agent' constructor. Add a debug flag to the Agent constructor to switch on the debug mode. The debug mode is useful when we don't need all the components working. For instance, this might be useful when we don't want to start a mailbox alongside the agent. --- tac/agents/v1/agent.py | 14 +++++- tac/agents/v1/base/participant_agent.py | 6 ++- tac/agents/v1/mail.py | 6 +-- tests/test_agent/test_misc.py | 63 +++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 tests/test_agent/test_misc.py diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index daa2c7af..e3316680 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -57,7 +57,12 @@ def is_stopped(self) -> bool: class Agent: """This class implements a template agent.""" - def __init__(self, name: str, oef_addr: str, oef_port: int = 10000, private_key_pem_path: Optional[str] = None, timeout: Optional[float] = 1.0) -> None: + def __init__(self, name: str, + oef_addr: str, + oef_port: int = 10000, + private_key_pem_path: Optional[str] = None, + timeout: Optional[float] = 1.0, + debug: bool = False) -> None: """ Instantiate the agent. @@ -66,6 +71,7 @@ def __init__(self, name: str, oef_addr: str, oef_port: int = 10000, private_key_ :param oef_port: TCP/IP port of the OEF Agent :param private_key_pem_path: the path to the private key of the agent. :param timeout: the time in (fractions of) seconds to time out an agent between act and react + :param debug: if True, run the agent in debug mode. :return: None """ @@ -74,6 +80,8 @@ def __init__(self, name: str, oef_addr: str, oef_port: int = 10000, private_key_ self._liveness = Liveness() self._timeout = timeout + self.debug = debug + self.mail_box = None # type: Optional[MailBox] self.in_box = None # type: Optional[InBox] self.out_box = None # type: Optional[OutBox] @@ -121,7 +129,9 @@ def start(self) -> None: :return: None """ - self.mail_box.start() + if not self.debug: + self.mail_box.start() + self.liveness._is_stopped = False self._run_main_loop() diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index 943c4f56..c4cb18cb 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -55,7 +55,8 @@ def __init__(self, name: str, services_interval: int = 10, pending_transaction_timeout: int = 30, dashboard: Optional[AgentDashboard] = None, - private_key_pem: Optional[str] = None): + private_key_pem: Optional[str] = None, + debug: bool = False): """ Initialize a participant agent. @@ -69,8 +70,9 @@ def __init__(self, name: str, :param pending_transaction_timeout: the timeout for cleanup of pending negotiations and unconfirmed transactions. :param dashboard: a Visdom dashboard to visualize agent statistics during the competition. :param private_key_pem: the path to a private key in PEM format. + :param debug: if True, run the agent in debug mode. """ - super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout) + super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) self.mail_box = FIPAMailBox(self.crypto.public_key, oef_addr, oef_port) self.in_box = InBox(self.mail_box) self.out_box = OutBox(self.mail_box) diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index 08d25774..08a29a8d 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -111,7 +111,6 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000) -> Non :return: None """ super().__init__(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) - self.connect() self.in_queue = Queue() self.out_queue = Queue() self._mail_box_thread = None # type: Optional[Thread] @@ -204,6 +203,7 @@ def start(self) -> None: :return: None """ + self.connect() self._mail_box_thread = Thread(target=super().run) self._mail_box_thread.start() @@ -358,7 +358,7 @@ def send_nowait(self) -> None: class FIPAMailBox(MailBox): """The FIPAMailBox enqueues additionally FIPA specific messages.""" - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000) -> None: + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, **kwargs) -> None: """ Instantiate the FIPAMailBox. @@ -368,7 +368,7 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000) -> Non :return: None """ - super().__init__(public_key, oef_addr, oef_port) + super().__init__(public_key, oef_addr, oef_port, **kwargs) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: """ diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py new file mode 100644 index 00000000..eb9ef0dc --- /dev/null +++ b/tests/test_agent/test_misc.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test miscellaneous features for the agent module.""" +import time +from threading import Thread + +import pytest + +from tac.agents.v1.agent import Agent + +from tac.agents.v1.base.participant_agent import ParticipantAgent +from tac.agents.v1.examples.strategy import BaselineStrategy +from tac.agents.v1.mail import FIPAMailBox + + +class TestAgent(Agent): + """A class to implement an agent for testing.""" + + def __init__(self, **kwargs): + """Initialize the test agent.""" + super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) + self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000) + + def act(self) -> None: + """Perform actions.""" + + def react(self) -> None: + """React to incoming events.""" + + def update(self) -> None: + """Update the current state of the agent.""" + + +def test_debug_flag_true(): + """ + Test that the debug mode works correctly. + + That is, an agent can be initialized without the OEF running. + """ + test_agent = TestAgent(debug=True) + job = Thread(target=test_agent.start) + job.start() + time.sleep(1.0) + test_agent.stop() + job.join() From 0a26c3f178a75037e4359c624704ab52162e097d Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 13 Aug 2019 10:35:56 +0200 Subject: [PATCH 007/107] add debug mode to mailbox. The mailbox in debug mode does not connect when started. Also, the OutBox does not actually send the enqueued messages, but drops them. --- tac/agents/v1/mail.py | 10 +++++- tests/test_agent/test_agent_state.py | 2 +- tests/test_agent/test_misc.py | 50 +++++++++++++++++++--------- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index 08a29a8d..34341b4a 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -100,13 +100,14 @@ def search_end(self, search_id: int, nb_search_results: int) -> None: class MailBox(OEFAgent): """The MailBox enqueues incoming messages, searches and errors from the OEF and sends outgoing messages to the OEF.""" - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000) -> None: + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, debug: bool = False) -> None: """ Instantiate the mailbox. :param public_key: the public key of the agent :param oef_addr: TCP/IP address of the OEF Agent :param oef_port: TCP/IP port of the OEF Agent + :param debug: start the mailbox in debug mode. :return: None """ @@ -116,6 +117,8 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000) -> Non self._mail_box_thread = None # type: Optional[Thread] self._mail_stats = MailStats() + self.debug = debug + @property def mail_stats(self) -> MailStats: """Get the mail stats.""" @@ -323,6 +326,11 @@ def send_nowait(self) -> None: logger.debug("Checking for message or search query on out queue...") while not self.out_queue.empty(): out = self.out_queue.get_nowait() + + if self.mail_box.debug: + logger.warning("Dropping message of type '{}' from the out queue...".format(type(out.message))) + continue + if isinstance(out, OutContainer) and out.message is not None: logger.debug("Outgoing message type: type={}".format(type(out.message))) self.mail_box.send_message(out.message_id, out.dialogue_id, out.destination, out.message) diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 5434e180..a55394ba 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -54,8 +54,8 @@ def test_agent_initiated(): def test_agent_connected(network_node): """Test that when the agent is connected, her state is AgentState.CONNECTED.""" test_agent = TestAgent() + test_agent.mail_box.connect() assert test_agent.agent_state == AgentState.CONNECTED - test_agent.stop() def test_agent_running(network_node): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index eb9ef0dc..4428ca42 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -19,16 +19,11 @@ # ------------------------------------------------------------------------------ """Test miscellaneous features for the agent module.""" -import time -from threading import Thread - -import pytest +from threading import Timer +from unittest.mock import patch, MagicMock from tac.agents.v1.agent import Agent - -from tac.agents.v1.base.participant_agent import ParticipantAgent -from tac.agents.v1.examples.strategy import BaselineStrategy -from tac.agents.v1.mail import FIPAMailBox +from tac.agents.v1.mail import FIPAMailBox, InBox, OutBox, OutContainer class TestAgent(Agent): @@ -37,7 +32,9 @@ class TestAgent(Agent): def __init__(self, **kwargs): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) - self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000, debug=kwargs.get("debug", False)) + self.in_box = InBox(self.mail_box) + self.out_box = OutBox(self.mail_box) def act(self) -> None: """Perform actions.""" @@ -49,15 +46,38 @@ def update(self) -> None: """Update the current state of the agent.""" -def test_debug_flag_true(): +def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): """ - Test that the debug mode works correctly. + Test that, in debug mode, the agent's main loop can be run without the OEF running. - That is, an agent can be initialized without the OEF running. + In particular, assert that the methods 'act', 'react' and 'update' are called. """ test_agent = TestAgent(debug=True) - job = Thread(target=test_agent.start) + + test_agent.act = MagicMock(test_agent.act) + test_agent.react = MagicMock(test_agent.react) + test_agent.update = MagicMock(test_agent.update) + + job = Timer(1.0, test_agent.stop) job.start() - time.sleep(1.0) - test_agent.stop() + test_agent.start() job.join() + + test_agent.act.assert_called() + test_agent.react.assert_called() + test_agent.update.assert_called() + + +def test_that_when_debug_flag_true_we_drop_out_messages(): + """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" + with patch('logging.Logger.warning') as mock: + test_agent = TestAgent(debug=True) + job = Timer(1.0, test_agent.stop) + job.start() + msg = OutContainer(b"this is a message.", 0, 0, "destination") + test_agent.out_box.out_queue.put_nowait(msg) + test_agent.out_box.send_nowait() + test_agent.start() + job.join() + + mock.assert_called_with("Dropping message of type '' from the out queue...") From 7991358a6a5528f788b3198a3d9979ebabd1a409 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 14 Aug 2019 11:38:26 +0100 Subject: [PATCH 008/107] Updates Pipfile and bumps oef version to 0.6.1 --- Pipfile | 2 +- Pipfile.lock | 213 +++++++++++++++++++++++++-------------------------- 2 files changed, 106 insertions(+), 109 deletions(-) diff --git a/Pipfile b/Pipfile index d288db05..f7d3b03a 100644 --- a/Pipfile +++ b/Pipfile @@ -26,7 +26,7 @@ python-dateutil = "*" visdom = "*" cryptography = "*" base58 = "*" -fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git", +fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} oef = {version="*", index="test-pypi"} sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 65d8f67a..b743d573 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -356,10 +356,10 @@ }, "nbconvert": { "hashes": [ - "sha256:138381baa41d83584459b5cfecfc38c800ccf1f37d9ddd0bd440783346a4c39c", - "sha256:4a978548d8383f6b2cfca4a3b0543afb77bc7cb5a96e8b424337ab58c12da9bc" + "sha256:427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", + "sha256:48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709" ], - "version": "==5.5.0" + "version": "==5.6.0" }, "nbformat": { "hashes": [ @@ -400,11 +400,11 @@ }, "oef": { "hashes": [ - "sha256:1944b11632c4af841e373f52e3e96941821f6b4bb4dbc430f790c1b436880618", - "sha256:cab9e251bfd1476f332de5046a83e0c01d0ce75ddfefb1bd614359a2b904d726" + "sha256:9498fde163c1252acd4afb0034732d0e95fb849c94a83399baa2aa9fd5b262d8", + "sha256:c61e9834b2f1716757fbbe143b0e8407a88b9ccbde2c7026688751348275ce61" ], "index": "test-pypi", - "version": "==0.6.0" + "version": "==0.6.1" }, "packaging": { "hashes": [ @@ -459,28 +459,25 @@ }, "protobuf": { "hashes": [ - "sha256:05c36022fef3c7d3562ac22402965c0c2b9fe8421f459bb377323598996e407f", - "sha256:139b7eadcca0a861d60b523cb37d9475505e0dfb07972436b15407c2b968d87e", - "sha256:15f683006cb77fb849b1f561e509b03dd2b7dcc749086b8dd1831090d0ba4740", - "sha256:2ad566b7b7cdd8717c7af1825e19f09e8fef2787b77fcb979588944657679604", - "sha256:35cfcf97642ef62108e10a9431c77733ec7eaab8e32fe4653de20403429907cb", - "sha256:387822859ecdd012fdc25ec879f7f487da6e1d5b1ae6115e227e6be208836f71", - "sha256:4df14cbe1e7134afcfdbb9f058949e31c466de27d9b2f7fb4da9e0b67231b538", - "sha256:573e3fe6582e0ec4e0ed6576127893f0824c48f01bce48e10114f04f3e953af8", - "sha256:586c4ca37a7146d4822c700059f150ac3445ce0aef6f3ea258640838bb892dc2", - "sha256:58b11e530e954d29ab3180c48dc558a409f705bf16739fd4e0d3e07924ad7add", - "sha256:63c8c98ccb8c95f41c18fb829aeeab21c6249adee4ed75354125bdc44488f30e", - "sha256:72edcbacd0c73eef507d2ff1af99a6c27df18e66a3ff4351e401182e4de62b03", - "sha256:83dc8a561b3b954fd7002c690bb83278b8d1742a1e28abba9aaef28b0c8b437d", - "sha256:913171ecc84c2726b86574e40549a0ea619d569657c5a5ff782a3be7d81401a5", - "sha256:aabb7c741d3416671c3e6fe7c52970a226e6a8274417a97d7d795f953fadef36", - "sha256:b3452bbda12b1cbe2187d416779de07b2ab4c497d83a050e43c344778763721d", - "sha256:c5d5b8d4a9212338297fa1fa44589f69b470c0ba1d38168b432d577176b386a8", - "sha256:d7ec7dda851e7f259ff8fdd844f88a7b01496ea2fc907569a01ec7b9f28527f4", - "sha256:d86ee389c2c4fc3cebabb8ce83a8e97b6b3b5dc727b7419c1ccdc7b6e545a233", - "sha256:f2db8c754de788ab8be5e108e1e967c774c0942342b4f8aaaf14063889a6cfdc" - ], - "version": "==3.9.0" + "sha256:00a1b0b352dc7c809749526d1688a64b62ea400c5b05416f93cfb1b11a036295", + "sha256:01acbca2d2c8c3f7f235f1842440adbe01bbc379fa1cbdd80753801432b3fae9", + "sha256:0a795bca65987b62d6b8a2d934aa317fd1a4d06a6dd4df36312f5b0ade44a8d9", + "sha256:0ec035114213b6d6e7713987a759d762dd94e9f82284515b3b7331f34bfaec7f", + "sha256:31b18e1434b4907cb0113e7a372cd4d92c047ce7ba0fa7ea66a404d6388ed2c1", + "sha256:32a3abf79b0bef073c70656e86d5bd68a28a1fbb138429912c4fc07b9d426b07", + "sha256:55f85b7808766e5e3f526818f5e2aeb5ba2edcc45bcccede46a3ccc19b569cb0", + "sha256:64ab9bc971989cbdd648c102a96253fdf0202b0c38f15bd34759a8707bdd5f64", + "sha256:64cf847e843a465b6c1ba90fb6c7f7844d54dbe9eb731e86a60981d03f5b2e6e", + "sha256:917c8662b585470e8fd42f052661fc66d59fccaae450a60044307dcbf82a3335", + "sha256:a657e3c27870f2b16d4bd415b00d46a846a827758d1414600872b1dc57202178", + "sha256:afed9003d7f2be2c3df20f64220c30faec441073731511728a2cb4cab4cd46a6", + "sha256:bf8e05d638b585d1752c5a84247134a0350d3a8b73d3632489a014a9f6f1e758", + "sha256:d831b047bd69becaf64019a47179eb22118a50dd008340655266a906c69c6417", + "sha256:de2760583ed28749ff885789c1cbc6c9c06d6de92fc825740ab99deb2f25ea4d", + "sha256:eabc4cf1bc19689af8022ba52fd668564a8d96e0d08f3b4732d26a64255216a4", + "sha256:fcff6086c86fb1628d94ea455c7b9de898afc50378042927a59df8065a79a549" + ], + "version": "==3.9.1" }, "pycparser": { "hashes": [ @@ -525,33 +522,33 @@ }, "pyzmq": { "hashes": [ - "sha256:00dd015159eaeb1c0731ad49310e1f5d839c9a35a15e4f3267f5052233fad99b", - "sha256:03913b6beb8e7b417b9910b0ee1fd5d62e9626d218faefbe879d70714ceab1a2", - "sha256:13f17386df81d5e6efb9a4faea341d8de22cdc82e49a326dded26e33f42a3112", - "sha256:16c6281d96885db1e15f7047ddc1a8f48ff4ea35d31ca709f4d2eb39f246d356", - "sha256:17efab4a804e31f58361631256d660214204046f9e2b962738b171b9ad674ea7", - "sha256:2b79919ddeff3d3c96aa6087c21d294c8db1c01f6bfeee73324944683685f419", - "sha256:2f832e4711657bb8d16ea1feba860f676ec5f14fb9fe3b449b5953a60e89edae", - "sha256:31a11d37ac73107363b47e14c94547dbfc6a550029c3fe0530be443199026fc2", - "sha256:33a3e928e6c3138c675e1d6702dd11f6b7050177d7aab3fc322db6e1d2274490", - "sha256:34a38195a6d3a9646cbcdaf8eb245b4d935c7a57f7e1b3af467814bc1a92467e", - "sha256:42900054f1500acef6df7428edf806abbf641bf92eb9ceded24aa863397c3bae", - "sha256:4ccc7f3c63aa9d744dadb62c49eda2d0e7de55649b80c45d7c684d70161a69af", - "sha256:5b220c37c346e6575db8c88a940c1fc234f99ce8e0068c408919bb8896c4b6d2", - "sha256:6074848da5c8b44a1ca40adf75cf65aa92bc80f635e8249aa8f37a69b2b9b6f5", - "sha256:61a4155964bd4a14ef95bf46cb1651bcf8dcbbed8c0108e9c974c1fcbb57788f", - "sha256:62b5774688326600c52f587f7a033ca6b6284bef4c8b1b5fda32480897759eac", - "sha256:65a9ffa4f9f085d696f16fd7541f34b3c357d25fe99c90e3bce2ea59c3b5b4b6", - "sha256:76a077d2c30f8adc5e919a55985a784b96aeca69b53c1ea6fd5723d3ae2e6f53", - "sha256:8e5b4c51557071d6379d6dc1f54f35e9f6a137f5e84e102efb869c8d3c13c8ff", - "sha256:917f73e07cc04f0678a96d93e7bb8b1adcccdde9ccfe202e622814f4d1d1ecfd", - "sha256:91c75d3c4c357f9643e739db9e79ab9681b2f6ae8ec5678d6ef2ea0d01532596", - "sha256:923dd91618b100bb4c92ab9ed7b65825a595b8524a094ce03c7cb2aaae7d353b", - "sha256:9849054e0355e2bc7f4668766a25517ba76095031c9ff5e39ae8949cee5bb024", - "sha256:c9d453933f0e3f44b9759189f2a18aa765f7f1a4345c727c18ebe8ad0d748d26", - "sha256:cb7514936277abce64c2f4c56883e5704d85ed04d98d2d432d1c6764003bb003" - ], - "version": "==18.0.2" + "sha256:01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", + "sha256:021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", + "sha256:0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", + "sha256:05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", + "sha256:1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", + "sha256:22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", + "sha256:260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", + "sha256:2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", + "sha256:2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", + "sha256:343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", + "sha256:41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", + "sha256:856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", + "sha256:85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", + "sha256:93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", + "sha256:98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", + "sha256:9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", + "sha256:a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", + "sha256:b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", + "sha256:cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", + "sha256:dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", + "sha256:dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", + "sha256:e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", + "sha256:ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", + "sha256:f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", + "sha256:fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45" + ], + "version": "==18.1.0" }, "requests": { "hashes": [ @@ -562,24 +559,24 @@ }, "scipy": { "hashes": [ - "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", - "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", - "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", - "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", - "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", - "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", - "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", - "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", - "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", - "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", - "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", - "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", - "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", - "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", - "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", - "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee" + "sha256:0baa64bf42592032f6f6445a07144e355ca876b177f47ad8d0612901c9375bef", + "sha256:243b04730d7223d2b844bda9500310eecc9eda0cba9ceaf0cde1839f8287dfa8", + "sha256:2643cfb46d97b7797d1dbdb6f3c23fe3402904e3c90e6facfe6a9b98d808c1b5", + "sha256:396eb4cdad421f846a1498299474f0a3752921229388f91f60dc3eda55a00488", + "sha256:3ae3692616975d3c10aca6d574d6b4ff95568768d4525f76222fb60f142075b9", + "sha256:435d19f80b4dcf67dc090cc04fde2c5c8a70b3372e64f6a9c58c5b806abfa5a8", + "sha256:46a5e55850cfe02332998b3aef481d33f1efee1960fe6cfee0202c7dd6fc21ab", + "sha256:75b513c462e58eeca82b22fc00f0d1875a37b12913eee9d979233349fce5c8b2", + "sha256:7ccfa44a08226825126c4ef0027aa46a38c928a10f0a8a8483c80dd9f9a0ad44", + "sha256:89dd6a6d329e3f693d1204d5562dd63af0fd7a17854ced17f9cbc37d5b853c8d", + "sha256:a81da2fe32f4eab8b60d56ad43e44d93d392da228a77e229e59b51508a00299c", + "sha256:a9d606d11eb2eec7ef893eb825017fbb6eef1e1d0b98a5b7fc11446ebeb2b9b1", + "sha256:ac37eb652248e2d7cbbfd89619dce5ecfd27d657e714ed049d82f19b162e8d45", + "sha256:cbc0611699e420774e945f6a4e2830f7ca2b3ee3483fca1aa659100049487dd5", + "sha256:d02d813ec9958ed63b390ded463163685af6025cb2e9a226ec2c477df90c6957", + "sha256:dd3b52e00f93fd1c86f2d78243dfb0d02743c94dd1d34ffea10055438e63b99d" ], - "version": "==1.3.0" + "version": "==1.3.1" }, "six": { "hashes": [ @@ -900,10 +897,10 @@ }, "ipykernel": { "hashes": [ - "sha256:346189536b88859937b5f4848a6fd85d1ad0729f01724a411de5cae9b618819c", - "sha256:f0e962052718068ad3b1d8bcc703794660858f58803c3798628817f492a8769c" + "sha256:167c3ef08450f5e060b76c749905acb0e0fbef9365899377a4a1eae728864383", + "sha256:b503913e0b4cce7ed2de965457dfb2edd633e8234161a60e23f2fe2161345d12" ], - "version": "==5.1.1" + "version": "==5.1.2" }, "ipython": { "hashes": [ @@ -929,10 +926,10 @@ }, "jedi": { "hashes": [ - "sha256:53c850f1a7d3cfcd306cc513e2450a54bdf5cacd7604b74e42dd1f0758eaaf36", - "sha256:e07457174ef7cb2342ff94fa56484fe41cec7ef69b0059f01d3f812379cb6f7c" + "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", + "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" ], - "version": "==0.14.1" + "version": "==0.15.1" }, "jinja2": { "hashes": [ @@ -1034,10 +1031,10 @@ }, "nbconvert": { "hashes": [ - "sha256:138381baa41d83584459b5cfecfc38c800ccf1f37d9ddd0bd440783346a4c39c", - "sha256:4a978548d8383f6b2cfca4a3b0543afb77bc7cb5a96e8b424337ab58c12da9bc" + "sha256:427a468ec26e7d68a529b95f578d5cbf018cb4c1f889e897681c2b6d11897695", + "sha256:48d3c342057a2cf21e8df820d49ff27ab9f25fc72b8f15606bd47967333b2709" ], - "version": "==5.5.0" + "version": "==5.6.0" }, "nbformat": { "hashes": [ @@ -1198,33 +1195,33 @@ }, "pyzmq": { "hashes": [ - "sha256:00dd015159eaeb1c0731ad49310e1f5d839c9a35a15e4f3267f5052233fad99b", - "sha256:03913b6beb8e7b417b9910b0ee1fd5d62e9626d218faefbe879d70714ceab1a2", - "sha256:13f17386df81d5e6efb9a4faea341d8de22cdc82e49a326dded26e33f42a3112", - "sha256:16c6281d96885db1e15f7047ddc1a8f48ff4ea35d31ca709f4d2eb39f246d356", - "sha256:17efab4a804e31f58361631256d660214204046f9e2b962738b171b9ad674ea7", - "sha256:2b79919ddeff3d3c96aa6087c21d294c8db1c01f6bfeee73324944683685f419", - "sha256:2f832e4711657bb8d16ea1feba860f676ec5f14fb9fe3b449b5953a60e89edae", - "sha256:31a11d37ac73107363b47e14c94547dbfc6a550029c3fe0530be443199026fc2", - "sha256:33a3e928e6c3138c675e1d6702dd11f6b7050177d7aab3fc322db6e1d2274490", - "sha256:34a38195a6d3a9646cbcdaf8eb245b4d935c7a57f7e1b3af467814bc1a92467e", - "sha256:42900054f1500acef6df7428edf806abbf641bf92eb9ceded24aa863397c3bae", - "sha256:4ccc7f3c63aa9d744dadb62c49eda2d0e7de55649b80c45d7c684d70161a69af", - "sha256:5b220c37c346e6575db8c88a940c1fc234f99ce8e0068c408919bb8896c4b6d2", - "sha256:6074848da5c8b44a1ca40adf75cf65aa92bc80f635e8249aa8f37a69b2b9b6f5", - "sha256:61a4155964bd4a14ef95bf46cb1651bcf8dcbbed8c0108e9c974c1fcbb57788f", - "sha256:62b5774688326600c52f587f7a033ca6b6284bef4c8b1b5fda32480897759eac", - "sha256:65a9ffa4f9f085d696f16fd7541f34b3c357d25fe99c90e3bce2ea59c3b5b4b6", - "sha256:76a077d2c30f8adc5e919a55985a784b96aeca69b53c1ea6fd5723d3ae2e6f53", - "sha256:8e5b4c51557071d6379d6dc1f54f35e9f6a137f5e84e102efb869c8d3c13c8ff", - "sha256:917f73e07cc04f0678a96d93e7bb8b1adcccdde9ccfe202e622814f4d1d1ecfd", - "sha256:91c75d3c4c357f9643e739db9e79ab9681b2f6ae8ec5678d6ef2ea0d01532596", - "sha256:923dd91618b100bb4c92ab9ed7b65825a595b8524a094ce03c7cb2aaae7d353b", - "sha256:9849054e0355e2bc7f4668766a25517ba76095031c9ff5e39ae8949cee5bb024", - "sha256:c9d453933f0e3f44b9759189f2a18aa765f7f1a4345c727c18ebe8ad0d748d26", - "sha256:cb7514936277abce64c2f4c56883e5704d85ed04d98d2d432d1c6764003bb003" - ], - "version": "==18.0.2" + "sha256:01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", + "sha256:021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", + "sha256:0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", + "sha256:05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", + "sha256:1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", + "sha256:22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", + "sha256:260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", + "sha256:2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", + "sha256:2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", + "sha256:343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", + "sha256:41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", + "sha256:856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", + "sha256:85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", + "sha256:93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", + "sha256:98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", + "sha256:9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", + "sha256:a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", + "sha256:b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", + "sha256:cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", + "sha256:dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", + "sha256:dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", + "sha256:e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", + "sha256:ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", + "sha256:f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", + "sha256:fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45" + ], + "version": "==18.1.0" }, "qtconsole": { "hashes": [ From f02c68c94a86b7b174397c7cfb97a54a9184f1af Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 15 Aug 2019 00:19:19 +0100 Subject: [PATCH 009/107] refactor mail module. --- tac/agents/v1/agent.py | 20 +- tac/agents/v1/base/actions.py | 38 +- tac/agents/v1/base/dialogues.py | 58 +-- tac/agents/v1/base/game_instance.py | 2 +- tac/agents/v1/base/handlers.py | 51 +- tac/agents/v1/base/helpers.py | 21 +- tac/agents/v1/base/interfaces.py | 20 +- tac/agents/v1/base/negotiation_behaviours.py | 38 +- tac/agents/v1/base/participant_agent.py | 35 +- tac/agents/v1/base/reactions.py | 79 ++-- tac/agents/v1/base/stats_manager.py | 2 +- tac/agents/v1/mail.py | 468 ------------------- tac/agents/v1/mail/__init__.py | 1 + tac/agents/v1/mail/base.py | 138 ++++++ tac/agents/v1/mail/messages.py | 172 +++++++ tac/agents/v1/mail/oef.py | 230 +++++++++ tac/platform/controller.py | 2 +- tests/test_agent/test_agent_state.py | 11 +- tests/test_agent/test_misc.py | 166 +++---- 19 files changed, 803 insertions(+), 749 deletions(-) delete mode 100644 tac/agents/v1/mail.py create mode 100644 tac/agents/v1/mail/__init__.py create mode 100644 tac/agents/v1/mail/base.py create mode 100644 tac/agents/v1/mail/messages.py create mode 100644 tac/agents/v1/mail/oef.py diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index 4e333ee4..e0b26df3 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -27,7 +27,8 @@ from enum import Enum from typing import Optional -from tac.agents.v1.mail import MailBox, InBox, OutBox +from tac.agents.v1.mail.base import InBox, OutBox, MailBox +from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.helpers.crypto import Crypto logger = logging.getLogger(__name__) @@ -83,8 +84,14 @@ def __init__(self, name: str, self.debug = debug self.mail_box = None # type: Optional[MailBox] - self.in_box = None # type: Optional[InBox] - self.out_box = None # type: Optional[OutBox] + + @property + def in_box(self) -> Optional[InBox]: + return self.mail_box.inbox if self.mail_box else None + + @property + def out_box(self) -> Optional[OutBox]: + return self.mail_box.outbox if self.mail_box else None @property def name(self) -> str: @@ -129,8 +136,8 @@ def start(self) -> None: :return: None """ - if not self.debug: - self.mail_box.start() + if not self.mail_box.is_connected: + self.mail_box.connect() self.liveness._is_stopped = False self._run_main_loop() @@ -165,7 +172,8 @@ def stop(self) -> None: """ logger.debug("[{}]: Stopping message processing...".format(self.name)) self.liveness._is_stopped = True - self.mail_box.stop() + if self.mail_box.is_connected: + self.mail_box.disconnect() @abstractmethod def setup(self) -> None: diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index eb6f677d..26dfd72a 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -33,7 +33,9 @@ from tac.agents.v1.agent import Liveness from tac.agents.v1.base.interfaces import ControllerActionInterface, OEFSearchActionInterface, DialogueActionInterface from tac.agents.v1.base.game_instance import GameInstance -from tac.agents.v1.mail import OutBox, OutContainer +from tac.agents.v1.mail.base import MailBox +from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFSearchServicesRequest, OEFUnregisterServiceRequest, \ + OEFRegisterServiceRequest from tac.helpers.crypto import Crypto from tac.platform.protocol import GetStateUpdate @@ -43,14 +45,14 @@ class ControllerActions(ControllerActionInterface): """The ControllerActions class defines the actions of an agent towards the ControllerAgent.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str) -> None: """ Instantiate the ControllerActions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :return: None @@ -58,7 +60,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name def request_state_update(self) -> None: @@ -68,20 +70,20 @@ def request_state_update(self) -> None: :return: None """ msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=self.game_instance.controller_pbk)) + self.mailbox.outbox.put(OEFAgentByteMessage(0, 0, self.game_instance.controller_pbk, msg)) class OEFActions(OEFSearchActionInterface): """The OEFActions class defines the actions of an agent towards the OEF.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str) -> None: """ Instantiate the OEFActions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :return: None @@ -89,7 +91,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name def search_for_tac(self) -> None: @@ -104,7 +106,7 @@ def search_for_tac(self) -> None: query = Query([Constraint("version", GtEq(1))]) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_tac.add(search_id) - self.out_box.out_queue.put(OutContainer(query=query, search_id=search_id)) + self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) def update_services(self) -> None: """ @@ -122,9 +124,9 @@ def unregister_service(self) -> None: :return: None """ if self.game_instance.goods_demanded_description is not None: - self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_demanded_description)) + self.mailbox.outbox.put(OEFUnregisterServiceRequest(1, self.game_instance.goods_demanded_description)) if self.game_instance.goods_supplied_description is not None: - self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_supplied_description)) + self.mailbox.outbox.put(OEFUnregisterServiceRequest(1, self.game_instance.goods_supplied_description)) def register_service(self) -> None: """ @@ -141,12 +143,12 @@ def register_service(self) -> None: logger.debug("[{}]: Updating service directory as seller with goods supplied.".format(self.agent_name)) goods_supplied_description = self.game_instance.get_service_description(is_supply=True) self.game_instance.goods_supplied_description = goods_supplied_description - self.out_box.out_queue.put(OutContainer(service_description=goods_supplied_description, message_id=1)) + self.mailbox.outbox.put(OEFRegisterServiceRequest(1, goods_supplied_description)) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description - self.out_box.out_queue.put(OutContainer(service_description=goods_demanded_description, message_id=1)) + self.mailbox.outbox.put(OEFRegisterServiceRequest(1, goods_demanded_description)) def search_services(self) -> None: """ @@ -168,7 +170,7 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for sellers which match the demand of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_sellers.add(search_id) - self.out_box.out_queue.put(OutContainer(query=query, search_id=search_id)) + self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) if self.game_instance.strategy.is_searching_for_buyers: query = self.game_instance.build_services_query(is_searching_for_sellers=False) if query is None: @@ -178,20 +180,20 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for buyers which match the supply of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_buyers.add(search_id) - self.out_box.out_queue.put(OutContainer(query=query, search_id=search_id)) + self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) class DialogueActions(DialogueActionInterface): """The DialogueActions class defines the actions of an agent in the context of a Dialogue.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: OutBox, agent_name: str) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str) -> None: """ Instantiate the DialogueActions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :return: None @@ -199,6 +201,6 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name self.dialogues = game_instance.dialogues diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index 49c5e9b8..df499a7d 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -27,18 +27,11 @@ """ import logging -from typing import List, Any, Dict, Union +from typing import List, Any, Dict -from oef.messages import CFP, Decline, Propose, Accept, Message as ByteMessage, \ - SearchResult, OEFErrorMessage, DialogueErrorMessage -from tac.agents.v1.mail import OutContainer +from tac.agents.v1.mail.messages import OEFAgentMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, OEFAgentDecline Action = Any -OEFMessage = Union[SearchResult, OEFErrorMessage, DialogueErrorMessage] -ControllerMessage = ByteMessage -AgentMessage = Union[ByteMessage, CFP, Propose, Accept, Decline, OutContainer] -Message = Union[OEFMessage, ControllerMessage, AgentMessage] - logger = logging.getLogger(__name__) STARTING_MESSAGE_ID = 1 @@ -105,9 +98,9 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: self._is_seller = is_seller self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk self._role = 'seller' if is_seller else 'buyer' - self._outgoing_messages = [] # type: List[AgentMessage] - self._outgoing_messages_controller = [] # type: List[AgentMessage] - self._incoming_messages = [] # type: List[AgentMessage] + self._outgoing_messages = [] # type: List[OEFAgentMessage] + self._outgoing_messages_controller = [] # type: List[OEFAgentMessage] + self._incoming_messages = [] # type: List[OEFAgentMessage] @property def dialogue_label(self) -> DialogueLabel: @@ -129,7 +122,7 @@ def role(self) -> str: """Get role of agent in dialogue.""" return self._role - def outgoing_extend(self, messages: List[AgentMessage]) -> None: + def outgoing_extend(self, messages: List[OEFAgentMessage]) -> None: """ Extend the list of messages which keeps track of outgoing messages. @@ -137,12 +130,9 @@ def outgoing_extend(self, messages: List[AgentMessage]) -> None: :return: None """ for message in messages: - if isinstance(message, OutContainer): - self._outgoing_messages_controller.extend([message]) - else: - self._outgoing_messages.extend([message]) + self._outgoing_messages.extend([message]) - def incoming_extend(self, messages: List[AgentMessage]) -> None: + def incoming_extend(self, messages: List[OEFAgentMessage]) -> None: """ Extend the list of messages which keeps track of incoming messages. @@ -158,7 +148,7 @@ def is_expecting_propose(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], CFP) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentCfp) return result def is_expecting_initial_accept(self) -> bool: @@ -168,7 +158,7 @@ def is_expecting_initial_accept(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], Propose) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentPropose) return result def is_expecting_matching_accept(self) -> bool: @@ -178,7 +168,7 @@ def is_expecting_matching_accept(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], Accept) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentAccept) return result def is_expecting_cfp_decline(self) -> bool: @@ -188,7 +178,7 @@ def is_expecting_cfp_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], CFP) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentCfp) return result def is_expecting_propose_decline(self) -> bool: @@ -198,7 +188,7 @@ def is_expecting_propose_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], Propose) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentPropose) return result def is_expecting_accept_decline(self) -> bool: @@ -208,7 +198,7 @@ def is_expecting_accept_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], Accept) + result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentAccept) return result @@ -241,7 +231,7 @@ def dialogues_as_buyer(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent acts as a buyer.""" return self._dialogues_as_buyer - def is_permitted_for_new_dialogue(self, msg: AgentMessage, known_pbks: List[str]) -> bool: + def is_permitted_for_new_dialogue(self, msg: OEFAgentMessage, known_pbks: List[str]) -> bool: """ Check whether an agent message is permitted for a new dialogue. @@ -255,10 +245,10 @@ def is_permitted_for_new_dialogue(self, msg: AgentMessage, known_pbks: List[str] :return: a boolean indicating whether the message is permitted for a new dialogue """ - result = isinstance(msg, CFP) and msg.msg_id == STARTING_MESSAGE_ID and msg.target == STARTING_MESSAGE_TARGET and (msg.destination in known_pbks) + result = isinstance(msg, OEFAgentCfp) and msg.msg_id == STARTING_MESSAGE_ID and msg.target == STARTING_MESSAGE_TARGET and (msg.destination in known_pbks) return result - def is_belonging_to_registered_dialogue(self, msg: AgentMessage, agent_pbk: str) -> bool: + def is_belonging_to_registered_dialogue(self, msg: OEFAgentMessage, agent_pbk: str) -> bool: """ Check whether an agent message is part of a registered dialogue. @@ -270,17 +260,17 @@ def is_belonging_to_registered_dialogue(self, msg: AgentMessage, agent_pbk: str) self_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, agent_pbk) other_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, msg.destination) result = False - if isinstance(msg, Propose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: + if isinstance(msg, OEFAgentPropose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_propose() - elif isinstance(msg, Accept): + elif isinstance(msg, OEFAgentAccept): if msg.target == 2 and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_initial_accept() elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_matching_accept() - elif isinstance(msg, Decline): + elif isinstance(msg, OEFAgentDecline): if msg.target == 1 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_cfp_decline() @@ -292,7 +282,7 @@ def is_belonging_to_registered_dialogue(self, msg: AgentMessage, agent_pbk: str) result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, msg: AgentMessage, agent_pbk: str) -> Dialogue: + def get_dialogue(self, msg: OEFAgentMessage, agent_pbk: str) -> Dialogue: """ Retrieve dialogue. @@ -303,16 +293,16 @@ def get_dialogue(self, msg: AgentMessage, agent_pbk: str) -> Dialogue: """ self_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, agent_pbk) other_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, msg.destination) - if isinstance(msg, Propose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: + if isinstance(msg, OEFAgentPropose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] - elif isinstance(msg, Accept): + elif isinstance(msg, OEFAgentAccept): if msg.target == 2 and other_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[other_initiated_dialogue_label] elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError('Should have found dialogue.') - elif isinstance(msg, Decline): + elif isinstance(msg, OEFAgentDecline): if msg.target == 1 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] elif msg.target == 2 and other_initiated_dialogue_label in self.dialogues: diff --git a/tac/agents/v1/base/game_instance.py b/tac/agents/v1/base/game_instance.py index 82215dca..8c7d4acd 100644 --- a/tac/agents/v1/base/game_instance.py +++ b/tac/agents/v1/base/game_instance.py @@ -28,7 +28,7 @@ from oef.query import Query from oef.schema import Description -from tac.agents.v1.mail import MailStats +from tac.agents.v1.mail.oef import MailStats from tac.agents.v1.base.dialogues import Dialogues, Dialogue from tac.agents.v1.base.transaction_manager import TransactionManager from tac.agents.v1.base.strategy import Strategy diff --git a/tac/agents/v1/base/handlers.py b/tac/agents/v1/base/handlers.py index bce232c2..0b751ea9 100644 --- a/tac/agents/v1/base/handlers.py +++ b/tac/agents/v1/base/handlers.py @@ -27,45 +27,40 @@ """ import logging -from typing import Any, Union - -from oef.messages import CFP, Decline, Propose, Accept, Message as SimpleMessage, \ - SearchResult, OEFErrorMessage, DialogueErrorMessage +from typing import Any from tac.agents.v1.agent import Liveness from tac.agents.v1.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions -from tac.agents.v1.mail import OutBox +from tac.agents.v1.mail.base import MailBox +from tac.agents.v1.mail.messages import OEFResponse, OEFSearchResult, OEFGenericError, OEFDialogueError, \ + OEFAgentByteMessage, OEFAgentMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled logger = logging.getLogger(__name__) Action = Any -OEFMessage = Union[SearchResult, OEFErrorMessage, DialogueErrorMessage] -ControllerMessage = SimpleMessage -AgentMessage = Union[SimpleMessage, CFP, Propose, Accept, Decline] -Message = Union[OEFMessage, ControllerMessage, AgentMessage] class DialogueHandler(DialogueActions, DialogueReactions): """Handle the dialogue with another agent.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: OutBox, agent_name: str): + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str): """ Instantiate the DialogueHandler. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox + :param mailbox: the mailbox :param agent_name: the agent name """ - DialogueActions.__init__(self, crypto, liveness, game_instance, out_box, agent_name) - DialogueReactions.__init__(self, crypto, liveness, game_instance, out_box, agent_name) + DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) + DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, msg: AgentMessage) -> None: + def handle_dialogue_message(self, msg: OEFAgentMessage) -> None: """ Handle messages from the other agents. @@ -87,20 +82,20 @@ def handle_dialogue_message(self, msg: AgentMessage) -> None: class ControllerHandler(ControllerActions, ControllerReactions): """Handle the message exchange with the controller.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str): + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str): """ Instantiate the ControllerHandler. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox + :param mailbox: the mailbox :param agent_name: the agent name """ - ControllerActions.__init__(self, crypto, liveness, game_instance, out_box, agent_name) - ControllerReactions.__init__(self, crypto, liveness, game_instance, out_box, agent_name) + ControllerActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) + ControllerReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_controller_message(self, msg: ControllerMessage) -> None: + def handle_controller_message(self, msg: OEFAgentByteMessage) -> None: """ Handle messages from the controller. @@ -110,7 +105,7 @@ def handle_controller_message(self, msg: ControllerMessage) -> None: :return: None """ - response = Response.from_pb(msg.msg, msg.destination, self.crypto) + response = Response.from_pb(msg.content, msg.destination, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: if msg.destination != self.game_instance.controller_pbk: @@ -141,21 +136,21 @@ def handle_controller_message(self, msg: ControllerMessage) -> None: class OEFHandler(OEFActions, OEFReactions): """Handle the message exchange with the OEF.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str, rejoin: bool = False): + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str, rejoin: bool = False): """ Instantiate the OEFHandler. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox + :param mailbox: the mailbox :param agent_name: the agent name :param rejoin: boolean indicating whether the agent will rejoin the TAC if losing connection """ - OEFActions.__init__(self, crypto, liveness, game_instance, out_box, agent_name) - OEFReactions.__init__(self, crypto, liveness, game_instance, out_box, agent_name, rejoin) + OEFActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) + OEFReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name, rejoin) - def handle_oef_message(self, msg: OEFMessage) -> None: + def handle_oef_message(self, msg: OEFResponse) -> None: """ Handle messages from the oef. @@ -166,11 +161,11 @@ def handle_oef_message(self, msg: OEFMessage) -> None: :return: None """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) - if isinstance(msg, SearchResult): + if isinstance(msg, OEFSearchResult): self.on_search_result(msg) - elif isinstance(msg, OEFErrorMessage): + elif isinstance(msg, OEFGenericError): self.on_oef_error(msg) - elif isinstance(msg, DialogueErrorMessage): + elif isinstance(msg, OEFDialogueError): self.on_dialogue_error(msg) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index c3c19f54..87d30159 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -20,19 +20,14 @@ """This module contains helper methods for base agent implementations.""" -from typing import Union - -from oef.messages import Message as SimpleMessage, SearchResult, OEFErrorMessage, DialogueErrorMessage - from tac.agents.v1.base.dialogues import DialogueLabel +from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFAgentMessage, OEFMessage, OEFSearchResult, \ + OEFDialogueError, OEFGenericError from tac.helpers.crypto import Crypto from tac.platform.protocol import Response -OEFMessage = Union[SearchResult, OEFErrorMessage, DialogueErrorMessage] -Message = Union[OEFMessage] - -def is_oef_message(msg: Message) -> bool: +def is_oef_message(msg: OEFMessage) -> bool: """ Check whether a message is from the oef. @@ -40,10 +35,10 @@ def is_oef_message(msg: Message) -> bool: :return: boolean indicating whether or not the message is from the oef """ msg_type = type(msg) - return msg_type in {SearchResult, OEFErrorMessage, DialogueErrorMessage} + return msg_type in {OEFSearchResult, OEFGenericError, OEFDialogueError} -def is_controller_message(msg: Message, crypto: Crypto) -> bool: +def is_controller_message(msg: OEFMessage, crypto: Crypto) -> bool: """ Check whether a message is from the controller. @@ -51,12 +46,12 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: :param crypto: the crypto of the agent :return: boolean indicating whether or not the message is from the controller """ - if not isinstance(msg, SimpleMessage): + if not isinstance(msg, OEFAgentByteMessage): return False try: - msg: SimpleMessage - byte_content = msg.msg + msg: OEFAgentByteMessage + byte_content = msg.content sender_pbk = msg.destination # now the origin is the destination! Response.from_pb(byte_content, sender_pbk, crypto) except Exception: diff --git a/tac/agents/v1/base/interfaces.py b/tac/agents/v1/base/interfaces.py index a2cadf00..1d03d546 100644 --- a/tac/agents/v1/base/interfaces.py +++ b/tac/agents/v1/base/interfaces.py @@ -21,20 +21,16 @@ """This module defines the interfaces which a TAC compatible agent must implement.""" from abc import abstractmethod -from typing import Union - -from oef.messages import CFP, Propose, Accept, Decline, Message as SimpleMessage, SearchResult, OEFErrorMessage, DialogueErrorMessage +from tac.agents.v1.mail.messages import OEFAgentMessage, OEFDialogueError, OEFGenericError, OEFSearchResult from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData -AgentMessage = Union[SimpleMessage, CFP, Propose, Accept, Decline] - class ControllerReactionInterface: """This interface contains the methods to react to events from the ControllerAgent.""" @abstractmethod - def on_dialogue_error(self, dialogue_error_msg: DialogueErrorMessage) -> None: + def on_dialogue_error(self, dialogue_error_msg: OEFDialogueError) -> None: """ Handle dialogue error event emitted by the controller. @@ -106,7 +102,7 @@ class OEFSearchReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod - def on_search_result(self, search_result: SearchResult) -> None: + def on_search_result(self, search_result: OEFSearchResult) -> None: """ Handle search results. @@ -116,7 +112,7 @@ def on_search_result(self, search_result: SearchResult) -> None: """ @abstractmethod - def on_oef_error(self, oef_error: OEFErrorMessage) -> None: + def on_oef_error(self, oef_error: OEFGenericError) -> None: """ Handle an OEF error message. @@ -126,7 +122,7 @@ def on_oef_error(self, oef_error: OEFErrorMessage) -> None: """ @abstractmethod - def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: + def on_dialogue_error(self, dialogue_error: OEFDialogueError) -> None: """ Handle a dialogue error message. @@ -184,7 +180,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, msg: AgentMessage) -> None: + def on_new_dialogue(self, msg: OEFAgentMessage) -> None: """ React to a message for a new dialogue. @@ -198,7 +194,7 @@ def on_new_dialogue(self, msg: AgentMessage) -> None: """ @abstractmethod - def on_existing_dialogue(self, msg: AgentMessage) -> None: + def on_existing_dialogue(self, msg: OEFAgentMessage) -> None: """ React to a message of an existing dialogue. @@ -208,7 +204,7 @@ def on_existing_dialogue(self, msg: AgentMessage) -> None: """ @abstractmethod - def on_unidentified_dialogue(self, msg: AgentMessage) -> None: + def on_unidentified_dialogue(self, msg: OEFAgentMessage) -> None: """ React to a message of an unidentified dialogue. diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index 16a39b90..743834ee 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -25,14 +25,12 @@ import pprint from typing import Union, List -from oef.messages import CFP, Decline, Propose, Accept -from oef.uri import Context - -from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.base.dialogues import Dialogue -from tac.agents.v1.mail import OutContainer +from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.base.helpers import generate_transaction_id from tac.agents.v1.base.stats_manager import EndState +from tac.agents.v1.mail.messages import OEFAgentPropose, OEFAgentAccept, OEFAgentDecline, OEFAgentByteMessage, \ + OEFAgentMessage, OEFAgentCfp from tac.helpers.crypto import Crypto from tac.platform.protocol import Transaction @@ -73,7 +71,7 @@ def agent_name(self) -> str: """Get the agent name.""" return self._agent_name - def on_cfp(self, cfp: CFP, dialogue: Dialogue) -> Union[Propose, Decline]: + def on_cfp(self, cfp: OEFAgentCfp, dialogue: Dialogue) -> Union[OEFAgentPropose, OEFAgentDecline]: """ Handle a CFP. @@ -103,7 +101,7 @@ def on_cfp(self, cfp: CFP, dialogue: Dialogue) -> Union[Propose, Decline]: "origin": cfp.destination, "target": cfp.msg_id }))) - response = Decline(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id, Context()) + response = OEFAgentDecline(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) else: transaction_id = generate_transaction_id(self.crypto.public_key, cfp.destination, dialogue.dialogue_label, dialogue.is_seller) @@ -122,10 +120,10 @@ def on_cfp(self, cfp: CFP, dialogue: Dialogue) -> Union[Propose, Decline]: "target": cfp.msg_id, "propose": proposal.values }))) - response = Propose(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id, [proposal], Context()) + response = OEFAgentPropose(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id, [proposal]) return response - def on_propose(self, propose: Propose, dialogue: Dialogue) -> Union[Accept, Decline]: + def on_propose(self, propose: OEFAgentPropose, dialogue: Dialogue) -> Union[OEFAgentAccept, OEFAgentDecline]: """ Handle a Propose. @@ -135,7 +133,7 @@ def on_propose(self, propose: Propose, dialogue: Dialogue) -> Union[Accept, Decl :return: an Accept or a Decline """ logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) - proposal = propose.proposals[0] + proposal = propose.proposal[0] transaction_id = generate_transaction_id(self.crypto.public_key, propose.destination, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, @@ -150,14 +148,14 @@ def on_propose(self, propose: Propose, dialogue: Dialogue) -> Union[Accept, Decl logger.debug("[{}]: Accepting propose (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance(dialogue.dialogue_label, new_msg_id, transaction) - result = Accept(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id, Context()) + result = OEFAgentAccept(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id) else: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) - result = Decline(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id, Context()) + result = OEFAgentDecline(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result - def on_decline(self, decline: Decline, dialogue: Dialogue) -> None: + def on_decline(self, decline: OEFAgentDecline, dialogue: Dialogue) -> None: """ Handle a Decline. @@ -183,7 +181,7 @@ def on_decline(self, decline: Decline, dialogue: Dialogue) -> None: return None - def on_accept(self, accept: Accept, dialogue: Dialogue) -> Union[List[Decline], List[Union[OutContainer, Accept]], List[OutContainer]]: + def on_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Union[List[OEFAgentDecline], List[Union[OEFAgentAccept]], List[OEFAgentByteMessage]]: """ Handle an Accept. @@ -202,7 +200,7 @@ def on_accept(self, accept: Accept, dialogue: Dialogue) -> Union[List[Decline], results = self._on_initial_accept(accept, dialogue) return results - def _on_initial_accept(self, accept: Accept, dialogue: Dialogue) -> Union[List[Decline], List[Union[OutContainer, Accept]]]: + def _on_initial_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Union[List[OEFAgentDecline], List[Union[OEFAgentByteMessage, OEFAgentAccept]]]: """ Handle an initial Accept. @@ -221,15 +219,15 @@ def _on_initial_accept(self, accept: Accept, dialogue: Dialogue) -> Union[List[D self.game_instance.world_state.update_on_initial_accept(transaction) logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - results.append(OutContainer(message=transaction.serialize(), message_id=STARTING_MESSAGE_ID, dialogue_id=accept.dialogue_id, destination=self.game_instance.controller_pbk)) - results.append(Accept(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id, Context())) + results.append(OEFAgentByteMessage(STARTING_MESSAGE_ID, accept.dialogue_id, self.game_instance.controller_pbk, transaction.serialize())) + results.append(OEFAgentAccept(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id)) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) - results.append(Decline(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id, Context())) + results.append(OEFAgentDecline(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results - def _on_match_accept(self, accept: Accept, dialogue: Dialogue) -> List[OutContainer]: + def _on_match_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> List[OEFAgentMessage]: """ Handle a matching Accept. @@ -241,5 +239,5 @@ def _on_match_accept(self, accept: Accept, dialogue: Dialogue) -> List[OutContai logger.debug("[{}]: on match accept".format(self.agent_name)) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, accept.target) - results.append(OutContainer(message=transaction.serialize(), message_id=STARTING_MESSAGE_ID, dialogue_id=accept.dialogue_id, destination=self.game_instance.controller_pbk)) + results.append(OEFAgentByteMessage(STARTING_MESSAGE_ID, accept.dialogue_id, self.game_instance.controller_pbk, transaction.serialize())) return results diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index e9206f43..ce30caed 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -24,23 +24,22 @@ import time from typing import Optional, Union -from oef.messages import CFP, Decline, Propose, Accept, Message as SimpleMessage, \ - SearchResult, OEFErrorMessage, DialogueErrorMessage - from tac.agents.v1.agent import Agent from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler from tac.agents.v1.base.helpers import is_oef_message, is_controller_message from tac.agents.v1.base.strategy import Strategy -from tac.agents.v1.mail import FIPAMailBox, InBox, OutBox +from tac.agents.v1.mail.messages import OEFMessage, OEFResponse, OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, \ + OEFAgentDecline, OEFAgentAccept +from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) -OEFMessage = Union[SearchResult, OEFErrorMessage, DialogueErrorMessage] -ControllerMessage = SimpleMessage -AgentMessage = Union[SimpleMessage, CFP, Propose, Accept, Decline] -Message = Union[OEFMessage, ControllerMessage, AgentMessage] + +ControllerMessage = OEFAgentByteMessage +AgentMessage = Union[OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, OEFAgentDecline] +Message = Union[ControllerMessage, AgentMessage] class ParticipantAgent(Agent): @@ -73,16 +72,14 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mail_box = FIPAMailBox(self.crypto.public_key, oef_addr, oef_port) - self.in_box = InBox(self.mail_box) - self.out_box = OutBox(self.mail_box) + self.mail_box = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) self._game_instance = GameInstance(name, strategy, self.mail_box.mail_stats, services_interval, pending_transaction_timeout, dashboard) # type: Optional[GameInstance] self.max_reactions = max_reactions - self.controller_handler = ControllerHandler(self.crypto, self.liveness, self.game_instance, self.out_box, self.name) - self.oef_handler = OEFHandler(self.crypto, self.liveness, self.game_instance, self.out_box, self.name) - self.dialogue_handler = DialogueHandler(self.crypto, self.liveness, self.game_instance, self.out_box, self.name) + self.controller_handler = ControllerHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) + self.oef_handler = OEFHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) + self.dialogue_handler = DialogueHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) @property def game_instance(self) -> GameInstance: @@ -103,8 +100,6 @@ def act(self) -> None: if self.game_instance.is_time_to_search_services(): self.oef_handler.search_services() - self.out_box.send_nowait() - def react(self) -> None: """ React to incoming events. @@ -112,12 +107,12 @@ def react(self) -> None: :return: None """ counter = 0 - while (not self.in_box.is_in_queue_empty() and counter < self.max_reactions): + while (not self.mail_box.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.in_box.get_no_wait() # type: Optional[Message] + msg = self.mail_box.inbox.get_nowait() # type: Optional[OEFMessage] if msg is not None: if is_oef_message(msg): - msg: OEFMessage + msg: OEFResponse self.oef_handler.handle_oef_message(msg) elif is_controller_message(msg, self.crypto): msg: ControllerMessage @@ -126,8 +121,6 @@ def react(self) -> None: msg: AgentMessage self.dialogue_handler.handle_dialogue_message(msg) - self.out_box.send_nowait() - def update(self) -> None: """ Update the state of the agent. diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 9802bd39..94273a83 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -30,10 +30,6 @@ import logging from typing import List, Union -from oef.messages import CFP, Propose, Accept, Decline, Message as ByteMessage, SearchResult, OEFErrorMessage, \ - DialogueErrorMessage -from oef.uri import Context - from tac.agents.v1.agent import Liveness from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.base.game_instance import GameInstance, GamePhase @@ -42,7 +38,9 @@ DialogueReactionInterface from tac.agents.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.v1.base.stats_manager import EndState -from tac.agents.v1.mail import OutBox, OutContainer +from tac.agents.v1.mail.base import MailBox +from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, \ + OEFAgentDecline, OEFSearchResult, OEFGenericError, OEFDialogueError, OEFAgentMessage from tac.helpers.crypto import Crypto from tac.helpers.misc import TAC_DEMAND_DATAMODEL_NAME from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ @@ -53,20 +51,18 @@ STARTING_MESSAGE_ID = 1 STARTING_MESSAGE_TARGET = 0 -AgentMessage = Union[ByteMessage, CFP, Propose, Accept, Decline, OutContainer] - class ControllerReactions(ControllerReactionInterface): """The ControllerReactions class defines the reactions of an agent towards the ControllerAgent.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str) -> None: """ Instantiate the ControllerReactions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :return: None @@ -74,10 +70,10 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, dialogue_error_msg: DialogueErrorMessage) -> None: + def on_dialogue_error(self, dialogue_error_msg: OEFDialogueError) -> None: """ Handle dialogue error event emitted by the controller. @@ -187,20 +183,20 @@ def _request_state_update(self) -> None: :return: None """ msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=self.game_instance.controller_pbk)) + self.mailbox.outbox.put(OEFAgentByteMessage(0, 0, self.game_instance.controller_pbk, msg)) class OEFReactions(OEFSearchReactionInterface): """The OEFReactions class defines the reactions of an agent towards the OEF.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str, rejoin: bool = False) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str, rejoin: bool = False) -> None: """ Instantiate the OEFReactions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :param rejoin: boolean indicating whether the agent will rejoin the TAC if losing connection @@ -209,11 +205,11 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name self.rejoin = rejoin - def on_search_result(self, search_result: SearchResult) -> None: + def on_search_result(self, search_result: OEFSearchResult) -> None: """ Split the search results from the OEF. @@ -221,7 +217,7 @@ def on_search_result(self, search_result: SearchResult) -> None: :return: None """ - search_id = search_result.msg_id + search_id = search_result.search_id logger.debug("[{}]: on search result: {} {}".format(self.agent_name, search_id, search_result.agents)) if search_id in self.game_instance.search.ids_for_tac: self._on_controller_search_result(search_result.agents) @@ -232,7 +228,7 @@ def on_search_result(self, search_result: SearchResult) -> None: else: logger.debug("[{}]: Unknown search id: search_id={}".format(self.agent_name, search_id)) - def on_oef_error(self, oef_error: OEFErrorMessage) -> None: + def on_oef_error(self, oef_error: OEFGenericError) -> None: """ Handle an OEF error message. @@ -241,9 +237,9 @@ def on_oef_error(self, oef_error: OEFErrorMessage) -> None: :return: None """ logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.agent_name, oef_error.msg_id, oef_error.oef_error_operation)) + .format(self.agent_name, oef_error.answer_id, oef_error.operation)) - def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: + def on_dialogue_error(self, dialogue_error: OEFDialogueError) -> None: """ Handle a dialogue error message. @@ -252,7 +248,7 @@ def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: :return: None """ logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.msg_id, dialogue_error.dialogue_id, dialogue_error.origin)) + .format(self.agent_name, dialogue_error.answer_id, dialogue_error.dialogue_id, dialogue_error.origin)) def _on_controller_search_result(self, agent_pbks: List[str]) -> None: """ @@ -303,11 +299,11 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel return for agent_pbk in agent_pbks: dialogue = self.game_instance.dialogues.create_self_initiated(agent_pbk, self.crypto.public_key, not is_searching_for_sellers) - cfp = CFP(STARTING_MESSAGE_ID, dialogue.dialogue_label.dialogue_id, agent_pbk, STARTING_MESSAGE_TARGET, json.dumps(services).encode('utf-8'), Context()) + cfp = OEFAgentCfp(STARTING_MESSAGE_ID, dialogue.dialogue_label.dialogue_id, agent_pbk, STARTING_MESSAGE_TARGET, json.dumps(services).encode('utf-8')) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" .format(self.agent_name, dialogue.role, cfp.msg_id, cfp.dialogue_id, cfp.destination, cfp.target, services)) dialogue.outgoing_extend([cfp]) - self.out_box.out_queue.put(cfp) + self.mailbox.outbox.put(cfp) def _register_to_tac(self, controller_pbk: str) -> None: """ @@ -320,7 +316,7 @@ def _register_to_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() - self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=controller_pbk)) + self.mailbox.outbox.put(OEFAgentByteMessage(0, 0, controller_pbk, msg)) def _rejoin_tac(self, controller_pbk: str) -> None: """ @@ -333,20 +329,20 @@ def _rejoin_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=controller_pbk)) + self.mailbox.outbox.put(OEFAgentByteMessage(0, 0, controller_pbk, msg)) class DialogueReactions(DialogueReactionInterface): """The DialogueReactions class defines the reactions of an agent in the context of a Dialogue.""" - def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: OutBox, agent_name: str) -> None: + def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, mailbox: MailBox, agent_name: str) -> None: """ Instantiate the DialogueReactions. :param crypto: the crypto module :param liveness: the liveness module :param game_instance: the game instance - :param out_box: the outbox of the agent + :param mailbox: the mailbox of the agent :param agent_name: the agent name :return: None @@ -354,12 +350,12 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.crypto = crypto self.liveness = liveness self.game_instance = game_instance - self.out_box = out_box + self.mailbox = mailbox self.agent_name = agent_name self.dialogues = game_instance.dialogues self.negotiation_behaviour = FIPABehaviour(crypto, game_instance, agent_name) - def on_new_dialogue(self, msg: AgentMessage) -> None: + def on_new_dialogue(self, msg: OEFAgentCfp) -> None: """ React to a new dialogue. @@ -373,9 +369,9 @@ def on_new_dialogue(self, msg: AgentMessage) -> None: logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format(self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) results = self._handle(msg, dialogue) for result in results: - self.out_box.out_queue.put(result) + self.mailbox.outbox.put(result) - def on_existing_dialogue(self, msg: AgentMessage) -> None: + def on_existing_dialogue(self, msg: OEFAgentMessage) -> None: """ React to an existing dialogue. @@ -387,9 +383,9 @@ def on_existing_dialogue(self, msg: AgentMessage) -> None: results = self._handle(msg, dialogue) for result in results: - self.out_box.out_queue.put(result) + self.mailbox.outbox.put(result) - def on_unidentified_dialogue(self, msg: AgentMessage) -> None: + def on_unidentified_dialogue(self, msg: OEFAgentMessage) -> None: """ React to an unidentified dialogue. @@ -398,10 +394,10 @@ def on_unidentified_dialogue(self, msg: AgentMessage) -> None: :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - result = ByteMessage(msg.msg_id + 1, msg.dialogue_id, msg.destination, b'This message belongs to an unidentified dialogue.', Context()) - self.out_box.out_queue.put(result) + result = OEFAgentByteMessage(msg.msg_id + 1, msg.dialogue_id, msg.destination, b'This message belongs to an unidentified dialogue.') + self.mailbox.outbox.put(result) - def _handle(self, msg: AgentMessage, dialogue: Dialogue) -> List[AgentMessage]: + def _handle(self, msg: OEFAgentMessage, dialogue: Dialogue) -> List[OEFAgentMessage]: """ Handle a message according to the defined behaviour. @@ -411,17 +407,18 @@ def _handle(self, msg: AgentMessage, dialogue: Dialogue) -> List[AgentMessage]: :return: a list of agent messages """ dialogue.incoming_extend([msg]) - results = [] # type: List[Union[OutContainer, Accept, Decline, Propose]] - if isinstance(msg, CFP): + results = [] # type: List[OEFAgentMessage] + if isinstance(msg, OEFAgentCfp): result = self.negotiation_behaviour.on_cfp(msg, dialogue) results = [result] - elif isinstance(msg, Propose): + elif isinstance(msg, OEFAgentPropose): result = self.negotiation_behaviour.on_propose(msg, dialogue) results = [result] - elif isinstance(msg, Accept): + elif isinstance(msg, OEFAgentAccept): results = self.negotiation_behaviour.on_accept(msg, dialogue) - elif isinstance(msg, Decline): + elif isinstance(msg, OEFAgentDecline): self.negotiation_behaviour.on_decline(msg, dialogue) results = [] + dialogue.outgoing_extend(results) return results diff --git a/tac/agents/v1/base/stats_manager.py b/tac/agents/v1/base/stats_manager.py index 3c3a5dcc..b4a1f19c 100644 --- a/tac/agents/v1/base/stats_manager.py +++ b/tac/agents/v1/base/stats_manager.py @@ -27,7 +27,7 @@ import numpy as np -from tac.agents.v1.mail import MailStats +from tac.agents.v1.mail.oef import MailStats class EndState(Enum): diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py deleted file mode 100644 index 34341b4a..00000000 --- a/tac/agents/v1/mail.py +++ /dev/null @@ -1,468 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -""" -This module contains the classes required for message management. - -- MailStats: The MailStats class tracks statistics on messages processed by MailBox. -- MailBox: The MailBox enqueues incoming messages, searches and errors from the OEF and sends outgoing messages to the OEF. -- InBox: Temporarily stores messages for the agent. -- OutBox: Temporarily stores and sends messages to the OEF and other agents. -""" - -import asyncio -import datetime -import logging -import time -from queue import Queue, Empty -from threading import Thread -from typing import List, Optional, Any, Union, Dict - -from oef.agents import OEFAgent -from oef.messages import PROPOSE_TYPES, CFP_TYPES, CFP, Decline, Propose, Accept, Message as ByteMessage, \ - SearchResult, OEFErrorOperation, OEFErrorMessage, DialogueErrorMessage -from oef.query import Query -from oef.schema import Description -from oef.uri import Context - -logger = logging.getLogger(__name__) - -Action = Any -OEFMessage = Union[SearchResult, OEFErrorMessage, DialogueErrorMessage] -ControllerMessage = ByteMessage -AgentMessage = Union[ByteMessage, CFP, Propose, Accept, Decline] -Message = Union[OEFMessage, ControllerMessage, AgentMessage] - - -class MailStats(object): - """The MailStats class tracks statistics on messages processed by MailBox.""" - - def __init__(self) -> None: - """ - Instantiate mail stats. - - :return: None - """ - self._search_count = 0 - self._search_start_time = {} # type: Dict[int, datetime.datetime] - self._search_timedelta = {} # type: Dict[int, float] - self._search_result_counts = {} # type: Dict[int, int] - - @property - def search_count(self) -> int: - """Get the search count.""" - return self._search_count - - def search_start(self, search_id: int) -> None: - """ - Add a search id and start time. - - :param search_id: the search id - - :return: None - """ - assert search_id not in self._search_start_time - self._search_count += 1 - self._search_start_time[search_id] = datetime.datetime.now() - - def search_end(self, search_id: int, nb_search_results: int) -> None: - """ - Add end time for a search id. - - :param search_id: the search id - :param nb_search_results: the number of agents returned in the search result - - :return: None - """ - assert search_id in self._search_start_time - assert search_id not in self._search_timedelta - self._search_timedelta[search_id] = (datetime.datetime.now() - self._search_start_time[search_id]).total_seconds() * 1000 - self._search_result_counts[search_id] = nb_search_results - - -class MailBox(OEFAgent): - """The MailBox enqueues incoming messages, searches and errors from the OEF and sends outgoing messages to the OEF.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, debug: bool = False) -> None: - """ - Instantiate the mailbox. - - :param public_key: the public key of the agent - :param oef_addr: TCP/IP address of the OEF Agent - :param oef_port: TCP/IP port of the OEF Agent - :param debug: start the mailbox in debug mode. - - :return: None - """ - super().__init__(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) - self.in_queue = Queue() - self.out_queue = Queue() - self._mail_box_thread = None # type: Optional[Thread] - self._mail_stats = MailStats() - - self.debug = debug - - @property - def mail_stats(self) -> MailStats: - """Get the mail stats.""" - return self._mail_stats - - @property - def is_running(self) -> bool: - """Check whether the mailbox is running.""" - return self._mail_box_thread is None - - @property - def is_connected(self) -> bool: - """Check whether the mailbox is connected to an OEF node.""" - return self._oef_proxy.is_connected() - - def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: - """ - Handle a message. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - :param content: the message body - - :return: None - """ - self.in_queue.put(ByteMessage(msg_id, dialogue_id, origin, content, Context())) - - def on_search_result(self, search_id: int, agents: List[str]) -> None: - """ - Handle a search result. - - :param search_id: the search id - :param agents: the list of agents returned by the search - - :return: None - """ - self.mail_stats.search_end(search_id, len(agents)) - self.in_queue.put(SearchResult(search_id, agents)) - - def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: - """ - Handle an oef error. - - :param answer_id: the answer id - :param operation: the oef operation - - :return: None - """ - self.in_queue.put(OEFErrorMessage(answer_id, operation)) - - def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: - """ - Handle a dialogue error. - - :param answer_id: the answer id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - - :return: None - """ - self.in_queue.put(DialogueErrorMessage(answer_id, dialogue_id, origin)) - - def connect(self) -> bool: - """ - Connect to the OEF Node. If it fails, then sleep for 3 seconds and try to reconnect again. - - :return: True if the connection has been established successfully, False otherwise. - """ - success = False - - while not success: - try: - success = super().connect() - except ConnectionError: - logger.error("Problems when connecting to the OEF. Retrying in 3 seconds...") - success = False - time.sleep(3.0) - - return success - - def start(self) -> None: - """ - Start the mailbox. - - :return: None - """ - self.connect() - self._mail_box_thread = Thread(target=super().run) - self._mail_box_thread.start() - - def stop(self) -> None: - """ - Stop the mailbox. - - :return: None - """ - self._loop.call_soon_threadsafe(super().stop) - if self._mail_box_thread is not None: - self._mail_box_thread.join() - self._mail_box_thread = None - - -class InBox(object): - """Temporarily stores messages for the agent.""" - - def __init__(self, mail_box: MailBox, timeout: float = 1.0) -> None: - """ - Instantiate the inbox. - - :param mail_box: the mailbox - :param timeout: the (fraction of) seconds for which the inbox times out - - :return: None - """ - self._mail_box = mail_box - self._timeout = timeout - - @property - def in_queue(self) -> Queue: - """Get the in_queue.""" - return self._mail_box.in_queue - - @property - def timeout(self) -> float: - """Get the timeout.""" - return self._timeout - - def is_in_queue_empty(self) -> bool: - """ - Check for a message on the in queue. - - :return: boolean indicating whether there is a message or not - """ - result = self.in_queue.empty() - return result - - def get_wait(self) -> AgentMessage: - """ - Wait for a message on the in queue and get it. Blocking. - - :return: the message object - """ - logger.debug("Waiting for message from the in queue...") - msg = self.in_queue.get() - logger.debug("Incoming message type: type={}".format(type(msg))) - return msg - - def get_some_wait(self, block: bool = True, timeout: Optional[float] = None) -> Optional[AgentMessage]: - """ - Check for a message on the in queue for some time and get it. - - :param block: if true makes it blocking - :param timeout: times out the block after timeout seconds - - :return: the message object - """ - logger.debug("Checks for message from the in queue...") - try: - msg = self.in_queue.get(block=block, timeout=timeout) - logger.debug("Incoming message type: type={}".format(type(msg))) - return msg - except Empty: - return None - - def get_no_wait(self) -> Optional[AgentMessage]: - """ - Check for a message on the in queue and wait for no time. - - :return: the message object - """ - result = self.get_some_wait(False) - return result - - -class OutBox(object): - """Temporarily stores and sends messages to the OEF and other agents.""" - - def __init__(self, mail_box: MailBox) -> None: - """ - Instantiate the outbox. - - :param mail_box: the mail box - - :return: None - """ - self._mail_box = mail_box - - @property - def out_queue(self) -> Queue: - """Get the out queue.""" - return self._mail_box.out_queue - - @property - def mail_box(self) -> MailBox: - """Get the mail box.""" - return self._mail_box - - def send_nowait(self) -> None: - """ - Check whether the out queue contains a message or search query and sends it in that case. Non-blocking. - - :return: None - """ - logger.debug("Checking for message or search query on out queue...") - while not self.out_queue.empty(): - out = self.out_queue.get_nowait() - - if self.mail_box.debug: - logger.warning("Dropping message of type '{}' from the out queue...".format(type(out.message))) - continue - - if isinstance(out, OutContainer) and out.message is not None: - logger.debug("Outgoing message type: type={}".format(type(out.message))) - self.mail_box.send_message(out.message_id, out.dialogue_id, out.destination, out.message) - elif isinstance(out, OutContainer) and (out.service_description is not None) and (not out.is_unregister): - logger.debug("Outgoing register service description: message_id={}".format(type(out.service_description), out.message_id)) - self.mail_box.register_service(out.message_id, out.service_description) - elif isinstance(out, OutContainer) and out.service_description is not None: - logger.debug("Outgoing unregister service description: message_id={}".format(type(out.service_description), out.message_id)) - self.mail_box.unregister_service(out.message_id, out.service_description) - elif isinstance(out, OutContainer) and out.query is not None: - logger.debug("Outgoing query: search_id={}".format(out.search_id)) - self.mail_box.mail_stats.search_start(out.search_id) - self.mail_box.search_services(out.search_id, out.query) - elif isinstance(out, CFP): - logger.debug("Outgoing cfp: msg_id={}, dialogue_id={}, origin={}, target={}, query={}".format(out.msg_id, out.dialogue_id, out.destination, out.target, out.query)) - self.mail_box.send_cfp(out.msg_id, out.dialogue_id, out.destination, out.target, out.query) - elif isinstance(out, Propose): - logger.debug("Outgoing propose: msg_id={}, dialogue_id={}, origin={}, target={}, propose={}".format(out.msg_id, out.dialogue_id, out.destination, out.target, out.proposals[0].values)) - self.mail_box.send_propose(out.msg_id, out.dialogue_id, out.destination, out.target, out.proposals) - elif isinstance(out, Accept): - logger.debug("Outgoing accept: msg_id={}, dialogue_id={}, origin={}, target={}".format(out.msg_id, out.dialogue_id, out.destination, out.target)) - self.mail_box.send_accept(out.msg_id, out.dialogue_id, out.destination, out.target) - elif isinstance(out, Decline): - logger.debug("Outgoing decline: msg_id={}, dialogue_id={}, origin={}, target={}".format(out.msg_id, out.dialogue_id, out.destination, out.target)) - self.mail_box.send_decline(out.msg_id, out.dialogue_id, out.destination, out.target) - elif isinstance(out, ByteMessage): - logger.debug("Outgoing dialogue error message: msg_id={}, dialogue_id={}, origin={}, message={}".format(out.msg_id, out.dialogue_id, out.destination, out.msg)) - # self.mail_box.send_message(out.msg_id, out.dialogue_id, out.destination, out.target, out.message) - else: - logger.debug("Unknown object on out queue... type={}".format(type(out))) - - -class FIPAMailBox(MailBox): - """The FIPAMailBox enqueues additionally FIPA specific messages.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, **kwargs) -> None: - """ - Instantiate the FIPAMailBox. - - :param public_key: the public key of the agent - :param oef_addr: TCP/IP address of the OEF Agent - :param oef_port: TCP/IP port of the OEF Agent - - :return: None - """ - super().__init__(public_key, oef_addr, oef_port, **kwargs) - - def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: - """ - Handle a CFP. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - :param target: the message id targetted by this message - :param query: the query - - :return: None - """ - self.in_queue.put(CFP(msg_id, dialogue_id, origin, target, query, Context())) - - def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: - """ - Handle a Propose. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - :param target: the message id targetted by this message - :param proposals: the proposals - - :return: None - """ - self.in_queue.put(Propose(msg_id, dialogue_id, origin, target, proposals, Context())) - - def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - Handle an Accept. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - :param target: the message id targetted by this message - - :return: None - """ - self.in_queue.put(Accept(msg_id, dialogue_id, origin, target, Context())) - - def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - Handle a Decline. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - :param target: the message id targetted by this message - - :return: None - """ - self.in_queue.put(Decline(msg_id, dialogue_id, origin, target, Context())) - - -class OutContainer: - """The OutContainer is a container to keep a message or search in whilst on the out queue.""" - - def __init__(self, message: Optional[bytes] = None, - message_id: Optional[int] = None, - dialogue_id: Optional[int] = None, - destination: Optional[str] = None, - query: Optional[Query] = None, - search_id: Optional[int] = None, - service_description: Optional[Description] = None, - is_unregister: Optional[bool] = False) -> None: - """ - Instantiate the out cointainer. - - :param message: the message body - :param message_id: the message id - :param dialogue_id: the dialogue id - :param destination: the public key of the message recipient - :param query: the query - :param search_id: the search id - :param service_description: the service description - :param is_unregister: boolean indicating whether this is an unregistration. - - :return: None - """ - self.message = message - self.message_id = message_id - self.dialogue_id = dialogue_id - self.destination = destination - self.query = query - self.search_id = search_id - self.service_description = service_description - self.is_unregister = is_unregister diff --git a/tac/agents/v1/mail/__init__.py b/tac/agents/v1/mail/__init__.py new file mode 100644 index 00000000..7c68785e --- /dev/null +++ b/tac/agents/v1/mail/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py new file mode 100644 index 00000000..bf9b877a --- /dev/null +++ b/tac/agents/v1/mail/base.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +Mail module v2. +""" + +import logging +from abc import abstractmethod, ABC +from queue import Queue +from typing import Optional + +logger = logging.getLogger(__name__) + + +class Message(ABC): + pass + + +class InBox(object): + """A queue from where you can only consume messages.""" + + def __init__(self, queue: Queue): + """Initialize the inbox.""" + super().__init__() + self._queue = queue + + def empty(self) -> bool: + """ + Check for a message on the in queue. + + :return: boolean indicating whether there is a message or not + """ + return self._queue.empty() + + def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[Message]: + """ + Check for a message on the in queue. + + :param block: if true makes it blocking. + :param timeout: times out the block after timeout seconds. + + :return: the message object. + """ + logger.debug("Checks for message from the in queue...") + msg = self._queue.get(block=block, timeout=timeout) + logger.debug("Incoming message type: type={}".format(type(msg))) + return msg + + def get_nowait(self): + """ + Check for a message on the in queue and wait for no time. + + :return: the message object + """ + item = self._queue.get_nowait() + return item + + +class OutBox(object): + """A queue from where you can only enqueue messages.""" + + def __init__(self, queue: Queue) -> None: + """Initialize the outbox.""" + super().__init__() + self._queue = queue + + def put(self, item: Message) -> None: + """Put an item into the queue.""" + logger.debug("Put a message in the queue...") + self._queue.put(item) + + +class Connection: + + def __init__(self): + self.in_queue = Queue() + self.out_queue = Queue() + + @abstractmethod + def connect(self): + """Set up the connection.""" + + @abstractmethod + def disconnect(self): + """Tear down the connection.""" + + @property + @abstractmethod + def is_established(self) -> bool: + """Check if the connection is established.""" + + @abstractmethod + def send(self, msg: Message): + """Send a message.""" + + +class MailBox(object): + """Abstract definition of a mailbox.""" + + def __init__(self, connection: Connection): + """Initialize the mailbox.""" + self._connection = connection + + self.inbox = InBox(self._connection.in_queue) + self.outbox = OutBox(self._connection.out_queue) + + @property + def is_connected(self) -> bool: + """Check whether the mailbox is processing messages.""" + return self._connection.is_established + + def connect(self): + self._connection.connect() + + def disconnect(self): + self._connection.disconnect() + + def send(self, out: Message): + self.outbox.put(out) + diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py new file mode 100644 index 00000000..3323d1dd --- /dev/null +++ b/tac/agents/v1/mail/messages.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Protocol module v2.""" +from abc import ABC +from typing import List + +from oef.messages import CFP_TYPES, PROPOSE_TYPES, OEFErrorOperation +from oef.query import Query +from oef.schema import Description + +from tac.agents.v1.mail.base import Message + +RequestId = int +SearchId = int +MessageId = int +DialogueId = int +AgentAddress = str + + +class OEFMessage(Message, ABC): + pass + + +class OEFRequest(OEFMessage, ABC): + pass + + +class OEFResponse(OEFMessage, ABC): + pass + + +class OEFError(OEFResponse, ABC): + pass + + +class OEFGenericError(OEFError): + + def __init__(self, answer_id: RequestId, operation: OEFErrorOperation): + super().__init__() + self.answer_id = answer_id + self.operation = operation + + +class OEFDialogueError(OEFError): + + def __init__(self, answer_id: MessageId, dialogue_id: DialogueId, origin: AgentAddress): + super().__init__() + self.answer_id = answer_id + self.dialogue_id = dialogue_id + self.origin = origin + + +class OEFRegisterServiceRequest(OEFRequest): + + def __init__(self, msg_id: RequestId, agent_description: Description, service_id: str = ""): + super().__init__() + self.msg_id = msg_id + self.agent_description = agent_description + self.service_id = service_id + + +class OEFUnregisterServiceRequest(OEFRequest): + + def __init__(self, msg_id: RequestId, agent_description: Description, service_id: str = ""): + super().__init__() + self.msg_id = msg_id + self.agent_description = agent_description + self.service_id = service_id + + +class OEFRegisterAgentRequest(OEFRequest): + + def __init__(self, msg_id: RequestId, agent_description: Description): + self.msg_id = msg_id + self.agent_description = agent_description + + +class OEFUnregisterAgentRequest(OEFRequest): + + def __init__(self, msg_id: RequestId): + self.msg_id = msg_id + + +class OEFSearchRequest(OEFRequest): + pass + + +class OEFSearchServicesRequest(OEFSearchRequest): + + def __init__(self, search_id: SearchId, query: Query): + super().__init__() + self.search_id = search_id + self.query = query + + +class OEFSearchAgentsRequest(OEFSearchRequest): + + def __init__(self, search_id: SearchId, query: Query): + super().__init__() + self.search_id = search_id + self.query = query + + +class OEFSearchResult(OEFResponse): + + def __init__(self, search_id: SearchId, agents: List[str]): + super().__init__() + self.search_id = search_id + self.agents = agents + + +class OEFAgentMessage(OEFMessage): + + def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress): + super().__init__() + self.msg_id = msg_id + self.dialogue_id = dialogue_id + self.destination = destination + + +class OEFAgentByteMessage(OEFAgentMessage): + + def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, content: bytes): + super().__init__(msg_id, dialogue_id, destination) + self.content = content + + +class OEFAgentFIPAMessage(OEFAgentMessage): + + def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId): + super().__init__(msg_id, dialogue_id, destination) + self.target = target + + +class OEFAgentCfp(OEFAgentFIPAMessage): + + def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId, query: CFP_TYPES): + super().__init__(msg_id, dialogue_id, destination, target) + self.query = query + + +class OEFAgentPropose(OEFAgentFIPAMessage): + + def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId, proposal: PROPOSE_TYPES): + super().__init__(msg_id, dialogue_id, destination, target) + self.proposal = proposal + + +class OEFAgentAccept(OEFAgentFIPAMessage): + pass + + +class OEFAgentDecline(OEFAgentFIPAMessage): + pass diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py new file mode 100644 index 00000000..9222c627 --- /dev/null +++ b/tac/agents/v1/mail/oef.py @@ -0,0 +1,230 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Extension to the OEF Python SDK.""" +import asyncio +import datetime +import logging +from queue import Empty, Queue +from threading import Thread +from typing import List, Union, Dict + +from oef.agents import Agent +from oef.core import OEFProxy +from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES +from oef.proxy import OEFNetworkProxy + +from tac.agents.v1.mail.base import Connection, MailBox +from tac.agents.v1.mail.messages import OEFRegisterServiceRequest, OEFRegisterAgentRequest, \ + OEFUnregisterServiceRequest, OEFUnregisterAgentRequest, OEFSearchAgentsRequest, OEFSearchServicesRequest, \ + OEFRequest, OEFAgentMessage, OEFAgentByteMessage, OEFAgentFIPAMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, \ + OEFAgentDecline, OEFSearchResult, OEFGenericError, OEFDialogueError + +logger = logging.getLogger(__name__) + + +class MailStats(object): + """The MailStats class tracks statistics on messages processed by MailBox.""" + + def __init__(self) -> None: + """ + Instantiate mail stats. + + :return: None + """ + self._search_count = 0 + self._search_start_time = {} # type: Dict[int, datetime.datetime] + self._search_timedelta = {} # type: Dict[int, float] + self._search_result_counts = {} # type: Dict[int, int] + + @property + def search_count(self) -> int: + """Get the search count.""" + return self._search_count + + def search_start(self, search_id: int) -> None: + """ + Add a search id and start time. + + :param search_id: the search id + + :return: None + """ + assert search_id not in self._search_start_time + self._search_count += 1 + self._search_start_time[search_id] = datetime.datetime.now() + + def search_end(self, search_id: int, nb_search_results: int) -> None: + """ + Add end time for a search id. + + :param search_id: the search id + :param nb_search_results: the number of agents returned in the search result + + :return: None + """ + assert search_id in self._search_start_time + assert search_id not in self._search_timedelta + self._search_timedelta[search_id] = (datetime.datetime.now() - self._search_start_time[search_id]).total_seconds() * 1000 + self._search_result_counts[search_id] = nb_search_results + + +class OEFChannel(Agent): + + def __init__(self, oef_proxy: OEFProxy, in_queue: Queue): + super().__init__(oef_proxy) + self.in_queue = in_queue + self.mail_stats = MailStats() + + def is_connected(self): + return self._oef_proxy.is_connected() + + def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): + self.in_queue.put(OEFAgentByteMessage(msg_id, dialogue_id, origin, content)) + + def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES): + self.in_queue.put(OEFAgentCfp(msg_id, dialogue_id, origin, target, query)) + + def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES): + self.in_queue.put(OEFAgentPropose(msg_id, dialogue_id, origin, target, proposals)) + + def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int): + self.in_queue.put(OEFAgentAccept(msg_id, dialogue_id, origin, target)) + + def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int): + self.in_queue.put(OEFAgentDecline(msg_id, dialogue_id, origin, target)) + + def on_search_result(self, search_id: int, agents: List[str]): + self.mail_stats.search_end(search_id, len(agents)) + self.in_queue.put(OEFSearchResult(search_id, agents)) + + def on_oef_error(self, answer_id: int, operation: OEFErrorOperation): + self.in_queue.put(OEFGenericError(answer_id, operation)) + + def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str): + self.in_queue.put(OEFDialogueError(answer_id, dialogue_id, origin)) + + def send(self, msg: Union[OEFRequest, OEFAgentMessage]): + if isinstance(msg, OEFRequest): + self.send_oef_request(msg) + elif isinstance(msg, OEFAgentMessage): + self.send_oef_agent_message(msg) + else: + raise ValueError("Cannot send message.") + + def send_oef_request(self, msg: OEFRequest): + if isinstance(msg, OEFRegisterServiceRequest): + self.register_service(msg.msg_id, msg.agent_description, msg.service_id) + elif isinstance(msg, OEFRegisterAgentRequest): + self.register_agent(msg.msg_id, msg.agent_description) + elif isinstance(msg, OEFUnregisterServiceRequest): + self.unregister_service(msg.msg_id, msg.agent_description, msg.service_id) + elif isinstance(msg, OEFUnregisterAgentRequest): + self.unregister_agent(msg.msg_id) + elif isinstance(msg, OEFSearchAgentsRequest): + self.search_agents(msg.search_id, msg.query) + elif isinstance(msg, OEFSearchServicesRequest): + self.mail_stats.search_start(msg.search_id) + self.search_services(msg.search_id, msg.query) + else: + raise ValueError("OEF request not recognized.") + + def send_oef_agent_message(self, msg: OEFAgentMessage): + if isinstance(msg, OEFAgentByteMessage): + self.send_message(msg.msg_id, msg.dialogue_id, msg.destination, msg.content) + elif isinstance(msg, OEFAgentFIPAMessage): + self.send_fipa_message(msg) + else: + raise ValueError("OEF Agent message not recognized.") + + def send_fipa_message(self, msg: OEFAgentFIPAMessage): + if isinstance(msg, OEFAgentCfp): + self.send_cfp(msg.msg_id, msg.dialogue_id, msg.destination, msg.target, msg.query) + elif isinstance(msg, OEFAgentPropose): + self.send_propose(msg.msg_id, msg.dialogue_id, msg.destination, msg.target, msg.proposal) + elif isinstance(msg, OEFAgentAccept): + self.send_accept(msg.msg_id, msg.dialogue_id, msg.destination, msg.target) + elif isinstance(msg, OEFAgentDecline): + self.send_decline(msg.msg_id, msg.dialogue_id, msg.destination, msg.target) + else: + raise ValueError("OEF FIPA message not recognized.") + + def is_active(self) -> bool: + return self._oef_proxy._active_loop + + +class OEFConnection(Connection): + + def __init__(self, oef_proxy: OEFProxy): + super().__init__() + + self.bridge = OEFChannel(oef_proxy, self.in_queue) + + self._stopped = True + self.in_thread = Thread(target=self.bridge.run) + self.out_thread = Thread(target=self._fetch) + + def _fetch(self): + while not self._stopped: + try: + msg = self.out_queue.get(block=True, timeout=1.0) + self.send(msg) + except Empty: + pass + + def connect(self): + if self._stopped: + self._stopped = False + self.bridge.connect() + self.in_thread.start() + self.out_thread.start() + + def disconnect(self): + if self.bridge.is_active(): + self.bridge.stop() + + self._stopped = True + self.in_thread.join() + self.out_thread.join() + self.bridge.disconnect() + + @property + def is_established(self) -> bool: + return self.bridge.is_connected() + + def send(self, msg: Union[OEFRequest, OEFAgentMessage]): + self.bridge.send(msg) + + +class OEFMailBox(MailBox): + + def __init__(self, proxy: OEFProxy): + connection = OEFConnection(proxy) + super().__init__(connection) + + @property + def mail_stats(self) -> MailStats: + return self._connection.bridge.mail_stats + + +class OEFNetworkMailBox(OEFMailBox): + + def __init__(self, public_key: str, oef_addr: str, port: int = 10000): + super().__init__(OEFNetworkProxy(public_key, oef_addr, port, loop=asyncio.new_event_loop())) diff --git a/tac/platform/controller.py b/tac/platform/controller.py index 28d4d7d0..ae71dbb8 100644 --- a/tac/platform/controller.py +++ b/tac/platform/controller.py @@ -50,7 +50,7 @@ import dateutil from oef.agents import OEFAgent -from oef.messages import Message as OEFErrorOperation +from oef.messages import OEFErrorOperation from oef.schema import Description, DataModel, AttributeSchema from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index a55394ba..cc801476 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -24,7 +24,7 @@ from threading import Thread from tac.agents.v1.agent import Agent, AgentState -from tac.agents.v1.mail import FIPAMailBox +from tac.agents.v1.mail.oef import OEFNetworkMailBox class TestAgent(Agent): @@ -33,7 +33,13 @@ class TestAgent(Agent): def __init__(self): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000) - self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + + def setup(self) -> None: + pass + + def teardown(self) -> None: + pass def act(self) -> None: """Perform actions.""" @@ -56,6 +62,7 @@ def test_agent_connected(network_node): test_agent = TestAgent() test_agent.mail_box.connect() assert test_agent.agent_state == AgentState.CONNECTED + test_agent.mail_box.disconnect() def test_agent_running(network_node): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 4428ca42..3bc4a9b8 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -1,83 +1,83 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Test miscellaneous features for the agent module.""" -from threading import Timer -from unittest.mock import patch, MagicMock - -from tac.agents.v1.agent import Agent -from tac.agents.v1.mail import FIPAMailBox, InBox, OutBox, OutContainer - - -class TestAgent(Agent): - """A class to implement an agent for testing.""" - - def __init__(self, **kwargs): - """Initialize the test agent.""" - super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) - self.mail_box = FIPAMailBox(self.crypto.public_key, "127.0.0.1", 10000, debug=kwargs.get("debug", False)) - self.in_box = InBox(self.mail_box) - self.out_box = OutBox(self.mail_box) - - def act(self) -> None: - """Perform actions.""" - - def react(self) -> None: - """React to incoming events.""" - - def update(self) -> None: - """Update the current state of the agent.""" - - -def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): - """ - Test that, in debug mode, the agent's main loop can be run without the OEF running. - - In particular, assert that the methods 'act', 'react' and 'update' are called. - """ - test_agent = TestAgent(debug=True) - - test_agent.act = MagicMock(test_agent.act) - test_agent.react = MagicMock(test_agent.react) - test_agent.update = MagicMock(test_agent.update) - - job = Timer(1.0, test_agent.stop) - job.start() - test_agent.start() - job.join() - - test_agent.act.assert_called() - test_agent.react.assert_called() - test_agent.update.assert_called() - - -def test_that_when_debug_flag_true_we_drop_out_messages(): - """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" - with patch('logging.Logger.warning') as mock: - test_agent = TestAgent(debug=True) - job = Timer(1.0, test_agent.stop) - job.start() - msg = OutContainer(b"this is a message.", 0, 0, "destination") - test_agent.out_box.out_queue.put_nowait(msg) - test_agent.out_box.send_nowait() - test_agent.start() - job.join() - - mock.assert_called_with("Dropping message of type '' from the out queue...") +# # -*- coding: utf-8 -*- +# +# # ------------------------------------------------------------------------------ +# # +# # Copyright 2018-2019 Fetch.AI Limited +# # +# # Licensed under the Apache License, Version 2.0 (the "License"); +# # you may not use this file except in compliance with the License. +# # You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, software +# # distributed under the License is distributed on an "AS IS" BASIS, +# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# # See the License for the specific language governing permissions and +# # limitations under the License. +# # +# # ------------------------------------------------------------------------------ +# +# """Test miscellaneous features for the agent module.""" +# from threading import Timer +# from unittest.mock import patch, MagicMock +# +# from tac.agents.v1.agent import Agent +# from tac.agents.v1.mail.oef import, OEFNetworkMailBox +# +# +# class TestAgent(Agent): +# """A class to implement an agent for testing.""" +# +# def __init__(self, **kwargs): +# """Initialize the test agent.""" +# super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) +# self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) +# self.in_box = InBox(self.mail_box) +# self.out_box = OutBox(self.mail_box) +# +# def act(self) -> None: +# """Perform actions.""" +# +# def react(self) -> None: +# """React to incoming events.""" +# +# def update(self) -> None: +# """Update the current state of the agent.""" +# +# +# def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): +# """ +# Test that, in debug mode, the agent's main loop can be run without the OEF running. +# +# In particular, assert that the methods 'act', 'react' and 'update' are called. +# """ +# test_agent = TestAgent(debug=True) +# +# test_agent.act = MagicMock(test_agent.act) +# test_agent.react = MagicMock(test_agent.react) +# test_agent.update = MagicMock(test_agent.update) +# +# job = Timer(1.0, test_agent.stop) +# job.start() +# test_agent.start() +# job.join() +# +# test_agent.act.assert_called() +# test_agent.react.assert_called() +# test_agent.update.assert_called() +# +# +# def test_that_when_debug_flag_true_we_drop_out_messages(): +# """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" +# with patch('logging.Logger.warning') as mock: +# test_agent = TestAgent(debug=True) +# job = Timer(1.0, test_agent.stop) +# job.start() +# msg = OutContainer(b"this is a message.", 0, 0, "destination") +# test_agent.out_box.out_queue.put_nowait(msg) +# test_agent.out_box.send_nowait() +# test_agent.start() +# job.join() +# +# mock.assert_called_with("Dropping message of type '' from the out queue...") From cf52f1d6454e9b02bd78fddb0d032c820098a926 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 15 Aug 2019 00:30:31 +0100 Subject: [PATCH 010/107] restore tests on debug mode. --- tac/agents/v1/agent.py | 2 +- tests/test_agent/test_misc.py | 156 ++++++++++++++++------------------ 2 files changed, 74 insertions(+), 84 deletions(-) diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index e0b26df3..690b730d 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -136,7 +136,7 @@ def start(self) -> None: :return: None """ - if not self.mail_box.is_connected: + if not self.debug and not self.mail_box.is_connected: self.mail_box.connect() self.liveness._is_stopped = False diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 3bc4a9b8..7c0df8ac 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -1,83 +1,73 @@ -# # -*- coding: utf-8 -*- -# -# # ------------------------------------------------------------------------------ -# # -# # Copyright 2018-2019 Fetch.AI Limited -# # -# # Licensed under the Apache License, Version 2.0 (the "License"); -# # you may not use this file except in compliance with the License. -# # You may obtain a copy of the License at -# # -# # http://www.apache.org/licenses/LICENSE-2.0 -# # -# # Unless required by applicable law or agreed to in writing, software -# # distributed under the License is distributed on an "AS IS" BASIS, -# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# # See the License for the specific language governing permissions and -# # limitations under the License. -# # -# # ------------------------------------------------------------------------------ -# -# """Test miscellaneous features for the agent module.""" -# from threading import Timer -# from unittest.mock import patch, MagicMock -# -# from tac.agents.v1.agent import Agent -# from tac.agents.v1.mail.oef import, OEFNetworkMailBox -# -# -# class TestAgent(Agent): -# """A class to implement an agent for testing.""" -# -# def __init__(self, **kwargs): -# """Initialize the test agent.""" -# super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) -# self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) -# self.in_box = InBox(self.mail_box) -# self.out_box = OutBox(self.mail_box) -# -# def act(self) -> None: -# """Perform actions.""" -# -# def react(self) -> None: -# """React to incoming events.""" -# -# def update(self) -> None: -# """Update the current state of the agent.""" -# -# -# def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): -# """ -# Test that, in debug mode, the agent's main loop can be run without the OEF running. -# -# In particular, assert that the methods 'act', 'react' and 'update' are called. -# """ -# test_agent = TestAgent(debug=True) -# -# test_agent.act = MagicMock(test_agent.act) -# test_agent.react = MagicMock(test_agent.react) -# test_agent.update = MagicMock(test_agent.update) -# -# job = Timer(1.0, test_agent.stop) -# job.start() -# test_agent.start() -# job.join() -# -# test_agent.act.assert_called() -# test_agent.react.assert_called() -# test_agent.update.assert_called() -# -# -# def test_that_when_debug_flag_true_we_drop_out_messages(): -# """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" -# with patch('logging.Logger.warning') as mock: -# test_agent = TestAgent(debug=True) -# job = Timer(1.0, test_agent.stop) -# job.start() -# msg = OutContainer(b"this is a message.", 0, 0, "destination") -# test_agent.out_box.out_queue.put_nowait(msg) -# test_agent.out_box.send_nowait() -# test_agent.start() -# job.join() -# -# mock.assert_called_with("Dropping message of type '' from the out queue...") +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test miscellaneous features for the agent module.""" +from threading import Timer +from unittest.mock import patch, MagicMock + +from tac.agents.v1.agent import Agent +from tac.agents.v1.mail.messages import OEFAgentByteMessage +from tac.agents.v1.mail.oef import OEFNetworkMailBox + + +class TestAgent(Agent): + """A class to implement an agent for testing.""" + + def __init__(self, **kwargs): + """Initialize the test agent.""" + super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) + self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + + def setup(self) -> None: + """Set up the agent.""" + + def teardown(self) -> None: + """Tear down the agent.""" + + def act(self) -> None: + """Perform actions.""" + + def react(self) -> None: + """React to incoming events.""" + + def update(self) -> None: + """Update the current state of the agent.""" + + +def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): + """ + Test that, in debug mode, the agent's main loop can be run without the OEF running. + + In particular, assert that the methods 'act', 'react' and 'update' are called. + """ + test_agent = TestAgent(debug=True) + + test_agent.act = MagicMock(test_agent.act) + test_agent.react = MagicMock(test_agent.react) + test_agent.update = MagicMock(test_agent.update) + + job = Timer(1.0, test_agent.stop) + job.start() + test_agent.start() + job.join() + + test_agent.act.assert_called() + test_agent.react.assert_called() + test_agent.update.assert_called() From 52f5991381e80db947d08ac83c91513dd13bdce9 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 15 Aug 2019 07:48:23 +0100 Subject: [PATCH 011/107] Bump to oef sdk 0.6.1 and refactor controller agent to v1 --- sandbox/oef_healthcheck.py | 12 +- scripts/launch_alt.py | 13 +- simulation/README.md | 1 + tac/agents/v1/base/actions.py | 4 +- tac/agents/v1/base/interfaces.py | 4 +- tac/agents/v1/base/reactions.py | 4 +- tac/agents/v1/mail.py | 13 +- tac/helpers/oef_health_check.py | 66 ++ tac/platform/controller.py | 929 -------------------- tac/platform/controller/__init__.py | 21 + tac/platform/controller/actions.py | 70 ++ tac/platform/controller/controller_agent.py | 309 +++++++ tac/platform/controller/handlers.py | 485 ++++++++++ tac/platform/controller/interfaces.py | 67 ++ tac/platform/controller/reactions.py | 78 ++ tac/platform/controller/tac_parameters.py | 177 ++++ tac/platform/helpers.py | 51 ++ tac/platform/simulation.py | 119 ++- tests/conftest.py | 13 +- tests/test_controller.py | 13 +- tests/test_simulation.py | 3 +- 21 files changed, 1428 insertions(+), 1024 deletions(-) create mode 100644 tac/helpers/oef_health_check.py delete mode 100644 tac/platform/controller.py create mode 100644 tac/platform/controller/__init__.py create mode 100644 tac/platform/controller/actions.py create mode 100644 tac/platform/controller/controller_agent.py create mode 100644 tac/platform/controller/handlers.py create mode 100644 tac/platform/controller/interfaces.py create mode 100644 tac/platform/controller/reactions.py create mode 100644 tac/platform/controller/tac_parameters.py create mode 100644 tac/platform/helpers.py diff --git a/sandbox/oef_healthcheck.py b/sandbox/oef_healthcheck.py index 469bac3e..eac30260 100644 --- a/sandbox/oef_healthcheck.py +++ b/sandbox/oef_healthcheck.py @@ -22,14 +22,18 @@ """This script waits until the OEF is up and running.""" import argparse -import asyncio +import logging -import oef.agents +from oef.agents import OEFAgent +from oef.core import AsyncioCore + +logger = logging.getLogger(__name__) parser = argparse.ArgumentParser("oef_healthcheck", description=__doc__) parser.add_argument("addr", type=str, help="IP address of the OEF node.") parser.add_argument("port", type=int, help="Port of the OEF node.") + if __name__ == '__main__': try: args = parser.parse_args() @@ -37,7 +41,9 @@ port = args.port pbk = 'check' print("Connecting to {}:{}".format(host, port)) - agent = oef.agents.OEFAgent(pbk, host, port, loop=asyncio.get_event_loop()) + core = AsyncioCore(logger=logger) + core.run_threaded() + agent = OEFAgent(pbk, oef_addr=host, oef_port=port, core=core) agent.connect() agent.disconnect() print("OK!") diff --git a/scripts/launch_alt.py b/scripts/launch_alt.py index a199a10c..011c547d 100644 --- a/scripts/launch_alt.py +++ b/scripts/launch_alt.py @@ -27,6 +27,7 @@ import re import subprocess import sys +import time import docker @@ -69,14 +70,10 @@ def _stop_oef_search_images(self): def _wait_for_oef(self): """Wait for the OEF to come live.""" print("Waiting for the OEF to be operative...") - wait_for_oef = subprocess.Popen([ - os.path.join("sandbox", "wait-for-oef.sh"), - "127.0.0.1", - "10000", - ":" - ], env=os.environ, cwd=ROOT_DIR) - - wait_for_oef.wait(30) + for loop in range(0, 30): + exit_status = os.system("netstat -nal | grep 10000") + if exit_status != 1: break + time.sleep(1) def __enter__(self): """Define what the context manager should do at the beginning of the block.""" diff --git a/simulation/README.md b/simulation/README.md index 4d5400cf..10268102 100644 --- a/simulation/README.md +++ b/simulation/README.md @@ -61,6 +61,7 @@ For a full list, do `python simulation/tac_agent_spawner.py -h` - `--visdom-port` is the TCP/IP port of the Visdom server - `--dashboard` is a flag to specify that the dashboard is live and expecting an event stream. - `--seed` is the seed for the random module. +- `--fraction-world-modeling` the fraction of world modelling baseline agents. Example: diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index eb6f677d..39641a0a 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -31,7 +31,7 @@ from oef.query import Query, Constraint, GtEq from tac.agents.v1.agent import Liveness -from tac.agents.v1.base.interfaces import ControllerActionInterface, OEFSearchActionInterface, DialogueActionInterface +from tac.agents.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.mail import OutBox, OutContainer from tac.helpers.crypto import Crypto @@ -71,7 +71,7 @@ def request_state_update(self) -> None: self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=self.game_instance.controller_pbk)) -class OEFActions(OEFSearchActionInterface): +class OEFActions(OEFActionInterface): """The OEFActions class defines the actions of an agent towards the OEF.""" def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str) -> None: diff --git a/tac/agents/v1/base/interfaces.py b/tac/agents/v1/base/interfaces.py index a2cadf00..b9d8f06f 100644 --- a/tac/agents/v1/base/interfaces.py +++ b/tac/agents/v1/base/interfaces.py @@ -102,7 +102,7 @@ def request_state_update(self) -> None: """ -class OEFSearchReactionInterface: +class OEFReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod @@ -136,7 +136,7 @@ def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: """ -class OEFSearchActionInterface: +class OEFActionInterface: """This interface contains the methods to interact with the OEF.""" @abstractmethod diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 9802bd39..5d0783e3 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -38,7 +38,7 @@ from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.helpers import dialogue_label_from_transaction_id -from tac.agents.v1.base.interfaces import ControllerReactionInterface, OEFSearchReactionInterface, \ +from tac.agents.v1.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ DialogueReactionInterface from tac.agents.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.v1.base.stats_manager import EndState @@ -190,7 +190,7 @@ def _request_state_update(self) -> None: self.out_box.out_queue.put(OutContainer(message=msg, message_id=0, dialogue_id=0, destination=self.game_instance.controller_pbk)) -class OEFReactions(OEFSearchReactionInterface): +class OEFReactions(OEFReactionInterface): """The OEFReactions class defines the reactions of an agent towards the OEF.""" def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstance, out_box: 'OutBox', agent_name: str, rejoin: bool = False) -> None: diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index 34341b4a..07d06483 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -27,7 +27,6 @@ - OutBox: Temporarily stores and sends messages to the OEF and other agents. """ -import asyncio import datetime import logging import time @@ -36,6 +35,7 @@ from typing import List, Optional, Any, Union, Dict from oef.agents import OEFAgent +from oef.core import AsyncioCore from oef.messages import PROPOSE_TYPES, CFP_TYPES, CFP, Decline, Propose, Accept, Message as ByteMessage, \ SearchResult, OEFErrorOperation, OEFErrorMessage, DialogueErrorMessage from oef.query import Query @@ -111,7 +111,9 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, debug: :return: None """ - super().__init__(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) + core = AsyncioCore(logger=logger) + super().__init__(public_key, oef_addr=oef_addr, oef_port=oef_port, core=core) + self.core.run_threaded() self.in_queue = Queue() self.out_queue = Queue() self._mail_box_thread = None # type: Optional[Thread] @@ -132,7 +134,7 @@ def is_running(self) -> bool: @property def is_connected(self) -> bool: """Check whether the mailbox is connected to an OEF node.""" - return self._oef_proxy.is_connected() + return True # self._oef_proxy.is_connected() TODO! def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: """ @@ -198,6 +200,7 @@ def connect(self) -> bool: success = False time.sleep(3.0) + logger.debug("Successfully connected to OEF!") return success def start(self) -> None: @@ -216,9 +219,11 @@ def stop(self) -> None: :return: None """ - self._loop.call_soon_threadsafe(super().stop) + self.core.stop() + # self._loop.call_soon_threadsafe(super().stop) if self._mail_box_thread is not None: self._mail_box_thread.join() + self.disconnect() self._mail_box_thread = None diff --git a/tac/helpers/oef_health_check.py b/tac/helpers/oef_health_check.py new file mode 100644 index 00000000..6a9a1331 --- /dev/null +++ b/tac/helpers/oef_health_check.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This script waits until the OEF is up and running.""" + +import logging + +from oef.agents import OEFAgent +from oef.core import AsyncioCore + +logger = logging.getLogger(__name__) + + +class OEFHealthCheck(object): + """A health check class.""" + + def __init__(self, addr: str, port: int): + """ + Initialize. + + :param addr: IP address of the OEF node. + :param port: Port of the OEF node. + """ + self.addr = addr + self.port = port + + def run(self) -> bool: + """ + Run the check. + + :return: + """ + result = False + try: + # import pdb; pdb.set_trace() + pbk = 'check' + print("Connecting to {}:{}".format(self.addr, self.port)) + core = AsyncioCore(logger=logger) + core.run_threaded() + agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) + agent.connect() + agent.disconnect() + print("OK!") + result = True + return result + except Exception as e: + print(str(e)) + return result diff --git a/tac/platform/controller.py b/tac/platform/controller.py deleted file mode 100644 index 28d4d7d0..00000000 --- a/tac/platform/controller.py +++ /dev/null @@ -1,929 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -""" -This module contains the classes that implements the Controller agent behaviour. - -The methods are split in three classes: -- TACParameters: this class contains the parameters for the TAC. -- ControllerAgent: extends OEFAgent, receives messages and dispatches them using the ControllerDispatcher. -- ControllerDispatcher: class to wrap the decoding procedure and dispatching the handling of the message to the right function. -- GameHandler: handles an instance of the game. -- RequestHandler: abstract class for a request handler. -- RegisterHandler: class for a register handler. -- UnregisterHandler: class for an unregister handler -- TransactionHandler: class for a transaction handler. -- GetStateUpdateHandler: class for a state update handler. -""" - -import argparse -import asyncio -import datetime -import json -import logging -import os -import pprint -import random -import time -from abc import ABC, abstractmethod -from collections import defaultdict -from threading import Thread -from typing import Any, Dict, Type, List, Union, Optional, Set - -import dateutil -from oef.agents import OEFAgent -from oef.messages import Message as OEFErrorOperation -from oef.schema import Description, DataModel, AttributeSchema - -from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor -from tac.helpers.crypto import Crypto -from tac.helpers.misc import generate_good_pbk_to_name -from tac.platform.game import Game -from tac.platform.protocol import Response, Request, Register, Unregister, Error, GameData, \ - Transaction, TransactionConfirmation, ErrorCode, Cancelled, GetStateUpdate, StateUpdate -from tac.platform.stats import GameStats - -if __name__ != "__main__": - logger = logging.getLogger(__name__) -else: - logger = logging.getLogger("tac.platform.controller") - - -class TACParameters(object): - """This class contains the parameters for the TAC.""" - - def __init__(self, min_nb_agents: int = 5, - money_endowment: int = 200, - nb_goods: int = 5, - tx_fee: float = 1.0, - base_good_endowment: int = 2, - lower_bound_factor: int = 1, - upper_bound_factor: int = 1, - start_time: datetime.datetime = datetime.datetime.now(), - registration_timeout: int = 10, - competition_timeout: int = 20, - inactivity_timeout: int = 10, - whitelist: Optional[Set[str]] = None): - """ - Initialize parameters for TAC. - - :param min_nb_agents: the number of agents to wait for the registration. - :param money_endowment: The money amount every agent receives. - :param nb_goods: the number of goods in the competition. - :param tx_fee: the fee for a transaction. - :param base_good_endowment:The base amount of per good instances every agent receives. - :param lower_bound_factor: the lower bound factor of a uniform distribution. - :param upper_bound_factor: the upper bound factor of a uniform distribution. - :param start_time: the datetime when the competition will start. - :param registration_timeout: the duration (in seconds) of the registration phase. - :param competition_timeout: the duration (in seconds) of the competition phase. - :param inactivity_timeout: the time when the competition will start. - :param whitelist: the set of agent names allowed. If None, no checks on the agent names. - """ - self._min_nb_agents = min_nb_agents - self._money_endowment = money_endowment - self._nb_goods = nb_goods - self._tx_fee = tx_fee - self._base_good_endowment = base_good_endowment - self._lower_bound_factor = lower_bound_factor - self._upper_bound_factor = upper_bound_factor - self._start_time = start_time - self._registration_timeout = registration_timeout - self._competition_timeout = competition_timeout - self._inactivity_timeout = inactivity_timeout - self._whitelist = whitelist - self._check_values() - - def _check_values(self) -> None: - """ - Check constructor parameters. - - :raises ValueError: if some parameter has not the right value. - """ - if self._start_time is None: - raise ValueError - if self._inactivity_timeout is None: - raise ValueError - - @property - def min_nb_agents(self) -> int: - """Minimum number of agents required for a TAC instance.""" - return self._min_nb_agents - - @property - def money_endowment(self): - """Money endowment per agent for a TAC instance.""" - return self._money_endowment - - @property - def nb_goods(self): - """Good number for a TAC instance.""" - return self._nb_goods - - @property - def tx_fee(self): - """Transaction fee for a TAC instance.""" - return self._tx_fee - - @property - def base_good_endowment(self): - """Minimum endowment of each agent for each good.""" - return self._base_good_endowment - - @property - def lower_bound_factor(self): - """Lower bound of a uniform distribution.""" - return self._lower_bound_factor - - @property - def upper_bound_factor(self): - """Upper bound of a uniform distribution.""" - return self._upper_bound_factor - - @property - def start_time(self) -> datetime.datetime: - """TAC start time.""" - return self._start_time - - @property - def end_time(self) -> datetime.datetime: - """TAC end time.""" - return self._start_time + self.registration_timedelta + self.competition_timedelta - - @property - def registration_timeout(self): - """Timeout of registration.""" - return self._registration_timeout - - @property - def competition_timeout(self): - """Timeout of competition.""" - return self._competition_timeout - - @property - def inactivity_timeout(self): - """Timeout of agent inactivity from controller perspective (no received transactions).""" - return self._inactivity_timeout - - @property - def registration_timedelta(self) -> datetime.timedelta: - """Time delta of the registration timeout.""" - return datetime.timedelta(0, self._registration_timeout) - - @property - def competition_timedelta(self) -> datetime.timedelta: - """Time delta of the competition timeout.""" - return datetime.timedelta(0, self._competition_timeout) - - @property - def inactivity_timedelta(self) -> datetime.timedelta: - """Time delta of the inactivity timeout.""" - return datetime.timedelta(0, self._inactivity_timeout) - - @property - def whitelist(self) -> Optional[Set[str]]: - """Whitelist of agent public keys allowed into the TAC instance.""" - return self._whitelist - - -class RequestHandler(ABC): - """Abstract class for a request handler.""" - - def __init__(self, controller_agent: 'ControllerAgent') -> None: - """ - Instantiate a request handler. - - :param controller_agent: the controller agent instance - :return: None - """ - self.controller_agent = controller_agent - - def __call__(self, request: Request) -> Optional[Response]: - """Call the handler.""" - return self.handle(request) - - @abstractmethod - def handle(self, request: Request) -> Optional[Response]: - """ - Handle a request from an OEF agent. - - :param request: the request message. - :return: a response, or None. - """ - - -class RegisterHandler(RequestHandler): - """Class for a register handler.""" - - def handle(self, request: Register) -> Optional[Response]: - """ - Handle a register message. - - If the public key is already registered, answer with an error message. - If this is the n_th registration request, where n is equal to nb_agents, then start the competition. - - :param request: the register request. - :return: an Error response if an error occurred, else None. - """ - whitelist = self.controller_agent.game_handler.tac_parameters.whitelist - if whitelist is not None and request.agent_name not in whitelist: - error_msg = "[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, request.agent_name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) - - if request.public_key in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key]) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_PBK_ALREADY_REGISTERED) - - if request.agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): - error_msg = "[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, request.agent_name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_ALREADY_REGISTERED) - - try: - self.controller_agent.monitor.dashboard.agent_pbk_to_name.update({request.public_key: request.agent_name}) - self.controller_agent.monitor.update() - except Exception as e: - logger.error(str(e)) - - self.controller_agent.game_handler.agent_pbk_to_name[request.public_key] = request.agent_name - logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) - self.controller_agent.game_handler.registered_agents.add(request.public_key) - return None - - -class UnregisterHandler(RequestHandler): - """Class for an unregister handler.""" - - def handle(self, request: Unregister) -> Optional[Response]: - """ - Handle a unregister message. - - If the public key is not registered, answer with an error message. - - :param request: the register request. - :return: an Error response if an error occurred, else None. - """ - if request.public_key not in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) - else: - logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) - self.controller_agent.game_handler.registered_agents.remove(request.public_key) - self.controller_agent.game_handler.agent_pbk_to_name.pop(request.public_key) - return None - - -class TransactionHandler(RequestHandler): - """Class for a transaction handler.""" - - def __init__(self, controller_agent: 'ControllerAgent') -> None: - """Instantiate a TransactionHandler.""" - super().__init__(controller_agent) - self._pending_transaction_requests = {} # type: Dict[str, Transaction] - - def handle(self, tx: Transaction) -> Optional[Response]: - """ - Handle a transaction request message. - - If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. - - :param request: the transaction request. - :return: an Error response if an error occurred, else None (no response to send back). - """ - logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, tx)) - - # if transaction arrives first time then put it into the pending pool - if tx.transaction_id not in self._pending_transaction_requests: - if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): - logger.debug("[{}]: Put transaction request in the pool: {}".format(self.controller_agent.name, tx.transaction_id)) - self._pending_transaction_requests[tx.transaction_id] = tx - else: - return self._handle_invalid_transaction(tx) - # if transaction arrives second time then process it - else: - pending_tx = self._pending_transaction_requests.pop(tx.transaction_id) - if tx.matches(pending_tx): - if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): - self.controller_agent.game_handler.confirmed_transaction_per_participant[pending_tx.sender].append(pending_tx) - self.controller_agent.game_handler.confirmed_transaction_per_participant[tx.sender].append(tx) - self._handle_valid_transaction(tx) - else: - return self._handle_invalid_transaction(tx) - else: - return self._handle_non_matching_transaction(tx) - - def _handle_valid_transaction(self, tx: 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 - """ - logger.debug("[{}]: Handling valid transaction: {}".format(self.controller_agent.name, tx.transaction_id)) - - # update the game state. - self.controller_agent.game_handler.current_game.settle_transaction(tx) - - # update the dashboard monitor - self.controller_agent.monitor.update() - - # send the transaction confirmation. - tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - self.controller_agent.send_message(0, 0, tx.public_key, tx_confirmation.serialize()) - self.controller_agent.send_message(0, 0, tx.counterparty, tx_confirmation.serialize()) - - # log messages - logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) - holdings_summary = self.controller_agent.game_handler.current_game.get_holdings_summary() - logger.debug("[{}]: Current state:\n{}".format(self.controller_agent.name, holdings_summary)) - - return None - - def _handle_invalid_transaction(self, tx: Transaction) -> Response: - """Handle an invalid transaction.""" - return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_VALID, - details={"transaction_id": tx.transaction_id}) - - def _handle_non_matching_transaction(self, tx: Transaction) -> Response: - """Handle non-matching transaction.""" - return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_MATCHING) - - -class GetStateUpdateHandler(RequestHandler): - """Class for a state update handler.""" - - def handle(self, request: GetStateUpdate) -> Optional[Response]: - """ - Handle a 'get agent state' request. - - If the public key is not registered, answer with an error message. - - :param request: the 'get agent state' request. - :return: an Error response if an error occurred, else None. - """ - logger.debug("[{}]: Handling the 'get agent state' request: {}".format(self.controller_agent.name, request)) - if not self.controller_agent.game_handler.is_game_running(): - error_msg = "[{}]: GetStateUpdate request is not valid while the competition is not running.".format(self.controller_agent.name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.COMPETITION_NOT_RUNNING) - if request.public_key not in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) - else: - transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[request.public_key] # type: List[Transaction] - initial_game_data = self.controller_agent.game_handler.game_data_per_participant[request.public_key] # type: GameData - return StateUpdate(request.public_key, self.controller_agent.crypto, initial_game_data, transactions) - - -class ControllerDispatcher(object): - """Class to wrap the decoding procedure and dispatching the handling of the message to the right function.""" - - def __init__(self, controller_agent: 'ControllerAgent'): - """ - Initialize a Controller handler, i.e. the class that manages the handling of incoming messages. - - :param controller_agent: The Controller Agent the handler is associated with. - """ - self.controller_agent = controller_agent - - self.handlers = { - Register: RegisterHandler(controller_agent), - Unregister: UnregisterHandler(controller_agent), - Transaction: TransactionHandler(controller_agent), - GetStateUpdate: GetStateUpdateHandler(controller_agent), - } # type: Dict[Type[Request], RequestHandler] - - def register_handler(self, request_type: Type[Request], request_handler: RequestHandler) -> None: - """ - Register a handler for a type of request. - - :param request_type: the type of request to handle. - :param request_handler: the handler associated with the type specified. - :return: None - """ - self.handlers[request_type] = request_handler - - def process_request(self, msg: bytes, public_key: str) -> Response: - """ - Handle a simple message coming from an agent. - - :param msg: the Protobuf message. - :param public_key: the agent's public key that sent the request. - :return: the Response object - """ - message = self.decode(msg, public_key) # type: Request - response = self.dispatch(message) - logger.debug("[{}]: Returning response: {}".format(self.controller_agent.name, str(response))) - return response - - def dispatch(self, request: Request) -> Response: - """ - Dispatch the request to the right handler. - - If no handler is found for the provided type of request, return an "invalid request" error. - If something bad happen, return a "generic" error. - - :param request: the request to handle - :return: the response. - """ - handle_request = self.handlers.get(type(request), None) # type: RequestHandler - if handle_request is None: - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) - try: - return handle_request(request) - except Exception as e: - logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) - logger.exception(e) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.GENERIC_ERROR) - - def decode(self, msg: bytes, public_key: str) -> Request: - """ - From bytes to a Request message. - - :param msg: the serialized message. - :param public_key: the public key of the sender agent. - :return: the deserialized Request - """ - request = Request.from_pb(msg, public_key, self.controller_agent.crypto) - return request - - -class GameHandler: - """A class to manage a TAC instance.""" - - def __init__(self, controller_agent: 'ControllerAgent', tac_parameters: TACParameters) -> None: - """ - Instantiate a GameHandler. - - :param controller_agent: the controller agent the handler is associated with. - :param tac_parameters: the tac parameters - :return: None - """ - self.controller_agent = controller_agent - self.tac_parameters = tac_parameters - - self.registered_agents = set() # type: Set[str] - self.agent_pbk_to_name = defaultdict() # type: Dict[str, str] - self.good_pbk_to_name = generate_good_pbk_to_name(self.tac_parameters.nb_goods) # type: Dict[str, str] - self.current_game = None # type: Optional[Game] - self.inactivity_timeout_timedelta = datetime.timedelta(seconds=tac_parameters.inactivity_timeout) \ - if tac_parameters.inactivity_timeout is not None else datetime.timedelta(seconds=15) - - self.game_data_per_participant = {} # type: Dict[str, GameData] - self.confirmed_transaction_per_participant = defaultdict(lambda: []) # type: Dict[str, List[Transaction]] - - def reset(self) -> None: - """Reset the game.""" - self.current_game = None - self.registered_agents = set() - self.agent_pbk_to_name = defaultdict() - self.good_pbk_to_name = defaultdict() - - def is_game_running(self) -> bool: - """ - Check if an instance of a game is already set up. - - :return: Return True if there is a game running, False otherwise. - """ - return self.current_game is not None - - def _start_competition(self): - """Create a game and send the game setting to every registered agent, and start the inactivity timeout checker.""" - # assert that there is no competition running. - assert not self.is_game_running() - self.current_game = self._create_game() - - try: - self.controller_agent.monitor.set_gamestats(GameStats(self.current_game)) - self.controller_agent.monitor.update() - except Exception as e: - logger.exception(e) - - self._send_game_data_to_agents() - - # log messages - logger.debug("[{}]: Started competition:\n{}".format(self.controller_agent.name, self.current_game.get_holdings_summary())) - logger.debug("[{}]: Computed equilibrium:\n{}".format(self.controller_agent.name, self.current_game.get_equilibrium_summary())) - - def _create_game(self) -> Game: - """ - Create a TAC game. - - :return: a Game instance. - """ - nb_agents = len(self.registered_agents) - - game = Game.generate_game(nb_agents, - self.tac_parameters.nb_goods, - self.tac_parameters.tx_fee, - self.tac_parameters.money_endowment, - self.tac_parameters.base_good_endowment, - self.tac_parameters.lower_bound_factor, - self.tac_parameters.upper_bound_factor, - self.agent_pbk_to_name, - self.good_pbk_to_name) - - return game - - def _send_game_data_to_agents(self) -> None: - """ - Send the data of every agent about the game (e.g. endowments, preferences, scores). - - Assuming that the agent labels are public keys of the OEF Agents. - - :return: None. - """ - for public_key in self.current_game.configuration.agent_pbks: - agent_state = self.current_game.get_agent_state_from_agent_pbk(public_key) - game_data_response = GameData( - public_key, - self.controller_agent.crypto, - agent_state.balance, - agent_state.current_holdings, - agent_state.utility_params, - self.current_game.configuration.nb_agents, - self.current_game.configuration.nb_goods, - self.current_game.configuration.tx_fee, - self.current_game.configuration.agent_pbk_to_name, - self.current_game.configuration.good_pbk_to_name - ) - logger.debug("[{}]: sending GameData to '{}': {}" - .format(self.controller_agent.name, public_key, str(game_data_response))) - - self.game_data_per_participant[public_key] = game_data_response - self.controller_agent.send_message(0, 1, public_key, game_data_response.serialize()) - - def handle_registration_phase(self) -> bool: - """ - Wait until the registration time expires. Then, if there are enough agents, start the competition. - - :return True if the competition has been successfully started. False otherwise. - """ - # just to make names shorter - min_nb_agents = self.tac_parameters.min_nb_agents - - seconds_to_wait = self.tac_parameters.registration_timedelta.seconds - seconds_to_wait = 0 if seconds_to_wait < 0 else seconds_to_wait - logger.debug("[{}]: Waiting for {} seconds...".format(self.controller_agent.name, seconds_to_wait)) - time.sleep(seconds_to_wait) - nb_reg_agents = len(self.registered_agents) - logger.debug("[{}]: Check if we can start the competition.".format(self.controller_agent.name)) - if len(self.registered_agents) >= self.tac_parameters.min_nb_agents: - logger.debug("[{}]: Start competition. Registered agents: {}, minimum number of agents: {}." - .format(self.controller_agent.name, nb_reg_agents, min_nb_agents)) - self._start_competition() - return True - else: - logger.debug("[{}]: Not enough agents to start TAC. Registered agents: {}, minimum number of agents: {}." - .format(self.controller_agent.name, nb_reg_agents, min_nb_agents)) - self.controller_agent.terminate() - return False - - def notify_tac_cancelled(self): - """Notify agents that the TAC is cancelled.""" - logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.controller_agent.name)) - for agent_pbk in self.registered_agents: - self.controller_agent.send_message(0, 0, agent_pbk, Cancelled(agent_pbk, self.controller_agent.crypto).serialize()) - - -class ControllerAgent(OEFAgent): - """Class for a controller agent.""" - - CONTROLLER_DATAMODEL = DataModel("tac", [ - AttributeSchema("version", int, True, "Version number of the TAC Controller Agent."), - ]) - - def __init__(self, name: str = "controller", - oef_addr: str = "127.0.0.1", - oef_port: int = 10000, - version: int = 1, - monitor: Optional[Monitor] = None, - **kwargs): - """ - Initialize a Controller Agent for TAC. - - :param name: The name of the OEF Agent. - :param oef_addr: the OEF address. - :param oef_port: the OEF listening port. - :param version: the version of the TAC controller. - :param monitor: the dashboard monitor. If None, defaults to a null (dummy) monitor. - """ - self.name = name - self.crypto = Crypto() - super().__init__(self.crypto.public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) - logger.debug("[{}]: Initialized myself as Controller Agent :\n{}".format(self.name, pprint.pformat(vars()))) - - self.dispatcher = ControllerDispatcher(self) - self.monitor = NullMonitor() if monitor is None else monitor # type: Monitor - - self.version = version - self.game_handler = None # type: Optional[GameHandler] - - self.last_activity = datetime.datetime.now() - - self._message_processing_task = None - self._timeout_checker_task = None - - self._is_running = False - self._terminated = False - - def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: - """ - Handle a simple message. - - The TAC Controller expects that 'content' is a Protobuf serialization of a tac.messages.Request object. - The request is dispatched to the right request handler (using the ControllerDispatcher). - The handler returns an optional response, that is sent back to the sender. - Notice: the message sent back has the same message id, such that the client knows to which request the response is associated to. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the public key of the sender. - :param content: the content of the message. - :return: None - """ - logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" - .format(self.name, msg_id, dialogue_id, origin)) - self.update_last_activity() - response = self.dispatcher.process_request(content, origin) # type: Optional[Response] - if response is not None: - self.send_message(msg_id, dialogue_id, origin, response.serialize()) - - def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: - """ - Handle an oef error. - - :param answer_id: the answer id - :param operation: the oef operation - - :return: None - """ - logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.name, answer_id, operation)) - - def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: - """ - Handle a dialogue error. - - :param answer_id: the answer id - :param dialogue_id: the dialogue id - :param origin: the public key of the sending agent - - :return: None - """ - logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.name, answer_id, dialogue_id, origin)) - - def register(self): - """ - Register on the OEF as a TAC controller agent. - - :return: None. - """ - desc = Description({"version": 1}, data_model=self.CONTROLLER_DATAMODEL) - logger.debug("[{}]: Registering with {} data model".format(self.name, desc.data_model.name)) - self.register_service(0, desc) - - def dump(self, directory: str, experiment_name: str) -> None: - """ - Dump the details of the simulation. - - :param directory: the directory where experiments details are listed. - :param experiment_name: the name of the folder where the data about experiment will be saved. - :return: None. - """ - experiment_dir = directory + "/" + experiment_name - - if self.game_handler is None or not self.game_handler.is_game_running(): - logger.warning("[{}]: Game not present. Using empty dictionary.".format(self.name)) - game_dict = {} # type: Dict[str, Any] - else: - game_dict = self.game_handler.current_game.to_dict() - - os.makedirs(experiment_dir, exist_ok=True) - with open(os.path.join(experiment_dir, "game.json"), "w") as f: - json.dump(game_dict, f) - - def terminate(self) -> None: - """ - Terminate the controller agent. - - :return: None - """ - if self._is_running: - logger.debug("[{}]: Terminating the controller...".format(self.name)) - self._is_running = False - self.game_handler.notify_tac_cancelled() - self._loop.call_soon_threadsafe(self.stop) - self._message_processing_task.join() - self._message_processing_task = None - if self.monitor.is_running: - self.monitor.stop() - - def check_inactivity_timeout(self, rate: Optional[float] = 2.0) -> None: - """ - Check periodically if the timeout for inactivity or competition expired. - - :param: rate: at which rate (in seconds) the frequency of the check. - :return: None - """ - logger.debug("[{}]: Started job to check for inactivity of {} seconds. Checking rate: {}" - .format(self.name, self.game_handler.tac_parameters.inactivity_timedelta.total_seconds(), rate)) - while True: - if self._is_running is False: - return - time.sleep(rate) - current_time = datetime.datetime.now() - inactivity_duration = current_time - self.last_activity - if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: - logger.debug("[{}]: Inactivity timeout expired. Terminating...".format(self.name)) - self.terminate() - return - elif current_time > self.game_handler.tac_parameters.end_time: - logger.debug("[{}]: Competition timeout expired. Terminating...".format(self.name)) - self.terminate() - return - - def update_last_activity(self): - """Update the last activity tracker.""" - self.last_activity = datetime.datetime.now() - - def handle_competition(self, tac_parameters: TACParameters): - """ - Start a Trading Agent Competition. - - :param tac_parameters: the parameter of the competition. - :return: - """ - logger.debug("[{}]: Starting competition with parameters: {}" - .format(self.name, pprint.pformat(tac_parameters.__dict__))) - self._is_running = True - self._message_processing_task = Thread(target=self.run) - - self.monitor.start(None) - self.monitor.update() - - self.game_handler = GameHandler(self, tac_parameters) - self._message_processing_task.start() - - if self.game_handler.handle_registration_phase(): - # start the inactivity timeout. - self._timeout_checker_task = Thread(target=self.check_inactivity_timeout) - self._timeout_checker_task.run() - else: - self.terminate() - - def wait_and_handle_competition(self, tac_parameters: TACParameters) -> None: - """ - Wait until the current time is greater than the start time, then, start the TAC. - - :param tac_parameters: the parameters for TAC. - :return: None - """ - now = datetime.datetime.now() - logger.debug("[{}]: waiting for starting the competition: start_time={}, current_time={}, timedelta ={}s" - .format(self.name, str(tac_parameters.start_time), str(now), - (tac_parameters.start_time - now).total_seconds())) - - seconds_to_wait = (tac_parameters.start_time - now).total_seconds() - time.sleep(0.5 if seconds_to_wait < 0 else seconds_to_wait) - self.handle_competition(tac_parameters) - - -def _parse_arguments(): - parser = argparse.ArgumentParser("controller", description="Launch the controller agent.") - parser.add_argument("--name", default="controller", help="Name of the agent.") - parser.add_argument("--nb-agents", default=5, type=int, help="Number of goods") - parser.add_argument("--nb-goods", default=5, type=int, help="Number of goods") - parser.add_argument("--money-endowment", type=int, default=200, help="Initial amount of money.") - parser.add_argument("--base-good-endowment", default=2, type=int, help="The base amount of per good instances every agent receives.") - parser.add_argument("--lower-bound-factor", default=0, type=int, help="The lower bound factor of a uniform distribution.") - parser.add_argument("--upper-bound-factor", default=0, type=int, help="The upper bound factor of a uniform distribution.") - parser.add_argument("--tx-fee", default=1.0, type=float, help="Number of goods") - parser.add_argument("--oef-addr", default="127.0.0.1", help="TCP/IP address of the OEF Agent") - parser.add_argument("--oef-port", default=10000, help="TCP/IP port of the OEF Agent") - parser.add_argument("--start-time", default=str(datetime.datetime.now() + datetime.timedelta(0, 10)), type=str, help="The start time for the competition (in UTC format).") - parser.add_argument("--registration-timeout", default=10, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") - parser.add_argument("--inactivity-timeout", default=60, type=int, help="The amount of time (in seconds) to wait during inactivity until the termination of the competition.") - parser.add_argument("--competition-timeout", default=240, type=int, help="The amount of time (in seconds) to wait from the start of the competition until the termination of the competition.") - parser.add_argument("--whitelist-file", default=None, type=str, help="The file that contains the list of agent names to be whitelisted.") - parser.add_argument("--verbose", default=False, action="store_true", help="Log debug messages.") - parser.add_argument("--dashboard", action="store_true", help="Show the agent dashboard.") - parser.add_argument("--visdom-addr", default="localhost", help="TCP/IP address of the Visdom server.") - parser.add_argument("--visdom-port", default=8097, help="TCP/IP port of the Visdom server.") - parser.add_argument("--data-output-dir", default="data", help="The output directory for the simulation data.") - parser.add_argument("--experiment-id", default=None, help="The experiment ID.") - parser.add_argument("--seed", default=42, help="The random seed for the generation of the game parameters.") - parser.add_argument("--version", default=1, help="The version of the controller.") - - return parser.parse_args() - - -def main( - name: str = "controller", - nb_agents: int = 5, - nb_goods: int = 5, - money_endowment: int = 200, - base_good_endowment: int = 2, - lower_bound_factor: int = 0, - upper_bound_factor: int = 0, - tx_fee: float = 1.0, - oef_addr: str = "127.0.0.1", - oef_port: int = 10000, - start_time: Union[str, datetime.datetime] = str(datetime.datetime.now() + datetime.timedelta(0, 10)), - registration_timeout: int = 10, - inactivity_timeout: int = 60, - competition_timeout: int = 240, - whitelist_file: Optional[str] = None, - verbose: bool = False, - dashboard: bool = False, - visdom_addr: str = "localhost", - visdom_port: int = 8097, - data_output_dir: str = "data", - experiment_id: Optional[str] = None, - seed: int = 42, - version: int = 1, - **kwargs -): - """Run the controller script.""" - agent = None # type: Optional[ControllerAgent] - random.seed(seed) - - if verbose: - logger.setLevel(logging.DEBUG) - else: - logger.setLevel(logging.INFO) - - monitor = VisdomMonitor(visdom_addr=visdom_addr, visdom_port=visdom_port) if dashboard else NullMonitor() - - try: - - agent = ControllerAgent(name=name, - oef_addr=oef_addr, - oef_port=oef_port, - monitor=monitor, - version=version) - - whitelist = set(open(whitelist_file).read().splitlines(keepends=False)) if whitelist_file else None - tac_parameters = TACParameters( - min_nb_agents=nb_agents, - money_endowment=money_endowment, - nb_goods=nb_goods, - tx_fee=tx_fee, - base_good_endowment=base_good_endowment, - lower_bound_factor=lower_bound_factor, - upper_bound_factor=upper_bound_factor, - start_time=dateutil.parser.parse(start_time) if type(start_time) == str else start_time, - registration_timeout=registration_timeout, - competition_timeout=competition_timeout, - inactivity_timeout=inactivity_timeout, - whitelist=whitelist - ) - - agent.connect() - agent.register() - agent.wait_and_handle_competition(tac_parameters) - - except Exception as e: - logger.exception(e) - except KeyboardInterrupt: - logger.debug("Controller interrupted...") - finally: - if agent is not None: - agent.terminate() - experiment_name = experiment_id if experiment_id is not None else str(datetime.datetime.now()).replace(" ", "_") - agent.dump(data_output_dir, experiment_name) - if agent.game_handler is not None and agent.game_handler.is_game_running(): - game_stats = GameStats(agent.game_handler.current_game) - game_stats.dump(data_output_dir, experiment_name) - - -if __name__ == '__main__': - arguments = _parse_arguments() - main(**arguments.__dict__) diff --git a/tac/platform/controller/__init__.py b/tac/platform/controller/__init__.py new file mode 100644 index 00000000..7b79e2a1 --- /dev/null +++ b/tac/platform/controller/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Contains the TAC controller package.""" diff --git a/tac/platform/controller/actions.py b/tac/platform/controller/actions.py new file mode 100644 index 00000000..495a4c05 --- /dev/null +++ b/tac/platform/controller/actions.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module contains the classes which define the actions of an agent. + +- OEFActions: The OEFActions class defines the actions of an agent towards the OEF. +""" + +import logging + +from oef.schema import Description, DataModel, AttributeSchema + +from tac.agents.v1.agent import Liveness +from tac.platform.controller.interfaces import OEFActionInterface +from tac.agents.v1.mail import OutBox, OutContainer +from tac.helpers.crypto import Crypto + +logger = logging.getLogger(__name__) + +CONTROLLER_DATAMODEL = DataModel("tac", [ + AttributeSchema("version", int, True, "Version number of the TAC Controller Agent."), +]) + + +class OEFActions(OEFActionInterface): + """The OEFActions class defines the actions of an agent towards the OEF.""" + + def __init__(self, crypto: Crypto, liveness: Liveness, out_box: 'OutBox', agent_name: str) -> None: + """ + Instantiate the OEFActions. + + :param crypto: the crypto module + :param liveness: the liveness module + :param out_box: the outbox of the agent + :param agent_name: the agent name + + :return: None + """ + self.crypto = crypto + self.liveness = liveness + self.out_box = out_box + self.agent_name = agent_name + + def register_tac(self) -> None: + """ + Register on the OEF as a TAC controller agent. + + :return: None. + """ + desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) + logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) + self.out_box.out_queue.put(OutContainer(service_description=desc, message_id=1)) diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py new file mode 100644 index 00000000..a77253cc --- /dev/null +++ b/tac/platform/controller/controller_agent.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the ControllerAgent.""" + +import argparse +import datetime +import logging +import pprint +import random +import time +from typing import Union, Optional + +import dateutil +from oef.messages import Message as ByteMessage, OEFErrorMessage, DialogueErrorMessage + +from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor +from tac.platform.controller.handlers import OEFHandler, GameHandler, AgentMessageDispatcher +from tac.platform.controller.tac_parameters import TACParameters + +from tac.agents.v1.agent import Agent +from tac.agents.v1.mail import MailBox, InBox, OutBox, OutContainer +from tac.agents.v1.base.game_instance import GamePhase +from tac.agents.v1.base.helpers import is_oef_message + +if __name__ != "__main__": + logger = logging.getLogger(__name__) +else: + logger = logging.getLogger("tac.platform.controller") + +AgentMessage = Union[ByteMessage, OutContainer] +OEFMessage = Union[OEFErrorMessage, DialogueErrorMessage] +Message = Union[AgentMessage, OEFMessage] + + +class ControllerAgent(Agent): + """The controller agent class implements a controller for TAC.""" + + def __init__(self, name: str, + oef_addr: str, + oef_port: int, + tac_parameters: TACParameters, + dashboard: Optional[Monitor] = None, + agent_timeout: Optional[float] = 1.0, + max_reactions: int = 100, + private_key_pem: Optional[str] = None, + debug: bool = False, + **kwargs): + """ + Initialize a participant agent. + + :param name: the name of the agent. + :param oef_addr: the TCP/IP address of the OEF node. + :param oef_port: the TCP/IP port of the OEF node. + :param strategy: the strategy object that specify the behaviour during the competition. + :param agent_timeout: the time in (fractions of) seconds to time out an agent between act and react. + :param max_reactions: the maximum number of reactions (messages processed) per call to react. + :param dashboard: a Visdom dashboard to visualize agent statistics during the competition. + :param private_key_pem: the path to a private key in PEM format. + :param debug: if True, run the agent in debug mode. + """ + super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) + self.mail_box = MailBox(self.crypto.public_key, oef_addr, oef_port) + self.in_box = InBox(self.mail_box) + self.out_box = OutBox(self.mail_box) + + self.oef_handler = OEFHandler(self.crypto, self.liveness, self.out_box, self.name) + self.agent_message_dispatcher = AgentMessageDispatcher(self) + self.game_handler = GameHandler(name, self.crypto, self.out_box, self.dashboard, tac_parameters) + + self.max_reactions = max_reactions + self.last_activity = datetime.datetime.now() + + logger.debug("[{}]: Initialized myself as Controller Agent :\n{}".format(self.name, pprint.pformat(vars()))) + + def act(self) -> None: + """ + Perform the agent's actions. + + :return: None + """ + if self.game_handler.game_phase == GamePhase.PRE_GAME: + now = datetime.datetime.now() + logger.debug("[{}]: waiting for starting the competition: start_time={}, current_time={}, timedelta ={}s" + .format(self.name, str(self.tac_parameters.start_time), str(now), + (self.tac_parameters.start_time - now).total_seconds())) + + seconds_to_wait = (self.tac_parameters.start_time - now).total_seconds() + self.game_handler.competition_start = now + seconds_to_wait + self.tac_parameters.registration_timedelta.seconds + time.sleep(0.5 if seconds_to_wait < 0 else seconds_to_wait) + logger.debug("[{}]: Register competition with parameters: {}" + .format(self.name, pprint.pformat(self.game_handler.tac_parameters.__dict__))) + self.oef_handler.register_tac() + if self.game_handler.game_phase == GamePhase.GAME_SETUP: + now = datetime.datetime.now() + if now >= self.game_handler.competition_start: + logger.debug("[{}]: Checking if we can start the competition.".format(self.controller_agent.name)) + min_nb_agents = self.tac_parameters.min_nb_agents + nb_reg_agents = len(self.registered_agents) + if nb_reg_agents >= min_nb_agents: + logger.debug("[{}]: Start competition. Registered agents: {}, minimum number of agents: {}." + .format(self.name, nb_reg_agents, min_nb_agents)) + self.game_handler.start_competition() + else: + logger.debug("[{}]: Not enough agents to start TAC. Registered agents: {}, minimum number of agents: {}." + .format(self.controller_agent.name, nb_reg_agents, min_nb_agents)) + self.stop() + self.teardown() + if self.game_handler.game_phase == GamePhase.GAME: + current_time = datetime.datetime.now() + inactivity_duration = current_time - self.last_activity + if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: + logger.debug("[{}]: Inactivity timeout expired. Terminating...".format(self.name)) + self.stop() + self.teardown() + return + elif current_time > self.game_handler.tac_parameters.end_time: + logger.debug("[{}]: Competition timeout expired. Terminating...".format(self.name)) + self.stop() + self.teardown() + + self.out_box.send_nowait() + + def react(self) -> None: + """ + React to incoming events. + + :return: None + """ + counter = 0 + while (not self.in_box.is_in_queue_empty() and counter < self.max_reactions): + counter += 1 + msg = self.in_box.get_no_wait() # type: Optional[Message] + if msg is not None: + if is_oef_message(msg): + msg: OEFMessage + self.oef_handler.handle_oef_message(msg) + else: + msg: AgentMessage + self.agent_message_dispatcher.handle_agent_message(msg) + self.last_activity = datetime.datetime.now() + + self.out_box.send_nowait() + + def update(self) -> None: + """ + Update the state of the agent. + + :return: None + """ + + def stop(self) -> None: + """ + Stop the agent. + + :return: None + """ + logger.debug("[{}]: Terminating the controller...".format(self.name)) + self.game_handler.notify_tac_cancelled() + super().stop() + self.monitor.stop() + + def start(self) -> None: + """ + Start the agent. + + :return: None + """ + try: + super().start() + return + except Exception as e: + logger.exception(e) + logger.debug("Stopping the agent...") + self.stop() + + # here only if an error occurred + logger.debug("Trying to rejoin in 5 seconds...") + time.sleep(2.0) + self.start(rejoin=False) + + def setup(self) -> None: + """Set up the agent.""" + + def teardown(self) -> None: + """Tear down the agent.""" + self.game_handler.simulation_dump() + + +def _parse_arguments(): + parser = argparse.ArgumentParser("controller", description="Launch the controller agent.") + parser.add_argument("--name", default="controller", help="Name of the agent.") + parser.add_argument("--nb-agents", default=5, type=int, help="Number of goods") + parser.add_argument("--nb-goods", default=5, type=int, help="Number of goods") + parser.add_argument("--money-endowment", type=int, default=200, help="Initial amount of money.") + parser.add_argument("--base-good-endowment", default=2, type=int, help="The base amount of per good instances every agent receives.") + parser.add_argument("--lower-bound-factor", default=0, type=int, help="The lower bound factor of a uniform distribution.") + parser.add_argument("--upper-bound-factor", default=0, type=int, help="The upper bound factor of a uniform distribution.") + parser.add_argument("--tx-fee", default=1.0, type=float, help="Number of goods") + parser.add_argument("--oef-addr", default="127.0.0.1", help="TCP/IP address of the OEF Agent") + parser.add_argument("--oef-port", default=10000, help="TCP/IP port of the OEF Agent") + parser.add_argument("--start-time", default=str(datetime.datetime.now() + datetime.timedelta(0, 10)), type=str, help="The start time for the competition (in UTC format).") + parser.add_argument("--registration-timeout", default=10, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") + parser.add_argument("--inactivity-timeout", default=60, type=int, help="The amount of time (in seconds) to wait during inactivity until the termination of the competition.") + parser.add_argument("--competition-timeout", default=240, type=int, help="The amount of time (in seconds) to wait from the start of the competition until the termination of the competition.") + parser.add_argument("--whitelist-file", default=None, type=str, help="The file that contains the list of agent names to be whitelisted.") + parser.add_argument("--verbose", default=False, action="store_true", help="Log debug messages.") + parser.add_argument("--dashboard", action="store_true", help="Show the agent dashboard.") + parser.add_argument("--visdom-addr", default="localhost", help="TCP/IP address of the Visdom server.") + parser.add_argument("--visdom-port", default=8097, help="TCP/IP port of the Visdom server.") + parser.add_argument("--data-output-dir", default="data", help="The output directory for the simulation data.") + parser.add_argument("--experiment-id", default=None, help="The experiment ID.") + parser.add_argument("--seed", default=42, help="The random seed for the generation of the game parameters.") + + return parser.parse_args() + + +def main( + name: str = "controller", + nb_agents: int = 5, + nb_goods: int = 5, + money_endowment: int = 200, + base_good_endowment: int = 2, + lower_bound_factor: int = 0, + upper_bound_factor: int = 0, + tx_fee: float = 1.0, + oef_addr: str = "127.0.0.1", + oef_port: int = 10000, + start_time: Union[str, datetime.datetime] = str(datetime.datetime.now() + datetime.timedelta(0, 10)), + registration_timeout: int = 10, + inactivity_timeout: int = 60, + competition_timeout: int = 240, + whitelist_file: Optional[str] = None, + verbose: bool = False, + dashboard: bool = False, + visdom_addr: str = "localhost", + visdom_port: int = 8097, + data_output_dir: str = "data", + experiment_id: Optional[str] = None, + seed: int = 42, + **kwargs +): + """Run the controller script.""" + agent = None # type: Optional[ControllerAgent] + random.seed(seed) + + if verbose: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + try: + monitor = VisdomMonitor(visdom_addr=visdom_addr, visdom_port=visdom_port) if dashboard else NullMonitor() + whitelist = set(open(whitelist_file).read().splitlines(keepends=False)) if whitelist_file else None + tac_parameters = TACParameters( + min_nb_agents=nb_agents, + money_endowment=money_endowment, + nb_goods=nb_goods, + tx_fee=tx_fee, + base_good_endowment=base_good_endowment, + lower_bound_factor=lower_bound_factor, + upper_bound_factor=upper_bound_factor, + start_time=dateutil.parser.parse(start_time) if type(start_time) == str else start_time, + registration_timeout=registration_timeout, + competition_timeout=competition_timeout, + inactivity_timeout=inactivity_timeout, + whitelist=whitelist, + data_output_dir=data_output_dir, + experiment_id=experiment_id + ) + agent = ControllerAgent(name=name, + oef_addr=oef_addr, + oef_port=oef_port, + tac_parameters=tac_parameters, + monitor=monitor) + agent.connect() + agent.start() + + except Exception as e: + logger.exception(e) + except KeyboardInterrupt: + logger.debug("Controller interrupted...") + finally: + if agent is not None: + agent.stop() + agent.teardown() + + +if __name__ == '__main__': + arguments = _parse_arguments() + main(**arguments.__dict__) diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py new file mode 100644 index 00000000..93408f1f --- /dev/null +++ b/tac/platform/controller/handlers.py @@ -0,0 +1,485 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module contains the classes that implements the Controller agent behaviour. + +The methods are split in three classes: +- AgentMessageDispatcher: class to wrap the decoding procedure and dispatching the handling of the message to the right function. +- GameHandler: handles an instance of the game. +- RequestHandler: abstract class for a request handler. +- RegisterHandler: class for a register handler. +- UnregisterHandler: class for an unregister handler +- TransactionHandler: class for a transaction handler. +- GetStateUpdateHandler: class for a state update handler. +""" + +import datetime +import json +import logging +import os + +from abc import ABC, abstractmethod +from collections import defaultdict +from typing import Any, Dict, Optional, List, Set, Type, Union, TYPE_CHECKING + +from oef.messages import Message as ByteMessage, DialogueErrorMessage, OEFErrorMessage + +from tac.platform.game import Game +from tac.platform.protocol import Response, Request, Register, Unregister, Error, GameData, \ + Transaction, TransactionConfirmation, ErrorCode, Cancelled, GetStateUpdate, StateUpdate +from tac.agents.v1.base.game_instance import GamePhase +from tac.agents.v1.mail import OutContainer, OutBox +from tac.agents.v1.agent import Liveness +from tac.gui.monitor import Monitor, NullMonitor +from tac.helpers.misc import generate_good_pbk_to_name +from tac.platform.controller.tac_parameters import TACParameters +from tac.helpers.crypto import Crypto +# from tac.platform.controller.controller_agent import ControllerAgent +from tac.platform.stats import GameStats +from tac.platform.controller.actions import OEFActions +from tac.platform.controller.reactions import OEFReactions + +if TYPE_CHECKING: + from tac.platform.controller.controller_agent import ControllerAgent + +logger = logging.getLogger(__name__) + +AgentMessage = Union[ByteMessage, OutContainer] +OEFMessage = Union[OEFErrorMessage, DialogueErrorMessage] +Message = Union[AgentMessage, OEFMessage] + + +class RequestHandler(ABC): + """Abstract class for a request handler.""" + + def __init__(self, controller_agent: 'ControllerAgent') -> None: + """ + Instantiate a request handler. + + :param controller_agent: the controller agent instance + :return: None + """ + self.controller_agent = controller_agent + + def __call__(self, request: Request) -> Optional[Response]: + """Call the handler.""" + return self.handle(request) + + @abstractmethod + def handle(self, request: Request) -> Optional[Response]: + """ + Handle a request from an OEF agent. + + :param request: the request message. + :return: a response, or None. + """ + + +class RegisterHandler(RequestHandler): + """Class for a register handler.""" + + def handle(self, request: Register) -> Optional[Response]: + """ + Handle a register message. + + If the public key is already registered, answer with an error message. + If this is the n_th registration request, where n is equal to nb_agents, then start the competition. + + :param request: the register request. + :return: an Error response if an error occurred, else None. + """ + whitelist = self.controller_agent.game_handler.tac_parameters.whitelist + if whitelist is not None and request.agent_name not in whitelist: + error_msg = "[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, request.agent_name) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) + + if request.public_key in self.controller_agent.game_handler.registered_agents: + error_msg = "[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key]) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_PBK_ALREADY_REGISTERED) + + if request.agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): + error_msg = "[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, request.agent_name) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_ALREADY_REGISTERED) + + try: + self.controller_agent.monitor.dashboard.agent_pbk_to_name.update({request.public_key: request.agent_name}) + self.controller_agent.monitor.update() + except Exception as e: + logger.error(str(e)) + + self.controller_agent.game_handler.agent_pbk_to_name[request.public_key] = request.agent_name + logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) + self.controller_agent.game_handler.registered_agents.add(request.public_key) + return None + + +class UnregisterHandler(RequestHandler): + """Class for an unregister handler.""" + + def handle(self, request: Unregister) -> Optional[Response]: + """ + Handle a unregister message. + + If the public key is not registered, answer with an error message. + + :param request: the register request. + :return: an Error response if an error occurred, else None. + """ + if request.public_key not in self.controller_agent.game_handler.registered_agents: + error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) + else: + logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) + self.controller_agent.game_handler.registered_agents.remove(request.public_key) + self.controller_agent.game_handler.agent_pbk_to_name.pop(request.public_key) + return None + + +class TransactionHandler(RequestHandler): + """Class for a transaction handler.""" + + def __init__(self, controller_agent: 'ControllerAgent') -> None: + """Instantiate a TransactionHandler.""" + super().__init__(controller_agent) + self._pending_transaction_requests = {} # type: Dict[str, Transaction] + + def handle(self, tx: Transaction) -> Optional[Response]: + """ + Handle a transaction request message. + + If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. + + :param request: the transaction request. + :return: an Error response if an error occurred, else None (no response to send back). + """ + logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, tx)) + + # if transaction arrives first time then put it into the pending pool + if tx.transaction_id not in self._pending_transaction_requests: + if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): + logger.debug("[{}]: Put transaction request in the pool: {}".format(self.controller_agent.name, tx.transaction_id)) + self._pending_transaction_requests[tx.transaction_id] = tx + else: + return self._handle_invalid_transaction(tx) + # if transaction arrives second time then process it + else: + pending_tx = self._pending_transaction_requests.pop(tx.transaction_id) + if tx.matches(pending_tx): + if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): + self.controller_agent.game_handler.confirmed_transaction_per_participant[pending_tx.sender].append(pending_tx) + self.controller_agent.game_handler.confirmed_transaction_per_participant[tx.sender].append(tx) + self._handle_valid_transaction(tx) + else: + return self._handle_invalid_transaction(tx) + else: + return self._handle_non_matching_transaction(tx) + + def _handle_valid_transaction(self, tx: 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 + """ + logger.debug("[{}]: Handling valid transaction: {}".format(self.controller_agent.name, tx.transaction_id)) + + # update the game state. + self.controller_agent.game_handler.current_game.settle_transaction(tx) + + # update the dashboard monitor + self.controller_agent.monitor.update() + + # send the transaction confirmation. + tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) + self.controller_agent.send_message(0, 0, tx.public_key, tx_confirmation.serialize()) + self.controller_agent.send_message(0, 0, tx.counterparty, tx_confirmation.serialize()) + + # log messages + logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) + holdings_summary = self.controller_agent.game_handler.current_game.get_holdings_summary() + logger.debug("[{}]: Current state:\n{}".format(self.controller_agent.name, holdings_summary)) + + return None + + def _handle_invalid_transaction(self, tx: Transaction) -> Response: + """Handle an invalid transaction.""" + return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_VALID, + details={"transaction_id": tx.transaction_id}) + + def _handle_non_matching_transaction(self, tx: Transaction) -> Response: + """Handle non-matching transaction.""" + return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_MATCHING) + + +class GetStateUpdateHandler(RequestHandler): + """Class for a state update handler.""" + + def handle(self, request: GetStateUpdate) -> Optional[Response]: + """ + Handle a 'get agent state' request. + + If the public key is not registered, answer with an error message. + + :param request: the 'get agent state' request. + :return: an Error response if an error occurred, else None. + """ + logger.debug("[{}]: Handling the 'get agent state' request: {}".format(self.controller_agent.name, request)) + if not self.controller_agent.game_handler.is_game_running(): + error_msg = "[{}]: GetStateUpdate request is not valid while the competition is not running.".format(self.controller_agent.name) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.COMPETITION_NOT_RUNNING) + if request.public_key not in self.controller_agent.game_handler.registered_agents: + error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) + logger.error(error_msg) + return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) + else: + transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[request.public_key] # type: List[Transaction] + initial_game_data = self.controller_agent.game_handler.game_data_per_participant[request.public_key] # type: GameData + return StateUpdate(request.public_key, self.controller_agent.crypto, initial_game_data, transactions) + + +class AgentMessageDispatcher(object): + """Class to wrap the decoding procedure and dispatching the handling of the message to the right function.""" + + def __init__(self, controller_agent: 'ControllerAgent'): + """ + Initialize a Controller handler, i.e. the class that manages the handling of incoming messages. + + :param controller_agent: The Controller Agent the handler is associated with. + """ + self.controller_agent = controller_agent + + self.handlers = { + Register: RegisterHandler(controller_agent), + Unregister: UnregisterHandler(controller_agent), + Transaction: TransactionHandler(controller_agent), + GetStateUpdate: GetStateUpdateHandler(controller_agent), + } # type: Dict[Type[Request], RequestHandler] + + def handle_agent_message(self, msg: AgentMessage) -> Response: + """ + Dispatch the request to the right handler. + + If no handler is found for the provided type of request, return an "invalid request" error. + If something bad happen, return a "generic" error. + + :param request: the request to handle + :return: the response. + """ + handle_request = self.handlers.get(type(msg), None) # type: RequestHandler + if handle_request is None: + return Error(msg.public_key, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) + try: + return handle_request(msg) + except Exception as e: + logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) + logger.exception(e) + return Error(msg.public_key, self.controller_agent.crypto, ErrorCode.GENERIC_ERROR) + + +class GameHandler: + """A class to manage a TAC instance.""" + + def __init__(self, agent_name: str, crypto: Crypto, out_box: OutBox, monitor: Monitor, tac_parameters: TACParameters) -> None: + """ + Instantiate a GameHandler. + + :param controller_agent: the controller agent the handler is associated with. + :param tac_parameters: the tac parameters + :return: None + """ + self.agent_name = agent_name + self.crypto = crypto + self.out_box = out_box + self.monitor = monitor + self.tac_parameters = tac_parameters + self.competition_start = None + self._game_phase = GamePhase.PRE_GAME + + self.registered_agents = set() # type: Set[str] + self.agent_pbk_to_name = defaultdict() # type: Dict[str, str] + self.good_pbk_to_name = generate_good_pbk_to_name(self.tac_parameters.nb_goods) # type: Dict[str, str] + self.current_game = None # type: Optional[Game] + self.inactivity_timeout_timedelta = datetime.timedelta(seconds=tac_parameters.inactivity_timeout) \ + if tac_parameters.inactivity_timeout is not None else datetime.timedelta(seconds=15) + + self.game_data_per_participant = {} # type: Dict[str, GameData] + self.confirmed_transaction_per_participant = defaultdict(lambda: []) # type: Dict[str, List[Transaction]] + + self.monitor = NullMonitor() if monitor is None else monitor # type: Monitor + self.monitor.start(None) + self.monitor.update() + + def reset(self) -> None: + """Reset the game.""" + self.current_game = None + self.registered_agents = set() + self.agent_pbk_to_name = defaultdict() + self.good_pbk_to_name = defaultdict() + + @property + def game_phase(self) -> GamePhase: + """Get the game phase.""" + return self._game_phase + + @property + def is_game_running(self) -> bool: + """ + Check if an instance of a game is already set up. + + :return: Return True if there is a game running, False otherwise. + """ + return self.current_game is not None + + def start_competition(self): + """Create a game and send the game setting to every registered agent.""" + # assert that there is no competition running. + assert not self.is_game_running + self.current_game = self._create_game() + + try: + self.monitor.set_gamestats(GameStats(self.current_game)) + self.monitor.update() + except Exception as e: + logger.exception(e) + + self._send_game_data_to_agents() + + # log messages + logger.debug("[{}]: Started competition:\n{}".format(self.agent_name, self.current_game.get_holdings_summary())) + logger.debug("[{}]: Computed equilibrium:\n{}".format(self.agent_name, self.current_game.get_equilibrium_summary())) + + def _create_game(self) -> Game: + """ + Create a TAC game. + + :return: a Game instance. + """ + nb_agents = len(self.registered_agents) + + game = Game.generate_game(nb_agents, + self.tac_parameters.nb_goods, + self.tac_parameters.tx_fee, + self.tac_parameters.money_endowment, + self.tac_parameters.base_good_endowment, + self.tac_parameters.lower_bound_factor, + self.tac_parameters.upper_bound_factor, + self.agent_pbk_to_name, + self.good_pbk_to_name) + + return game + + def _send_game_data_to_agents(self) -> None: + """ + Send the data of every agent about the game (e.g. endowments, preferences, scores). + + Assuming that the agent labels are public keys of the OEF Agents. + + :return: None. + """ + for public_key in self.current_game.configuration.agent_pbks: + agent_state = self.current_game.get_agent_state_from_agent_pbk(public_key) + game_data_response = GameData( + public_key, + self.crypto, + agent_state.balance, + agent_state.current_holdings, + agent_state.utility_params, + self.current_game.configuration.nb_agents, + self.current_game.configuration.nb_goods, + self.current_game.configuration.tx_fee, + self.current_game.configuration.agent_pbk_to_name, + self.current_game.configuration.good_pbk_to_name + ) + logger.debug("[{}]: sending GameData to '{}': {}" + .format(self.controller_agent.name, public_key, str(game_data_response))) + + self.game_data_per_participant[public_key] = game_data_response + self.out_box.put(OutContainer(message_id=1, dialogue_id=1, destination=public_key, message=game_data_response.serialize())) + + def end_competition(self): + """Notify agents that the TAC is cancelled.""" + logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.controller_agent.name)) + for agent_pbk in self.registered_agents: + self.out_box.put(OutContainer(message_id=1, dialogue_id=1, destination=agent_pbk, message=Cancelled(agent_pbk, self.controller_agent.crypto).serialize())) + + def simulation_dump(self) -> None: + """ + Dump the details of the simulation. + + :param directory: the directory where experiments details are listed. + :param experiment_name: the name of the folder where the data about experiment will be saved. + :return: None. + """ + experiment_dir = self.tac_parameters.data_output_dir + "/" + self.tac_parameters.experiment_id + + if self.game_handler is None or not self.game_handler.is_game_running: + logger.warning("[{}]: Game not present. Using empty dictionary.".format(self.name)) + game_dict = {} # type: Dict[str, Any] + else: + game_dict = self.game_handler.current_game.to_dict() + + os.makedirs(experiment_dir, exist_ok=True) + with open(os.path.join(experiment_dir, "game.json"), "w") as f: + json.dump(game_dict, f) + + +class OEFHandler(OEFActions, OEFReactions): + """Handle the message exchange with the OEF.""" + + def __init__(self, crypto: Crypto, liveness: Liveness, out_box: 'OutBox', agent_name: str): + """ + Instantiate the OEFHandler. + + :param crypto: the crypto module + :param liveness: the liveness module + :param out_box: the outbox + :param agent_name: the agent name + """ + OEFActions.__init__(self, crypto, liveness, out_box, agent_name) + OEFReactions.__init__(self, crypto, liveness, out_box, agent_name) + + def handle_oef_message(self, msg: OEFMessage) -> None: + """ + Handle messages from the oef. + + The oef does not expect a response for any of these messages. + + :param msg: the OEF message + + :return: None + """ + logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) + if isinstance(msg, OEFErrorMessage): + self.on_oef_error(msg) + elif isinstance(msg, DialogueErrorMessage): + self.on_dialogue_error(msg) + else: + logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/platform/controller/interfaces.py b/tac/platform/controller/interfaces.py new file mode 100644 index 00000000..8f037f2c --- /dev/null +++ b/tac/platform/controller/interfaces.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module defines the interfaces which a TAC compatible agent must implement. + +It cointains: +- OEFReactionInterface: This interface contains the methods to react to events from the OEF. +- OEFActionInterface: This interface contains the methods to interact with the OEF. +""" + +from abc import abstractmethod + +from oef.messages import Message as OEFErrorMessage, DialogueErrorMessage + + +class OEFReactionInterface: + """This interface contains the methods to react to events from the OEF.""" + + @abstractmethod + def on_oef_error(self, oef_error: OEFErrorMessage) -> None: + """ + Handle an OEF error message. + + :param oef_error: the oef error + + :return: None + """ + + @abstractmethod + def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: + """ + Handle a dialogue error message. + + :param dialogue_error_msg: the dialogue error message + + :return: None + """ + + +class OEFActionInterface: + """This interface contains the methods to interact with the OEF.""" + + @abstractmethod + def register_service(self) -> None: + """ + Register services to OEF Service Directory. + + :return: None + """ diff --git a/tac/platform/controller/reactions.py b/tac/platform/controller/reactions.py new file mode 100644 index 00000000..cfb1d677 --- /dev/null +++ b/tac/platform/controller/reactions.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module contains the classes which define the reactions of an agent. + +- OEFReactions: The OEFReactions class defines the reactions of an agent towards the OEF. +""" + +import logging + +from oef.messages import Message as OEFErrorMessage, DialogueErrorMessage + +from tac.agents.v1.agent import Liveness +from tac.platform.controller.interfaces import OEFReactionInterface +from tac.agents.v1.mail import OutBox +from tac.helpers.crypto import Crypto + +logger = logging.getLogger(__name__) + + +class OEFReactions(OEFReactionInterface): + """The OEFReactions class defines the reactions of an agent towards the OEF.""" + + def __init__(self, crypto: Crypto, liveness: Liveness, out_box: 'OutBox', agent_name: str) -> None: + """ + Instantiate the OEFReactions. + + :param crypto: the crypto module + :param liveness: the liveness module + :param out_box: the outbox of the agent + :param agent_name: the agent name + + :return: None + """ + self.crypto = crypto + self.liveness = liveness + self.out_box = out_box + self.agent_name = agent_name + + def on_oef_error(self, oef_error: OEFErrorMessage) -> None: + """ + Handle an OEF error message. + + :param oef_error: the oef error + + :return: None + """ + logger.error("[{}]: Received OEF error: answer_id={}, operation={}" + .format(self.agent_name, oef_error.msg_id, oef_error.oef_error_operation)) + + def on_dialogue_error(self, dialogue_error: DialogueErrorMessage) -> None: + """ + Handle a dialogue error message. + + :param dialogue_error: the dialogue error message + + :return: None + """ + logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" + .format(self.agent_name, dialogue_error.msg_id, dialogue_error.dialogue_id, dialogue_error.origin)) diff --git a/tac/platform/controller/tac_parameters.py b/tac/platform/controller/tac_parameters.py new file mode 100644 index 00000000..0e645d46 --- /dev/null +++ b/tac/platform/controller/tac_parameters.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""TACParameters: this class contains the parameters for the TAC.""" + +import datetime + +from typing import Set, Optional + + +class TACParameters(object): + """This class contains the parameters for the TAC.""" + + def __init__(self, min_nb_agents: int = 5, + money_endowment: int = 200, + nb_goods: int = 5, + tx_fee: float = 1.0, + base_good_endowment: int = 2, + lower_bound_factor: int = 1, + upper_bound_factor: int = 1, + start_time: datetime.datetime = datetime.datetime.now(), + registration_timeout: int = 10, + competition_timeout: int = 20, + inactivity_timeout: int = 10, + whitelist: Optional[Set[str]] = None, + data_output_dir: str = "data", + experiment_id: Optional[str] = None): + """ + Initialize parameters for TAC. + + :param min_nb_agents: the number of agents to wait for the registration. + :param money_endowment: The money amount every agent receives. + :param nb_goods: the number of goods in the competition. + :param tx_fee: the fee for a transaction. + :param base_good_endowment:The base amount of per good instances every agent receives. + :param lower_bound_factor: the lower bound factor of a uniform distribution. + :param upper_bound_factor: the upper bound factor of a uniform distribution. + :param start_time: the datetime when the competition will start. + :param registration_timeout: the duration (in seconds) of the registration phase. + :param competition_timeout: the duration (in seconds) of the competition phase. + :param inactivity_timeout: the time when the competition will start. + :param whitelist: the set of agent names allowed. If None, no checks on the agent names. + """ + self._min_nb_agents = min_nb_agents + self._money_endowment = money_endowment + self._nb_goods = nb_goods + self._tx_fee = tx_fee + self._base_good_endowment = base_good_endowment + self._lower_bound_factor = lower_bound_factor + self._upper_bound_factor = upper_bound_factor + self._start_time = start_time + self._registration_timeout = registration_timeout + self._competition_timeout = competition_timeout + self._inactivity_timeout = inactivity_timeout + self._whitelist = whitelist + self._data_output_dir = data_output_dir + self._experiment_id = experiment_id + self._check_values() + + def _check_values(self) -> None: + """ + Check constructor parameters. + + :raises ValueError: if some parameter has not the right value. + """ + if self._start_time is None: + raise ValueError + if self._inactivity_timeout is None: + raise ValueError + + @property + def min_nb_agents(self) -> int: + """Minimum number of agents required for a TAC instance.""" + return self._min_nb_agents + + @property + def money_endowment(self): + """Money endowment per agent for a TAC instance.""" + return self._money_endowment + + @property + def nb_goods(self): + """Good number for a TAC instance.""" + return self._nb_goods + + @property + def tx_fee(self): + """Transaction fee for a TAC instance.""" + return self._tx_fee + + @property + def base_good_endowment(self): + """Minimum endowment of each agent for each good.""" + return self._base_good_endowment + + @property + def lower_bound_factor(self): + """Lower bound of a uniform distribution.""" + return self._lower_bound_factor + + @property + def upper_bound_factor(self): + """Upper bound of a uniform distribution.""" + return self._upper_bound_factor + + @property + def start_time(self) -> datetime.datetime: + """TAC start time.""" + return self._start_time + + @property + def end_time(self) -> datetime.datetime: + """TAC end time.""" + return self._start_time + self.registration_timedelta + self.competition_timedelta + + @property + def registration_timeout(self): + """Timeout of registration.""" + return self._registration_timeout + + @property + def competition_timeout(self): + """Timeout of competition.""" + return self._competition_timeout + + @property + def inactivity_timeout(self): + """Timeout of agent inactivity from controller perspective (no received transactions).""" + return self._inactivity_timeout + + @property + def registration_timedelta(self) -> datetime.timedelta: + """Time delta of the registration timeout.""" + return datetime.timedelta(0, self._registration_timeout) + + @property + def competition_timedelta(self) -> datetime.timedelta: + """Time delta of the competition timeout.""" + return datetime.timedelta(0, self._competition_timeout) + + @property + def inactivity_timedelta(self) -> datetime.timedelta: + """Time delta of the inactivity timeout.""" + return datetime.timedelta(0, self._inactivity_timeout) + + @property + def whitelist(self) -> Optional[Set[str]]: + """Whitelist of agent public keys allowed into the TAC instance.""" + return self._whitelist + + @property + def data_output_dir(self) -> str: + """Get data output dir.""" + return self._data_output_dir + + @property + def experiment_id(self) -> Optional[str]: + """Experiment id.""" + return self._experiment_id diff --git a/tac/platform/helpers.py b/tac/platform/helpers.py new file mode 100644 index 00000000..5929e620 --- /dev/null +++ b/tac/platform/helpers.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains helpers for platform.""" + +import math + + +def make_agent_name(agent_id: int, is_world_modeling: bool, nb_agents: int) -> str: + """ + Make the name for baseline agents from an integer identifier. + + E.g.: + + >>> _make_id(2, False, 10) + 'tac_agent_2' + >>> _make_id(2, False, 100) + 'tac_agent_02' + >>> _make_id(2, False, 101) + 'tac_agent_002' + + :param agent_id: the agent id. + :param is_world_modeling: the boolean indicated whether the baseline agent models the world around her or not. + :param nb_agents: the overall number of agents. + :return: the formatted name. + :return: the string associated to the integer id. + """ + max_number_of_digits = math.ceil(math.log10(nb_agents)) + if is_world_modeling: + string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}_wm" + else: + string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}" + result = string_format.format(agent_id) + return result diff --git a/tac/platform/simulation.py b/tac/platform/simulation.py index 4b7af7f0..367c2d6c 100644 --- a/tac/platform/simulation.py +++ b/tac/platform/simulation.py @@ -28,14 +28,13 @@ You can also run it as a script. To check the available arguments: - python3 -m tac.platform.simulation -h + python -m tac.platform.simulation -h """ import argparse import datetime import logging -import math import multiprocessing import pprint import random @@ -46,8 +45,9 @@ from tac.agents.v1.base.strategy import RegisterAs, SearchFor from tac.agents.v1.examples.baseline import main as baseline_main -from tac.platform.controller import TACParameters -from tac.platform.controller import main as controller_main +from tac.platform.controller.tac_parameters import TACParameters +from tac.platform.controller.controller_agent import main as controller_main +from tac.platform.helpers import make_agent_name logger = logging.getLogger(__name__) @@ -70,6 +70,7 @@ def __init__(self, data_output_dir: Optional[str] = "data", experiment_id: int = None, seed: int = 42, + nb_baseline_agents_world_modeling: int = 1, tac_parameters: Optional[TACParameters] = None): """ Initialize a SimulationParams class. @@ -88,6 +89,7 @@ def __init__(self, :param data_output_dir: the path to the output directory. :param experiment_id: the name of the experiment. :param seed: the random seed. + :param nb_baseline_agents_world_modeling: the number of world modelling baseline agents. :param tac_parameters: the parameters for the TAC. """ self.tac_parameters = tac_parameters if tac_parameters is not None else TACParameters() @@ -105,39 +107,17 @@ def __init__(self, self.data_output_dir = data_output_dir self.experiment_id = experiment_id self.seed = seed + self.nb_baseline_agents_world_modeling = nb_baseline_agents_world_modeling -def _make_id(agent_id: int, is_world_modeling: bool, nb_agents: int) -> str: +def spawn_controller_agent(params: SimulationParams) -> multiprocessing.Process: """ - Make the name for baseline agents from an integer identifier. - - E.g.: - - >>> _make_id(2, False, 10) - 'tac_agent_2' - >>> _make_id(2, False, 100) - 'tac_agent_02' - >>> _make_id(2, False, 101) - 'tac_agent_002' - - :param agent_id: the agent id. - :param is_world_modeling: the boolean indicated whether the baseline agent models the world around her or not. - :param nb_agents: the overall number of agents. - :return: the formatted name. - :return: the string associated to the integer id. + Spawn a controller agent. + + :param params: the simulation params. + :return: the process running the controller. """ - max_number_of_digits = math.ceil(math.log10(nb_agents)) - if is_world_modeling: - string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}_wm" - else: - string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}" - result = string_format.format(agent_id) - return result - - -def spawn_controller_agent(params: SimulationParams): - """Spawn a controller agent.""" - result = multiprocessing.Process(target=controller_main, kwargs=dict( + process = multiprocessing.Process(target=controller_main, kwargs=dict( name="tac_controller", nb_agents=params.tac_parameters.min_nb_agents, nb_goods=params.tac_parameters.nb_goods, @@ -162,39 +142,34 @@ def spawn_controller_agent(params: SimulationParams): seed=params.seed, version=1, )) - result.start() - return result - - -def run_baseline_agent(**kwargs) -> None: - """Run a baseline agent.""" - # give the time to the controller to connect to the OEF - time.sleep(5.0) - baseline_main(**kwargs) + process.start() + return process def spawn_baseline_agents(params: SimulationParams) -> List[multiprocessing.Process]: - """Spawn baseline agents.""" - fraction_world_modeling = 0.1 - nb_baseline_agents_world_modeling = round(params.nb_baseline_agents * fraction_world_modeling) + """ + Spawn baseline agents. - threads = [multiprocessing.Process(target=run_baseline_agent, kwargs=dict( - name=_make_id(i, i < nb_baseline_agents_world_modeling, params.nb_baseline_agents), + :param params: the simulation params. + :return: the processes running the agents (as a list). + """ + processes = [multiprocessing.Process(target=baseline_main, kwargs=dict( + name=make_agent_name(i, i < params.nb_baseline_agents_world_modeling, params.nb_baseline_agents), oef_addr=params.oef_addr, oef_port=params.oef_port, register_as=params.register_as, search_for=params.search_for, - is_world_modeling=i < nb_baseline_agents_world_modeling, + is_world_modeling=i < params.nb_baseline_agents_world_modeling, services_interval=params.services_interval, pending_transaction_timeout=params.pending_transaction_timeout, dashboard=params.dashboard, visdom_addr=params.visdom_addr, visdom_port=params.visdom_port)) for i in range(params.nb_baseline_agents)] - for t in threads: - t.start() + for process in processes: + process.start() - return threads + return processes def parse_arguments(): @@ -224,6 +199,7 @@ def parse_arguments(): parser.add_argument("--visdom-addr", default="localhost", help="TCP/IP address of the Visdom server") parser.add_argument("--visdom-port", default=8097, help="TCP/IP port of the Visdom server") parser.add_argument("--seed", default=42, help="The random seed of the simulation.") + parser.add_argument("--fraction-world-modeling", default=0.1, type=float, min=0.0, max=1.0, help="The fraction of world modelling baseline agents.") parser.add_argument("--whitelist-file", nargs="?", default=None, type=str, help="The file that contains the list of agent names to be whitelisted.") arguments = parser.parse_args() @@ -233,7 +209,12 @@ def parse_arguments(): def build_simulation_parameters(arguments: argparse.Namespace) -> SimulationParams: - """From argparse output, build an instance of SimulationParams.""" + """ + From argparse output, build an instance of SimulationParams. + + :param arguments: the arguments + :return: the simulation parameters + """ tac_parameters = TACParameters( min_nb_agents=arguments.nb_agents, money_endowment=arguments.money_endowment, @@ -259,24 +240,32 @@ def build_simulation_parameters(arguments: argparse.Namespace) -> SimulationPara data_output_dir=arguments.data_output_dir, experiment_id=arguments.experiment_id, seed=arguments.seed, + nb_baseline_agents_world_modeling=round(arguments.nb_baseline_agents * arguments.fraction_world_modeling), tac_parameters=tac_parameters ) return simulation_params -def run(params: SimulationParams): - """Run the simulation.""" +def run(params: SimulationParams) -> None: + """ + Run the simulation. + + :param params: the simulation parameters + :return: None + """ random.seed(params.seed) - controller_thread = None # type: Optional[multiprocessing.Process] - baseline_threads = [] # type: List[multiprocessing.Process] + controller_process = None # type: Optional[multiprocessing.Process] + baseline_processes = [] # type: List[multiprocessing.Process] try: - controller_thread = spawn_controller_agent(params) - baseline_threads = spawn_baseline_agents(params) - controller_thread.join() + controller_process = spawn_controller_agent(params) + # give the time to the controller to connect to the OEF + time.sleep(2.0) + baseline_processes = spawn_baseline_agents(params) + controller_process.join() except KeyboardInterrupt: logger.debug("Simulation interrupted...") @@ -284,13 +273,13 @@ def run(params: SimulationParams): logger.exception("Unexpected exception.") exit(-1) finally: - if controller_thread is not None: - controller_thread.join(timeout=5) - controller_thread.terminate() + if controller_process is not None: + controller_process.join(timeout=5) + controller_process.terminate() - for t in baseline_threads: - t.join(timeout=5) - t.terminate() + for process in baseline_processes: + process.join(timeout=5) + process.terminate() if __name__ == '__main__': diff --git a/tests/conftest.py b/tests/conftest.py index a48134e7..0ba3d01a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,12 +22,13 @@ import inspect import logging import os -import subprocess +# import subprocess import time import docker import pytest from docker.models.containers import Container +from tac.helpers.oef_health_check import OEFHealthCheck logger = logging.getLogger(__name__) @@ -66,10 +67,12 @@ def _wait_for_oef(max_attempts: int = 15, sleep_rate: float = 1.0): while not success and attempt < max_attempts: attempt += 1 logger.info("Attempt {}...".format(attempt)) - oef_healthcheck = subprocess.Popen(["python3", ROOT_DIR + "/sandbox/oef_healthcheck.py", "127.0.0.1", "10000"]) - oef_healthcheck.wait() - oef_healthcheck.terminate() - if oef_healthcheck.returncode == 0: + # oef_healthcheck = subprocess.Popen(["python3", ROOT_DIR + "/sandbox/oef_healthcheck.py", "127.0.0.1", "10000"]) + # oef_healthcheck.wait() + # oef_healthcheck.terminate() + oef_healthcheck = OEFHealthCheck("127.0.0.1", 10000) + result = oef_healthcheck.run() + if result: success = True else: logger.info("OEF not available yet - sleeping for {} second...".format(sleep_rate)) diff --git a/tests/test_controller.py b/tests/test_controller.py index 3258328c..8d045dbd 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -19,16 +19,20 @@ """This module contains the tests of the controller module.""" -import asyncio import datetime +import logging from threading import Thread from oef.agents import OEFAgent +from oef.core import AsyncioCore from tac.helpers.crypto import Crypto -from tac.platform.controller import ControllerAgent, TACParameters +from tac.platform.controller.controller_agent import ControllerAgent +from tac.platform.controller.tac_parameters import TACParameters from tac.platform.protocol import Register +logger = logging.getLogger(__name__) + class TestController: """Class to test the controller.""" @@ -43,7 +47,10 @@ def test_competition_stops_too_few_registered_agents(self, network_node): job.start() crypto = Crypto() - agent1 = OEFAgent(crypto.public_key, "127.0.0.1", 10000, loop=asyncio.new_event_loop()) + + core = AsyncioCore(logger=logger) + core.run_threaded() + agent1 = OEFAgent(crypto.public_key, oef_addr="127.0.0.1", oef_port=10000, core=core) agent1.connect() agent1.send_message(0, 0, controller_agent.public_key, Register(agent1.public_key, crypto, 'agent_name').serialize()) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 2f2a0bba..e9e54ee5 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -33,7 +33,8 @@ from tac.agents.v1.examples.baseline import BaselineAgent as BaselineAgentV1 -from tac.platform.controller import ControllerAgent, TACParameters +from tac.platform.controller.controller_agent import ControllerAgent +from tac.platform.controller.tac_parameters import TACParameters def _init_baseline_agents(n: int, version: str, oef_addr: str, oef_port: int) -> List[BaselineAgentV1]: From f7ce7fe0d503df4fc302a6fc81eb466e9625e5c3 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 15 Aug 2019 10:20:49 +0100 Subject: [PATCH 012/107] Stop core in oef_health_check --- tac/helpers/oef_health_check.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tac/helpers/oef_health_check.py b/tac/helpers/oef_health_check.py index 6a9a1331..56335a20 100644 --- a/tac/helpers/oef_health_check.py +++ b/tac/helpers/oef_health_check.py @@ -52,15 +52,19 @@ def run(self) -> bool: try: # import pdb; pdb.set_trace() pbk = 'check' + # import pdb; pdb.set_trace()n print("Connecting to {}:{}".format(self.addr, self.port)) core = AsyncioCore(logger=logger) core.run_threaded() agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) agent.connect() agent.disconnect() + core.stop() print("OK!") result = True return result except Exception as e: print(str(e)) return result + finally: + core.stop() From ecde48061893e1b3c43009a513d378a2e952e1ca Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 15 Aug 2019 18:30:44 +0100 Subject: [PATCH 013/107] Refactor messages module. --- sandbox/playground.py | 3 +- tac/agents/v1/mail/base.py | 6 +- tac/agents/v1/mail/messages.py | 316 ++++++++++++++++++--------------- 3 files changed, 173 insertions(+), 152 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index c63c5d30..f9692d84 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -38,6 +38,7 @@ from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.examples.baseline import BaselineAgent from tac.agents.v1.examples.strategy import BaselineStrategy +from tac.agents.v1.mail.messages import OEFAgentCfp from tac.platform.protocol import GameData CUR_PATH = inspect.getfile(inspect.currentframe()) @@ -132,7 +133,7 @@ def launch_oef(): starting_message_id = 1 starting_message_target = 0 services = agent_one.game_instance.build_services_dict(is_supply=is_seller) # type: Dict - cfp = CFP(starting_message_id, dialogue.dialogue_label.dialogue_id, agent_two.crypto.public_key, starting_message_target, json.dumps(services).encode('utf-8'), Context()) + cfp = OEFAgentCfp(starting_message_id, dialogue.dialogue_label.dialogue_id, agent_two.crypto.public_key, starting_message_target, json.dumps(services).encode('utf-8'), Context()) dialogue.outgoing_extend([cfp]) agent_one.out_box.out_queue.put(cfp) diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py index bf9b877a..94abb361 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/agents/v1/mail/base.py @@ -27,11 +27,9 @@ from queue import Queue from typing import Optional -logger = logging.getLogger(__name__) - +from tac.agents.v1.mail.messages import Message -class Message(ABC): - pass +logger = logging.getLogger(__name__) class InBox(object): diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 3323d1dd..53c0c036 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -19,154 +19,176 @@ # ------------------------------------------------------------------------------ """Protocol module v2.""" -from abc import ABC -from typing import List +from copy import deepcopy +from enum import Enum +from typing import Optional, Dict, Any -from oef.messages import CFP_TYPES, PROPOSE_TYPES, OEFErrorOperation from oef.query import Query from oef.schema import Description -from tac.agents.v1.mail.base import Message - -RequestId = int -SearchId = int -MessageId = int -DialogueId = int -AgentAddress = str - - -class OEFMessage(Message, ABC): - pass - - -class OEFRequest(OEFMessage, ABC): - pass - - -class OEFResponse(OEFMessage, ABC): - pass - - -class OEFError(OEFResponse, ABC): - pass - - -class OEFGenericError(OEFError): - - def __init__(self, answer_id: RequestId, operation: OEFErrorOperation): - super().__init__() - self.answer_id = answer_id - self.operation = operation - - -class OEFDialogueError(OEFError): - - def __init__(self, answer_id: MessageId, dialogue_id: DialogueId, origin: AgentAddress): - super().__init__() - self.answer_id = answer_id - self.dialogue_id = dialogue_id - self.origin = origin - - -class OEFRegisterServiceRequest(OEFRequest): - - def __init__(self, msg_id: RequestId, agent_description: Description, service_id: str = ""): - super().__init__() - self.msg_id = msg_id - self.agent_description = agent_description - self.service_id = service_id - - -class OEFUnregisterServiceRequest(OEFRequest): - - def __init__(self, msg_id: RequestId, agent_description: Description, service_id: str = ""): - super().__init__() - self.msg_id = msg_id - self.agent_description = agent_description - self.service_id = service_id - - -class OEFRegisterAgentRequest(OEFRequest): - - def __init__(self, msg_id: RequestId, agent_description: Description): - self.msg_id = msg_id - self.agent_description = agent_description - - -class OEFUnregisterAgentRequest(OEFRequest): - - def __init__(self, msg_id: RequestId): - self.msg_id = msg_id - - -class OEFSearchRequest(OEFRequest): - pass - - -class OEFSearchServicesRequest(OEFSearchRequest): - - def __init__(self, search_id: SearchId, query: Query): - super().__init__() - self.search_id = search_id - self.query = query - - -class OEFSearchAgentsRequest(OEFSearchRequest): - - def __init__(self, search_id: SearchId, query: Query): - super().__init__() - self.search_id = search_id - self.query = query - - -class OEFSearchResult(OEFResponse): - - def __init__(self, search_id: SearchId, agents: List[str]): - super().__init__() - self.search_id = search_id - self.agents = agents - - -class OEFAgentMessage(OEFMessage): - - def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress): - super().__init__() - self.msg_id = msg_id - self.dialogue_id = dialogue_id - self.destination = destination - - -class OEFAgentByteMessage(OEFAgentMessage): - - def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, content: bytes): - super().__init__(msg_id, dialogue_id, destination) - self.content = content - - -class OEFAgentFIPAMessage(OEFAgentMessage): - - def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId): - super().__init__(msg_id, dialogue_id, destination) - self.target = target - - -class OEFAgentCfp(OEFAgentFIPAMessage): - - def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId, query: CFP_TYPES): - super().__init__(msg_id, dialogue_id, destination, target) - self.query = query - - -class OEFAgentPropose(OEFAgentFIPAMessage): - - def __init__(self, msg_id: MessageId, dialogue_id: DialogueId, destination: AgentAddress, target: MessageId, proposal: PROPOSE_TYPES): - super().__init__(msg_id, dialogue_id, destination, target) - self.proposal = proposal - - -class OEFAgentAccept(OEFAgentFIPAMessage): - pass - - -class OEFAgentDecline(OEFAgentFIPAMessage): - pass +from tac.agents.v1.base.dialogues import DialogueLabel + +Address = str +ProtocolId = str + + +class Message: + + def __init__(self, to: Address, + sender: Address, + body: Optional[Dict[str, Any]] = None, + protocol_id: Optional[ProtocolId] = None): + self._to = to + self._sender = sender + self._body = deepcopy(body) if body else {} + self._protocol_id = protocol_id + + @property + def to(self) -> Address: + return self._to + + @to.setter + def to(self, to: Address): + self._to = to + + @property + def sender(self) -> Address: + return self._sender + + @sender.setter + def sender(self, sender: Address): + self._sender = sender + + @property + def protocol_id(self) -> Optional[ProtocolId]: + return self._protocol_id + + def set(self, key: str, value: Any) -> None: + self._body[key] = value + + def get(self, key: str) -> Optional[Any]: + return self._body.get(key, None) + + def unset(self, key: str) -> None: + self._body.pop(key, None) + + def is_set(self, key: str) -> bool: + return key in self._body + + def check_consistency(self) -> bool: + """Check that the data are consistent.""" + return True + + +class OEFMessage(Message): + + protocol_id = "oef" + + class Type(Enum): + REGISTER_SERVICE = "register_service" + REGISTER_AGENT = "register_agent" + UNREGISTER_SERVICE = "unregister_service" + UNREGISTER_AGENT = "unregister_agent" + SEARCH_SERVICES = "search_services" + SEARCH_AGENTS = "search_agents" + + SEARCH_RESULT = "search_result" + + def __init__(self, to: Address, + sender: Address, + message_id: int, + oef_type: Type, + body: Dict[str, Any]): + _body = dict(**body) + _body["type"] = oef_type + _body["id"] = message_id + super().__init__(to, sender, body=body, protocol_id=self.protocol_id) + + def check_consistency(self) -> bool: + try: + assert self.is_set("id") + assert self.is_set("type") + oef_type = OEFMessage.Type(self.get("type")) + if oef_type == OEFMessage.Type.REGISTER_SERVICE: + service_description = self.get("service_description") + service_id = self.get("service_id") + assert isinstance(service_description, Description) + assert isinstance(service_id, str) + elif oef_type == OEFMessage.Type.REGISTER_AGENT: + agent_description = self.get("agent_description") + assert isinstance(agent_description, Description) + elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: + service_id = self.get("service_id") + assert isinstance(service_id, str) + elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: + pass + elif oef_type == OEFMessage.Type.SEARCH_SERVICES: + query = self.get("query") + assert isinstance(query, Query) + elif oef_type == OEFMessage.Type.SEARCH_AGENTS: + query = self.get("query") + assert isinstance(query, Query) + elif oef_type == OEFMessage.Type.SEARCH_RESULT: + agents = self.get("agents") + assert type(agents) == list and all(type(a) == str for a in agents) + else: + raise ValueError("Type not recognized.") + except (AssertionError, ValueError): + return False + + return True + + +class ByteMessage(Message): + + protocol_id = "bytes" + + def __init__(self, to: Address, + sender: Address, + dialogue_id: DialogueLabel, + content: bytes = b""): + body = dict(dialogue_id=dialogue_id, content=content) + super().__init__(to, sender, body=body, protocol_id=self.protocol_id) + + +class FIPAMessage(Message): + + protocol_id = "fipa" + + class Type(Enum): + CFP = "cfp" + PROPOSE = "propose" + ACCEPT = "accept" + DECLINE = "decline" + + def __init__(self, to: Address, + sender: Address, + dialogue_id: DialogueLabel, + target: int, + performative: Type, + body: Dict[str, Any]): + _body = dict(**body) + _body["dialogue_id"] = dialogue_id + _body["target"] = target + _body["performative"] = performative + super().__init__(to, sender, body=_body, protocol_id=self.protocol_id) + + def check_consistency(self) -> bool: + try: + assert self.is_set("target") + performative = FIPAMessage.Type(self.get("performative")) + if performative == FIPAMessage.Type.CFP: + query = self.get("query") + assert isinstance(query, Query) or query is None + elif performative == FIPAMessage.Type.PROPOSE: + proposal = self.get("proposal") + assert type(proposal) == list and all(isinstance(d, Description) for d in proposal) + elif performative == FIPAMessage.Type.ACCEPT or performative == FIPAMessage.Type.DECLINE: + pass + else: + raise ValueError("Performative not recognized.") + + except (AssertionError, ValueError, KeyError): + return False + + return True From 3b9920fc526fa141df30ea1b35a95d824ab54a7c Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 15 Aug 2019 18:46:41 +0100 Subject: [PATCH 014/107] add simple byte message. --- tac/agents/v1/mail/messages.py | 23 +++++++++++++++-------- tac/agents/v1/mail/oef.py | 6 +----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 53c0c036..e770ac01 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -34,8 +34,8 @@ class Message: - def __init__(self, to: Address, - sender: Address, + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, body: Optional[Dict[str, Any]] = None, protocol_id: Optional[ProtocolId] = None): self._to = to @@ -91,46 +91,54 @@ class Type(Enum): UNREGISTER_AGENT = "unregister_agent" SEARCH_SERVICES = "search_services" SEARCH_AGENTS = "search_agents" + BYTES = "bytes" SEARCH_RESULT = "search_result" def __init__(self, to: Address, sender: Address, - message_id: int, oef_type: Type, body: Dict[str, Any]): _body = dict(**body) _body["type"] = oef_type - _body["id"] = message_id super().__init__(to, sender, body=body, protocol_id=self.protocol_id) def check_consistency(self) -> bool: try: - assert self.is_set("id") assert self.is_set("type") oef_type = OEFMessage.Type(self.get("type")) if oef_type == OEFMessage.Type.REGISTER_SERVICE: service_description = self.get("service_description") service_id = self.get("service_id") + assert self.is_set("id") assert isinstance(service_description, Description) assert isinstance(service_id, str) elif oef_type == OEFMessage.Type.REGISTER_AGENT: agent_description = self.get("agent_description") + assert self.is_set("id") assert isinstance(agent_description, Description) elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: service_id = self.get("service_id") + assert self.is_set("id") assert isinstance(service_id, str) elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - pass + assert self.is_set("id") elif oef_type == OEFMessage.Type.SEARCH_SERVICES: query = self.get("query") + assert self.is_set("id") assert isinstance(query, Query) elif oef_type == OEFMessage.Type.SEARCH_AGENTS: query = self.get("query") + assert self.is_set("id") assert isinstance(query, Query) elif oef_type == OEFMessage.Type.SEARCH_RESULT: agents = self.get("agents") + assert self.is_set("id") assert type(agents) == list and all(type(a) == str for a in agents) + elif oef_type == OEFMessage.Type.BYTES: + content = self.get("content") + assert self.is_set("dialogue_id") + assert type(content) == bytes else: raise ValueError("Type not recognized.") except (AssertionError, ValueError): @@ -145,9 +153,8 @@ class ByteMessage(Message): def __init__(self, to: Address, sender: Address, - dialogue_id: DialogueLabel, content: bytes = b""): - body = dict(dialogue_id=dialogue_id, content=content) + body = dict(content=content) super().__init__(to, sender, body=body, protocol_id=self.protocol_id) diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 9222c627..43369fb5 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -32,11 +32,6 @@ from oef.proxy import OEFNetworkProxy from tac.agents.v1.mail.base import Connection, MailBox -from tac.agents.v1.mail.messages import OEFRegisterServiceRequest, OEFRegisterAgentRequest, \ - OEFUnregisterServiceRequest, OEFUnregisterAgentRequest, OEFSearchAgentsRequest, OEFSearchServicesRequest, \ - OEFRequest, OEFAgentMessage, OEFAgentByteMessage, OEFAgentFIPAMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, \ - OEFAgentDecline, OEFSearchResult, OEFGenericError, OEFDialogueError - logger = logging.getLogger(__name__) @@ -97,6 +92,7 @@ def is_connected(self): return self._oef_proxy.is_connected() def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): + ByteMessage() self.in_queue.put(OEFAgentByteMessage(msg_id, dialogue_id, origin, content)) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES): From 17bfd5c5654d378edc53cfacaeb5fe9d7f179396 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 15 Aug 2019 19:34:08 +0100 Subject: [PATCH 015/107] make all arguments of message constructors optional. --- tac/agents/v1/mail/messages.py | 92 ++++++++++++------- tac/agents/v1/mail/oef.py | 161 +++++++++++++++++++++++---------- tests/test_mail.py | 63 +++++++++++++ 3 files changed, 234 insertions(+), 82 deletions(-) create mode 100644 tests/test_mail.py diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index e770ac01..c77d66e9 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -21,12 +21,14 @@ """Protocol module v2.""" from copy import deepcopy from enum import Enum -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, Union +from oef.messages import OEFErrorOperation from oef.query import Query from oef.schema import Description -from tac.agents.v1.base.dialogues import DialogueLabel +# from tac.agents.v1.base.dialogues import DialogueLabel +DialogueLabel = int Address = str ProtocolId = str @@ -36,8 +38,8 @@ class Message: def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, - body: Optional[Dict[str, Any]] = None, - protocol_id: Optional[ProtocolId] = None): + protocol_id: Optional[ProtocolId] = None, + **body): self._to = to self._sender = sender self._body = deepcopy(body) if body else {} @@ -91,17 +93,19 @@ class Type(Enum): UNREGISTER_AGENT = "unregister_agent" SEARCH_SERVICES = "search_services" SEARCH_AGENTS = "search_agents" + OEF_ERROR = "oef_error" + DIALOGUE_ERROR = "dialogue_error" BYTES = "bytes" SEARCH_RESULT = "search_result" - def __init__(self, to: Address, - sender: Address, - oef_type: Type, - body: Dict[str, Any]): + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, + oef_type: Optional[Type] = None, + **body): _body = dict(**body) _body["type"] = oef_type - super().__init__(to, sender, body=body, protocol_id=self.protocol_id) + super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, **_body) def check_consistency(self) -> bool: try: @@ -109,35 +113,52 @@ def check_consistency(self) -> bool: oef_type = OEFMessage.Type(self.get("type")) if oef_type == OEFMessage.Type.REGISTER_SERVICE: service_description = self.get("service_description") - service_id = self.get("service_id") assert self.is_set("id") + assert self.is_set("service_id") + service_id = self.get("service_id") assert isinstance(service_description, Description) assert isinstance(service_id, str) elif oef_type == OEFMessage.Type.REGISTER_AGENT: - agent_description = self.get("agent_description") assert self.is_set("id") + assert self.is_set("agent_description") + agent_description = self.get("agent_description") assert isinstance(agent_description, Description) elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - service_id = self.get("service_id") assert self.is_set("id") + assert self.is_set("service_id") + service_id = self.get("service_id") assert isinstance(service_id, str) elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: assert self.is_set("id") elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - query = self.get("query") assert self.is_set("id") + assert self.is_set("query") + query = self.get("query") assert isinstance(query, Query) elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - query = self.get("query") assert self.is_set("id") + assert self.is_set("query") + query = self.get("query") assert isinstance(query, Query) elif oef_type == OEFMessage.Type.SEARCH_RESULT: - agents = self.get("agents") assert self.is_set("id") + assert self.is_set("agents") + agents = self.get("agents") assert type(agents) == list and all(type(a) == str for a in agents) + elif oef_type == OEFMessage.Type.OEF_ERROR: + assert self.is_set("id") + assert self.is_set("operation") + operation = self.get("operation") + assert operation in set(OEFErrorOperation) + elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: + assert self.is_set("id") + assert self.is_set("dialogue_id") + assert self.is_set("origin") elif oef_type == OEFMessage.Type.BYTES: - content = self.get("content") + assert self.is_set("id") assert self.is_set("dialogue_id") + assert self.is_set("content") + content = self.get("content") assert type(content) == bytes else: raise ValueError("Type not recognized.") @@ -151,46 +172,47 @@ class ByteMessage(Message): protocol_id = "bytes" - def __init__(self, to: Address, - sender: Address, + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, content: bytes = b""): - body = dict(content=content) - super().__init__(to, sender, body=body, protocol_id=self.protocol_id) + super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, content=content) class FIPAMessage(Message): protocol_id = "fipa" - class Type(Enum): + class Performative(Enum): CFP = "cfp" PROPOSE = "propose" ACCEPT = "accept" DECLINE = "decline" - def __init__(self, to: Address, - sender: Address, - dialogue_id: DialogueLabel, - target: int, - performative: Type, - body: Dict[str, Any]): + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, + msg_id: Optional[int] = None, + dialogue_id: Optional[DialogueLabel] = None, + target: Optional[int] = None, + performative: Optional[Union[str, Performative]] = None, + **body): _body = dict(**body) + _body["id"] = msg_id _body["dialogue_id"] = dialogue_id _body["target"] = target - _body["performative"] = performative - super().__init__(to, sender, body=_body, protocol_id=self.protocol_id) + _body["performative"] = FIPAMessage.Performative(performative) + super().__init__(to, sender, protocol_id=self.protocol_id, **_body) def check_consistency(self) -> bool: try: assert self.is_set("target") - performative = FIPAMessage.Type(self.get("performative")) - if performative == FIPAMessage.Type.CFP: + performative = FIPAMessage.Performative(self.get("performative")) + if performative == FIPAMessage.Performative.CFP: query = self.get("query") - assert isinstance(query, Query) or query is None - elif performative == FIPAMessage.Type.PROPOSE: + assert isinstance(query, Query) or isinstance(query, bytes) or query is None + elif performative == FIPAMessage.Performative.PROPOSE: proposal = self.get("proposal") - assert type(proposal) == list and all(isinstance(d, Description) for d in proposal) - elif performative == FIPAMessage.Type.ACCEPT or performative == FIPAMessage.Type.DECLINE: + assert type(proposal) == list and all(isinstance(d, Description) or type(d) == bytes for d in proposal) + elif performative == FIPAMessage.Performative.ACCEPT or performative == FIPAMessage.Performative.DECLINE: pass else: raise ValueError("Performative not recognized.") diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 43369fb5..af1cbfe5 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -32,6 +32,8 @@ from oef.proxy import OEFNetworkProxy from tac.agents.v1.mail.base import Connection, MailBox +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, Message + logger = logging.getLogger(__name__) @@ -92,73 +94,138 @@ def is_connected(self): return self._oef_proxy.is_connected() def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): - ByteMessage() - self.in_queue.put(OEFAgentByteMessage(msg_id, dialogue_id, origin, content)) + msg = OEFMessage(to=self.public_key, + sender=origin, + msg_id=msg_id, + dialogue_id=dialogue_id, + oef_type=OEFMessage.Type.BYTES, + content=content) + self.in_queue.put(msg) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES): - self.in_queue.put(OEFAgentCfp(msg_id, dialogue_id, origin, target, query)) + msg = FIPAMessage(to=self.public_key, + sender=origin, + msg_id=msg_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative.CFP, + query=query) + self.in_queue.put(msg) def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES): - self.in_queue.put(OEFAgentPropose(msg_id, dialogue_id, origin, target, proposals)) + msg = FIPAMessage(to=self.public_key, + sender=origin, + msg_id=msg_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative.PROPOSE, + proposal=proposals) + self.in_queue.put(msg) def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int): - self.in_queue.put(OEFAgentAccept(msg_id, dialogue_id, origin, target)) + msg = FIPAMessage(to=self.public_key, + sender=origin, + msg_id=msg_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative.ACCEPT) + self.in_queue.put(msg) def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int): - self.in_queue.put(OEFAgentDecline(msg_id, dialogue_id, origin, target)) + msg = FIPAMessage(to=self.public_key, + sender=origin, + msg_id=msg_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative.DECLINE) + self.in_queue.put(msg) def on_search_result(self, search_id: int, agents: List[str]): self.mail_stats.search_end(search_id, len(agents)) - self.in_queue.put(OEFSearchResult(search_id, agents)) + msg = OEFMessage(to=self.public_key, + sender=None, + oef_type=OEFMessage.Type.SEARCH_RESULT, + id=search_id, + agents=agents) + self.in_queue.put(msg) def on_oef_error(self, answer_id: int, operation: OEFErrorOperation): - self.in_queue.put(OEFGenericError(answer_id, operation)) + msg = OEFMessage(to=self.public_key, + sender=None, + oef_type=OEFMessage.Type.OEF_ERROR, + id=answer_id, + operation=operation) + self.in_queue.put(msg) def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str): - self.in_queue.put(OEFDialogueError(answer_id, dialogue_id, origin)) - - def send(self, msg: Union[OEFRequest, OEFAgentMessage]): - if isinstance(msg, OEFRequest): - self.send_oef_request(msg) - elif isinstance(msg, OEFAgentMessage): - self.send_oef_agent_message(msg) + msg = OEFMessage(to=self.public_key, + sender=None, + oef_type=OEFMessage.Type.DIALOGUE_ERROR, + id=answer_id, + dialogue_id=dialogue_id, + origin=origin) + self.in_queue.put(msg) + + def send(self, msg: Message): + if msg.protocol_id == "oef": + self.send_oef_message(msg) + elif msg.protocol_id == "fipa": + self.send_fipa_message(msg) else: raise ValueError("Cannot send message.") - def send_oef_request(self, msg: OEFRequest): - if isinstance(msg, OEFRegisterServiceRequest): - self.register_service(msg.msg_id, msg.agent_description, msg.service_id) - elif isinstance(msg, OEFRegisterAgentRequest): - self.register_agent(msg.msg_id, msg.agent_description) - elif isinstance(msg, OEFUnregisterServiceRequest): - self.unregister_service(msg.msg_id, msg.agent_description, msg.service_id) - elif isinstance(msg, OEFUnregisterAgentRequest): - self.unregister_agent(msg.msg_id) - elif isinstance(msg, OEFSearchAgentsRequest): - self.search_agents(msg.search_id, msg.query) - elif isinstance(msg, OEFSearchServicesRequest): - self.mail_stats.search_start(msg.search_id) - self.search_services(msg.search_id, msg.query) + def send_oef_message(self, msg: Message): + oef_type = msg.get("type") + if oef_type == OEFMessage.Type.REGISTER_SERVICE: + id = msg.get("id") + service_description = msg.get("service_description") + service_id = msg.get("service_id") + self.register_service(id, service_description, service_id) + elif oef_type == OEFMessage.Type.REGISTER_AGENT: + id = msg.get("id") + agent_description = msg.get("agent_description") + self.register_agent(id, agent_description) + elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: + id = msg.get("id") + service_description = msg.get("service_description") + service_id = msg.get("service_id") + self.unregister_service(id, service_description, service_id) + elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: + id = msg.get("id") + self.unregister_agent(id) + elif oef_type == OEFMessage.Type.SEARCH_AGENTS: + id = msg.get("id") + query = msg.get("query") + self.search_agents(id, query) + elif oef_type == OEFMessage.Type.SEARCH_SERVICES: + id = msg.get("id") + query = msg.get("query") + self.mail_stats.search_start(id) + self.search_services(id, query) + elif oef_type == OEFMessage.Type.BYTES: + id = msg.get("id") + dialogue_id = msg.get("dialogue_id") + content = msg.get("content") + self.send_message(id, dialogue_id, msg.to, content) else: raise ValueError("OEF request not recognized.") - def send_oef_agent_message(self, msg: OEFAgentMessage): - if isinstance(msg, OEFAgentByteMessage): - self.send_message(msg.msg_id, msg.dialogue_id, msg.destination, msg.content) - elif isinstance(msg, OEFAgentFIPAMessage): - self.send_fipa_message(msg) - else: - raise ValueError("OEF Agent message not recognized.") - - def send_fipa_message(self, msg: OEFAgentFIPAMessage): - if isinstance(msg, OEFAgentCfp): - self.send_cfp(msg.msg_id, msg.dialogue_id, msg.destination, msg.target, msg.query) - elif isinstance(msg, OEFAgentPropose): - self.send_propose(msg.msg_id, msg.dialogue_id, msg.destination, msg.target, msg.proposal) - elif isinstance(msg, OEFAgentAccept): - self.send_accept(msg.msg_id, msg.dialogue_id, msg.destination, msg.target) - elif isinstance(msg, OEFAgentDecline): - self.send_decline(msg.msg_id, msg.dialogue_id, msg.destination, msg.target) + def send_fipa_message(self, msg: Message): + id = msg.get("id") + dialogue_id = msg.get("dialogue_id") + destination = msg.to + target = msg.get("target") + performative = msg.get("performative") + if performative == FIPAMessage.Performative.CFP: + query = msg.get("query") + self.send_cfp(id, dialogue_id, destination, target, query) + elif performative == FIPAMessage.Performative.PROPOSE: + proposal = msg.get("proposal") + self.send_propose(id, dialogue_id, destination, target, proposal) + elif performative == FIPAMessage.Performative.ACCEPT: + self.send_accept(id, dialogue_id, destination, target) + elif performative == FIPAMessage.Performative.DECLINE: + self.send_decline(id, dialogue_id, destination, target) else: raise ValueError("OEF FIPA message not recognized.") @@ -205,7 +272,7 @@ def disconnect(self): def is_established(self) -> bool: return self.bridge.is_connected() - def send(self, msg: Union[OEFRequest, OEFAgentMessage]): + def send(self, msg: Message): self.bridge.send(msg) diff --git a/tests/test_mail.py b/tests/test_mail.py new file mode 100644 index 00000000..c5ed94d2 --- /dev/null +++ b/tests/test_mail.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains tests for the mail module.""" +import time + +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage +from tac.agents.v1.mail.oef import OEFNetworkMailBox + + +def test_example(network_node): + + mailbox1 = OEFNetworkMailBox("mailbox1", "127.0.0.1", 10000) + mailbox2 = OEFNetworkMailBox("mailbox2", "127.0.0.1", 10000) + + mailbox1.connect() + mailbox2.connect() + + msg = OEFMessage("mailbox2", "mailbox1", OEFMessage.Type.BYTES, id=0, dialogue_id=0, content=b"hello") + mailbox1.send(msg) + msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None) + mailbox1.send(msg) + msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) + mailbox1.send(msg) + msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT) + mailbox1.send(msg) + msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE) + mailbox1.send(msg) + + time.sleep(1.0) + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.get("content") == b"hello" + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.CFP + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.PROPOSE + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.ACCEPT + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.DECLINE + + mailbox1.disconnect() + mailbox2.disconnect() From 10a14ef7363a148f88135d227de32c6e0dcf7974 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 09:54:27 +0100 Subject: [PATCH 016/107] Finish transition to controller in v1 architecture, revert to 0.6.0 --- Pipfile | 2 +- Pipfile.lock | 13 +++-- scripts/launch_alt.py | 8 ++- simulation/README.md | 2 +- tac/agents/v1/base/actions.py | 4 +- tac/agents/v1/mail.py | 19 ++++--- tac/helpers/oef_health_check.py | 18 +++--- tac/platform/controller/actions.py | 3 +- tac/platform/controller/controller_agent.py | 46 +++++++++------- tac/platform/controller/handlers.py | 61 ++++++++++++++------- tac/platform/simulation.py | 6 +- tests/test_controller.py | 6 +- 12 files changed, 113 insertions(+), 75 deletions(-) diff --git a/Pipfile b/Pipfile index f7d3b03a..75a56bcf 100644 --- a/Pipfile +++ b/Pipfile @@ -27,7 +27,7 @@ visdom = "*" cryptography = "*" base58 = "*" fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} -oef = {version="*", index="test-pypi"} +oef = {version="==0.6.0", index="test-pypi"} sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" sphinx = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b743d573..58baf330 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "6117d1728abb2c7068f63e101220ef6089d74955f3efd21ae650b812e279bdd6" + "sha256": "1e0d0b06715a16acdba107f48c5050043159caac35c27994b4847a21c5801aed" }, "pipfile-spec": 6, "requires": { @@ -400,11 +400,11 @@ }, "oef": { "hashes": [ - "sha256:9498fde163c1252acd4afb0034732d0e95fb849c94a83399baa2aa9fd5b262d8", - "sha256:c61e9834b2f1716757fbbe143b0e8407a88b9ccbde2c7026688751348275ce61" + "sha256:1944b11632c4af841e373f52e3e96941821f6b4bb4dbc430f790c1b436880618", + "sha256:cab9e251bfd1476f332de5046a83e0c01d0ce75ddfefb1bd614359a2b904d726" ], "index": "test-pypi", - "version": "==0.6.1" + "version": "==0.6.0" }, "packaging": { "hashes": [ @@ -1138,9 +1138,10 @@ }, "pydocstyle": { "hashes": [ - "sha256:58c421dd605eec0bce65df8b8e5371bb7ae421582cdf0ba8d9435ac5b0ffc36a" + "sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058", + "sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59" ], - "version": "==4.0.0" + "version": "==4.0.1" }, "pyflakes": { "hashes": [ diff --git a/scripts/launch_alt.py b/scripts/launch_alt.py index 011c547d..13ab231b 100644 --- a/scripts/launch_alt.py +++ b/scripts/launch_alt.py @@ -33,6 +33,7 @@ import tac from tac.platform.simulation import parse_arguments, build_simulation_parameters +from tac.helpers.oef_health_check import OEFHealthCheck CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") @@ -71,8 +72,11 @@ def _wait_for_oef(self): """Wait for the OEF to come live.""" print("Waiting for the OEF to be operative...") for loop in range(0, 30): - exit_status = os.system("netstat -nal | grep 10000") - if exit_status != 1: break + oef_healthcheck = OEFHealthCheck("127.0.0.1", 10000) + is_success = oef_healthcheck.run() + # exit_status = os.system("netstat -nal | grep 10000 | grep LISTEN") + # if exit_status != 1: break + if is_success: break time.sleep(1) def __enter__(self): diff --git a/simulation/README.md b/simulation/README.md index 10268102..3070a8b5 100644 --- a/simulation/README.md +++ b/simulation/README.md @@ -65,7 +65,7 @@ For a full list, do `python simulation/tac_agent_spawner.py -h` Example: - python examples/simulation_demo/tac_agent_spawner.py + python simulation/v1/tac_agent_spawner.py --nb-agents 10 --nb-goods 10 --nb-baseline-agents 10 diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index 39641a0a..ea7bc472 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -122,9 +122,9 @@ def unregister_service(self) -> None: :return: None """ if self.game_instance.goods_demanded_description is not None: - self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_demanded_description)) + self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_demanded_description, is_unregister=True)) if self.game_instance.goods_supplied_description is not None: - self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_supplied_description)) + self.out_box.out_queue.put(OutContainer(message_id=1, service_description=self.game_instance.goods_supplied_description, is_unregister=True)) def register_service(self) -> None: """ diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index 07d06483..7c5c7fb0 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -35,7 +35,7 @@ from typing import List, Optional, Any, Union, Dict from oef.agents import OEFAgent -from oef.core import AsyncioCore +# from oef.core import AsyncioCore # OEF-SDK 0.6.1 from oef.messages import PROPOSE_TYPES, CFP_TYPES, CFP, Decline, Propose, Accept, Message as ByteMessage, \ SearchResult, OEFErrorOperation, OEFErrorMessage, DialogueErrorMessage from oef.query import Query @@ -111,9 +111,11 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, debug: :return: None """ - core = AsyncioCore(logger=logger) - super().__init__(public_key, oef_addr=oef_addr, oef_port=oef_port, core=core) - self.core.run_threaded() + # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 + # core.run_threaded() # OEF-SDK 0.6.1 + import asyncio + super().__init__(public_key, oef_addr=oef_addr, oef_port=oef_port, loop=asyncio.new_event_loop()) + # super().__init__(public_key, oef_addr=oef_addr, oef_port=oef_port, core=core) # OEF-SDK 0.6.1 self.in_queue = Queue() self.out_queue = Queue() self._mail_box_thread = None # type: Optional[Thread] @@ -194,6 +196,7 @@ def connect(self) -> bool: while not success: try: + print('HERE!') success = super().connect() except ConnectionError: logger.error("Problems when connecting to the OEF. Retrying in 3 seconds...") @@ -219,8 +222,8 @@ def stop(self) -> None: :return: None """ - self.core.stop() - # self._loop.call_soon_threadsafe(super().stop) + # self.core.stop() # OEF-SDK 0.6.1 + self._loop.call_soon_threadsafe(super().stop) if self._mail_box_thread is not None: self._mail_box_thread.join() self.disconnect() @@ -340,10 +343,10 @@ def send_nowait(self) -> None: logger.debug("Outgoing message type: type={}".format(type(out.message))) self.mail_box.send_message(out.message_id, out.dialogue_id, out.destination, out.message) elif isinstance(out, OutContainer) and (out.service_description is not None) and (not out.is_unregister): - logger.debug("Outgoing register service description: message_id={}".format(type(out.service_description), out.message_id)) + logger.debug("Outgoing register service description: type={}, message_id={}".format(type(out.service_description), out.message_id)) self.mail_box.register_service(out.message_id, out.service_description) elif isinstance(out, OutContainer) and out.service_description is not None: - logger.debug("Outgoing unregister service description: message_id={}".format(type(out.service_description), out.message_id)) + logger.debug("Outgoing unregister service description: type={}, message_id={}".format(type(out.service_description), out.message_id)) self.mail_box.unregister_service(out.message_id, out.service_description) elif isinstance(out, OutContainer) and out.query is not None: logger.debug("Outgoing query: search_id={}".format(out.search_id)) diff --git a/tac/helpers/oef_health_check.py b/tac/helpers/oef_health_check.py index 56335a20..ef9ebd52 100644 --- a/tac/helpers/oef_health_check.py +++ b/tac/helpers/oef_health_check.py @@ -24,7 +24,7 @@ import logging from oef.agents import OEFAgent -from oef.core import AsyncioCore +# from oef.core import AsyncioCore # OEF-SDK 0.6.1 logger = logging.getLogger(__name__) @@ -50,21 +50,21 @@ def run(self) -> bool: """ result = False try: - # import pdb; pdb.set_trace() pbk = 'check' - # import pdb; pdb.set_trace()n print("Connecting to {}:{}".format(self.addr, self.port)) - core = AsyncioCore(logger=logger) - core.run_threaded() - agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) + # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 + # core.run_threaded() # OEF-SDK 0.6.1 + import asyncio + agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, loop=asyncio.new_event_loop()) + # agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) # OEF-SDK 0.6.1 agent.connect() agent.disconnect() - core.stop() + # core.stop() # OEF-SDK 0.6.1 print("OK!") result = True return result except Exception as e: print(str(e)) return result - finally: - core.stop() + # finally: + # core.stop(). # OEF-SDK 0.6.1 diff --git a/tac/platform/controller/actions.py b/tac/platform/controller/actions.py index 495a4c05..1ceac73a 100644 --- a/tac/platform/controller/actions.py +++ b/tac/platform/controller/actions.py @@ -67,4 +67,5 @@ def register_tac(self) -> None: """ desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) - self.out_box.out_queue.put(OutContainer(service_description=desc, message_id=1)) + out = OutContainer(message_id=1, service_description=desc) + self.out_box.out_queue.put(out) diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index a77253cc..ed668009 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -58,7 +58,7 @@ def __init__(self, name: str, oef_addr: str, oef_port: int, tac_parameters: TACParameters, - dashboard: Optional[Monitor] = None, + monitor: Optional[Monitor] = None, agent_timeout: Optional[float] = 1.0, max_reactions: int = 100, private_key_pem: Optional[str] = None, @@ -73,7 +73,7 @@ def __init__(self, name: str, :param strategy: the strategy object that specify the behaviour during the competition. :param agent_timeout: the time in (fractions of) seconds to time out an agent between act and react. :param max_reactions: the maximum number of reactions (messages processed) per call to react. - :param dashboard: a Visdom dashboard to visualize agent statistics during the competition. + :param monitor: a Visdom dashboard to visualize agent statistics during the competition. :param private_key_pem: the path to a private key in PEM format. :param debug: if True, run the agent in debug mode. """ @@ -84,7 +84,7 @@ def __init__(self, name: str, self.oef_handler = OEFHandler(self.crypto, self.liveness, self.out_box, self.name) self.agent_message_dispatcher = AgentMessageDispatcher(self) - self.game_handler = GameHandler(name, self.crypto, self.out_box, self.dashboard, tac_parameters) + self.game_handler = GameHandler(name, self.crypto, self.out_box, monitor, tac_parameters) self.max_reactions = max_reactions self.last_activity = datetime.datetime.now() @@ -99,31 +99,32 @@ def act(self) -> None: """ if self.game_handler.game_phase == GamePhase.PRE_GAME: now = datetime.datetime.now() + seconds_to_wait = (self.game_handler.tac_parameters.start_time - now).total_seconds() + seconds_to_wait = 0.5 if seconds_to_wait < 0 else seconds_to_wait logger.debug("[{}]: waiting for starting the competition: start_time={}, current_time={}, timedelta ={}s" - .format(self.name, str(self.tac_parameters.start_time), str(now), - (self.tac_parameters.start_time - now).total_seconds())) - - seconds_to_wait = (self.tac_parameters.start_time - now).total_seconds() - self.game_handler.competition_start = now + seconds_to_wait + self.tac_parameters.registration_timedelta.seconds - time.sleep(0.5 if seconds_to_wait < 0 else seconds_to_wait) + .format(self.name, str(self.game_handler.tac_parameters.start_time), str(now), seconds_to_wait)) + self.game_handler.competition_start = now + datetime.timedelta(seconds=seconds_to_wait + self.game_handler.tac_parameters.registration_timedelta.seconds) + time.sleep(seconds_to_wait) logger.debug("[{}]: Register competition with parameters: {}" .format(self.name, pprint.pformat(self.game_handler.tac_parameters.__dict__))) self.oef_handler.register_tac() + self.game_handler._game_phase = GamePhase.GAME_SETUP if self.game_handler.game_phase == GamePhase.GAME_SETUP: now = datetime.datetime.now() if now >= self.game_handler.competition_start: - logger.debug("[{}]: Checking if we can start the competition.".format(self.controller_agent.name)) - min_nb_agents = self.tac_parameters.min_nb_agents - nb_reg_agents = len(self.registered_agents) + logger.debug("[{}]: Checking if we can start the competition.".format(self.name)) + min_nb_agents = self.game_handler.tac_parameters.min_nb_agents + nb_reg_agents = len(self.game_handler.registered_agents) if nb_reg_agents >= min_nb_agents: logger.debug("[{}]: Start competition. Registered agents: {}, minimum number of agents: {}." .format(self.name, nb_reg_agents, min_nb_agents)) self.game_handler.start_competition() else: logger.debug("[{}]: Not enough agents to start TAC. Registered agents: {}, minimum number of agents: {}." - .format(self.controller_agent.name, nb_reg_agents, min_nb_agents)) + .format(self.name, nb_reg_agents, min_nb_agents)) self.stop() self.teardown() + return if self.game_handler.game_phase == GamePhase.GAME: current_time = datetime.datetime.now() inactivity_duration = current_time - self.last_activity @@ -136,6 +137,7 @@ def act(self) -> None: logger.debug("[{}]: Competition timeout expired. Terminating...".format(self.name)) self.stop() self.teardown() + return self.out_box.send_nowait() @@ -173,10 +175,11 @@ def stop(self) -> None: :return: None """ - logger.debug("[{}]: Terminating the controller...".format(self.name)) - self.game_handler.notify_tac_cancelled() + logger.debug("[{}]: Stopping myself...".format(self.name)) + if self.game_handler.game_phase == GamePhase.GAME or self.game_handler.game_phase == GamePhase.GAME_SETUP: + self.game_handler.notify_competition_cancelled() + self.out_box.send_nowait() super().stop() - self.monitor.stop() def start(self) -> None: """ @@ -186,22 +189,24 @@ def start(self) -> None: """ try: super().start() + logger.debug("[{}]: Starting myself...".format(self.name)) return except Exception as e: logger.exception(e) - logger.debug("Stopping the agent...") + logger.debug("[{}]: Stopping myself...".format(self.name)) self.stop() # here only if an error occurred - logger.debug("Trying to rejoin in 5 seconds...") + logger.debug("[{}]: Trying to rejoin in 5 seconds...".format(self.name)) time.sleep(2.0) - self.start(rejoin=False) + self.start() def setup(self) -> None: """Set up the agent.""" def teardown(self) -> None: """Tear down the agent.""" + self.game_handler.monitor.stop() self.game_handler.simulation_dump() @@ -218,7 +223,7 @@ def _parse_arguments(): parser.add_argument("--oef-addr", default="127.0.0.1", help="TCP/IP address of the OEF Agent") parser.add_argument("--oef-port", default=10000, help="TCP/IP port of the OEF Agent") parser.add_argument("--start-time", default=str(datetime.datetime.now() + datetime.timedelta(0, 10)), type=str, help="The start time for the competition (in UTC format).") - parser.add_argument("--registration-timeout", default=10, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") + parser.add_argument("--registration-timeout", default=20, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") parser.add_argument("--inactivity-timeout", default=60, type=int, help="The amount of time (in seconds) to wait during inactivity until the termination of the competition.") parser.add_argument("--competition-timeout", default=240, type=int, help="The amount of time (in seconds) to wait from the start of the competition until the termination of the competition.") parser.add_argument("--whitelist-file", default=None, type=str, help="The file that contains the list of agent names to be whitelisted.") @@ -291,7 +296,6 @@ def main( oef_port=oef_port, tac_parameters=tac_parameters, monitor=monitor) - agent.connect() agent.start() except Exception as e: diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index 93408f1f..18bcf3e2 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -124,8 +124,8 @@ def handle(self, request: Register) -> Optional[Response]: return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_ALREADY_REGISTERED) try: - self.controller_agent.monitor.dashboard.agent_pbk_to_name.update({request.public_key: request.agent_name}) - self.controller_agent.monitor.update() + self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({request.public_key: request.agent_name}) + self.controller_agent.game_handler.monitor.update() except Exception as e: logger.error(str(e)) @@ -214,12 +214,13 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: self.controller_agent.game_handler.current_game.settle_transaction(tx) # update the dashboard monitor - self.controller_agent.monitor.update() + self.controller_agent.game_handler.monitor.update() # send the transaction confirmation. tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - self.controller_agent.send_message(0, 0, tx.public_key, tx_confirmation.serialize()) - self.controller_agent.send_message(0, 0, tx.counterparty, tx_confirmation.serialize()) + + self.controller_agent.out_box.out_queue.put(OutContainer(message_id=1, dialogue_id=1, destination=tx.public_key, message=tx_confirmation.serialize())) + self.controller_agent.out_box.out_queue.put(OutContainer(message_id=1, dialogue_id=1, destination=tx.counterparty, message=tx_confirmation.serialize())) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) @@ -293,15 +294,29 @@ def handle_agent_message(self, msg: AgentMessage) -> Response: :param request: the request to handle :return: the response. """ - handle_request = self.handlers.get(type(msg), None) # type: RequestHandler + logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, msg.msg_id, msg.dialogue_id, msg.destination)) + request = self.decode(msg.msg, msg.destination) + handle_request = self.handlers.get(type(request), None) # type: RequestHandler if handle_request is None: - return Error(msg.public_key, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) + logger.debug("[{}]: Unknown message: msg_id={}, dialogue_id={}, origin={}".format(self.controller_agent.name, msg.msg_id, msg.dialogue_id, msg.destination)) + return Error(msg.destination, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) try: - return handle_request(msg) + return handle_request(request) except Exception as e: logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) logger.exception(e) - return Error(msg.public_key, self.controller_agent.crypto, ErrorCode.GENERIC_ERROR) + return Error(msg.destination, self.controller_agent.crypto, ErrorCode.GENERIC_ERROR) + + def decode(self, msg: bytes, public_key: str) -> Request: + """ + From bytes to a Request message. + + :param msg: the serialized message. + :param public_key: the public key of the sender agent. + :return: the deserialized Request + """ + request = Request.from_pb(msg, public_key, self.controller_agent.crypto) + return request class GameHandler: @@ -311,8 +326,11 @@ def __init__(self, agent_name: str, crypto: Crypto, out_box: OutBox, monitor: Mo """ Instantiate a GameHandler. - :param controller_agent: the controller agent the handler is associated with. - :param tac_parameters: the tac parameters + :param agent_name: the name of the agent. + :param crypto: the crypto module of the agent. + :param out_box: the outbox. + :param monitor: the monitor. + :param tac_parameters: the tac parameters. :return: None """ self.agent_name = agent_name @@ -372,6 +390,7 @@ def start_competition(self): self._send_game_data_to_agents() + self._game_phase = GamePhase.GAME # log messages logger.debug("[{}]: Started competition:\n{}".format(self.agent_name, self.current_game.get_holdings_summary())) logger.debug("[{}]: Computed equilibrium:\n{}".format(self.agent_name, self.current_game.get_equilibrium_summary())) @@ -419,16 +438,17 @@ def _send_game_data_to_agents(self) -> None: self.current_game.configuration.good_pbk_to_name ) logger.debug("[{}]: sending GameData to '{}': {}" - .format(self.controller_agent.name, public_key, str(game_data_response))) + .format(self.agent_name, public_key, str(game_data_response))) self.game_data_per_participant[public_key] = game_data_response - self.out_box.put(OutContainer(message_id=1, dialogue_id=1, destination=public_key, message=game_data_response.serialize())) + self.out_box.out_queue.put(OutContainer(message_id=1, dialogue_id=1, destination=public_key, message=game_data_response.serialize())) - def end_competition(self): + def notify_competition_cancelled(self): """Notify agents that the TAC is cancelled.""" - logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.controller_agent.name)) + logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.agent_name)) for agent_pbk in self.registered_agents: - self.out_box.put(OutContainer(message_id=1, dialogue_id=1, destination=agent_pbk, message=Cancelled(agent_pbk, self.controller_agent.crypto).serialize())) + self.out_box.out_queue.put(OutContainer(message_id=1, dialogue_id=1, destination=agent_pbk, message=Cancelled(agent_pbk, self.crypto).serialize())) + self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: """ @@ -438,13 +458,14 @@ def simulation_dump(self) -> None: :param experiment_name: the name of the folder where the data about experiment will be saved. :return: None. """ - experiment_dir = self.tac_parameters.data_output_dir + "/" + self.tac_parameters.experiment_id + experiment_id = str(self.tac_parameters.experiment_id) if self.tac_parameters.experiment_id is not None else str(datetime.datetime.now()) + experiment_dir = self.tac_parameters.data_output_dir + "/" + experiment_id - if self.game_handler is None or not self.game_handler.is_game_running: - logger.warning("[{}]: Game not present. Using empty dictionary.".format(self.name)) + if not self.is_game_running: + logger.warning("[{}]: Game not present. Using empty dictionary.".format(self.agent_name)) game_dict = {} # type: Dict[str, Any] else: - game_dict = self.game_handler.current_game.to_dict() + game_dict = self.current_game.to_dict() os.makedirs(experiment_dir, exist_ok=True) with open(os.path.join(experiment_dir, "game.json"), "w") as f: diff --git a/tac/platform/simulation.py b/tac/platform/simulation.py index 367c2d6c..587f5612 100644 --- a/tac/platform/simulation.py +++ b/tac/platform/simulation.py @@ -186,7 +186,7 @@ def parse_arguments(): parser.add_argument("--oef-port", default=10000, help="TCP/IP port of the OEF Agent") parser.add_argument("--nb-baseline-agents", type=int, default=10, help="Number of baseline agent to run. Defaults to the number of agents of the competition.") parser.add_argument("--start-time", default=str(datetime.datetime.now() + datetime.timedelta(0, 10)), type=str, help="The start time for the competition (in UTC format).") - parser.add_argument("--registration-timeout", default=10, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") + parser.add_argument("--registration-timeout", default=20, type=int, help="The amount of time (in seconds) to wait for agents to register before attempting to start the competition.") parser.add_argument("--inactivity-timeout", default=60, type=int, help="The amount of time (in seconds) to wait during inactivity until the termination of the competition.") parser.add_argument("--competition-timeout", default=240, type=int, help="The amount of time (in seconds) to wait from the start of the competition until the termination of the competition.") parser.add_argument("--services-interval", default=5, type=int, help="The amount of time (in seconds) the baseline agents wait until it updates services again.") @@ -199,7 +199,7 @@ def parse_arguments(): parser.add_argument("--visdom-addr", default="localhost", help="TCP/IP address of the Visdom server") parser.add_argument("--visdom-port", default=8097, help="TCP/IP port of the Visdom server") parser.add_argument("--seed", default=42, help="The random seed of the simulation.") - parser.add_argument("--fraction-world-modeling", default=0.1, type=float, min=0.0, max=1.0, help="The fraction of world modelling baseline agents.") + parser.add_argument("--fraction-world-modeling", default=0.1, type=float, choices=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0], help="The fraction of world modelling baseline agents.") parser.add_argument("--whitelist-file", nargs="?", default=None, type=str, help="The file that contains the list of agent names to be whitelisted.") arguments = parser.parse_args() @@ -263,7 +263,7 @@ def run(params: SimulationParams) -> None: controller_process = spawn_controller_agent(params) # give the time to the controller to connect to the OEF - time.sleep(2.0) + time.sleep(5.0) baseline_processes = spawn_baseline_agents(params) controller_process.join() diff --git a/tests/test_controller.py b/tests/test_controller.py index 8d045dbd..ccf9f6ed 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -50,7 +50,11 @@ def test_competition_stops_too_few_registered_agents(self, network_node): core = AsyncioCore(logger=logger) core.run_threaded() - agent1 = OEFAgent(crypto.public_key, oef_addr="127.0.0.1", oef_port=10000, core=core) + # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 + # core.run_threaded() # OEF-SDK 0.6.1 + import asyncio + agent1 = OEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) + # agent1 = OEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, core=core) # OEF-SDK 0.6.1 agent1.connect() agent1.send_message(0, 0, controller_agent.public_key, Register(agent1.public_key, crypto, 'agent_name').serialize()) From 0945d9df54a9178a6e9a4c41552b7414737e5e18 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 10:09:35 +0100 Subject: [PATCH 017/107] Fix some tests --- sandbox/oef_healthcheck.py | 10 ++++++---- tac/platform/helpers.py | 6 +++--- tac/platform/simulation.py | 1 - tests/test_controller.py | 4 +--- tests/test_simulation.py | 8 +++----- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/sandbox/oef_healthcheck.py b/sandbox/oef_healthcheck.py index eac30260..11cd8f33 100644 --- a/sandbox/oef_healthcheck.py +++ b/sandbox/oef_healthcheck.py @@ -25,7 +25,7 @@ import logging from oef.agents import OEFAgent -from oef.core import AsyncioCore +# from oef.core import AsyncioCore # OEF-SDK 0.6.1 logger = logging.getLogger(__name__) @@ -41,9 +41,11 @@ port = args.port pbk = 'check' print("Connecting to {}:{}".format(host, port)) - core = AsyncioCore(logger=logger) - core.run_threaded() - agent = OEFAgent(pbk, oef_addr=host, oef_port=port, core=core) + # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 + # core.run_threaded() # OEF-SDK 0.6.1 + # agent = OEFAgent(pbk, oef_addr=host, oef_port=port, core=core) # OEF-SDK 0.6.1 + import asyncio + agent = OEFAgent(pbk, oef_addr=host, oef_port=port, loop=asyncio.get_event_loop()) agent.connect() agent.disconnect() print("OK!") diff --git a/tac/platform/helpers.py b/tac/platform/helpers.py index 5929e620..0cec0e6e 100644 --- a/tac/platform/helpers.py +++ b/tac/platform/helpers.py @@ -29,11 +29,11 @@ def make_agent_name(agent_id: int, is_world_modeling: bool, nb_agents: int) -> s E.g.: - >>> _make_id(2, False, 10) + >>> make_agent_name(2, False, 10) 'tac_agent_2' - >>> _make_id(2, False, 100) + >>> make_agent_name(2, False, 100) 'tac_agent_02' - >>> _make_id(2, False, 101) + >>> make_agent_name(2, False, 101) 'tac_agent_002' :param agent_id: the agent id. diff --git a/tac/platform/simulation.py b/tac/platform/simulation.py index 587f5612..9553e98a 100644 --- a/tac/platform/simulation.py +++ b/tac/platform/simulation.py @@ -140,7 +140,6 @@ def spawn_controller_agent(params: SimulationParams) -> multiprocessing.Process: data_output_dir=params.data_output_dir, experiment_id=params.experiment_id, seed=params.seed, - version=1, )) process.start() return process diff --git a/tests/test_controller.py b/tests/test_controller.py index ccf9f6ed..06130034 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -24,7 +24,7 @@ from threading import Thread from oef.agents import OEFAgent -from oef.core import AsyncioCore +# from oef.core import AsyncioCore # OEF-SDK 0.6.1 from tac.helpers.crypto import Crypto from tac.platform.controller.controller_agent import ControllerAgent @@ -48,8 +48,6 @@ def test_competition_stops_too_few_registered_agents(self, network_node): crypto = Crypto() - core = AsyncioCore(logger=logger) - core.run_threaded() # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 # core.run_threaded() # OEF-SDK 0.6.1 import asyncio diff --git a/tests/test_simulation.py b/tests/test_simulation.py index e9e54ee5..186a7a22 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -69,10 +69,6 @@ def _start_oef_node(self, network_node): @classmethod def setup_class(cls): """Class setup.""" - cls.tac_controller = ControllerAgent(loop=asyncio.new_event_loop()) - cls.tac_controller.connect() - cls.tac_controller.register() - cls.baseline_agents = _init_baseline_agents(5, "v1", "127.0.0.1", 10000) cls.tac_parameters = TACParameters(min_nb_agents=5, @@ -87,10 +83,12 @@ def setup_class(cls): competition_timeout=20, inactivity_timeout=10) + cls.tac_controller = ControllerAgent('controller', '127.0.0.1', 1000, cls.tac_parameters, loop=asyncio.get_event_loop()) + # run the simulation try: # generate task for the controller - controller_thread = Thread(target=cls.tac_controller.handle_competition, args=(cls.tac_parameters,)) + controller_thread = Thread(target=cls.tac_controller.start) baseline_threads = [Thread(target=_run_baseline_agent, args=[baseline_agent, "v1"]) for baseline_agent in cls.baseline_agents] From d83063a6f9676589b171a08d58ed399fc1718f4e Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 11:32:30 +0100 Subject: [PATCH 018/107] Minor fixes to address comments of Marco --- tac/agents/v1/mail.py | 3 +-- tac/helpers/oef_health_check.py | 31 ++++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/tac/agents/v1/mail.py b/tac/agents/v1/mail.py index 7c5c7fb0..b7d68004 100644 --- a/tac/agents/v1/mail.py +++ b/tac/agents/v1/mail.py @@ -136,7 +136,7 @@ def is_running(self) -> bool: @property def is_connected(self) -> bool: """Check whether the mailbox is connected to an OEF node.""" - return True # self._oef_proxy.is_connected() TODO! + return self._oef_proxy.is_connected() # OEF-SDK 0.6.1 TODO! def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: """ @@ -196,7 +196,6 @@ def connect(self) -> bool: while not success: try: - print('HERE!') success = super().connect() except ConnectionError: logger.error("Problems when connecting to the OEF. Retrying in 3 seconds...") diff --git a/tac/helpers/oef_health_check.py b/tac/helpers/oef_health_check.py index ef9ebd52..3c448709 100644 --- a/tac/helpers/oef_health_check.py +++ b/tac/helpers/oef_health_check.py @@ -21,6 +21,7 @@ """This script waits until the OEF is up and running.""" +import argparse import logging from oef.agents import OEFAgent @@ -29,18 +30,23 @@ logger = logging.getLogger(__name__) +parser = argparse.ArgumentParser("oef_healthcheck", description=__doc__) +parser.add_argument("--oef-addr", default="127.0.0.1", type=str, help="TCP/IP address of the OEF Agent") +parser.add_argument("--oef-port", default=10000, type=int, help="TCP/IP port of the OEF Agent") + + class OEFHealthCheck(object): """A health check class.""" - def __init__(self, addr: str, port: int): + def __init__(self, oef_addr: str, oef_port: int): """ Initialize. - :param addr: IP address of the OEF node. - :param port: Port of the OEF node. + :param oef_addr: IP address of the OEF node. + :param oef_port: Port of the OEF node. """ - self.addr = addr - self.port = port + self.oef_addr = oef_addr + self.oef_port = oef_port def run(self) -> bool: """ @@ -51,11 +57,11 @@ def run(self) -> bool: result = False try: pbk = 'check' - print("Connecting to {}:{}".format(self.addr, self.port)) + print("Connecting to {}:{}".format(self.oef_addr, self.oef_port)) # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 # core.run_threaded() # OEF-SDK 0.6.1 import asyncio - agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, loop=asyncio.new_event_loop()) + agent = OEFAgent(pbk, oef_addr=self.oef_addr, oef_port=self.oef_port, loop=asyncio.new_event_loop()) # agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) # OEF-SDK 0.6.1 agent.connect() agent.disconnect() @@ -68,3 +74,14 @@ def run(self) -> bool: return result # finally: # core.stop(). # OEF-SDK 0.6.1 + + +def main(oef_addr, oef_port): + """Launch the health check.""" + oef_health_check = OEFHealthCheck(oef_addr, oef_port) + return oef_health_check.run() + + +if __name__ == "__main__": + args = parser.parse_args() + main(args.oef_addr, args.oef_port) From 48701028bd4ea05a0b8348be0c7162ee88c160fe Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 13:41:28 +0100 Subject: [PATCH 019/107] Fixes all but one test --- tac/platform/controller/controller_agent.py | 2 +- tests/common.py | 97 +++++++++++++++++++++ tests/conftest.py | 4 +- tests/test_agent/test_agent_state.py | 6 +- tests/test_agent/test_misc.py | 6 +- tests/test_controller.py | 69 ++++++++++++--- tests/test_simulation.py | 5 +- 7 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 tests/common.py diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index ed668009..24fc2dd9 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -197,7 +197,7 @@ def start(self) -> None: self.stop() # here only if an error occurred - logger.debug("[{}]: Trying to rejoin in 5 seconds...".format(self.name)) + logger.debug("[{}]: Trying to rejoin in 2 seconds...".format(self.name)) time.sleep(2.0) self.start() diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 00000000..2e6a874a --- /dev/null +++ b/tests/common.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains an OEF agent for testing.""" + +from typing import List + +from oef.agents import OEFAgent +from oef.messages import PROPOSE_TYPES, BaseMessage, Message, CFP, CFP_TYPES, Propose, Accept, Decline +from oef.uri import Context + + +class TOEFAgent(OEFAgent): + """An OEF agent for testing.""" + + def __init__(self, *args, **kwargs): + """Initialize.""" + super().__init__(*args, **kwargs) + self.messages = [] # type: List[BaseMessage] + + def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: + """ + On message handler. + + :param msg_id: the message id + :param dialogue_id: the dialogue id + :param origin: the origin public key + :param content: the message content + :return: None + """ + self.messages.append(Message(msg_id, dialogue_id, origin, content, Context())) + + def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: + """ + On cfp handler. + + :param msg_id: the message id + :param dialogue_id: the dialogue id + :param origin: the origin public key + :param target: the message target + :param query: the query object + :return: None + """ + self.messages.append(CFP(msg_id, dialogue_id, origin, target, query, Context())) + + def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: + """ + On propose handler. + + :param msg_id: the message id + :param dialogue_id: the dialogue id + :param origin: the origin public key + :param target: the message target + :param proposals: the proposals + :return: None + """ + self.messages.append(Propose(msg_id, dialogue_id, origin, target, proposals, Context())) + + def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: + """ + On accept handler. + + :param msg_id: the message id + :param dialogue_id: the dialogue id + :param origin: the origin public key + :param target: the message target + :return: None + """ + self.messages.append(Accept(msg_id, dialogue_id, origin, target, Context())) + + def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: + """ + On message handler. + + :param msg_id: the message id + :param dialogue_id: the dialogue id + :param origin: the origin public key + :param target: the message target + :return: None + """ + self.messages.append(Decline(msg_id, dialogue_id, origin, target, Context())) diff --git a/tests/conftest.py b/tests/conftest.py index 0ba3d01a..bddf9902 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -103,7 +103,7 @@ def network_node(oef_addr, oef_port, pytestconfig): if pytestconfig.getoption("ci"): logger.warning("Skipping creation of OEF Docker image...") - success = _wait_for_oef(max_attempts=15, sleep_rate=3.0) + success = _wait_for_oef(max_attempts=10, sleep_rate=2.0) if not success: pytest.fail("OEF doesn't work. Exiting...") else: @@ -115,7 +115,7 @@ def network_node(oef_addr, oef_port, pytestconfig): # wait for the setup... logger.info("Setting up the OEF node...") - success = _wait_for_oef(max_attempts=15, sleep_rate=3.0) + success = _wait_for_oef(max_attempts=10, sleep_rate=2.0) if not success: c.stop() diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index a55394ba..89d2d2bf 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -27,7 +27,7 @@ from tac.agents.v1.mail import FIPAMailBox -class TestAgent(Agent): +class TAgent(Agent): """A class to implement an agent for testing.""" def __init__(self): @@ -53,14 +53,14 @@ def test_agent_initiated(): def test_agent_connected(network_node): """Test that when the agent is connected, her state is AgentState.CONNECTED.""" - test_agent = TestAgent() + test_agent = TAgent() test_agent.mail_box.connect() assert test_agent.agent_state == AgentState.CONNECTED def test_agent_running(network_node): """Test that when the agent is running, her state is AgentState.RUNNING.""" - test_agent = TestAgent() + test_agent = TAgent() job = Thread(target=test_agent.start) job.start() time.sleep(1.0) diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 4428ca42..4fb578f8 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -26,7 +26,7 @@ from tac.agents.v1.mail import FIPAMailBox, InBox, OutBox, OutContainer -class TestAgent(Agent): +class TAgent(Agent): """A class to implement an agent for testing.""" def __init__(self, **kwargs): @@ -52,7 +52,7 @@ def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): In particular, assert that the methods 'act', 'react' and 'update' are called. """ - test_agent = TestAgent(debug=True) + test_agent = TAgent(debug=True) test_agent.act = MagicMock(test_agent.act) test_agent.react = MagicMock(test_agent.react) @@ -71,7 +71,7 @@ def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): def test_that_when_debug_flag_true_we_drop_out_messages(): """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" with patch('logging.Logger.warning') as mock: - test_agent = TestAgent(debug=True) + test_agent = TAgent(debug=True) job = Timer(1.0, test_agent.stop) job.start() msg = OutContainer(b"this is a message.", 0, 0, "destination") diff --git a/tests/test_controller.py b/tests/test_controller.py index 06130034..6c7d67ac 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -21,9 +21,12 @@ import datetime import logging +# import multiprocessing +import pytest +import time from threading import Thread -from oef.agents import OEFAgent +from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 from tac.helpers.crypto import Crypto @@ -34,28 +37,66 @@ logger = logging.getLogger(__name__) -class TestController: - """Class to test the controller.""" +class TestCompetitionStopsNoAgentRegistered: + """Test the case when the controller starts, and no agent registers.""" - def test_competition_stops_too_few_registered_agents(self, network_node): + @pytest.fixture(autouse=True) + def _start_oef_node(self, network_node): + pass + + @classmethod + def setup_class(cls): """Test that if the controller agent does not receive enough registrations, it stops.""" - controller_agent = ControllerAgent(version=1) - controller_agent.connect() + tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) + cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) - parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) - job = Thread(target=controller_agent.handle_competition, args=(parameters,)) + def test_no_registered_agents(self): + """Test no agent is registered.""" + job = Thread(target=self.controller_agent.start) job.start() + # process = multiprocessing.Process(target=self.controller_agent.start) + # process.start() + time.sleep(1.0) + assert len(self.controller_agent.game_handler.registered_agents) == 0 + self.controller_agent.stop() + # process.join() + job.join() + # process.terminate() - crypto = Crypto() +class TestCompetitionStopsTooFewAgentRegistered: + """Test the case when the controller starts, and not enough agents register for TAC.""" + + @pytest.fixture(autouse=True) + def _start_oef_node(self, network_node): + pass + + @classmethod + def setup_class(cls): + """Test that if the controller agent does not receive enough registrations, it stops.""" + tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) + cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) + + job = Thread(target=cls.controller_agent.start) + job.start() + + crypto = Crypto() # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 # core.run_threaded() # OEF-SDK 0.6.1 import asyncio - agent1 = OEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) - # agent1 = OEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, core=core) # OEF-SDK 0.6.1 - agent1.connect() - agent1.send_message(0, 0, controller_agent.public_key, Register(agent1.public_key, crypto, 'agent_name').serialize()) + cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) + # agent1 = TestOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, core=core) # OEF-SDK 0.6.1 + cls.agent1.connect() + cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, Register(crypto.public_key, crypto, 'agent_name').serialize()) + + time.sleep(20.0) job.join() - assert len(controller_agent.game_handler.registered_agents) == 1 + def test_only_one_agent_registered(self): + """Test exactly one agent is registered.""" + assert len(self.controller_agent.game_handler.registered_agents) == 1 + + def test_agent_receives_cancelled_message(self): + """Test the agent receives a cancelled message.""" + assert len(self.agent1.messages) == 1 diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 186a7a22..d09b7f65 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -81,13 +81,12 @@ def setup_class(cls): start_time=datetime.datetime.now() + datetime.timedelta(0, 2), registration_timeout=8, competition_timeout=20, - inactivity_timeout=10) + inactivity_timeout=15) - cls.tac_controller = ControllerAgent('controller', '127.0.0.1', 1000, cls.tac_parameters, loop=asyncio.get_event_loop()) + cls.tac_controller = ControllerAgent('controller', '127.0.0.1', 10000, cls.tac_parameters) # run the simulation try: - # generate task for the controller controller_thread = Thread(target=cls.tac_controller.start) baseline_threads = [Thread(target=_run_baseline_agent, args=[baseline_agent, "v1"]) From d1658d22d7338482bac35b353dfce94faa77bf59 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 14:11:09 +0100 Subject: [PATCH 020/107] use new message module on agents module, except controller. --- tac/agents/v1/base/actions.py | 22 ++-- tac/agents/v1/base/dialogues.py | 105 +++++++++++-------- tac/agents/v1/base/handlers.py | 22 ++-- tac/agents/v1/base/helpers.py | 17 ++- tac/agents/v1/base/interfaces.py | 16 +-- tac/agents/v1/base/negotiation_behaviours.py | 103 ++++++++++-------- tac/agents/v1/base/participant_agent.py | 13 +-- tac/agents/v1/base/reactions.py | 22 ++-- tac/agents/v1/mail/messages.py | 44 ++++---- tac/agents/v1/mail/oef.py | 7 +- 10 files changed, 204 insertions(+), 167 deletions(-) diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index 26dfd72a..3c52df20 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -34,8 +34,7 @@ from tac.agents.v1.base.interfaces import ControllerActionInterface, OEFSearchActionInterface, DialogueActionInterface from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFSearchServicesRequest, OEFUnregisterServiceRequest, \ - OEFRegisterServiceRequest +from tac.agents.v1.mail.messages import ByteMessage, OEFMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import GetStateUpdate @@ -70,7 +69,8 @@ def request_state_update(self) -> None: :return: None """ msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.mailbox.outbox.put(OEFAgentByteMessage(0, 0, self.game_instance.controller_pbk, msg)) + message = ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, message_id=0, dialogue_id=0, content=msg) + self.mailbox.outbox.put(message) class OEFActions(OEFSearchActionInterface): @@ -106,7 +106,9 @@ def search_for_tac(self) -> None: query = Query([Constraint("version", GtEq(1))]) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_tac.add(search_id) - self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) + + message = OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=0, query=query) + self.mailbox.outbox.put(message) def update_services(self) -> None: """ @@ -124,9 +126,9 @@ def unregister_service(self) -> None: :return: None """ if self.game_instance.goods_demanded_description is not None: - self.mailbox.outbox.put(OEFUnregisterServiceRequest(1, self.game_instance.goods_demanded_description)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) if self.game_instance.goods_supplied_description is not None: - self.mailbox.outbox.put(OEFUnregisterServiceRequest(1, self.game_instance.goods_supplied_description)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) def register_service(self) -> None: """ @@ -143,12 +145,12 @@ def register_service(self) -> None: logger.debug("[{}]: Updating service directory as seller with goods supplied.".format(self.agent_name)) goods_supplied_description = self.game_instance.get_service_description(is_supply=True) self.game_instance.goods_supplied_description = goods_supplied_description - self.mailbox.outbox.put(OEFRegisterServiceRequest(1, goods_supplied_description)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description - self.mailbox.outbox.put(OEFRegisterServiceRequest(1, goods_demanded_description)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) def search_services(self) -> None: """ @@ -170,7 +172,7 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for sellers which match the demand of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_sellers.add(search_id) - self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) if self.game_instance.strategy.is_searching_for_buyers: query = self.game_instance.build_services_query(is_searching_for_sellers=False) if query is None: @@ -180,7 +182,7 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for buyers which match the supply of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_buyers.add(search_id) - self.mailbox.outbox.put(OEFSearchServicesRequest(search_id, query)) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) class DialogueActions(DialogueActionInterface): diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index df499a7d..460c0876 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -27,9 +27,9 @@ """ import logging -from typing import List, Any, Dict +from typing import List, Any, Dict, Optional -from tac.agents.v1.mail.messages import OEFAgentMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, OEFAgentDecline +from tac.agents.v1.mail.messages import Message, FIPAMessage Action = Any logger = logging.getLogger(__name__) @@ -98,9 +98,9 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: self._is_seller = is_seller self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk self._role = 'seller' if is_seller else 'buyer' - self._outgoing_messages = [] # type: List[OEFAgentMessage] - self._outgoing_messages_controller = [] # type: List[OEFAgentMessage] - self._incoming_messages = [] # type: List[OEFAgentMessage] + self._outgoing_messages = [] # type: List[Message] + self._outgoing_messages_controller = [] # type: List[Message] + self._incoming_messages = [] # type: List[Message] @property def dialogue_label(self) -> DialogueLabel: @@ -122,7 +122,7 @@ def role(self) -> str: """Get role of agent in dialogue.""" return self._role - def outgoing_extend(self, messages: List[OEFAgentMessage]) -> None: + def outgoing_extend(self, messages: List[Message]) -> None: """ Extend the list of messages which keeps track of outgoing messages. @@ -132,7 +132,7 @@ def outgoing_extend(self, messages: List[OEFAgentMessage]) -> None: for message in messages: self._outgoing_messages.extend([message]) - def incoming_extend(self, messages: List[OEFAgentMessage]) -> None: + def incoming_extend(self, messages: List[Message]) -> None: """ Extend the list of messages which keeps track of incoming messages. @@ -147,8 +147,8 @@ def is_expecting_propose(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentCfp) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.CFP return result def is_expecting_initial_accept(self) -> bool: @@ -157,8 +157,8 @@ def is_expecting_initial_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentPropose) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.PROPOSE return result def is_expecting_matching_accept(self) -> bool: @@ -167,8 +167,8 @@ def is_expecting_matching_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentAccept) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.ACCEPT return result def is_expecting_cfp_decline(self) -> bool: @@ -177,8 +177,8 @@ def is_expecting_cfp_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentCfp) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.CFP return result def is_expecting_propose_decline(self) -> bool: @@ -187,8 +187,8 @@ def is_expecting_propose_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentPropose) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.PROPOSE return result def is_expecting_accept_decline(self) -> bool: @@ -197,8 +197,8 @@ def is_expecting_accept_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1:] - result = (last_sent_message is not []) and isinstance(last_sent_message[0], OEFAgentAccept) + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.ACCEPT return result @@ -231,7 +231,7 @@ def dialogues_as_buyer(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent acts as a buyer.""" return self._dialogues_as_buyer - def is_permitted_for_new_dialogue(self, msg: OEFAgentMessage, known_pbks: List[str]) -> bool: + def is_permitted_for_new_dialogue(self, msg: Message, known_pbks: List[str]) -> bool: """ Check whether an agent message is permitted for a new dialogue. @@ -245,10 +245,19 @@ def is_permitted_for_new_dialogue(self, msg: OEFAgentMessage, known_pbks: List[s :return: a boolean indicating whether the message is permitted for a new dialogue """ - result = isinstance(msg, OEFAgentCfp) and msg.msg_id == STARTING_MESSAGE_ID and msg.target == STARTING_MESSAGE_TARGET and (msg.destination in known_pbks) + protocol = msg.protocol_id + msg_id = msg.get("id") + target = msg.get("target") + performative = msg.get("performative") + + result = protocol == "fipa"\ + and performative == FIPAMessage.Performative.CFP \ + and msg_id == STARTING_MESSAGE_ID\ + and target == STARTING_MESSAGE_TARGET \ + and (msg.sender in known_pbks) return result - def is_belonging_to_registered_dialogue(self, msg: OEFAgentMessage, agent_pbk: str) -> bool: + def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> bool: """ Check whether an agent message is part of a registered dialogue. @@ -257,32 +266,37 @@ def is_belonging_to_registered_dialogue(self, msg: OEFAgentMessage, agent_pbk: s :return: boolean indicating whether the message belongs to a registered dialogue """ - self_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, agent_pbk) - other_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, msg.destination) + assert msg.protocol_id == "fipa" + dialogue_id = msg.get("dialogue_id") + destination = msg.to + target = msg.get("target") + performative = msg.get("performative") + self_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, agent_pbk) + other_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, destination) result = False - if isinstance(msg, OEFAgentPropose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: + if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_propose() - elif isinstance(msg, OEFAgentAccept): - if msg.target == 2 and other_initiated_dialogue_label in self.dialogues: + elif performative == FIPAMessage.Performative.ACCEPT: + if target == 2 and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_initial_accept() - elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: + elif target == 3 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_matching_accept() - elif isinstance(msg, OEFAgentDecline): - if msg.target == 1 and self_initiated_dialogue_label in self.dialogues: + elif performative == FIPAMessage.Performative.DECLINE: + if target == 1 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_cfp_decline() - elif msg.target == 2 and other_initiated_dialogue_label in self.dialogues: + elif target == 2 and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_propose_decline() - elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: + elif target == 3 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, msg: OEFAgentMessage, agent_pbk: str) -> Dialogue: + def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: """ Retrieve dialogue. @@ -291,23 +305,28 @@ def get_dialogue(self, msg: OEFAgentMessage, agent_pbk: str) -> Dialogue: :return: the dialogue """ - self_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, agent_pbk) - other_initiated_dialogue_label = DialogueLabel(msg.dialogue_id, msg.destination, msg.destination) - if isinstance(msg, OEFAgentPropose) and (msg.target == 1) and self_initiated_dialogue_label in self.dialogues: + assert msg.protocol_id == "fipa" + dialogue_id = msg.get("dialogue_id") + destination = msg.to + target = msg.get("target") + performative = msg.get("performative") + self_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, agent_pbk) + other_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, destination) + if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] - elif isinstance(msg, OEFAgentAccept): - if msg.target == 2 and other_initiated_dialogue_label in self.dialogues: + elif performative == FIPAMessage.Performative.ACCEPT: + if target == 2 and other_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[other_initiated_dialogue_label] - elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: + elif target == 3 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError('Should have found dialogue.') - elif isinstance(msg, OEFAgentDecline): - if msg.target == 1 and self_initiated_dialogue_label in self.dialogues: + elif performative == FIPAMessage.Performative.DECLINE: + if target == 1 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] - elif msg.target == 2 and other_initiated_dialogue_label in self.dialogues: + elif target == 2 and other_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[other_initiated_dialogue_label] - elif msg.target == 3 and self_initiated_dialogue_label in self.dialogues: + elif target == 3 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError('Should have found dialogue.') diff --git a/tac/agents/v1/base/handlers.py b/tac/agents/v1/base/handlers.py index 0b751ea9..dd1fc65a 100644 --- a/tac/agents/v1/base/handlers.py +++ b/tac/agents/v1/base/handlers.py @@ -34,8 +34,7 @@ from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import OEFResponse, OEFSearchResult, OEFGenericError, OEFDialogueError, \ - OEFAgentByteMessage, OEFAgentMessage +from tac.agents.v1.mail.messages import Message, OEFMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled @@ -60,7 +59,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, msg: OEFAgentMessage) -> None: + def handle_dialogue_message(self, msg: Message) -> None: """ Handle messages from the other agents. @@ -95,7 +94,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan ControllerActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) ControllerReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_controller_message(self, msg: OEFAgentByteMessage) -> None: + def handle_controller_message(self, msg: Message) -> None: """ Handle messages from the controller. @@ -105,10 +104,11 @@ def handle_controller_message(self, msg: OEFAgentByteMessage) -> None: :return: None """ - response = Response.from_pb(msg.content, msg.destination, self.crypto) + assert msg.protocol_id == "bytes" + response = Response.from_pb(msg.get("content"), msg.to, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: - if msg.destination != self.game_instance.controller_pbk: + if msg.to != self.game_instance.controller_pbk: raise ValueError("The sender of the message is not the controller agent we registered with.") if isinstance(response, Error): @@ -150,7 +150,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan OEFActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) OEFReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name, rejoin) - def handle_oef_message(self, msg: OEFResponse) -> None: + def handle_oef_message(self, msg: Message) -> None: """ Handle messages from the oef. @@ -161,11 +161,13 @@ def handle_oef_message(self, msg: OEFResponse) -> None: :return: None """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) - if isinstance(msg, OEFSearchResult): + assert msg.protocol_id == "oef" + oef_type = msg.get("type") + if oef_type == OEFMessage.Type.SEARCH_RESULT: self.on_search_result(msg) - elif isinstance(msg, OEFGenericError): + elif oef_type == OEFMessage.Type.OEF_ERROR: self.on_oef_error(msg) - elif isinstance(msg, OEFDialogueError): + elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: self.on_dialogue_error(msg) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index 87d30159..d6f3c34d 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -21,24 +21,22 @@ """This module contains helper methods for base agent implementations.""" from tac.agents.v1.base.dialogues import DialogueLabel -from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFAgentMessage, OEFMessage, OEFSearchResult, \ - OEFDialogueError, OEFGenericError +from tac.agents.v1.mail.messages import Message, OEFMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import Response -def is_oef_message(msg: OEFMessage) -> bool: +def is_oef_message(msg: Message) -> bool: """ Check whether a message is from the oef. :param msg: the message :return: boolean indicating whether or not the message is from the oef """ - msg_type = type(msg) - return msg_type in {OEFSearchResult, OEFGenericError, OEFDialogueError} + return msg.protocol_id == "fipa" and msg.get("type") in set(OEFMessage.Type) -def is_controller_message(msg: OEFMessage, crypto: Crypto) -> bool: +def is_controller_message(msg: Message, crypto: Crypto) -> bool: """ Check whether a message is from the controller. @@ -46,13 +44,12 @@ def is_controller_message(msg: OEFMessage, crypto: Crypto) -> bool: :param crypto: the crypto of the agent :return: boolean indicating whether or not the message is from the controller """ - if not isinstance(msg, OEFAgentByteMessage): + if not msg.protocol_id == "bytes": return False try: - msg: OEFAgentByteMessage - byte_content = msg.content - sender_pbk = msg.destination # now the origin is the destination! + byte_content = msg.get("content") + sender_pbk = msg.sender # now the origin is the destination! Response.from_pb(byte_content, sender_pbk, crypto) except Exception: return False diff --git a/tac/agents/v1/base/interfaces.py b/tac/agents/v1/base/interfaces.py index 1d03d546..87603118 100644 --- a/tac/agents/v1/base/interfaces.py +++ b/tac/agents/v1/base/interfaces.py @@ -22,7 +22,7 @@ from abc import abstractmethod -from tac.agents.v1.mail.messages import OEFAgentMessage, OEFDialogueError, OEFGenericError, OEFSearchResult +from tac.agents.v1.mail.messages import Message from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData @@ -30,7 +30,7 @@ class ControllerReactionInterface: """This interface contains the methods to react to events from the ControllerAgent.""" @abstractmethod - def on_dialogue_error(self, dialogue_error_msg: OEFDialogueError) -> None: + def on_dialogue_error(self, dialogue_error_msg: Message) -> None: """ Handle dialogue error event emitted by the controller. @@ -102,7 +102,7 @@ class OEFSearchReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod - def on_search_result(self, search_result: OEFSearchResult) -> None: + def on_search_result(self, search_result: Message) -> None: """ Handle search results. @@ -112,7 +112,7 @@ def on_search_result(self, search_result: OEFSearchResult) -> None: """ @abstractmethod - def on_oef_error(self, oef_error: OEFGenericError) -> None: + def on_oef_error(self, oef_error: Message) -> None: """ Handle an OEF error message. @@ -122,7 +122,7 @@ def on_oef_error(self, oef_error: OEFGenericError) -> None: """ @abstractmethod - def on_dialogue_error(self, dialogue_error: OEFDialogueError) -> None: + def on_dialogue_error(self, dialogue_error: Message) -> None: """ Handle a dialogue error message. @@ -180,7 +180,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, msg: OEFAgentMessage) -> None: + def on_new_dialogue(self, msg: Message) -> None: """ React to a message for a new dialogue. @@ -194,7 +194,7 @@ def on_new_dialogue(self, msg: OEFAgentMessage) -> None: """ @abstractmethod - def on_existing_dialogue(self, msg: OEFAgentMessage) -> None: + def on_existing_dialogue(self, msg: Message) -> None: """ React to a message of an existing dialogue. @@ -204,7 +204,7 @@ def on_existing_dialogue(self, msg: OEFAgentMessage) -> None: """ @abstractmethod - def on_unidentified_dialogue(self, msg: OEFAgentMessage) -> None: + def on_unidentified_dialogue(self, msg: Message) -> None: """ React to a message of an unidentified dialogue. diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index 743834ee..9605f429 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -29,8 +29,7 @@ from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.base.helpers import generate_transaction_id from tac.agents.v1.base.stats_manager import EndState -from tac.agents.v1.mail.messages import OEFAgentPropose, OEFAgentAccept, OEFAgentDecline, OEFAgentByteMessage, \ - OEFAgentMessage, OEFAgentCfp +from tac.agents.v1.mail.messages import Message, FIPAMessage, ByteMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import Transaction @@ -71,7 +70,7 @@ def agent_name(self) -> str: """Get the agent name.""" return self._agent_name - def on_cfp(self, cfp: OEFAgentCfp, dialogue: Dialogue) -> Union[OEFAgentPropose, OEFAgentDecline]: + def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Message: """ Handle a CFP. @@ -80,10 +79,11 @@ def on_cfp(self, cfp: OEFAgentCfp, dialogue: Dialogue) -> Union[OEFAgentPropose, :return: a Propose or a Decline """ + assert cfp.protocol_id == "fipa" and cfp.get("performative") == FIPAMessage.Performative.CFP goods_description = self.game_instance.get_service_description(is_supply=dialogue.is_seller) - new_msg_id = cfp.msg_id + 1 + new_msg_id = cfp.get("id") + 1 decline = False - cfp_services = json.loads(cfp.query.decode('utf-8')) + cfp_services = json.loads(cfp.get("query").decode('utf-8')) if not self.game_instance.is_matching(cfp_services, goods_description): decline = True logger.debug("[{}]: Current holdings do not satisfy CFP query.".format(self.agent_name)) @@ -94,36 +94,38 @@ def on_cfp(self, cfp: OEFAgentCfp, dialogue: Dialogue) -> Union[OEFAgentPropose, logger.debug("[{}]: Current strategy does not generate proposal that satisfies CFP query.".format(self.agent_name)) if decline: - logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, cfp.destination, + logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, cfp.sender, pprint.pformat({ "msg_id": new_msg_id, - "dialogue_id": cfp.dialogue_id, - "origin": cfp.destination, - "target": cfp.msg_id + "dialogue_id": cfp.get("dialogue_id"), + "origin": cfp.sender, + "target": cfp.get("target") }))) - response = OEFAgentDecline(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id) + response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, msg_id=new_msg_id, + dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id")) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) else: - transaction_id = generate_transaction_id(self.crypto.public_key, cfp.destination, dialogue.dialogue_label, dialogue.is_seller) + transaction_id = generate_transaction_id(self.crypto.public_key, cfp.sender, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=cfp.destination, + counterparty=cfp.sender, sender=self.crypto.public_key, crypto=self.crypto) self.game_instance.transaction_manager.add_pending_proposal(dialogue.dialogue_label, new_msg_id, transaction) - logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, cfp.destination, + logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, cfp.sender, pprint.pformat({ "msg_id": new_msg_id, - "dialogue_id": cfp.dialogue_id, - "origin": cfp.destination, - "target": cfp.msg_id, + "dialogue_id": cfp.get("dialogue_id"), + "origin": cfp.sender, + "target": cfp.get("id"), "propose": proposal.values }))) - response = OEFAgentPropose(new_msg_id, cfp.dialogue_id, cfp.destination, cfp.msg_id, [proposal]) + response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, performative=FIPAMessage.Performative.PROPOSE, + msg_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) return response - def on_propose(self, propose: OEFAgentPropose, dialogue: Dialogue) -> Union[OEFAgentAccept, OEFAgentDecline]: + def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: """ Handle a Propose. @@ -133,29 +135,34 @@ def on_propose(self, propose: OEFAgentPropose, dialogue: Dialogue) -> Union[OEFA :return: an Accept or a Decline """ logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) - proposal = propose.proposal[0] - transaction_id = generate_transaction_id(self.crypto.public_key, propose.destination, dialogue.dialogue_label, dialogue.is_seller) + assert propose.protocol_id == "fipa" and propose.get("type") == FIPAMessage.Performative.PROPOSE + proposal = propose.get("proposal")[0] + transaction_id = generate_transaction_id(self.crypto.public_key, propose.sender, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=propose.destination, + counterparty=propose.sender, sender=self.crypto.public_key, crypto=self.crypto) - new_msg_id = propose.msg_id + 1 + new_msg_id = propose.get("id") + 1 is_profitable_transaction, message = self.game_instance.is_profitable_transaction(transaction, dialogue) logger.debug(message) if is_profitable_transaction: logger.debug("[{}]: Accepting propose (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance(dialogue.dialogue_label, new_msg_id, transaction) - result = OEFAgentAccept(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id) + result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, msg_id=propose.get("id"), + dialogue_id=propose.get("dialogue_id"), target=propose.get("target"), + performative=FIPAMessage.Performative.ACCEPT) else: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) - result = OEFAgentDecline(new_msg_id, propose.dialogue_id, propose.destination, propose.msg_id) + result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, msg_id=propose.get("id"), + dialogue_id=propose.get("dialogue_id"), target=propose.get("target"), + performative=FIPAMessage.Performative.ACCEPT) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result - def on_decline(self, decline: OEFAgentDecline, dialogue: Dialogue) -> None: + def on_decline(self, decline: Message, dialogue: Dialogue) -> None: """ Handle a Decline. @@ -164,24 +171,25 @@ def on_decline(self, decline: OEFAgentDecline, dialogue: Dialogue) -> None: :return: None """ + assert decline.protocol_id == "fipa" and decline.get("type") == FIPAMessage.Performative.DECLINE logger.debug("[{}]: on_decline: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, decline.msg_id, decline.dialogue_id, decline.destination, decline.target)) - - if decline.target == 1: + .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), decline.sender, decline.get("target"))) + target = decline.get("target") + if target == 1: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) - elif decline.target == 2: + elif target == 2: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, decline.target) if self.game_instance.strategy.is_world_modeling: self.game_instance.world_state.update_on_declined_propose(transaction) - elif decline.target == 3: + elif target == 3: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, decline.target) self.game_instance.transaction_manager.pop_locked_tx(transaction.transaction_id) return None - def on_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Union[List[OEFAgentDecline], List[Union[OEFAgentAccept]], List[OEFAgentByteMessage]]: + def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: """ Handle an Accept. @@ -190,17 +198,19 @@ def on_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Union[List[OE :return: a Deline or an Accept and a Transaction (in OutContainer) or a Transaction (in OutContainer) """ + assert accept.protocol_id == "fipa" and accept.get("type") == FIPAMessage.Performative.ACCEPT logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, accept.msg_id, accept.dialogue_id, accept.destination, accept.target)) + .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), accept.sender, accept.get("target"))) + target = accept.get("target") if dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ - and accept.target in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label]: + and target in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label]: results = self._on_match_accept(accept, dialogue) else: results = self._on_initial_accept(accept, dialogue) return results - def _on_initial_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Union[List[OEFAgentDecline], List[Union[OEFAgentByteMessage, OEFAgentAccept]]]: + def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: """ Handle an initial Accept. @@ -209,8 +219,8 @@ def _on_initial_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Unio :return: a Deline or an Accept and a Transaction (in OutContainer """ - transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.target) - new_msg_id = accept.msg_id + 1 + transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) + new_msg_id = accept.get("id") + 1 results = [] is_profitable_transaction, message = self.game_instance.is_profitable_transaction(transaction, dialogue) logger.debug(message) @@ -219,15 +229,22 @@ def _on_initial_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> Unio self.game_instance.world_state.update_on_initial_accept(transaction) logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - results.append(OEFAgentByteMessage(STARTING_MESSAGE_ID, accept.dialogue_id, self.game_instance.controller_pbk, transaction.serialize())) - results.append(OEFAgentAccept(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id)) + results.append(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, + message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), + content=transaction.serialize())) + results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, + msg_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), + performative=FIPAMessage.Performative.MATCH_ACCEPT)) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) - results.append(OEFAgentDecline(new_msg_id, accept.dialogue_id, accept.destination, accept.msg_id)) + results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, + msg_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), + target=accept.get("id"), + performative=FIPAMessage.Performative.DECLINE)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results - def _on_match_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> List[OEFAgentMessage]: + def _on_match_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: """ Handle a matching Accept. @@ -238,6 +255,8 @@ def _on_match_accept(self, accept: OEFAgentAccept, dialogue: Dialogue) -> List[O """ logger.debug("[{}]: on match accept".format(self.agent_name)) results = [] - transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, accept.target) - results.append(OEFAgentByteMessage(STARTING_MESSAGE_ID, accept.dialogue_id, self.game_instance.controller_pbk, transaction.serialize())) + transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, accept.get("target")) + results.append(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, + message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), + content=transaction.serialize())) return results diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index ce30caed..cbb834c0 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -29,19 +29,13 @@ from tac.agents.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler from tac.agents.v1.base.helpers import is_oef_message, is_controller_message from tac.agents.v1.base.strategy import Strategy -from tac.agents.v1.mail.messages import OEFMessage, OEFResponse, OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, \ - OEFAgentDecline, OEFAgentAccept +from tac.agents.v1.mail.messages import Message from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) -ControllerMessage = OEFAgentByteMessage -AgentMessage = Union[OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, OEFAgentDecline] -Message = Union[ControllerMessage, AgentMessage] - - class ParticipantAgent(Agent): """The participant agent class implements a base agent for TAC.""" @@ -109,16 +103,13 @@ def react(self) -> None: counter = 0 while (not self.mail_box.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.mail_box.inbox.get_nowait() # type: Optional[OEFMessage] + msg = self.mail_box.inbox.get_nowait() # type: Optional[Message] if msg is not None: if is_oef_message(msg): - msg: OEFResponse self.oef_handler.handle_oef_message(msg) elif is_controller_message(msg, self.crypto): - msg: ControllerMessage self.controller_handler.handle_controller_message(msg) else: - msg: AgentMessage self.dialogue_handler.handle_dialogue_message(msg) def update(self) -> None: diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 94273a83..236bde1a 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -39,8 +39,7 @@ from tac.agents.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.v1.base.stats_manager import EndState from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import OEFAgentByteMessage, OEFAgentCfp, OEFAgentPropose, OEFAgentAccept, \ - OEFAgentDecline, OEFSearchResult, OEFGenericError, OEFDialogueError, OEFAgentMessage +from tac.agents.v1.mail.messages import Message from tac.helpers.crypto import Crypto from tac.helpers.misc import TAC_DEMAND_DATAMODEL_NAME from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ @@ -73,7 +72,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, dialogue_error_msg: OEFDialogueError) -> None: + def on_dialogue_error(self, dialogue_error_msg: Message) -> None: """ Handle dialogue error event emitted by the controller. @@ -81,8 +80,9 @@ def on_dialogue_error(self, dialogue_error_msg: OEFDialogueError) -> None: :return: None """ + assert dialogue_error_msg.protocol_id == "oef" logger.warning("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error_msg.msg_id, dialogue_error_msg.dialogue_id, dialogue_error_msg.origin)) + .format(self.agent_name, dialogue_error_msg.get("id"), dialogue_error_msg.get("dialogue_id"), dialogue_error_msg.get("origin"))) def on_start(self, game_data: GameData) -> None: """ @@ -209,7 +209,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.agent_name = agent_name self.rejoin = rejoin - def on_search_result(self, search_result: OEFSearchResult) -> None: + def on_search_result(self, search_result: Message) -> None: """ Split the search results from the OEF. @@ -217,14 +217,16 @@ def on_search_result(self, search_result: OEFSearchResult) -> None: :return: None """ - search_id = search_result.search_id - logger.debug("[{}]: on search result: {} {}".format(self.agent_name, search_id, search_result.agents)) + assert search_result.protocol_id == "oef" + search_id = search_result.get("id") + agents = search_result.get("agents") + logger.debug("[{}]: on search result: {} {}".format(self.agent_name, search_id, agents)) if search_id in self.game_instance.search.ids_for_tac: - self._on_controller_search_result(search_result.agents) + self._on_controller_search_result(agents) elif search_id in self.game_instance.search.ids_for_sellers: - self._on_services_search_result(search_result.agents, is_searching_for_sellers=True) + self._on_services_search_result(agents, is_searching_for_sellers=True) elif search_id in self.game_instance.search.ids_for_buyers: - self._on_services_search_result(search_result.agents, is_searching_for_sellers=False) + self._on_services_search_result(agents, is_searching_for_sellers=False) else: logger.debug("[{}]: Unknown search id: search_id={}".format(self.agent_name, search_id)) diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index c77d66e9..7388d000 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -42,8 +42,8 @@ def __init__(self, to: Optional[Address] = None, **body): self._to = to self._sender = sender - self._body = deepcopy(body) if body else {} self._protocol_id = protocol_id + self._body = deepcopy(body) if body else {} @property def to(self) -> Address: @@ -95,7 +95,6 @@ class Type(Enum): SEARCH_AGENTS = "search_agents" OEF_ERROR = "oef_error" DIALOGUE_ERROR = "dialogue_error" - BYTES = "bytes" SEARCH_RESULT = "search_result" @@ -103,9 +102,7 @@ def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, oef_type: Optional[Type] = None, **body): - _body = dict(**body) - _body["type"] = oef_type - super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, **_body) + super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=oef_type, **body) def check_consistency(self) -> bool: try: @@ -154,12 +151,6 @@ def check_consistency(self) -> bool: assert self.is_set("id") assert self.is_set("dialogue_id") assert self.is_set("origin") - elif oef_type == OEFMessage.Type.BYTES: - assert self.is_set("id") - assert self.is_set("dialogue_id") - assert self.is_set("content") - content = self.get("content") - assert type(content) == bytes else: raise ValueError("Type not recognized.") except (AssertionError, ValueError): @@ -172,6 +163,18 @@ class ByteMessage(Message): protocol_id = "bytes" + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, + message_id: Optional[int] = None, + dialogue_id: Optional[int] = None, + content: bytes = b""): + super().__init__(to=to, sender=sender, id=message_id, dialogue_id=dialogue_id, content=content) + + +class SimpleByteMessage(Message): + + protocol_id = "default" + def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, content: bytes = b""): @@ -186,6 +189,7 @@ class Performative(Enum): CFP = "cfp" PROPOSE = "propose" ACCEPT = "accept" + MATCH_ACCEPT = "match_accept" DECLINE = "decline" def __init__(self, to: Optional[Address] = None, @@ -195,12 +199,14 @@ def __init__(self, to: Optional[Address] = None, target: Optional[int] = None, performative: Optional[Union[str, Performative]] = None, **body): - _body = dict(**body) - _body["id"] = msg_id - _body["dialogue_id"] = dialogue_id - _body["target"] = target - _body["performative"] = FIPAMessage.Performative(performative) - super().__init__(to, sender, protocol_id=self.protocol_id, **_body) + super().__init__(to, + sender, + protocol_id=self.protocol_id, + id=msg_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative(performative), + **body) def check_consistency(self) -> bool: try: @@ -212,7 +218,9 @@ def check_consistency(self) -> bool: elif performative == FIPAMessage.Performative.PROPOSE: proposal = self.get("proposal") assert type(proposal) == list and all(isinstance(d, Description) or type(d) == bytes for d in proposal) - elif performative == FIPAMessage.Performative.ACCEPT or performative == FIPAMessage.Performative.DECLINE: + elif performative == FIPAMessage.Performative.ACCEPT \ + or performative == FIPAMessage.Performative.MATCH_ACCEPT \ + or performative == FIPAMessage.Performative.DECLINE: pass else: raise ValueError("Performative not recognized.") diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index af1cbfe5..f4178a0b 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -202,11 +202,6 @@ def send_oef_message(self, msg: Message): query = msg.get("query") self.mail_stats.search_start(id) self.search_services(id, query) - elif oef_type == OEFMessage.Type.BYTES: - id = msg.get("id") - dialogue_id = msg.get("dialogue_id") - content = msg.get("content") - self.send_message(id, dialogue_id, msg.to, content) else: raise ValueError("OEF request not recognized.") @@ -224,6 +219,8 @@ def send_fipa_message(self, msg: Message): self.send_propose(id, dialogue_id, destination, target, proposal) elif performative == FIPAMessage.Performative.ACCEPT: self.send_accept(id, dialogue_id, destination, target) + elif performative == FIPAMessage.Performative.MATCH_ACCEPT: + self.send_accept(id, dialogue_id, destination, target) elif performative == FIPAMessage.Performative.DECLINE: self.send_decline(id, dialogue_id, destination, target) else: From 8b2503b49968a1efa0fcd97fe1ff609b098d94e1 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 14:41:01 +0100 Subject: [PATCH 021/107] Add stacktracer, fixes #295 --- scripts/launch_alt.py | 3 + scripts/stack_tracer.py | 153 +++++++++++++++++++++++++++++++++++++++ tests/test_simulation.py | 1 - 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 scripts/stack_tracer.py diff --git a/scripts/launch_alt.py b/scripts/launch_alt.py index 13ab231b..70fd45a5 100644 --- a/scripts/launch_alt.py +++ b/scripts/launch_alt.py @@ -34,6 +34,7 @@ import tac from tac.platform.simulation import parse_arguments, build_simulation_parameters from tac.helpers.oef_health_check import OEFHealthCheck +import stack_tracer CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") @@ -109,4 +110,6 @@ def _get_image_id(self): simulation_params = build_simulation_parameters(args) with VisdomServer(), OEFNode(): + stack_tracer.start_trace(os.path.join(ROOT_DIR, "data/trace.html"), interval=5, auto=True) tac.platform.simulation.run(simulation_params) + stack_tracer.stop_trace() diff --git a/scripts/stack_tracer.py b/scripts/stack_tracer.py new file mode 100644 index 00000000..bc963a69 --- /dev/null +++ b/scripts/stack_tracer.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module implements a stack tracer for multi-threaded applications. + +This is based on: http://code.activestate.com/recipes/577334-how-to-debug-deadlocked-multi-threaded-programs/ + +Usage: + +import stacktracer +stacktracer.start_trace("trace.html",interval=5,auto=True) # Set auto flag to always update file! +.... +stacktracer.stop_trace() +""" + +import os +import sys +import time +import threading +import traceback +from pygments import highlight +from pygments.lexers import PythonLexer +from pygments.formatters import HtmlFormatter + + +def stacktraces() -> str: + """ + Tracks stack traces. + """ + print('Stacktraces captured!') + code = [] + for threadId, stack in sys._current_frames().items(): + code.append("\n# ThreadID: {}".format(threadId)) + for filename, lineno, name, line in traceback.extract_stack(stack): + code.append('File: "{}", line {}, in {}'.format(filename, lineno, name)) + if line: + code.append(" %s" % (line.strip())) + + return highlight("\n".join(code), PythonLexer(), HtmlFormatter(full=False, style="native", noclasses=True)) + + +class TraceDumper(threading.Thread): + """Dump stack traces into a given file periodically.""" + + def __init__(self, fpath: str, interval: int, auto: bool) -> None: + """ + Initialize. + + :param fpath: File path to output HTML (stack trace file) + :param auto: Set flag (True) to update trace continuously. + Clear flag (False) to update only if file not exists. + (Then delete the file to force update.) + :param interval: In seconds: how often to update the trace file. + :return: None + """ + assert(interval > 0.1) + self.auto = auto + self.interval = interval + self.fpath = os.path.abspath(fpath) + self.stop_requested = threading.Event() + threading.Thread.__init__(self) + + def run(self) -> None: + """ + Run. + + :return: None + """ + while not self.stop_requested.isSet(): + time.sleep(self.interval) + if self.auto or not os.path.isfile(self.fpath): + self.stacktraces() + + def stop(self) -> None: + """ + Stop. + + :return: None + """ + self.stop_requested.set() + self.join() + try: + if os.path.isfile(self.fpath): + os.unlink(self.fpath) + except: # noqa: E722 + pass + + def stacktraces(self) -> None: + """ + Stacktraces write. + + :return: None + """ + fout = open(self.fpath, "w+") + try: + fout.write(stacktraces()) + finally: + fout.close() + + +_tracer = None + + +def start_trace(fpath: str, interval: int = 5, auto: bool = True) -> None: + """ + Start tracing into the given file. + + :param fpath: File path to output HTML (stack trace file) + :param auto: Set flag (True) to update trace continuously. + Clear flag (False) to update only if file not exists. + (Then delete the file to force update.) + :param interval: In seconds: how often to update the trace file. + :return: None + """ + global _tracer + if _tracer is None: + _tracer = TraceDumper(fpath, interval, auto) + _tracer.setDaemon(True) + _tracer.start() + else: + raise Exception("Already tracing to {}".format(_tracer.fpath)) + + +def stop_trace() -> None: + """ + Stop tracing. + + :return: None + """ + global _tracer + if _tracer is None: + raise Exception("Not tracing, cannot stop.") + else: + _tracer.stop() + _tracer = None diff --git a/tests/test_simulation.py b/tests/test_simulation.py index d09b7f65..dd848528 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -19,7 +19,6 @@ """This module contains the tests of the simulation.""" -import asyncio import datetime from threading import Thread from typing import List From 8ad8ff6db70be63d39cb3f2037d8d7794e73a332 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 15:07:22 +0100 Subject: [PATCH 022/107] removing one test on debug mode. --- tests/test_agent/test_misc.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 67267bcd..0a4a9ce7 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -72,19 +72,3 @@ def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): test_agent.react.assert_called() test_agent.update.assert_called() - - -def test_that_when_debug_flag_true_we_drop_out_messages(): - """Test that, in debug mode, the out messages are dropped and a warning message is logged.""" - with patch('logging.Logger.warning') as mock: - test_agent = TAgent(debug=True) - job = Timer(1.0, test_agent.stop) - job.start() - msg = ByteMessage(to="destination", sender=test_agent.crypto.public_key, message_id=0, dialogue_id=0, content=b"this is a message.") - test_agent.out_box.out_queue.put_nowait(msg) - test_agent.out_box.send_nowait() - test_agent.start() - job.join() - - mock.assert_called_with("Dropping message of type '' from the out queue...") - From 095a196e64dbced9fdad72cff3dc1634696a7560 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 16:08:14 +0100 Subject: [PATCH 023/107] fix bugs and make test work (except simulation tests). --- sandbox/playground.py | 17 +++++++++-------- tac/agents/v1/base/actions.py | 2 +- tac/agents/v1/base/handlers.py | 4 ++-- tac/agents/v1/base/helpers.py | 2 +- tac/agents/v1/base/participant_agent.py | 1 + tac/agents/v1/mail/base.py | 8 ++++++++ tac/agents/v1/mail/messages.py | 6 +++--- tac/agents/v1/mail/oef.py | 19 ++++++++++++------- tac/platform/controller/controller_agent.py | 17 ++++++----------- tac/platform/controller/handlers.py | 2 +- tests/test_controller.py | 19 ++++++++++++------- tests/test_mail.py | 7 ++++--- 12 files changed, 60 insertions(+), 44 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index f9692d84..1091e176 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -32,13 +32,11 @@ from typing import Dict, Optional -from oef.messages import CFP, Message -from oef.uri import Context from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.examples.baseline import BaselineAgent from tac.agents.v1.examples.strategy import BaselineStrategy -from tac.agents.v1.mail.messages import OEFAgentCfp +from tac.agents.v1.mail.messages import FIPAMessage, Message from tac.platform.protocol import GameData CUR_PATH = inspect.getfile(inspect.currentframe()) @@ -133,25 +131,28 @@ def launch_oef(): starting_message_id = 1 starting_message_target = 0 services = agent_one.game_instance.build_services_dict(is_supply=is_seller) # type: Dict - cfp = OEFAgentCfp(starting_message_id, dialogue.dialogue_label.dialogue_id, agent_two.crypto.public_key, starting_message_target, json.dumps(services).encode('utf-8'), Context()) + cfp = FIPAMessage(agent_two.crypto.public_key, + msg_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, + target=starting_message_target, performative=FIPAMessage.Performative.CFP, + query=json.dumps(services).encode('utf-8')) dialogue.outgoing_extend([cfp]) agent_one.out_box.out_queue.put(cfp) # Send the messages in the outbox - agent_one.out_box.send_nowait() + # agent_one.out_box.send_nowait() # Check the message arrived in the inbox of agent_two checks = 0 while checks < 10: - if agent_two.in_box.is_in_queue_empty(): + if agent_two.in_box.empty(): # Wait a bit print("Sleeping for 1 second ...") time.sleep(1.0) checks += 1 else: checks = 10 - msg = agent_two.in_box.get_no_wait() # type: Optional[Message] - print("The msg is a CFP: {}".format(isinstance(msg, CFP))) + msg = agent_two.in_box.get_nowait() # type: Optional[Message] + print("The msg is a CFP: {}".format(msg.get("performative") == FIPAMessage.Performative.CFP)) # Set the debugger print("Setting debugger ... To continue enter: c + Enter") diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index 829b56b0..d910181a 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -107,7 +107,7 @@ def search_for_tac(self) -> None: search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_tac.add(search_id) - message = OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=0, query=query) + message = OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) self.mailbox.outbox.put(message) def update_services(self) -> None: diff --git a/tac/agents/v1/base/handlers.py b/tac/agents/v1/base/handlers.py index dd1fc65a..05023eee 100644 --- a/tac/agents/v1/base/handlers.py +++ b/tac/agents/v1/base/handlers.py @@ -105,10 +105,10 @@ def handle_controller_message(self, msg: Message) -> None: :return: None """ assert msg.protocol_id == "bytes" - response = Response.from_pb(msg.get("content"), msg.to, self.crypto) + response = Response.from_pb(msg.get("content"), msg.sender, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: - if msg.to != self.game_instance.controller_pbk: + if msg.sender != self.game_instance.controller_pbk: raise ValueError("The sender of the message is not the controller agent we registered with.") if isinstance(response, Error): diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index d6f3c34d..0c9b09b6 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -33,7 +33,7 @@ def is_oef_message(msg: Message) -> bool: :param msg: the message :return: boolean indicating whether or not the message is from the oef """ - return msg.protocol_id == "fipa" and msg.get("type") in set(OEFMessage.Type) + return msg.protocol_id == "oef" and msg.get("type") in set(OEFMessage.Type) def is_controller_message(msg: Message, crypto: Crypto) -> bool: diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index cbb834c0..77270787 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -104,6 +104,7 @@ def react(self) -> None: while (not self.mail_box.inbox.empty() and counter < self.max_reactions): counter += 1 msg = self.mail_box.inbox.get_nowait() # type: Optional[Message] + logger.debug("processing message of protocol={}".format(msg.protocol_id)) if msg is not None: if is_oef_message(msg): self.oef_handler.handle_oef_message(msg) diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py index 94abb361..52b0aae6 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/agents/v1/mail/base.py @@ -80,6 +80,14 @@ def __init__(self, queue: Queue) -> None: super().__init__() self._queue = queue + def empty(self) -> bool: + """ + Check for a message on the out queue. + + :return: boolean indicating whether there is a message or not + """ + return self._queue.empty() + def put(self, item: Message) -> None: """Put an item into the queue.""" logger.debug("Put a message in the queue...") diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 7388d000..96333950 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -19,9 +19,9 @@ # ------------------------------------------------------------------------------ """Protocol module v2.""" -from copy import deepcopy +from copy import copy from enum import Enum -from typing import Optional, Dict, Any, Union +from typing import Optional, Any, Union from oef.messages import OEFErrorOperation from oef.query import Query @@ -43,7 +43,7 @@ def __init__(self, to: Optional[Address] = None, self._to = to self._sender = sender self._protocol_id = protocol_id - self._body = deepcopy(body) if body else {} + self._body = copy(body) if body else {} @property def to(self) -> Address: diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index f4178a0b..3aa1d3d9 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -32,7 +32,7 @@ from oef.proxy import OEFNetworkProxy from tac.agents.v1.mail.base import Connection, MailBox -from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, Message +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, Message, ByteMessage logger = logging.getLogger(__name__) @@ -94,12 +94,11 @@ def is_connected(self): return self._oef_proxy.is_connected() def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): - msg = OEFMessage(to=self.public_key, - sender=origin, - msg_id=msg_id, - dialogue_id=dialogue_id, - oef_type=OEFMessage.Type.BYTES, - content=content) + msg = ByteMessage(to=self.public_key, + sender=origin, + message_id=msg_id, + dialogue_id=dialogue_id, + content=content) self.in_queue.put(msg) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES): @@ -171,6 +170,10 @@ def send(self, msg: Message): self.send_oef_message(msg) elif msg.protocol_id == "fipa": self.send_fipa_message(msg) + elif msg.protocol_id == "bytes": + self.send_message(msg.get("id"), msg.get("dialogue_id"), msg.to, msg.get("content")) + elif msg.protocol_id == "default": + self.send_message(msg.get("id"), 0, msg.to, msg.get("content")) else: raise ValueError("Cannot send message.") @@ -263,6 +266,8 @@ def disconnect(self): self._stopped = True self.in_thread.join() self.out_thread.join() + self.in_thread = Thread(target=self.bridge.run) + self.out_thread = Thread(target=self._fetch) self.bridge.disconnect() @property diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index 89961348..13e91a83 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -77,9 +77,9 @@ def __init__(self, name: str, super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) self.mail_box = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) - self.oef_handler = OEFHandler(self.crypto, self.liveness, self.out_box, self.name) + self.oef_handler = OEFHandler(self.crypto, self.liveness, self.mail_box, self.name) self.agent_message_dispatcher = AgentMessageDispatcher(self) - self.game_handler = GameHandler(name, self.crypto, self.out_box, monitor, tac_parameters) + self.game_handler = GameHandler(name, self.crypto, self.mail_box, monitor, tac_parameters) self.max_reactions = max_reactions self.last_activity = datetime.datetime.now() @@ -104,7 +104,7 @@ def act(self) -> None: .format(self.name, pprint.pformat(self.game_handler.tac_parameters.__dict__))) self.oef_handler.register_tac() self.game_handler._game_phase = GamePhase.GAME_SETUP - if self.game_handler.game_phase == GamePhase.GAME_SETUP: + elif self.game_handler.game_phase == GamePhase.GAME_SETUP: now = datetime.datetime.now() if now >= self.game_handler.competition_start: logger.debug("[{}]: Checking if we can start the competition.".format(self.name)) @@ -120,7 +120,7 @@ def act(self) -> None: self.stop() self.teardown() return - if self.game_handler.game_phase == GamePhase.GAME: + elif self.game_handler.game_phase == GamePhase.GAME: current_time = datetime.datetime.now() inactivity_duration = current_time - self.last_activity if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: @@ -134,8 +134,6 @@ def act(self) -> None: self.teardown() return - self.out_box.send_nowait() - def react(self) -> None: """ React to incoming events. @@ -143,9 +141,9 @@ def react(self) -> None: :return: None """ counter = 0 - while (not self.in_box.is_in_queue_empty() and counter < self.max_reactions): + while (not self.in_box.empty() and counter < self.max_reactions): counter += 1 - msg = self.in_box.get_no_wait() # type: Optional[Message] + msg = self.in_box.get_nowait() # type: Optional[Message] if msg is not None: if is_oef_message(msg): self.oef_handler.handle_oef_message(msg) @@ -153,8 +151,6 @@ def react(self) -> None: self.agent_message_dispatcher.handle_agent_message(msg) self.last_activity = datetime.datetime.now() - self.out_box.send_nowait() - def update(self) -> None: """ Update the state of the agent. @@ -171,7 +167,6 @@ def stop(self) -> None: logger.debug("[{}]: Stopping myself...".format(self.name)) if self.game_handler.game_phase == GamePhase.GAME or self.game_handler.game_phase == GamePhase.GAME_SETUP: self.game_handler.notify_competition_cancelled() - self.out_box.send_nowait() super().stop() def start(self) -> None: diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index 79e9dbc4..3c7aafc4 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -292,7 +292,7 @@ def handle_agent_message(self, msg: Message) -> Response: assert msg.protocol_id == "bytes" msg_id = msg.get("id") dialogue_id = msg.get("dialogue_id") - sender = msg.get("sender") + sender = msg.sender logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, msg.get("id"), dialogue_id, sender)) request = self.decode(msg.get("content"), sender) handle_request = self.handlers.get(type(request), None) # type: RequestHandler diff --git a/tests/test_controller.py b/tests/test_controller.py index 6c7d67ac..38c684a4 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -77,21 +77,26 @@ def setup_class(cls): tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) - job = Thread(target=cls.controller_agent.start) - job.start() - + import asyncio crypto = Crypto() + cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) + cls.agent1.connect() # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 # core.run_threaded() # OEF-SDK 0.6.1 - import asyncio - cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) # agent1 = TestOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, core=core) # OEF-SDK 0.6.1 - cls.agent1.connect() + + job = Thread(target=cls.controller_agent.start) + job.start() + agent_job = Thread(target=cls.agent1.run) + agent_job.start() + cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, Register(crypto.public_key, crypto, 'agent_name').serialize()) - time.sleep(20.0) + time.sleep(1.0) job.join() + cls.agent1.stop() + agent_job.join() def test_only_one_agent_registered(self): """Test exactly one agent is registered.""" diff --git a/tests/test_mail.py b/tests/test_mail.py index c5ed94d2..593b64f0 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,7 +20,7 @@ """This module contains tests for the mail module.""" import time -from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, ByteMessage from tac.agents.v1.mail.oef import OEFNetworkMailBox @@ -32,7 +32,7 @@ def test_example(network_node): mailbox1.connect() mailbox2.connect() - msg = OEFMessage("mailbox2", "mailbox1", OEFMessage.Type.BYTES, id=0, dialogue_id=0, content=b"hello") + msg = ByteMessage("mailbox2", "mailbox1", message_id=0, dialogue_id=0, content=b"hello") mailbox1.send(msg) msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None) mailbox1.send(msg) @@ -43,7 +43,8 @@ def test_example(network_node): msg = FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE) mailbox1.send(msg) - time.sleep(1.0) + time.sleep(5.0) + msg = mailbox2.inbox.get(block=True, timeout=1.0) assert msg.get("content") == b"hello" msg = mailbox2.inbox.get(block=True, timeout=1.0) From 15aa188f25feaad92cd31c3bb393b9862756d59b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 17:04:05 +0100 Subject: [PATCH 024/107] fix bugs and address PR comments. --- tac/agents/v1/agent.py | 20 ++++++++++---------- tac/agents/v1/base/actions.py | 4 ++-- tac/agents/v1/base/helpers.py | 9 +++++++-- tac/agents/v1/base/participant_agent.py | 14 +++++++------- tac/platform/controller/actions.py | 2 +- tac/platform/controller/controller_agent.py | 14 ++++++-------- tac/platform/controller/handlers.py | 2 -- templates/v1/expert.py | 4 +--- tests/test_agent/test_agent_state.py | 6 +++--- tests/test_agent/test_misc.py | 2 +- 10 files changed, 38 insertions(+), 39 deletions(-) diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index 690b730d..8350cab4 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -83,15 +83,15 @@ def __init__(self, name: str, self.debug = debug - self.mail_box = None # type: Optional[MailBox] + self.mailbox = None # type: Optional[MailBox] @property def in_box(self) -> Optional[InBox]: - return self.mail_box.inbox if self.mail_box else None + return self.mailbox.inbox if self.mailbox else None @property def out_box(self) -> Optional[OutBox]: - return self.mail_box.outbox if self.mail_box else None + return self.mailbox.outbox if self.mailbox else None @property def name(self) -> str: @@ -121,11 +121,11 @@ def agent_state(self) -> AgentState: :return the agent state. :raises ValueError: if the state does not satisfy any of the foreseen conditions. """ - if self.mail_box is None or not self.mail_box.is_connected: + if self.mailbox is None or not self.mailbox.is_connected: return AgentState.INITIATED - elif self.mail_box.is_connected and self.liveness.is_stopped: + elif self.mailbox.is_connected and self.liveness.is_stopped: return AgentState.CONNECTED - elif self.mail_box.is_connected and not self.liveness.is_stopped: + elif self.mailbox.is_connected and not self.liveness.is_stopped: return AgentState.RUNNING else: raise ValueError("Agent state not recognized.") @@ -136,8 +136,8 @@ def start(self) -> None: :return: None """ - if not self.debug and not self.mail_box.is_connected: - self.mail_box.connect() + if not self.debug and not self.mailbox.is_connected: + self.mailbox.connect() self.liveness._is_stopped = False self._run_main_loop() @@ -172,8 +172,8 @@ def stop(self) -> None: """ logger.debug("[{}]: Stopping message processing...".format(self.name)) self.liveness._is_stopped = True - if self.mail_box.is_connected: - self.mail_box.disconnect() + if self.mailbox.is_connected: + self.mailbox.disconnect() @abstractmethod def setup(self) -> None: diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index d910181a..a55886dc 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -145,12 +145,12 @@ def register_service(self) -> None: logger.debug("[{}]: Updating service directory as seller with goods supplied.".format(self.agent_name)) goods_supplied_description = self.game_instance.get_service_description(is_supply=True) self.game_instance.goods_supplied_description = goods_supplied_description - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="")) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) + self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="")) def search_services(self) -> None: """ diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index 0c9b09b6..8049b61d 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -19,6 +19,7 @@ # ------------------------------------------------------------------------------ """This module contains helper methods for base agent implementations.""" +import logging from tac.agents.v1.base.dialogues import DialogueLabel from tac.agents.v1.mail.messages import Message, OEFMessage @@ -26,6 +27,9 @@ from tac.platform.protocol import Response +logger = logging.getLogger(__name__) + + def is_oef_message(msg: Message) -> bool: """ Check whether a message is from the oef. @@ -49,9 +53,10 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: try: byte_content = msg.get("content") - sender_pbk = msg.sender # now the origin is the destination! + sender_pbk = msg.sender Response.from_pb(byte_content, sender_pbk, crypto) - except Exception: + except Exception as e: + logger.debug("Not a Controller message: {}".format(str(e))) return False return True diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index 77270787..a67a605b 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -66,14 +66,14 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mail_box = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) - self._game_instance = GameInstance(name, strategy, self.mail_box.mail_stats, services_interval, pending_transaction_timeout, dashboard) # type: Optional[GameInstance] + self._game_instance = GameInstance(name, strategy, self.mailbox.mail_stats, services_interval, pending_transaction_timeout, dashboard) # type: Optional[GameInstance] self.max_reactions = max_reactions - self.controller_handler = ControllerHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) - self.oef_handler = OEFHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) - self.dialogue_handler = DialogueHandler(self.crypto, self.liveness, self.game_instance, self.mail_box, self.name) + self.controller_handler = ControllerHandler(self.crypto, self.liveness, self.game_instance, self.mailbox, self.name) + self.oef_handler = OEFHandler(self.crypto, self.liveness, self.game_instance, self.mailbox, self.name) + self.dialogue_handler = DialogueHandler(self.crypto, self.liveness, self.game_instance, self.mailbox, self.name) @property def game_instance(self) -> GameInstance: @@ -101,9 +101,9 @@ def react(self) -> None: :return: None """ counter = 0 - while (not self.mail_box.inbox.empty() and counter < self.max_reactions): + while (not self.mailbox.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.mail_box.inbox.get_nowait() # type: Optional[Message] + msg = self.mailbox.inbox.get_nowait() # type: Optional[Message] logger.debug("processing message of protocol={}".format(msg.protocol_id)) if msg is not None: if is_oef_message(msg): diff --git a/tac/platform/controller/actions.py b/tac/platform/controller/actions.py index 7380b137..80bcbd5b 100644 --- a/tac/platform/controller/actions.py +++ b/tac/platform/controller/actions.py @@ -68,5 +68,5 @@ def register_tac(self) -> None: """ desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) - out = OEFMessage(sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc) + out = OEFMessage(sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") self.mailbox.outbox.put(out) diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index 13e91a83..c892b82c 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -30,18 +30,16 @@ from typing import Union, Optional import dateutil -from oef.messages import Message as ByteMessage, OEFErrorMessage, DialogueErrorMessage +from tac.agents.v1.agent import Agent +from tac.agents.v1.base.game_instance import GamePhase +from tac.agents.v1.base.helpers import is_oef_message from tac.agents.v1.mail.messages import Message from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor from tac.platform.controller.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.platform.controller.tac_parameters import TACParameters -from tac.agents.v1.agent import Agent -from tac.agents.v1.base.game_instance import GamePhase -from tac.agents.v1.base.helpers import is_oef_message - if __name__ != "__main__": logger = logging.getLogger(__name__) else: @@ -75,11 +73,11 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mail_box = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) - self.oef_handler = OEFHandler(self.crypto, self.liveness, self.mail_box, self.name) + self.oef_handler = OEFHandler(self.crypto, self.liveness, self.mailbox, self.name) self.agent_message_dispatcher = AgentMessageDispatcher(self) - self.game_handler = GameHandler(name, self.crypto, self.mail_box, monitor, tac_parameters) + self.game_handler = GameHandler(name, self.crypto, self.mailbox, monitor, tac_parameters) self.max_reactions = max_reactions self.last_activity = datetime.datetime.now() diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index 3c7aafc4..2b20c6a9 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -455,8 +455,6 @@ def simulation_dump(self) -> None: """ Dump the details of the simulation. - :param directory: the directory where experiments details are listed. - :param experiment_name: the name of the folder where the data about experiment will be saved. :return: None. """ experiment_id = str(self.tac_parameters.experiment_id) if self.tac_parameters.experiment_id is not None else str(datetime.datetime.now()) diff --git a/templates/v1/expert.py b/templates/v1/expert.py index 8b906552..0d5940eb 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -49,9 +49,7 @@ class MyAgent(Agent): def __init__(self, name: str, oef_addr: str, oef_port: int, agent_timeout: float = 1.0, private_key_pem_path: Optional[str] = None): """Agent initialization.""" super().__init__(name, oef_addr, oef_port, private_key_pem_path, agent_timeout) - self.mail_box = FIPAMailBox(self.crypto.public_key, oef_addr, oef_port) - self.in_box = InBox(self.mail_box) - self.out_box = OutBox(self.mail_box) + self.mailbox = FIPAMailBox(self.crypto.public_key, oef_addr, oef_port) raise NotImplementedError("Your agent must implement the interface defined in Agent.") diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index b7361bf1..1a5c9626 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -33,7 +33,7 @@ class TAgent(Agent): def __init__(self): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000) - self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: pass @@ -60,9 +60,9 @@ def test_agent_initiated(): def test_agent_connected(network_node): """Test that when the agent is connected, her state is AgentState.CONNECTED.""" test_agent = TAgent() - test_agent.mail_box.connect() + test_agent.mailbox.connect() assert test_agent.agent_state == AgentState.CONNECTED - test_agent.mail_box.disconnect() + test_agent.mailbox.disconnect() def test_agent_running(network_node): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 0a4a9ce7..3246dcf6 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -33,7 +33,7 @@ class TAgent(Agent): def __init__(self, **kwargs): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) - self.mail_box = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: """Set up the agent.""" From 7145b79562a50cc7e82af030f8f13d8746520a95 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 17:56:10 +0100 Subject: [PATCH 025/107] do minor changes. - Add 'pygments' as dev dependency in 'Pipfile'. - Import the 'stack_tracer' script dynamically from the absolute path, so to make the 'launch_alt' script executable from anywhere. - Make some import statements more detailed. --- Pipfile | 3 ++- Pipfile.lock | 29 +++++++++++------------------ scripts/launch_alt.py | 4 ++-- scripts/stack_tracer.py | 4 ++-- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/Pipfile b/Pipfile index 75a56bcf..023b64d4 100644 --- a/Pipfile +++ b/Pipfile @@ -18,6 +18,7 @@ tox-pipenv = "*" pytest-cov = "*" docker = "*" flake8-docstrings = "*" +pygments = "*" [packages] numpy = "*" @@ -27,7 +28,7 @@ visdom = "*" cryptography = "*" base58 = "*" fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} -oef = {version="==0.6.0", index="test-pypi"} +oef = {version = "==0.6.0",index = "test-pypi"} sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" sphinx = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 58baf330..e37a3d3c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1e0d0b06715a16acdba107f48c5050043159caac35c27994b4847a21c5801aed" + "sha256": "f67c5d7b25cef332c01bb413338d4cb74a55c207930de615e989c9ea97dc2052" }, "pipfile-spec": 6, "requires": { @@ -734,14 +734,6 @@ } }, "develop": { - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -893,6 +885,7 @@ "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" ], + "markers": "python_version < '3.8'", "version": "==0.19" }, "ipykernel": { @@ -1172,11 +1165,11 @@ }, "pytest": { "hashes": [ - "sha256:6ef6d06de77ce2961156013e9dff62f1b2688aa04d0dc244299fe7d67e09370d", - "sha256:a736fed91c12681a7b34617c8fcefe39ea04599ca72c608751c31d89579a3f77" + "sha256:3805d095f1ea279b9870c3eeae5dddf8a81b10952c8835cd628cf1875b0ef031", + "sha256:abc562321c2d190dd63c2faadf70b86b7af21a553b61f0df5f5e1270717dc5a3" ], "index": "pypi", - "version": "==5.0.1" + "version": "==5.1.0" }, "pytest-cov": { "hashes": [ @@ -1226,10 +1219,10 @@ }, "qtconsole": { "hashes": [ - "sha256:6a85456af7a98b0f554d140922b7b6a219757b039adb2b95e847cf115eaa20ae", - "sha256:767eb9ec3f9943bc84270198b5ff95d2d86d68d6b57792fafa4df4fc6b16cd7c" + "sha256:0877c4c66cb4f102dc796dc63a08ddfa55a981c36a83690b45dee819d15c1a38", + "sha256:84b43391ad0a54d91a628dbcd95562651052ea20457a75af85a66fea35397950" ], - "version": "==4.5.2" + "version": "==4.5.3" }, "requests": { "hashes": [ @@ -1323,10 +1316,10 @@ }, "virtualenv": { "hashes": [ - "sha256:6cb2e4c18d22dbbe283d0a0c31bb7d90771a606b2cb3415323eea008eaee6a9d", - "sha256:909fe0d3f7c9151b2df0a2cb53e55bdb7b0d61469353ff7a49fd47b0f0ab9285" + "sha256:5e4d92f9a36359a745ddb113cabb662e6100e71072a1e566eb6ddfcc95fdb7ed", + "sha256:b6711690882013bc79e0eac55889d901596f0967165d80adfa338c5729db1c71" ], - "version": "==16.7.2" + "version": "==16.7.3" }, "virtualenv-clone": { "hashes": [ diff --git a/scripts/launch_alt.py b/scripts/launch_alt.py index 70fd45a5..f194b3b3 100644 --- a/scripts/launch_alt.py +++ b/scripts/launch_alt.py @@ -20,7 +20,7 @@ # ------------------------------------------------------------------------------ """Start a Visdom server, an OEF node instance, and run the simulation script.""" - +import importlib import inspect import os import platform @@ -34,10 +34,10 @@ import tac from tac.platform.simulation import parse_arguments, build_simulation_parameters from tac.helpers.oef_health_check import OEFHealthCheck -import stack_tracer CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") +stack_tracer = importlib.import_module("stack_tracer", package=CUR_PATH) class VisdomServer: diff --git a/scripts/stack_tracer.py b/scripts/stack_tracer.py index bc963a69..a9feb08a 100644 --- a/scripts/stack_tracer.py +++ b/scripts/stack_tracer.py @@ -37,8 +37,8 @@ import threading import traceback from pygments import highlight -from pygments.lexers import PythonLexer -from pygments.formatters import HtmlFormatter +from pygments.formatters.html import HtmlFormatter +from pygments.lexers.python import PythonLexer def stacktraces() -> str: From d1d0a3e494089882ae6b927cd9a62927279cfe99 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 16 Aug 2019 19:37:46 +0100 Subject: [PATCH 026/107] wip --- sandbox/playground.py | 6 +- tac/agents/v1/agent.py | 4 +- tac/agents/v1/base/dialogues.py | 14 +- tac/agents/v1/base/helpers.py | 13 +- tac/agents/v1/base/participant_agent.py | 6 +- tac/agents/v1/mail/messages.py | 11 +- tac/platform/controller/controller_agent.py | 4 +- tac/platform/controller/handlers.py | 9 +- tac/platform/controller/reactions.py | 4 +- tac/tac_pb2.py | 141 ++++++++++---------- 10 files changed, 113 insertions(+), 99 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index 1091e176..6bf56ce8 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -136,7 +136,7 @@ def launch_oef(): target=starting_message_target, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) dialogue.outgoing_extend([cfp]) - agent_one.out_box.out_queue.put(cfp) + agent_one.outbox.out_queue.put(cfp) # Send the messages in the outbox # agent_one.out_box.send_nowait() @@ -144,14 +144,14 @@ def launch_oef(): # Check the message arrived in the inbox of agent_two checks = 0 while checks < 10: - if agent_two.in_box.empty(): + if agent_two.inbox.empty(): # Wait a bit print("Sleeping for 1 second ...") time.sleep(1.0) checks += 1 else: checks = 10 - msg = agent_two.in_box.get_nowait() # type: Optional[Message] + msg = agent_two.inbox.get_nowait() # type: Optional[Message] print("The msg is a CFP: {}".format(msg.get("performative") == FIPAMessage.Performative.CFP)) # Set the debugger diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index 8350cab4..47c4275d 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -86,11 +86,11 @@ def __init__(self, name: str, self.mailbox = None # type: Optional[MailBox] @property - def in_box(self) -> Optional[InBox]: + def inbox(self) -> Optional[InBox]: return self.mailbox.inbox if self.mailbox else None @property - def out_box(self) -> Optional[OutBox]: + def outbox(self) -> Optional[OutBox]: return self.mailbox.outbox if self.mailbox else None @property diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index 460c0876..f991c02e 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -148,7 +148,7 @@ def is_expecting_propose(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.CFP + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_initial_accept(self) -> bool: @@ -158,7 +158,7 @@ def is_expecting_initial_accept(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.PROPOSE + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_matching_accept(self) -> bool: @@ -168,7 +168,7 @@ def is_expecting_matching_accept(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.ACCEPT + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT return result def is_expecting_cfp_decline(self) -> bool: @@ -178,7 +178,7 @@ def is_expecting_cfp_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.CFP + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_propose_decline(self) -> bool: @@ -188,7 +188,7 @@ def is_expecting_propose_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.PROPOSE + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_accept_decline(self) -> bool: @@ -198,7 +198,7 @@ def is_expecting_accept_decline(self) -> bool: :return: True if yes, False otherwise. """ last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("type") == FIPAMessage.Performative.ACCEPT + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT return result @@ -268,7 +268,7 @@ def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> b """ assert msg.protocol_id == "fipa" dialogue_id = msg.get("dialogue_id") - destination = msg.to + destination = msg.sender target = msg.get("target") performative = msg.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, agent_pbk) diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index 8049b61d..e1cc6757 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -22,7 +22,7 @@ import logging from tac.agents.v1.base.dialogues import DialogueLabel -from tac.agents.v1.mail.messages import Message, OEFMessage +from tac.agents.v1.mail.messages import Message, OEFMessage, FIPAMessage from tac.helpers.crypto import Crypto from tac.platform.protocol import Response @@ -57,11 +57,22 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: Response.from_pb(byte_content, sender_pbk, crypto) except Exception as e: logger.debug("Not a Controller message: {}".format(str(e))) + try: + byte_content = msg.get("content") + sender_pbk = msg.sender + Response.from_pb(byte_content, sender_pbk, crypto) + except: + pass return False return True +def is_fipa_message(msg: Message) -> bool: + """Chcek whether a message is a FIPA message.""" + return msg.protocol_id == "fipa" and msg.get("performative") in set(FIPAMessage.Performative) + + def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: """ Make a transaction id. diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index a67a605b..f60f7b44 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -27,7 +27,7 @@ from tac.agents.v1.agent import Agent from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler -from tac.agents.v1.base.helpers import is_oef_message, is_controller_message +from tac.agents.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message from tac.agents.v1.base.strategy import Strategy from tac.agents.v1.mail.messages import Message from tac.agents.v1.mail.oef import OEFNetworkMailBox @@ -110,8 +110,10 @@ def react(self) -> None: self.oef_handler.handle_oef_message(msg) elif is_controller_message(msg, self.crypto): self.controller_handler.handle_controller_message(msg) - else: + elif is_fipa_message(msg): self.dialogue_handler.handle_dialogue_message(msg) + else: + logger.warning("Message type not recognized: sender={}".format(msg.sender)) def update(self) -> None: """ diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 96333950..e710ae79 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -171,14 +171,19 @@ def __init__(self, to: Optional[Address] = None, super().__init__(to=to, sender=sender, id=message_id, dialogue_id=dialogue_id, content=content) -class SimpleByteMessage(Message): +class SimpleMessage(Message): protocol_id = "default" + class Type(Enum): + BYTES = "bytes" + ERROR = "error" + def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, - content: bytes = b""): - super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, content=content) + type: Optional[Type] = None, + **body): + super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=type, **body) class FIPAMessage(Message): diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index c892b82c..d2a2ec97 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -139,9 +139,9 @@ def react(self) -> None: :return: None """ counter = 0 - while (not self.in_box.empty() and counter < self.max_reactions): + while (not self.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.in_box.get_nowait() # type: Optional[Message] + msg = self.inbox.get_nowait() # type: Optional[Message] if msg is not None: if is_oef_message(msg): self.oef_handler.handle_oef_message(msg) diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index 2b20c6a9..76abcc6a 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -211,11 +211,10 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: # send the transaction confirmation. tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - - self.controller_agent.out_box.put(ByteMessage(to=tx.public_key, sender=self.controller_agent.crypto.public_key, - message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) - self.controller_agent.out_box.put(ByteMessage(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, - message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) + self.controller_agent.outbox.put(ByteMessage(to=tx.public_key, sender=self.controller_agent.crypto.public_key, + message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) + self.controller_agent.outbox.put(ByteMessage(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, + message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) diff --git a/tac/platform/controller/reactions.py b/tac/platform/controller/reactions.py index a2a3bc51..2796d475 100644 --- a/tac/platform/controller/reactions.py +++ b/tac/platform/controller/reactions.py @@ -26,13 +26,11 @@ import logging -from oef.messages import Message as OEFErrorMessage, DialogueErrorMessage - from tac.agents.v1.agent import Liveness from tac.agents.v1.mail.base import MailBox from tac.agents.v1.mail.messages import Message -from tac.platform.controller.interfaces import OEFReactionInterface from tac.helpers.crypto import Crypto +from tac.platform.controller.interfaces import OEFReactionInterface logger = logging.getLogger(__name__) diff --git a/tac/tac_pb2.py b/tac/tac_pb2.py index 1ec53eea..3de6d28b 100644 --- a/tac/tac_pb2.py +++ b/tac/tac_pb2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: tac.proto @@ -8,6 +7,7 @@ from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -20,10 +20,10 @@ name='tac.proto', package='fetch.oef.pb', syntax='proto3', - serialized_options=None, serialized_pb=_b('\n\ttac.proto\x12\x0c\x66\x65tch.oef.pb\x1a\x1cgoogle/protobuf/struct.proto\"+\n\nStrIntPair\x12\r\n\x05\x66irst\x18\x01 \x01(\t\x12\x0e\n\x06second\x18\x02 \x01(\x05\"+\n\nStrStrPair\x12\r\n\x05\x66irst\x18\x01 \x01(\t\x12\x0e\n\x06second\x18\x02 \x01(\t\"\xe9\n\n\rTACController\x1a\x0c\n\nRegistered\x1a\x0e\n\x0cUnregistered\x1a\x0b\n\tCancelled\x1a\xe2\x01\n\x08GameData\x12\r\n\x05money\x18\x01 \x01(\x01\x12\x11\n\tendowment\x18\x02 \x03(\x05\x12\x16\n\x0eutility_params\x18\x03 \x03(\x01\x12\x11\n\tnb_agents\x18\x04 \x01(\x05\x12\x10\n\x08nb_goods\x18\x05 \x01(\x05\x12\x0e\n\x06tx_fee\x18\x06 \x01(\x01\x12\x33\n\x11\x61gent_pbk_to_name\x18\x07 \x03(\x0b\x32\x18.fetch.oef.pb.StrStrPair\x12\x32\n\x10good_pbk_to_name\x18\x08 \x03(\x0b\x32\x18.fetch.oef.pb.StrStrPair\x1a\x31\n\x17TransactionConfirmation\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x1a{\n\x0bStateUpdate\x12;\n\rinitial_state\x18\x01 \x01(\x0b\x32$.fetch.oef.pb.TACController.GameData\x12/\n\x03txs\x18\x02 \x03(\x0b\x32\".fetch.oef.pb.TACAgent.Transaction\x1a\x93\x03\n\x05\x45rror\x12?\n\nerror_code\x18\x01 \x01(\x0e\x32+.fetch.oef.pb.TACController.Error.ErrorCode\x12\x11\n\terror_msg\x18\x02 \x01(\t\x12(\n\x07\x64\x65tails\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x8b\x02\n\tErrorCode\x12\x11\n\rGENERIC_ERROR\x10\x00\x12\x15\n\x11REQUEST_NOT_VALID\x10\x01\x12 \n\x1c\x41GENT_PBK_ALREADY_REGISTERED\x10\x02\x12!\n\x1d\x41GENT_NAME_ALREADY_REGISTERED\x10\x03\x12\x18\n\x14\x41GENT_NOT_REGISTERED\x10\x04\x12\x19\n\x15TRANSACTION_NOT_VALID\x10\x05\x12\x1c\n\x18TRANSACTION_NOT_MATCHING\x10\x06\x12\x1f\n\x1b\x41GENT_NAME_NOT_IN_WHITELIST\x10\x07\x12\x1b\n\x17\x43OMPETITION_NOT_RUNNING\x10\x08\x1a\xcc\x03\n\x07Message\x12<\n\nregistered\x18\x01 \x01(\x0b\x32&.fetch.oef.pb.TACController.RegisteredH\x00\x12@\n\x0cunregistered\x18\x02 \x01(\x0b\x32(.fetch.oef.pb.TACController.UnregisteredH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.fetch.oef.pb.TACController.CancelledH\x00\x12\x39\n\tgame_data\x18\x04 \x01(\x0b\x32$.fetch.oef.pb.TACController.GameDataH\x00\x12N\n\x0ftx_confirmation\x18\x05 \x01(\x0b\x32\x33.fetch.oef.pb.TACController.TransactionConfirmationH\x00\x12?\n\x0cstate_update\x18\x06 \x01(\x0b\x32\'.fetch.oef.pb.TACController.StateUpdateH\x00\x12\x32\n\x05\x65rror\x18\x07 \x01(\x0b\x32!.fetch.oef.pb.TACController.ErrorH\x00\x42\x05\n\x03msg\x1a\x33\n\rSignedMessage\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x64 \x01(\x0c\"\x89\x04\n\x08TACAgent\x1a\x1e\n\x08Register\x12\x12\n\nagent_name\x18\x01 \x01(\t\x1a\x0c\n\nUnregister\x1a\x88\x01\n\x0bTransaction\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\r\n\x05\x62uyer\x18\x02 \x01(\x08\x12\x14\n\x0c\x63ounterparty\x18\x03 \x01(\t\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x01\x12,\n\nquantities\x18\x05 \x03(\x0b\x32\x18.fetch.oef.pb.StrIntPair\x1a\x10\n\x0eGetStateUpdate\x1a\xfc\x01\n\x07Message\x12\x33\n\x08register\x18\x01 \x01(\x0b\x32\x1f.fetch.oef.pb.TACAgent.RegisterH\x00\x12\x37\n\nunregister\x18\x02 \x01(\x0b\x32!.fetch.oef.pb.TACAgent.UnregisterH\x00\x12\x39\n\x0btransaction\x18\x03 \x01(\x0b\x32\".fetch.oef.pb.TACAgent.TransactionH\x00\x12\x41\n\x10get_state_update\x18\x04 \x01(\x0b\x32%.fetch.oef.pb.TACAgent.GetStateUpdateH\x00\x42\x05\n\x03msg\x1a\x33\n\rSignedMessage\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x64 \x01(\x0c\x62\x06proto3') , dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -35,43 +35,43 @@ values=[ _descriptor.EnumValueDescriptor( name='GENERIC_ERROR', index=0, number=0, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='REQUEST_NOT_VALID', index=1, number=1, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='AGENT_PBK_ALREADY_REGISTERED', index=2, number=2, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='AGENT_NAME_ALREADY_REGISTERED', index=3, number=3, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='AGENT_NOT_REGISTERED', index=4, number=4, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='TRANSACTION_NOT_VALID', index=5, number=5, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='TRANSACTION_NOT_MATCHING', index=6, number=6, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='AGENT_NAME_NOT_IN_WHITELIST', index=7, number=7, - serialized_options=None, + options=None, type=None), _descriptor.EnumValueDescriptor( name='COMPETITION_NOT_RUNNING', index=8, number=8, - serialized_options=None, + options=None, type=None), ], containing_type=None, - serialized_options=None, + options=None, serialized_start=750, serialized_end=1017, ) @@ -91,21 +91,21 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='second', full_name='fetch.oef.pb.StrIntPair.second', index=1, number=2, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -129,21 +129,21 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='second', full_name='fetch.oef.pb.StrStrPair.second', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -167,7 +167,7 @@ nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -190,7 +190,7 @@ nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -213,7 +213,7 @@ nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -236,63 +236,63 @@ has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='endowment', full_name='fetch.oef.pb.TACController.GameData.endowment', index=1, number=2, type=5, cpp_type=1, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='utility_params', full_name='fetch.oef.pb.TACController.GameData.utility_params', index=2, number=3, type=1, cpp_type=5, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='nb_agents', full_name='fetch.oef.pb.TACController.GameData.nb_agents', index=3, number=4, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='nb_goods', full_name='fetch.oef.pb.TACController.GameData.nb_goods', index=4, number=5, type=5, cpp_type=1, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='tx_fee', full_name='fetch.oef.pb.TACController.GameData.tx_fee', index=5, number=6, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='agent_pbk_to_name', full_name='fetch.oef.pb.TACController.GameData.agent_pbk_to_name', index=6, number=7, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='good_pbk_to_name', full_name='fetch.oef.pb.TACController.GameData.good_pbk_to_name', index=7, number=8, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -315,14 +315,14 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -345,21 +345,21 @@ has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='txs', full_name='fetch.oef.pb.TACController.StateUpdate.txs', index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -382,21 +382,21 @@ has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='error_msg', full_name='fetch.oef.pb.TACController.Error.error_msg', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='details', full_name='fetch.oef.pb.TACController.Error.details', index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], @@ -404,7 +404,7 @@ enum_types=[ _TACCONTROLLER_ERROR_ERRORCODE, ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -427,56 +427,56 @@ has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='unregistered', full_name='fetch.oef.pb.TACController.Message.unregistered', index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='cancelled', full_name='fetch.oef.pb.TACController.Message.cancelled', index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='game_data', full_name='fetch.oef.pb.TACController.Message.game_data', index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='tx_confirmation', full_name='fetch.oef.pb.TACController.Message.tx_confirmation', index=4, number=5, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='state_update', full_name='fetch.oef.pb.TACController.Message.state_update', index=5, number=6, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='error', full_name='fetch.oef.pb.TACController.Message.error', index=6, number=7, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -502,21 +502,21 @@ has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='message', full_name='fetch.oef.pb.TACController.SignedMessage.message', index=1, number=100, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -539,7 +539,7 @@ nested_types=[_TACCONTROLLER_REGISTERED, _TACCONTROLLER_UNREGISTERED, _TACCONTROLLER_CANCELLED, _TACCONTROLLER_GAMEDATA, _TACCONTROLLER_TRANSACTIONCONFIRMATION, _TACCONTROLLER_STATEUPDATE, _TACCONTROLLER_ERROR, _TACCONTROLLER_MESSAGE, _TACCONTROLLER_SIGNEDMESSAGE, ], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -563,14 +563,14 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -593,7 +593,7 @@ nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -616,42 +616,42 @@ has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='buyer', full_name='fetch.oef.pb.TACAgent.Transaction.buyer', index=1, number=2, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='counterparty', full_name='fetch.oef.pb.TACAgent.Transaction.counterparty', index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='amount', full_name='fetch.oef.pb.TACAgent.Transaction.amount', index=3, number=4, type=1, cpp_type=5, label=1, has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='quantities', full_name='fetch.oef.pb.TACAgent.Transaction.quantities', index=4, number=5, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -674,7 +674,7 @@ nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -697,35 +697,35 @@ has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='unregister', full_name='fetch.oef.pb.TACAgent.Message.unregister', index=1, number=2, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='transaction', full_name='fetch.oef.pb.TACAgent.Message.transaction', index=2, number=3, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='get_state_update', full_name='fetch.oef.pb.TACAgent.Message.get_state_update', index=3, number=4, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -751,21 +751,21 @@ has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), _descriptor.FieldDescriptor( name='message', full_name='fetch.oef.pb.TACAgent.SignedMessage.message', index=1, number=100, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -788,7 +788,7 @@ nested_types=[_TACAGENT_REGISTER, _TACAGENT_UNREGISTER, _TACAGENT_TRANSACTION, _TACAGENT_GETSTATEUPDATE, _TACAGENT_MESSAGE, _TACAGENT_SIGNEDMESSAGE, ], enum_types=[ ], - serialized_options=None, + options=None, is_extendable=False, syntax='proto3', extension_ranges=[], @@ -869,7 +869,6 @@ DESCRIPTOR.message_types_by_name['StrStrPair'] = _STRSTRPAIR DESCRIPTOR.message_types_by_name['TACController'] = _TACCONTROLLER DESCRIPTOR.message_types_by_name['TACAgent'] = _TACAGENT -_sym_db.RegisterFileDescriptor(DESCRIPTOR) StrIntPair = _reflection.GeneratedProtocolMessageType('StrIntPair', (_message.Message,), dict( DESCRIPTOR = _STRINTPAIR, From e184a741d900005cab3ca20d60c0a1a486bf944b Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 21:35:21 +0100 Subject: [PATCH 027/107] Fix linting errors. --- scripts/stack_tracer.py | 4 +- tac/agents/v1/agent.py | 3 +- tac/agents/v1/base/dialogues.py | 8 +- tac/agents/v1/base/helpers.py | 12 +- tac/agents/v1/base/negotiation_behaviours.py | 2 +- tac/agents/v1/base/participant_agent.py | 2 +- tac/agents/v1/base/reactions.py | 4 +- tac/agents/v1/mail/__init__.py | 23 ++- tac/agents/v1/mail/base.py | 39 ++-- tac/agents/v1/mail/messages.py | 79 +++++++- tac/agents/v1/mail/oef.py | 188 ++++++++++++++++--- tac/platform/controller/handlers.py | 1 + templates/v1/expert.py | 2 +- tests/test_agent/test_agent_state.py | 2 + tests/test_agent/test_misc.py | 4 +- tests/test_mail.py | 4 +- 16 files changed, 309 insertions(+), 68 deletions(-) diff --git a/scripts/stack_tracer.py b/scripts/stack_tracer.py index bc963a69..d63a4ec6 100644 --- a/scripts/stack_tracer.py +++ b/scripts/stack_tracer.py @@ -42,9 +42,7 @@ def stacktraces() -> str: - """ - Tracks stack traces. - """ + """Tracks stack traces.""" print('Stacktraces captured!') code = [] for threadId, stack in sys._current_frames().items(): diff --git a/tac/agents/v1/agent.py b/tac/agents/v1/agent.py index 47c4275d..7e24ce3c 100644 --- a/tac/agents/v1/agent.py +++ b/tac/agents/v1/agent.py @@ -28,7 +28,6 @@ from typing import Optional from tac.agents.v1.mail.base import InBox, OutBox, MailBox -from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.helpers.crypto import Crypto logger = logging.getLogger(__name__) @@ -87,10 +86,12 @@ def __init__(self, name: str, @property def inbox(self) -> Optional[InBox]: + """Get the inbox.""" return self.mailbox.inbox if self.mailbox else None @property def outbox(self) -> Optional[OutBox]: + """Get the outbox.""" return self.mailbox.outbox if self.mailbox else None @property diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index f991c02e..c95438f9 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -251,10 +251,10 @@ def is_permitted_for_new_dialogue(self, msg: Message, known_pbks: List[str]) -> performative = msg.get("performative") result = protocol == "fipa"\ - and performative == FIPAMessage.Performative.CFP \ - and msg_id == STARTING_MESSAGE_ID\ - and target == STARTING_MESSAGE_TARGET \ - and (msg.sender in known_pbks) + and performative == FIPAMessage.Performative.CFP \ + and msg_id == STARTING_MESSAGE_ID\ + and target == STARTING_MESSAGE_TARGET \ + and (msg.sender in known_pbks) return result def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> bool: diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index e1cc6757..c6e9f826 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -57,12 +57,12 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: Response.from_pb(byte_content, sender_pbk, crypto) except Exception as e: logger.debug("Not a Controller message: {}".format(str(e))) - try: - byte_content = msg.get("content") - sender_pbk = msg.sender - Response.from_pb(byte_content, sender_pbk, crypto) - except: - pass + # try: + # byte_content = msg.get("content") + # sender_pbk = msg.sender + # Response.from_pb(byte_content, sender_pbk, crypto) + # except: + # pass return False return True diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index 9605f429..e0046daa 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -23,7 +23,7 @@ import json import logging import pprint -from typing import Union, List +from typing import List from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.base.game_instance import GameInstance diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index f60f7b44..6613a9bc 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -22,7 +22,7 @@ import logging import time -from typing import Optional, Union +from typing import Optional from tac.agents.v1.agent import Agent from tac.agents.v1.base.game_instance import GameInstance, GamePhase diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 5783c620..1251657c 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -28,7 +28,7 @@ import json import logging -from typing import List, Union +from typing import List from tac.agents.v1.agent import Liveness from tac.agents.v1.base.dialogues import Dialogue @@ -402,7 +402,7 @@ def on_unidentified_dialogue(self, msg: Message) -> None: """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) result = ByteMessage(to=msg.sender, sender=self.crypto.public_key, message_id=msg.get("id") + 1, - dialogue_id=msg.get("dialogue_id"), content=b'This message belongs to an unidentified dialogue.') + dialogue_id=msg.get("dialogue_id"), content=b'This message belongs to an unidentified dialogue.') self.mailbox.outbox.put(result) def _handle(self, msg: Message, dialogue: Dialogue) -> List[Message]: diff --git a/tac/agents/v1/mail/__init__.py b/tac/agents/v1/mail/__init__.py index 7c68785e..4d952845 100644 --- a/tac/agents/v1/mail/__init__.py +++ b/tac/agents/v1/mail/__init__.py @@ -1 +1,22 @@ -# -*- coding: utf-8 -*- \ No newline at end of file +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the mail modules.""" diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py index 52b0aae6..d8b59ba3 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/agents/v1/mail/base.py @@ -18,12 +18,10 @@ # # ------------------------------------------------------------------------------ -""" -Mail module v2. -""" +"""Mail module abstract base classes.""" import logging -from abc import abstractmethod, ABC +from abc import abstractmethod from queue import Queue from typing import Optional @@ -36,7 +34,11 @@ class InBox(object): """A queue from where you can only consume messages.""" def __init__(self, queue: Queue): - """Initialize the inbox.""" + """ + Initialize the inbox. + + :param queue: the queue. + """ super().__init__() self._queue = queue @@ -62,7 +64,7 @@ def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[M logger.debug("Incoming message type: type={}".format(type(msg))) return msg - def get_nowait(self): + def get_nowait(self) -> Optional[Message]: """ Check for a message on the in queue and wait for no time. @@ -76,7 +78,11 @@ class OutBox(object): """A queue from where you can only enqueue messages.""" def __init__(self, queue: Queue) -> None: - """Initialize the outbox.""" + """ + Initialize the outbox. + + :param queue: the queue. + """ super().__init__() self._queue = queue @@ -89,14 +95,21 @@ def empty(self) -> bool: return self._queue.empty() def put(self, item: Message) -> None: - """Put an item into the queue.""" + """ + Put an item into the queue. + + :param item: the message. + :return: None + """ logger.debug("Put a message in the queue...") self._queue.put(item) class Connection: + """Abstract definition of a connection.""" def __init__(self): + """Initialize the connection.""" self.in_queue = Queue() self.out_queue = Queue() @@ -133,12 +146,14 @@ def is_connected(self) -> bool: """Check whether the mailbox is processing messages.""" return self._connection.is_established - def connect(self): + def connect(self) -> None: + """Connect.""" self._connection.connect() - def disconnect(self): + def disconnect(self) -> None: + """Disconnect.""" self._connection.disconnect() - def send(self, out: Message): + def send(self, out: Message) -> None: + """Send.""" self.outbox.put(out) - diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index e710ae79..8502f3c8 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -35,11 +35,19 @@ class Message: + """The message class.""" def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, protocol_id: Optional[ProtocolId] = None, **body): + """ + Initialize. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param protocol_id: the protocol id. + """ self._to = to self._sender = sender self._protocol_id = protocol_id @@ -47,46 +55,64 @@ def __init__(self, to: Optional[Address] = None, @property def to(self) -> Address: + """Get public key of receiver.""" return self._to @to.setter - def to(self, to: Address): + def to(self, to: Address) -> None: + """Set public key of receiver.""" self._to = to @property def sender(self) -> Address: + """Get public key of sender.""" return self._sender @sender.setter - def sender(self, sender: Address): + def sender(self, sender: Address) -> None: + """Set public key of sender.""" self._sender = sender @property def protocol_id(self) -> Optional[ProtocolId]: + """Get protocol id.""" return self._protocol_id def set(self, key: str, value: Any) -> None: + """ + Set key and value pair. + + :param key: the key. + :param value: the value. + :return: None + """ self._body[key] = value def get(self, key: str) -> Optional[Any]: + """Get value for key.""" return self._body.get(key, None) def unset(self, key: str) -> None: + """Unset valye for key.""" self._body.pop(key, None) def is_set(self, key: str) -> bool: + """Check value is set for key.""" return key in self._body def check_consistency(self) -> bool: - """Check that the data are consistent.""" + """Check that the data is consistent.""" return True class OEFMessage(Message): + """The OEF message class.""" protocol_id = "oef" class Type(Enum): + """OEF Message types.""" + REGISTER_SERVICE = "register_service" REGISTER_AGENT = "register_agent" UNREGISTER_SERVICE = "unregister_service" @@ -95,16 +121,23 @@ class Type(Enum): SEARCH_AGENTS = "search_agents" OEF_ERROR = "oef_error" DIALOGUE_ERROR = "dialogue_error" - SEARCH_RESULT = "search_result" def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, oef_type: Optional[Type] = None, **body): + """ + Initialize. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param protocol_id: the protocol id. + """ super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=oef_type, **body) def check_consistency(self) -> bool: + """Check that the data is consistent.""" try: assert self.is_set("type") oef_type = OEFMessage.Type(self.get("type")) @@ -160,6 +193,7 @@ def check_consistency(self) -> bool: class ByteMessage(Message): + """The Byte message class.""" protocol_id = "bytes" @@ -168,14 +202,26 @@ def __init__(self, to: Optional[Address] = None, message_id: Optional[int] = None, dialogue_id: Optional[int] = None, content: bytes = b""): + """ + Initialize. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param message_id: the message id. + :param dialogue_id: the dialogue id. + :param content: the message content. + """ super().__init__(to=to, sender=sender, id=message_id, dialogue_id=dialogue_id, content=content) class SimpleMessage(Message): + """The Simple message class.""" protocol_id = "default" class Type(Enum): + """Simple message types.""" + BYTES = "bytes" ERROR = "error" @@ -183,14 +229,24 @@ def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, type: Optional[Type] = None, **body): + """ + Initialize. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param type: the type. + """ super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=type, **body) class FIPAMessage(Message): + """The FIPA message class.""" protocol_id = "fipa" class Performative(Enum): + """FIPA performatives.""" + CFP = "cfp" PROPOSE = "propose" ACCEPT = "accept" @@ -199,21 +255,32 @@ class Performative(Enum): def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, - msg_id: Optional[int] = None, + message_id: Optional[int] = None, dialogue_id: Optional[DialogueLabel] = None, target: Optional[int] = None, performative: Optional[Union[str, Performative]] = None, **body): + """ + Initialize. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param message_id: the message id. + :param dialogue_id: the dialogue id. + :param target: the message target. + :param performative: the message performative. + """ super().__init__(to, sender, protocol_id=self.protocol_id, - id=msg_id, + id=message_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative(performative), **body) def check_consistency(self) -> bool: + """Check that the data is consistent.""" try: assert self.is_set("target") performative = FIPAMessage.Performative(self.get("performative")) diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 3aa1d3d9..f8103c58 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -24,7 +24,7 @@ import logging from queue import Empty, Queue from threading import Thread -from typing import List, Union, Dict +from typing import List, Dict from oef.agents import Agent from oef.core import OEFProxy @@ -84,16 +84,37 @@ def search_end(self, search_id: int, nb_search_results: int) -> None: class OEFChannel(Agent): + """The OEFChannel connects the OEF Agent with the connection.""" def __init__(self, oef_proxy: OEFProxy, in_queue: Queue): + """ + Initialize. + + :param oef_proxy: the OEFProxy. + :param in_queue: the in queue. + """ super().__init__(oef_proxy) self.in_queue = in_queue self.mail_stats = MailStats() - def is_connected(self): + def is_connected(self) -> bool: + """Get connected status.""" return self._oef_proxy.is_connected() - def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): + def is_active(self) -> bool: + """Get active status.""" + return self._oef_proxy._active_loop + + def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: + """ + On message event handler. + + :param msg_id: the message id. + :param dialogue_id: the dialogue id. + :param origin: the public key of the sender. + :param content: the bytes content. + :return: None + """ msg = ByteMessage(to=self.public_key, sender=origin, message_id=msg_id, @@ -101,7 +122,17 @@ def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) content=content) self.in_queue.put(msg) - def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES): + def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, 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 public key of the sender. + :param target: the message target. + :param query: the query. + :return: None + """ msg = FIPAMessage(to=self.public_key, sender=origin, msg_id=msg_id, @@ -111,7 +142,17 @@ def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: query=query) self.in_queue.put(msg) - def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES): + def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: + """ + On propose event handler. + + :param msg_id: the message id. + :param dialogue_id: the dialogue id. + :param origin: the public key of the sender. + :param target: the message target. + :param proposals: the proposals. + :return: None + """ msg = FIPAMessage(to=self.public_key, sender=origin, msg_id=msg_id, @@ -121,7 +162,16 @@ def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, pr proposal=proposals) self.in_queue.put(msg) - def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int): + def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: + """ + On accept event handler. + + :param msg_id: the message id. + :param dialogue_id: the dialogue id. + :param origin: the public key of the sender. + :param target: the message target. + :return: None + """ msg = FIPAMessage(to=self.public_key, sender=origin, msg_id=msg_id, @@ -130,7 +180,16 @@ def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int): performative=FIPAMessage.Performative.ACCEPT) self.in_queue.put(msg) - def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int): + def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: + """ + On decline event handler. + + :param msg_id: the message id. + :param dialogue_id: the dialogue id. + :param origin: the public key of the sender. + :param target: the message target. + :return: None + """ msg = FIPAMessage(to=self.public_key, sender=origin, msg_id=msg_id, @@ -139,7 +198,14 @@ def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int): performative=FIPAMessage.Performative.DECLINE) self.in_queue.put(msg) - def on_search_result(self, search_id: int, agents: List[str]): + def on_search_result(self, search_id: int, agents: List[str]) -> None: + """ + On accept event handler. + + :param search_id: the search id. + :param agents: the list of agents. + :return: None + """ self.mail_stats.search_end(search_id, len(agents)) msg = OEFMessage(to=self.public_key, sender=None, @@ -148,7 +214,14 @@ def on_search_result(self, search_id: int, agents: List[str]): agents=agents) self.in_queue.put(msg) - def on_oef_error(self, answer_id: int, operation: OEFErrorOperation): + def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: + """ + On oef error event handler. + + :param answer_id: the answer id. + :param operation: the error operation. + :return: None + """ msg = OEFMessage(to=self.public_key, sender=None, oef_type=OEFMessage.Type.OEF_ERROR, @@ -156,7 +229,15 @@ def on_oef_error(self, answer_id: int, operation: OEFErrorOperation): operation=operation) self.in_queue.put(msg) - def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str): + def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: + """ + On dialogue error event handler. + + :param answer_id: the answer id. + :param dialogue_id: the dialogue id. + :param origin: the message sender. + :return: None + """ msg = OEFMessage(to=self.public_key, sender=None, oef_type=OEFMessage.Type.DIALOGUE_ERROR, @@ -165,7 +246,13 @@ def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str): origin=origin) self.in_queue.put(msg) - def send(self, msg: Message): + def send(self, msg: Message) -> None: + """ + Send message handler. + + :param msg: the message. + :return: None + """ if msg.protocol_id == "oef": self.send_oef_message(msg) elif msg.protocol_id == "fipa": @@ -177,7 +264,13 @@ def send(self, msg: Message): else: raise ValueError("Cannot send message.") - def send_oef_message(self, msg: Message): + def send_oef_message(self, msg: Message) -> None: + """ + Send oef message handler. + + :param msg: the message. + :return: None + """ oef_type = msg.get("type") if oef_type == OEFMessage.Type.REGISTER_SERVICE: id = msg.get("id") @@ -208,7 +301,13 @@ def send_oef_message(self, msg: Message): else: raise ValueError("OEF request not recognized.") - def send_fipa_message(self, msg: Message): + def send_fipa_message(self, msg: Message) -> None: + """ + Send fipa message handler. + + :param msg: the message. + :return: None + """ id = msg.get("id") dialogue_id = msg.get("dialogue_id") destination = msg.to @@ -229,13 +328,16 @@ def send_fipa_message(self, msg: Message): else: raise ValueError("OEF FIPA message not recognized.") - def is_active(self) -> bool: - return self._oef_proxy._active_loop - class OEFConnection(Connection): + """The OEFConnection connects the to the mailbox.""" def __init__(self, oef_proxy: OEFProxy): + """ + Initialize. + + :param oef_proxy: the OEFProxy + """ super().__init__() self.bridge = OEFChannel(oef_proxy, self.in_queue) @@ -244,22 +346,37 @@ def __init__(self, oef_proxy: OEFProxy): self.in_thread = Thread(target=self.bridge.run) self.out_thread = Thread(target=self._fetch) - def _fetch(self): + def _fetch(self) -> None: + """ + Fetch the messages from the outqueue and send them. + + :return: None + """ while not self._stopped: try: msg = self.out_queue.get(block=True, timeout=1.0) - self.send(msg) + self._send(msg) except Empty: pass - def connect(self): + def connect(self) -> None: + """ + Connect to the bridge. + + :return: None + """ if self._stopped: self._stopped = False self.bridge.connect() self.in_thread.start() self.out_thread.start() - def disconnect(self): + def disconnect(self) -> None: + """ + Disconnect from the bridge. + + :return: None + """ if self.bridge.is_active(): self.bridge.stop() @@ -272,24 +389,45 @@ def disconnect(self): @property def is_established(self) -> bool: + """Get the connection status.""" return self.bridge.is_connected() - def send(self, msg: Message): + def _send(self, msg: Message) -> None: + """ + Send messages. + + :return: None + """ self.bridge.send(msg) class OEFMailBox(MailBox): + """The OEF mail box.""" + + def __init__(self, oef_proxy: OEFProxy): + """ + Initialize. - def __init__(self, proxy: OEFProxy): - connection = OEFConnection(proxy) + :param oef_proxy: the oef proxy. + """ + connection = OEFConnection(oef_proxy) super().__init__(connection) @property def mail_stats(self) -> MailStats: + """Get the mail stats object.""" return self._connection.bridge.mail_stats class OEFNetworkMailBox(OEFMailBox): + """The OEF network mail box.""" + + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): + """ + Initialize. - def __init__(self, public_key: str, oef_addr: str, port: int = 10000): - super().__init__(OEFNetworkProxy(public_key, oef_addr, port, loop=asyncio.new_event_loop())) + :param public_key: the public key of the agent. + :param oef_addr: the OEF address. + :param oef_port: the oef port. + """ + super().__init__(OEFNetworkProxy(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop())) diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index 76abcc6a..e23b9709 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -61,6 +61,7 @@ logger = logging.getLogger(__name__) + class RequestHandler(ABC): """Abstract class for a request handler.""" diff --git a/templates/v1/expert.py b/templates/v1/expert.py index 0d5940eb..46c796d0 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -26,7 +26,7 @@ from typing import Optional from tac.agents.v1.agent import Agent -from tac.agents.v1.mail import FIPAMailBox, InBox, OutBox +from tac.agents.v1.mail import FIPAMailBox logger = logging.getLogger(__name__) diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 1a5c9626..de1d5c7a 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -36,9 +36,11 @@ def __init__(self): self.mailbox = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: + """Setup.""" pass def teardown(self) -> None: + """Teardown.""" pass def act(self) -> None: diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 3246dcf6..cdf95962 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -20,10 +20,9 @@ """Test miscellaneous features for the agent module.""" from threading import Timer -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock from tac.agents.v1.agent import Agent -from tac.agents.v1.mail.messages import ByteMessage from tac.agents.v1.mail.oef import OEFNetworkMailBox @@ -71,4 +70,3 @@ def test_that_when_debug_flag_true_we_can_run_main_loop_without_oef(): test_agent.act.assert_called() test_agent.react.assert_called() test_agent.update.assert_called() - diff --git a/tests/test_mail.py b/tests/test_mail.py index 593b64f0..d5912365 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,12 +20,12 @@ """This module contains tests for the mail module.""" import time -from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, ByteMessage +from tac.agents.v1.mail.messages import FIPAMessage, ByteMessage # OEFMessage from tac.agents.v1.mail.oef import OEFNetworkMailBox def test_example(network_node): - + """Test the mailbox.""" mailbox1 = OEFNetworkMailBox("mailbox1", "127.0.0.1", 10000) mailbox2 = OEFNetworkMailBox("mailbox2", "127.0.0.1", 10000) From 4ae836e05823d5faabcf701b9bf1ad0d139f33b1 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Fri, 16 Aug 2019 21:41:54 +0100 Subject: [PATCH 028/107] Small class import error fix. --- templates/v1/expert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/v1/expert.py b/templates/v1/expert.py index 46c796d0..33166729 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -26,7 +26,7 @@ from typing import Optional from tac.agents.v1.agent import Agent -from tac.agents.v1.mail import FIPAMailBox +from tac.agents.v1.mail.oef import OEFNetworkMailBox logger = logging.getLogger(__name__) @@ -49,7 +49,7 @@ class MyAgent(Agent): def __init__(self, name: str, oef_addr: str, oef_port: int, agent_timeout: float = 1.0, private_key_pem_path: Optional[str] = None): """Agent initialization.""" super().__init__(name, oef_addr, oef_port, private_key_pem_path, agent_timeout) - self.mailbox = FIPAMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) raise NotImplementedError("Your agent must implement the interface defined in Agent.") From 74dff91ff84c5d42f6d114bdd58d1b773c86d6a4 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sat, 17 Aug 2019 12:21:26 +0100 Subject: [PATCH 029/107] fix more bugs. Some bugs introduced by bad argument passing when building Message objects. --- sandbox/playground.py | 2 +- tac/agents/v1/base/dialogues.py | 12 ++++---- tac/agents/v1/base/negotiation_behaviours.py | 32 ++++++++++---------- tac/agents/v1/base/reactions.py | 4 ++- tac/agents/v1/mail/oef.py | 8 ++--- tac/platform/controller/controller_agent.py | 3 +- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index 6bf56ce8..1e153e8e 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -132,7 +132,7 @@ def launch_oef(): starting_message_target = 0 services = agent_one.game_instance.build_services_dict(is_supply=is_seller) # type: Dict cfp = FIPAMessage(agent_two.crypto.public_key, - msg_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, + message_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, target=starting_message_target, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) dialogue.outgoing_extend([cfp]) diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index c95438f9..1e8ed08d 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -268,11 +268,11 @@ def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> b """ assert msg.protocol_id == "fipa" dialogue_id = msg.get("dialogue_id") - destination = msg.sender + opponent = msg.sender target = msg.get("target") performative = msg.get("performative") - self_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, agent_pbk) - other_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, destination) + self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) + other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) result = False if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] @@ -307,11 +307,11 @@ def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: """ assert msg.protocol_id == "fipa" dialogue_id = msg.get("dialogue_id") - destination = msg.to + opponent = msg.sender target = msg.get("target") performative = msg.get("performative") - self_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, agent_pbk) - other_initiated_dialogue_label = DialogueLabel(dialogue_id, destination, destination) + self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) + other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] elif performative == FIPAMessage.Performative.ACCEPT: diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index e0046daa..ccc4a378 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -101,7 +101,7 @@ def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Message: "origin": cfp.sender, "target": cfp.get("target") }))) - response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, msg_id=new_msg_id, + response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id")) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) else: @@ -122,7 +122,7 @@ def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Message: "propose": proposal.values }))) response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, performative=FIPAMessage.Performative.PROPOSE, - msg_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) + message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) return response def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: @@ -135,7 +135,7 @@ def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: :return: an Accept or a Decline """ logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) - assert propose.protocol_id == "fipa" and propose.get("type") == FIPAMessage.Performative.PROPOSE + assert propose.protocol_id == "fipa" and propose.get("performative") == FIPAMessage.Performative.PROPOSE proposal = propose.get("proposal")[0] transaction_id = generate_transaction_id(self.crypto.public_key, propose.sender, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, @@ -151,13 +151,13 @@ def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: logger.debug("[{}]: Accepting propose (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance(dialogue.dialogue_label, new_msg_id, transaction) - result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, msg_id=propose.get("id"), - dialogue_id=propose.get("dialogue_id"), target=propose.get("target"), + result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, message_id=new_msg_id, + dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT) else: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) - result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, msg_id=propose.get("id"), - dialogue_id=propose.get("dialogue_id"), target=propose.get("target"), + result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, message_id=new_msg_id, + dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result @@ -171,7 +171,7 @@ def on_decline(self, decline: Message, dialogue: Dialogue) -> None: :return: None """ - assert decline.protocol_id == "fipa" and decline.get("type") == FIPAMessage.Performative.DECLINE + assert decline.protocol_id == "fipa" and decline.get("performative") == FIPAMessage.Performative.DECLINE logger.debug("[{}]: on_decline: msg_id={}, dialogue_id={}, origin={}, target={}" .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), decline.sender, decline.get("target"))) target = decline.get("target") @@ -179,12 +179,12 @@ def on_decline(self, decline: Message, dialogue: Dialogue) -> None: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) elif target == 2: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) - transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, decline.target) + transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, target) if self.game_instance.strategy.is_world_modeling: self.game_instance.world_state.update_on_declined_propose(transaction) elif target == 3: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) - transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, decline.target) + transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, target) self.game_instance.transaction_manager.pop_locked_tx(transaction.transaction_id) return None @@ -196,9 +196,9 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: :param accept: the accept :param dialogue: the dialogue - :return: a Deline or an Accept and a Transaction (in OutContainer) or a Transaction (in OutContainer) + :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ - assert accept.protocol_id == "fipa" and accept.get("type") == FIPAMessage.Performative.ACCEPT + assert accept.protocol_id == "fipa" and accept.get("performative") == FIPAMessage.Performative.ACCEPT logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), accept.sender, accept.get("target"))) @@ -217,7 +217,7 @@ def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Messag :param accept: the accept :param dialogue: the dialogue - :return: a Deline or an Accept and a Transaction (in OutContainer + :return: a Decline or an Accept and a Transaction (in OutContainer """ transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) new_msg_id = accept.get("id") + 1 @@ -233,12 +233,12 @@ def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Messag message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), content=transaction.serialize())) results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, - msg_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), - performative=FIPAMessage.Performative.MATCH_ACCEPT)) + message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), + performative=FIPAMessage.Performative.ACCEPT)) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, - msg_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), + message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 1251657c..69522107 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -302,7 +302,7 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel return for agent_pbk in agent_pbks: dialogue = self.game_instance.dialogues.create_self_initiated(agent_pbk, self.crypto.public_key, not is_searching_for_sellers) - cfp = FIPAMessage(to=agent_pbk, sender=self.crypto.public_key, msg_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, + cfp = FIPAMessage(to=agent_pbk, sender=self.crypto.public_key, message_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, target=STARTING_MESSAGE_TARGET, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), cfp.to, cfp.get("target"), services)) @@ -428,6 +428,8 @@ def _handle(self, msg: Message, dialogue: Dialogue) -> List[Message]: elif performative == FIPAMessage.Performative.DECLINE: self.negotiation_behaviour.on_decline(msg, dialogue) results = [] + else: + raise ValueError("Performative not supported: {}".format(str(performative))) dialogue.outgoing_extend(results) return results diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index f8103c58..3c4f22b8 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -135,7 +135,7 @@ def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: """ msg = FIPAMessage(to=self.public_key, sender=origin, - msg_id=msg_id, + message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.CFP, @@ -155,7 +155,7 @@ def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, pr """ msg = FIPAMessage(to=self.public_key, sender=origin, - msg_id=msg_id, + message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.PROPOSE, @@ -174,7 +174,7 @@ def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> """ msg = FIPAMessage(to=self.public_key, sender=origin, - msg_id=msg_id, + message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.ACCEPT) @@ -192,7 +192,7 @@ def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> """ msg = FIPAMessage(to=self.public_key, sender=origin, - msg_id=msg_id, + message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.DECLINE) diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index d2a2ec97..fd889419 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -192,7 +192,8 @@ def setup(self) -> None: def teardown(self) -> None: """Tear down the agent.""" - self.game_handler.monitor.stop() + if self.game_handler.monitor.is_running: + self.game_handler.monitor.stop() self.game_handler.simulation_dump() From afe4dcbf8dee02b0165ec602e25e53f1c556c8e2 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sat, 17 Aug 2019 13:21:55 +0100 Subject: [PATCH 030/107] fix negotiation behaviours some performative fields of outgoing messages were not correct. --- tac/agents/v1/base/negotiation_behaviours.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index ccc4a378..982ab945 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -158,7 +158,7 @@ def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), - performative=FIPAMessage.Performative.ACCEPT) + performative=FIPAMessage.Performative.DECLINE) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result @@ -234,7 +234,7 @@ def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Messag content=transaction.serialize())) results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), - performative=FIPAMessage.Performative.ACCEPT)) + performative=FIPAMessage.Performative.MATCH_ACCEPT)) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, From 7097c760b94b2400e21d2eb952c1098cfd521244 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sat, 17 Aug 2019 13:34:44 +0100 Subject: [PATCH 031/107] address PR comment. --- tac/agents/v1/base/reactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 69522107..8607488a 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -401,7 +401,8 @@ def on_unidentified_dialogue(self, msg: Message) -> None: :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - result = ByteMessage(to=msg.sender, sender=self.crypto.public_key, message_id=msg.get("id") + 1, + message_id = msg.get("id") if msg.get("id") is not None else 1 + result = ByteMessage(to=msg.sender, sender=self.crypto.public_key, message_id=message_id + 1, dialogue_id=msg.get("dialogue_id"), content=b'This message belongs to an unidentified dialogue.') self.mailbox.outbox.put(result) From c07fe4155a4efa03e777cf287d56c15766c84f94 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sat, 17 Aug 2019 23:19:50 +0100 Subject: [PATCH 032/107] Fully implement match accept performative --- tac/agents/v1/base/dialogues.py | 8 +++- tac/agents/v1/base/negotiation_behaviours.py | 40 ++++++++------------ tac/agents/v1/base/reactions.py | 2 + tac/agents/v1/mail/oef.py | 3 +- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index 1e8ed08d..e6bc90e2 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -281,7 +281,8 @@ def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> b if target == 2 and other_initiated_dialogue_label in self.dialogues: other_initiated_dialogue = self.dialogues[other_initiated_dialogue_label] result = other_initiated_dialogue.is_expecting_initial_accept() - elif target == 3 and self_initiated_dialogue_label in self.dialogues: + elif performative == FIPAMessage.Performative.MATCH_ACCEPT: + if target == 3 and self_initiated_dialogue_label in self.dialogues: self_initiated_dialogue = self.dialogues[self_initiated_dialogue_label] result = self_initiated_dialogue.is_expecting_matching_accept() elif performative == FIPAMessage.Performative.DECLINE: @@ -317,7 +318,10 @@ def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: elif performative == FIPAMessage.Performative.ACCEPT: if target == 2 and other_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[other_initiated_dialogue_label] - elif target == 3 and self_initiated_dialogue_label in self.dialogues: + else: + raise ValueError('Should have found dialogue.') + elif performative == FIPAMessage.Performative.MATCH_ACCEPT: + if target == 3 and self_initiated_dialogue_label in self.dialogues: dialogue = self.dialogues[self_initiated_dialogue_label] else: raise ValueError('Should have found dialogue.') diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index 982ab945..6532c933 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -198,30 +198,15 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ - assert accept.protocol_id == "fipa" and accept.get("performative") == FIPAMessage.Performative.ACCEPT + assert accept.protocol_id == "fipa" \ + and accept.get("performative") == FIPAMessage.Performative.ACCEPT \ + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ + and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), accept.sender, accept.get("target"))) - - target = accept.get("target") - if dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ - and target in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label]: - results = self._on_match_accept(accept, dialogue) - else: - results = self._on_initial_accept(accept, dialogue) - return results - - def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: - """ - Handle an initial Accept. - - :param accept: the accept - :param dialogue: the dialogue - - :return: a Decline or an Accept and a Transaction (in OutContainer - """ - transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) new_msg_id = accept.get("id") + 1 results = [] + transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) is_profitable_transaction, message = self.game_instance.is_profitable_transaction(transaction, dialogue) logger.debug(message) if is_profitable_transaction: @@ -244,19 +229,24 @@ def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Messag self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results - def _on_match_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: + def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Message]: """ Handle a matching Accept. - :param accept: the accept + :param match_accept: the match_accept :param dialogue: the dialogue :return: a Transaction """ - logger.debug("[{}]: on match accept".format(self.agent_name)) + assert match_accept.protocol_id == "fipa" \ + and match_accept.get("performative") == FIPAMessage.Performative.MATCH_ACCEPT \ + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ + and match_accept.get("target") in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label] + logger.debug("[{}]: on_match_accept: msg_id={}, dialogue_id={}, origin={}, target={}" + .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), match_accept.sender, match_accept.get("target"))) results = [] - transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, accept.get("target")) + transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, match_accept.get("target")) results.append(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), + message_id=STARTING_MESSAGE_ID, dialogue_id=match_accept.get("dialogue_id"), content=transaction.serialize())) return results diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 8607488a..d73b314a 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -426,6 +426,8 @@ def _handle(self, msg: Message, dialogue: Dialogue) -> List[Message]: results = [result] elif performative == FIPAMessage.Performative.ACCEPT: results = self.negotiation_behaviour.on_accept(msg, dialogue) + elif performative == FIPAMessage.Performative.MATCH_ACCEPT: + results = self.negotiation_behaviour.on_match_accept(msg, dialogue) elif performative == FIPAMessage.Performative.DECLINE: self.negotiation_behaviour.on_decline(msg, dialogue) results = [] diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 3c4f22b8..48ef6608 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -172,12 +172,13 @@ def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> :param target: the message target. :return: None """ + performative = FIPAMessage.Performative.MATCH_ACCEPT if msg_id == 4 and target == 3 else FIPAMessage.Performative.ACCEPT msg = FIPAMessage(to=self.public_key, sender=origin, message_id=msg_id, dialogue_id=dialogue_id, target=target, - performative=FIPAMessage.Performative.ACCEPT) + performative=performative) self.in_queue.put(msg) def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: From fd6fcc0e28db68c1ca03e141fdb84ceace10e018 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sat, 17 Aug 2019 20:28:20 +0100 Subject: [PATCH 033/107] wip --- tac/agents/v1/helpers/__init__.py | 0 tac/agents/v1/helpers/local_oef.py | 0 tac/agents/v1/mail/messages.py | 32 ++++- tac/agents/v1/mail/protocol.py | 128 ++++++++++++++++++ tac/agents/v1/protocols/__init__.py | 22 +++ tac/agents/v1/protocols/base.proto | 12 ++ tac/agents/v1/protocols/base_pb2.py | 93 +++++++++++++ tac/agents/v1/protocols/fipa/fipa.proto | 17 +++ tac/agents/v1/protocols/oef/oef.proto | 10 ++ tac/agents/v1/protocols/simple/__init__.py | 0 .../v1/protocols/simple/serialization.py | 79 +++++++++++ tests/test_messages/__init__.py | 0 tests/test_messages/test_base.py | 42 ++++++ tests/test_messages/test_simple.py | 44 ++++++ 14 files changed, 474 insertions(+), 5 deletions(-) create mode 100644 tac/agents/v1/helpers/__init__.py create mode 100644 tac/agents/v1/helpers/local_oef.py create mode 100644 tac/agents/v1/mail/protocol.py create mode 100644 tac/agents/v1/protocols/__init__.py create mode 100644 tac/agents/v1/protocols/base.proto create mode 100644 tac/agents/v1/protocols/base_pb2.py create mode 100644 tac/agents/v1/protocols/fipa/fipa.proto create mode 100644 tac/agents/v1/protocols/oef/oef.proto create mode 100644 tac/agents/v1/protocols/simple/__init__.py create mode 100644 tac/agents/v1/protocols/simple/serialization.py create mode 100644 tests/test_messages/__init__.py create mode 100644 tests/test_messages/test_base.py create mode 100644 tests/test_messages/test_simple.py diff --git a/tac/agents/v1/helpers/__init__.py b/tac/agents/v1/helpers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tac/agents/v1/helpers/local_oef.py b/tac/agents/v1/helpers/local_oef.py new file mode 100644 index 00000000..e69de29b diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 8502f3c8..10105ea3 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -21,7 +21,7 @@ """Protocol module v2.""" from copy import copy from enum import Enum -from typing import Optional, Any, Union +from typing import Optional, Any, Union, Dict from oef.messages import OEFErrorOperation from oef.query import Query @@ -40,18 +40,21 @@ class Message: def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, protocol_id: Optional[ProtocolId] = None, - **body): + body: Optional[Dict[str, Any]] = None, + **kwargs): """ - Initialize. + Initialize a Message object. :param to: the public key of the receiver. :param sender: the public key of the sender. :param protocol_id: the protocol id. + "param body: a dictionary of """ self._to = to self._sender = sender self._protocol_id = protocol_id - self._body = copy(body) if body else {} + self._body = copy(body) if body else {} # type: Dict[str, Any] + self._body.update(kwargs) @property def to(self) -> Address: @@ -78,6 +81,18 @@ def protocol_id(self) -> Optional[ProtocolId]: """Get protocol id.""" return self._protocol_id + @protocol_id.setter + def protocol_id(self, protocol_id: ProtocolId) -> None: + self._protocol_id = protocol_id + + @property + def body(self): + return self.body + + @body.setter + def body(self, body: Dict[str, Any]): + self._body = body + def set(self, key: str, value: Any) -> None: """ Set key and value pair. @@ -104,6 +119,13 @@ def check_consistency(self) -> bool: """Check that the data is consistent.""" return True + def __eq__(self, other): + return isinstance(other, Message) \ + and self.to == other.to \ + and self.sender == other.sender \ + and self.protocol_id == other.protocol_id \ + and self._body == other._body + class OEFMessage(Message): """The OEF message class.""" @@ -217,7 +239,7 @@ def __init__(self, to: Optional[Address] = None, class SimpleMessage(Message): """The Simple message class.""" - protocol_id = "default" + protocol_id = "simple" class Type(Enum): """Simple message types.""" diff --git a/tac/agents/v1/mail/protocol.py b/tac/agents/v1/mail/protocol.py new file mode 100644 index 00000000..37ff7208 --- /dev/null +++ b/tac/agents/v1/mail/protocol.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Protocol module v2.""" +import json +from abc import abstractmethod, ABC +from typing import List + +from google.protobuf.struct_pb2 import Struct + +from tac.agents.v1.mail.messages import Message +from tac.agents.v1.protocols import base_pb2 + + +class Encoder(ABC): + + @abstractmethod + def encode(self, msg: Message) -> bytes: + """ + Encode a message. + + :param msg: the message to be encoded. + :return: the encoded message. + """ + + +class Decoder(ABC): + + @abstractmethod + def decode(self, obj: bytes) -> Message: + """ + Decode a message. + + :param obj: the sequence of bytes to be decoded. + :return: the decoded message. + """ + + +class Serializer(Encoder, Decoder, ABC): + """The implementations of this class defines a serialization layer for a protocol.""" + + +class DefaultProtobufSerializer(Serializer): + """ + Default Protobuf serializer. + + It assumes that the Message contains a JSON-serializable body, + and uses the Protobuf serialization for the Message objects.""" + + def encode(self, msg: Message) -> bytes: + + msg_pb = base_pb2.Message() + msg_pb.sender = msg.sender + msg_pb.to = msg.to + msg_pb.protocol_id = msg.protocol_id + + json_body = Struct() + json_body.update(msg.body) + msg_pb.body.CopyFrom(json_body) + + msg_bytes = msg_pb.SerializeToString() + return msg_bytes + + def decode(self, obj: bytes) -> Message: + msg_pb = base_pb2.Message() + msg_pb.ParseFromString(obj) + body = dict(msg_pb.body) + msg = Message(to=msg_pb.to, sender=msg_pb.sender, protocol_id=msg_pb.protocol_id, **body) + return msg + + +class DefaultJSONSerializer(Serializer): + """Default serialization in JSON for the Message object.""" + + def encode(self, msg: Message) -> bytes: + json_msg = { + "to": msg.to, + "sender": msg.sender, + "protocol_id": msg.protocol_id, + "body": msg.body + } + + bytes_msg = json.dumps(json_msg).encode("utf-8") + return bytes_msg + + def decode(self, obj: bytes) -> Message: + json_msg = json.loads(obj.decode("utf-8")) + + msg = Message( + to=json_msg["to"], + sender=json_msg["sender"], + protocol_id=json_msg["protocol_id"], + body=json_msg["body"] + ) + + return msg + + +class Protocol: + + def __init__(self, serializer: Serializer): + """ + Define a protocol. + + :param serializer: the serialization layer. + """ + self.serializer = serializer + + @abstractmethod + def is_valid(self, trace: List[Message]) -> bool: + """Determine whether a sequence of messages follows the protocol.""" diff --git a/tac/agents/v1/protocols/__init__.py b/tac/agents/v1/protocols/__init__.py new file mode 100644 index 00000000..5e911a4e --- /dev/null +++ b/tac/agents/v1/protocols/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the protocol modules.""" diff --git a/tac/agents/v1/protocols/base.proto b/tac/agents/v1/protocols/base.proto new file mode 100644 index 00000000..db45c8e3 --- /dev/null +++ b/tac/agents/v1/protocols/base.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package fetch.aea; + +import "google/protobuf/struct.proto"; + +message Message{ + string to = 1; + string sender = 2; + string protocol_id = 3; + google.protobuf.Struct body = 4; +} diff --git a/tac/agents/v1/protocols/base_pb2.py b/tac/agents/v1/protocols/base_pb2.py new file mode 100644 index 00000000..bdaab803 --- /dev/null +++ b/tac/agents/v1/protocols/base_pb2.py @@ -0,0 +1,93 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: base.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='base.proto', + package='fetch.aea', + syntax='proto3', + serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\x1a\x1cgoogle/protobuf/struct.proto\"a\n\x07Message\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12%\n\x04\x62ody\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Structb\x06proto3') + , + dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_MESSAGE = _descriptor.Descriptor( + name='Message', + full_name='fetch.aea.Message', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='to', full_name='fetch.aea.Message.to', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sender', full_name='fetch.aea.Message.sender', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='protocol_id', full_name='fetch.aea.Message.protocol_id', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='body', full_name='fetch.aea.Message.body', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=55, + serialized_end=152, +) + +_MESSAGE.fields_by_name['body'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +DESCRIPTOR.message_types_by_name['Message'] = _MESSAGE + +Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), dict( + DESCRIPTOR = _MESSAGE, + __module__ = 'base_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.Message) + )) +_sym_db.RegisterMessage(Message) + + +# @@protoc_insertion_point(module_scope) diff --git a/tac/agents/v1/protocols/fipa/fipa.proto b/tac/agents/v1/protocols/fipa/fipa.proto new file mode 100644 index 00000000..2f64cd04 --- /dev/null +++ b/tac/agents/v1/protocols/fipa/fipa.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package fetch.aea.fipa; + + +message FIPAMessage{ + enum Type { + CFP = 0; + PROPOSE = 1; + ACCEPT = 2; + MATCH_ACCEPT = 3; + DECLINE = 4; + } + int32 message_id = 1; + int32 dialogue_id = 2; + int32 target = 3; +} diff --git a/tac/agents/v1/protocols/oef/oef.proto b/tac/agents/v1/protocols/oef/oef.proto new file mode 100644 index 00000000..2b04be9a --- /dev/null +++ b/tac/agents/v1/protocols/oef/oef.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package fetch.aea.oef; + + +message OEFMessage{ + int32 message_id = 1; + int32 dialogue_id = 2; + +} \ No newline at end of file diff --git a/tac/agents/v1/protocols/simple/__init__.py b/tac/agents/v1/protocols/simple/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tac/agents/v1/protocols/simple/serialization.py b/tac/agents/v1/protocols/simple/serialization.py new file mode 100644 index 00000000..de94f2d3 --- /dev/null +++ b/tac/agents/v1/protocols/simple/serialization.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Serialization module for the default protocol.""" +import json + +import base58 + +from tac.agents.v1.mail.messages import Message, SimpleMessage +from tac.agents.v1.mail.protocol import Serializer + + +class SimpleSerializer(Serializer): + """Serializer for the 'simple' protocol.""" + + def encode(self, msg: Message) -> bytes: + assert msg.protocol_id == SimpleMessage.protocol_id + + body = {} + + msg_type = msg.get("type") + assert msg_type in set(SimpleMessage.Type) + body["type"] = str(msg_type.value) + + if msg_type == SimpleMessage.Type.BYTES: + body["content"] = base58.b58encode(msg.get("content")).decode("utf-8") + elif msg_type == SimpleMessage.Type.ERROR: + body["error_code"] = msg.get("error_code") + body["error_msg"] = msg.get("error_msg") + else: + raise ValueError("Type not recognized.") + + json_msg = { + "to": msg.to, + "sender": msg.sender, + "protocol_id": msg.protocol_id, + "body": body + } + + bytes_msg = json.dumps(json_msg).encode("utf-8") + return bytes_msg + + def decode(self, obj: bytes) -> Message: + json_msg = json.loads(obj.decode("utf-8")) + + to = json_msg["to"] + sender = json_msg["sender"] + protocol_id = json_msg["protocol_id"] + body = {} + + json_body = json_msg["body"] + msg_type = SimpleMessage.Type(json_body["type"]) + if msg_type == SimpleMessage.Type.BYTES: + content = base58.b58decode(json_body["content"].encode("utf-8")) + body["content"] = content + elif msg_type == SimpleMessage.Type.ERROR: + body["error_code"] = json_body["error_code"] + body["error_msg"] = json_body["error_msg"] + else: + raise ValueError("Type not recognized.") + + return Message(to=to, sender=sender, protocol_id=protocol_id, body=body) diff --git a/tests/test_messages/__init__.py b/tests/test_messages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py new file mode 100644 index 00000000..c8d65860 --- /dev/null +++ b/tests/test_messages/test_base.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the tests of the messages module.""" +from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer + + +def test_default_protobuf_serialization(): + """Test that the default protobuf serialization works.""" + msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) + msg_bytes = DefaultProtobufSerializer().encode(msg) + actual_msg = DefaultProtobufSerializer().decode(msg_bytes) + expected_msg = msg + + assert expected_msg == actual_msg + + +def test_default_json_serialization(): + """Test that the default json serialization works.""" + msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) + msg_bytes = DefaultJSONSerializer().encode(msg) + actual_msg = DefaultJSONSerializer().decode(msg_bytes) + expected_msg = msg + + assert expected_msg == actual_msg diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py new file mode 100644 index 00000000..39f68d91 --- /dev/null +++ b/tests/test_messages/test_simple.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the tests of the messages module.""" +from tac.agents.v1.protocols.simple.serialization import SimpleSerializer + +from tac.agents.v1.mail.messages import Message, SimpleMessage +from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer + + +def test_simple_serialization(): + """Test that the serialization for the 'simple' protocol.""" + msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.BYTES, body=dict(content=b"hello")) + msg_bytes = SimpleSerializer().encode(msg) + actual_msg = SimpleSerializer().decode(msg_bytes) + expected_msg = msg + + assert expected_msg == actual_msg + + +def test_default_json_serialization(): + """Test that the default json serialization works.""" + msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) + msg_bytes = DefaultJSONSerializer().encode(msg) + actual_msg = DefaultJSONSerializer().decode(msg_bytes) + expected_msg = msg + + assert expected_msg == actual_msg From 2e46f15a69318ad6f73fbc2098adebee2d9c5ca7 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sun, 18 Aug 2019 10:51:42 +0100 Subject: [PATCH 034/107] fix 'simple' protocol --- tac/agents/v1/mail/messages.py | 8 ++++---- tac/agents/v1/mail/protocol.py | 6 +++++- tests/test_messages/test_simple.py | 13 +++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 10105ea3..8ac3221c 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -40,7 +40,7 @@ class Message: def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, protocol_id: Optional[ProtocolId] = None, - body: Optional[Dict[str, Any]] = None, + body: Optional[Dict] = None, **kwargs): """ Initialize a Message object. @@ -124,7 +124,7 @@ def __eq__(self, other): and self.to == other.to \ and self.sender == other.sender \ and self.protocol_id == other.protocol_id \ - and self._body == other._body + and self.body == other.body class OEFMessage(Message): @@ -250,7 +250,7 @@ class Type(Enum): def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, type: Optional[Type] = None, - **body): + **kwargs): """ Initialize. @@ -258,7 +258,7 @@ def __init__(self, to: Optional[Address] = None, :param sender: the public key of the sender. :param type: the type. """ - super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=type, **body) + super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=type, **kwargs) class FIPAMessage(Message): diff --git a/tac/agents/v1/mail/protocol.py b/tac/agents/v1/mail/protocol.py index 37ff7208..ecfe47c7 100644 --- a/tac/agents/v1/mail/protocol.py +++ b/tac/agents/v1/mail/protocol.py @@ -124,5 +124,9 @@ def __init__(self, serializer: Serializer): self.serializer = serializer @abstractmethod - def is_valid(self, trace: List[Message]) -> bool: + def is_message_valid(self, msg: Message): + """Determine whether a message is valid.""" + + @abstractmethod + def is_trace_valid(self, trace: List[Message]) -> bool: """Determine whether a sequence of messages follows the protocol.""" diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index 39f68d91..73ecf435 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -24,9 +24,9 @@ from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer -def test_simple_serialization(): - """Test that the serialization for the 'simple' protocol.""" - msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.BYTES, body=dict(content=b"hello")) +def test_simple_bytes_serialization(): + """Test that the serialization for the 'simple' protocol works for the BYTES message.""" + msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.BYTES, content=b"hello") msg_bytes = SimpleSerializer().encode(msg) actual_msg = SimpleSerializer().decode(msg_bytes) expected_msg = msg @@ -34,9 +34,10 @@ def test_simple_serialization(): assert expected_msg == actual_msg -def test_default_json_serialization(): - """Test that the default json serialization works.""" - msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) +def test_simple_error_serialization(): + """Test that the serialization for the 'simple' protocol works for the ERROR message.""" + msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.ERROR, + error_code=-1, error_msg="An error") msg_bytes = DefaultJSONSerializer().encode(msg) actual_msg = DefaultJSONSerializer().decode(msg_bytes) expected_msg = msg From fafea599ee39378e1f115d736981130f4191312b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sun, 18 Aug 2019 14:49:45 +0100 Subject: [PATCH 035/107] complete 'fipa' protocol and 'simple' protocol. --- tac/agents/v1/mail/messages.py | 6 +- tac/agents/v1/mail/protocol.py | 12 +- tac/agents/v1/protocols/base.proto | 4 +- tac/agents/v1/protocols/base_pb2.py | 15 +- tac/agents/v1/protocols/fipa/fipa.proto | 24 +- tac/agents/v1/protocols/fipa/fipa_pb2.py | 319 ++++++++++++++++++ tac/agents/v1/protocols/fipa/serialization.py | 117 +++++++ .../v1/protocols/simple/serialization.py | 1 + tests/test_messages/test_fipa.py | 36 ++ tests/test_messages/test_simple.py | 8 +- 10 files changed, 512 insertions(+), 30 deletions(-) create mode 100644 tac/agents/v1/protocols/fipa/fipa_pb2.py create mode 100644 tac/agents/v1/protocols/fipa/serialization.py create mode 100644 tests/test_messages/test_fipa.py diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 8ac3221c..c0439f1d 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -87,7 +87,7 @@ def protocol_id(self, protocol_id: ProtocolId) -> None: @property def body(self): - return self.body + return self._body @body.setter def body(self, body: Dict[str, Any]): @@ -281,7 +281,7 @@ def __init__(self, to: Optional[Address] = None, dialogue_id: Optional[DialogueLabel] = None, target: Optional[int] = None, performative: Optional[Union[str, Performative]] = None, - **body): + **kwargs): """ Initialize. @@ -299,7 +299,7 @@ def __init__(self, to: Optional[Address] = None, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative(performative), - **body) + **kwargs) def check_consistency(self) -> bool: """Check that the data is consistent.""" diff --git a/tac/agents/v1/mail/protocol.py b/tac/agents/v1/mail/protocol.py index ecfe47c7..84114de4 100644 --- a/tac/agents/v1/mail/protocol.py +++ b/tac/agents/v1/mail/protocol.py @@ -71,9 +71,10 @@ def encode(self, msg: Message) -> bytes: msg_pb.to = msg.to msg_pb.protocol_id = msg.protocol_id - json_body = Struct() - json_body.update(msg.body) - msg_pb.body.CopyFrom(json_body) + body_json = Struct() + body_json.update(msg.body) + body_bytes = body_json.SerializeToString() + msg_pb.body = body_bytes msg_bytes = msg_pb.SerializeToString() return msg_bytes @@ -81,7 +82,10 @@ def encode(self, msg: Message) -> bytes: def decode(self, obj: bytes) -> Message: msg_pb = base_pb2.Message() msg_pb.ParseFromString(obj) - body = dict(msg_pb.body) + + body_json = Struct() + body_json.ParseFromString(msg_pb.body) + body = dict(body_json) msg = Message(to=msg_pb.to, sender=msg_pb.sender, protocol_id=msg_pb.protocol_id, **body) return msg diff --git a/tac/agents/v1/protocols/base.proto b/tac/agents/v1/protocols/base.proto index db45c8e3..becc58f4 100644 --- a/tac/agents/v1/protocols/base.proto +++ b/tac/agents/v1/protocols/base.proto @@ -2,11 +2,9 @@ syntax = "proto3"; package fetch.aea; -import "google/protobuf/struct.proto"; - message Message{ string to = 1; string sender = 2; string protocol_id = 3; - google.protobuf.Struct body = 4; + bytes body = 4; } diff --git a/tac/agents/v1/protocols/base_pb2.py b/tac/agents/v1/protocols/base_pb2.py index bdaab803..d2655a3d 100644 --- a/tac/agents/v1/protocols/base_pb2.py +++ b/tac/agents/v1/protocols/base_pb2.py @@ -13,16 +13,14 @@ _sym_db = _symbol_database.Default() -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 DESCRIPTOR = _descriptor.FileDescriptor( name='base.proto', package='fetch.aea', syntax='proto3', - serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\x1a\x1cgoogle/protobuf/struct.proto\"a\n\x07Message\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12%\n\x04\x62ody\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Structb\x06proto3') - , - dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) + serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\"H\n\x07Message\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x62\x06proto3') +) _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -58,8 +56,8 @@ options=None), _descriptor.FieldDescriptor( name='body', full_name='fetch.aea.Message.body', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), @@ -75,11 +73,10 @@ extension_ranges=[], oneofs=[ ], - serialized_start=55, - serialized_end=152, + serialized_start=25, + serialized_end=97, ) -_MESSAGE.fields_by_name['body'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT DESCRIPTOR.message_types_by_name['Message'] = _MESSAGE Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), dict( diff --git a/tac/agents/v1/protocols/fipa/fipa.proto b/tac/agents/v1/protocols/fipa/fipa.proto index 2f64cd04..7624bd8a 100644 --- a/tac/agents/v1/protocols/fipa/fipa.proto +++ b/tac/agents/v1/protocols/fipa/fipa.proto @@ -2,16 +2,28 @@ syntax = "proto3"; package fetch.aea.fipa; +import "google/protobuf/struct.proto"; message FIPAMessage{ - enum Type { - CFP = 0; - PROPOSE = 1; - ACCEPT = 2; - MATCH_ACCEPT = 3; - DECLINE = 4; + + message CFP{ + google.protobuf.Struct query = 1; + } + message Propose{ + repeated google.protobuf.Struct proposal = 4; } + message Accept{} + message MatchAccept{} + message Decline{} + int32 message_id = 1; int32 dialogue_id = 2; int32 target = 3; + oneof performative{ + CFP cfp = 4; + Propose propose = 5; + Accept accept = 6; + MatchAccept match_accept = 7; + Decline decline = 8; + } } diff --git a/tac/agents/v1/protocols/fipa/fipa_pb2.py b/tac/agents/v1/protocols/fipa/fipa_pb2.py new file mode 100644 index 00000000..a589dc96 --- /dev/null +++ b/tac/agents/v1/protocols/fipa/fipa_pb2.py @@ -0,0 +1,319 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: fipa.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='fipa.proto', + package='fetch.aea.fipa', + syntax='proto3', + serialized_pb=_b('\n\nfipa.proto\x12\x0e\x66\x65tch.aea.fipa\x1a\x1cgoogle/protobuf/struct.proto\"\xf6\x03\n\x0b\x46IPAMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12\x13\n\x0b\x64ialogue_id\x18\x02 \x01(\x05\x12\x0e\n\x06target\x18\x03 \x01(\x05\x12.\n\x03\x63\x66p\x18\x04 \x01(\x0b\x32\x1f.fetch.aea.fipa.FIPAMessage.CFPH\x00\x12\x36\n\x07propose\x18\x05 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.ProposeH\x00\x12\x34\n\x06\x61\x63\x63\x65pt\x18\x06 \x01(\x0b\x32\".fetch.aea.fipa.FIPAMessage.AcceptH\x00\x12?\n\x0cmatch_accept\x18\x07 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.MatchAcceptH\x00\x12\x36\n\x07\x64\x65\x63line\x18\x08 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.DeclineH\x00\x1a-\n\x03\x43\x46P\x12&\n\x05query\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x1a\x34\n\x07Propose\x12)\n\x08proposal\x18\x04 \x03(\x0b\x32\x17.google.protobuf.Struct\x1a\x08\n\x06\x41\x63\x63\x65pt\x1a\r\n\x0bMatchAccept\x1a\t\n\x07\x44\x65\x63lineB\x0e\n\x0cperformativeb\x06proto3') + , + dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_FIPAMESSAGE_CFP = _descriptor.Descriptor( + name='CFP', + full_name='fetch.aea.fipa.FIPAMessage.CFP', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='query', full_name='fetch.aea.fipa.FIPAMessage.CFP.query', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=412, + serialized_end=457, +) + +_FIPAMESSAGE_PROPOSE = _descriptor.Descriptor( + name='Propose', + full_name='fetch.aea.fipa.FIPAMessage.Propose', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='proposal', full_name='fetch.aea.fipa.FIPAMessage.Propose.proposal', index=0, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=459, + serialized_end=511, +) + +_FIPAMESSAGE_ACCEPT = _descriptor.Descriptor( + name='Accept', + full_name='fetch.aea.fipa.FIPAMessage.Accept', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=513, + serialized_end=521, +) + +_FIPAMESSAGE_MATCHACCEPT = _descriptor.Descriptor( + name='MatchAccept', + full_name='fetch.aea.fipa.FIPAMessage.MatchAccept', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=523, + serialized_end=536, +) + +_FIPAMESSAGE_DECLINE = _descriptor.Descriptor( + name='Decline', + full_name='fetch.aea.fipa.FIPAMessage.Decline', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=538, + serialized_end=547, +) + +_FIPAMESSAGE = _descriptor.Descriptor( + name='FIPAMessage', + full_name='fetch.aea.fipa.FIPAMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message_id', full_name='fetch.aea.fipa.FIPAMessage.message_id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dialogue_id', full_name='fetch.aea.fipa.FIPAMessage.dialogue_id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='target', full_name='fetch.aea.fipa.FIPAMessage.target', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='cfp', full_name='fetch.aea.fipa.FIPAMessage.cfp', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='propose', full_name='fetch.aea.fipa.FIPAMessage.propose', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='accept', full_name='fetch.aea.fipa.FIPAMessage.accept', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='match_accept', full_name='fetch.aea.fipa.FIPAMessage.match_accept', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='decline', full_name='fetch.aea.fipa.FIPAMessage.decline', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_FIPAMESSAGE_CFP, _FIPAMESSAGE_PROPOSE, _FIPAMESSAGE_ACCEPT, _FIPAMESSAGE_MATCHACCEPT, _FIPAMESSAGE_DECLINE, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name='performative', full_name='fetch.aea.fipa.FIPAMessage.performative', + index=0, containing_type=None, fields=[]), + ], + serialized_start=61, + serialized_end=563, +) + +_FIPAMESSAGE_CFP.fields_by_name['query'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_FIPAMESSAGE_CFP.containing_type = _FIPAMESSAGE +_FIPAMESSAGE_PROPOSE.fields_by_name['proposal'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_FIPAMESSAGE_PROPOSE.containing_type = _FIPAMESSAGE +_FIPAMESSAGE_ACCEPT.containing_type = _FIPAMESSAGE +_FIPAMESSAGE_MATCHACCEPT.containing_type = _FIPAMESSAGE +_FIPAMESSAGE_DECLINE.containing_type = _FIPAMESSAGE +_FIPAMESSAGE.fields_by_name['cfp'].message_type = _FIPAMESSAGE_CFP +_FIPAMESSAGE.fields_by_name['propose'].message_type = _FIPAMESSAGE_PROPOSE +_FIPAMESSAGE.fields_by_name['accept'].message_type = _FIPAMESSAGE_ACCEPT +_FIPAMESSAGE.fields_by_name['match_accept'].message_type = _FIPAMESSAGE_MATCHACCEPT +_FIPAMESSAGE.fields_by_name['decline'].message_type = _FIPAMESSAGE_DECLINE +_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( + _FIPAMESSAGE.fields_by_name['cfp']) +_FIPAMESSAGE.fields_by_name['cfp'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] +_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( + _FIPAMESSAGE.fields_by_name['propose']) +_FIPAMESSAGE.fields_by_name['propose'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] +_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( + _FIPAMESSAGE.fields_by_name['accept']) +_FIPAMESSAGE.fields_by_name['accept'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] +_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( + _FIPAMESSAGE.fields_by_name['match_accept']) +_FIPAMESSAGE.fields_by_name['match_accept'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] +_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( + _FIPAMESSAGE.fields_by_name['decline']) +_FIPAMESSAGE.fields_by_name['decline'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] +DESCRIPTOR.message_types_by_name['FIPAMessage'] = _FIPAMESSAGE + +FIPAMessage = _reflection.GeneratedProtocolMessageType('FIPAMessage', (_message.Message,), dict( + + CFP = _reflection.GeneratedProtocolMessageType('CFP', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_CFP, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.CFP) + )) + , + + Propose = _reflection.GeneratedProtocolMessageType('Propose', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_PROPOSE, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Propose) + )) + , + + Accept = _reflection.GeneratedProtocolMessageType('Accept', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_ACCEPT, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Accept) + )) + , + + MatchAccept = _reflection.GeneratedProtocolMessageType('MatchAccept', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_MATCHACCEPT, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.MatchAccept) + )) + , + + Decline = _reflection.GeneratedProtocolMessageType('Decline', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_DECLINE, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Decline) + )) + , + DESCRIPTOR = _FIPAMESSAGE, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage) + )) +_sym_db.RegisterMessage(FIPAMessage) +_sym_db.RegisterMessage(FIPAMessage.CFP) +_sym_db.RegisterMessage(FIPAMessage.Propose) +_sym_db.RegisterMessage(FIPAMessage.Accept) +_sym_db.RegisterMessage(FIPAMessage.MatchAccept) +_sym_db.RegisterMessage(FIPAMessage.Decline) + + +# @@protoc_insertion_point(module_scope) diff --git a/tac/agents/v1/protocols/fipa/serialization.py b/tac/agents/v1/protocols/fipa/serialization.py new file mode 100644 index 00000000..8a48fdf1 --- /dev/null +++ b/tac/agents/v1/protocols/fipa/serialization.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Serialization for the FIPA protocol.""" +import base58 +from google.protobuf.struct_pb2 import Struct +from oef.schema import Description + +from tac.agents.v1.mail.messages import Message, FIPAMessage +from tac.agents.v1.mail.protocol import Serializer +from tac.agents.v1.protocols import base_pb2 +from tac.agents.v1.protocols.fipa import fipa_pb2 + + +class FIPASerializer(Serializer): + + def encode(self, msg: Message) -> bytes: + assert msg.protocol_id == "fipa" + + msg_pb = base_pb2.Message() + msg_pb.sender = msg.sender + msg_pb.to = msg.to + msg_pb.protocol_id = msg.protocol_id + + fipa_msg = fipa_pb2.FIPAMessage() + fipa_msg.message_id = msg.get("id") + fipa_msg.dialogue_id = msg.get("dialogue_id") + fipa_msg.target = msg.get("target") + + performative_id = msg.get("performative").value + if performative_id == "cfp": + performative = fipa_pb2.FIPAMessage.CFP() + performative.query.update(msg.get("query")) + fipa_msg.cfp.CopyFrom(performative) + elif performative_id == "propose": + performative = fipa_pb2.FIPAMessage.Propose() + proposal = msg.get("proposal") + for p in proposal: + p: Description + new_struct = performative.proposal.add() + new_struct.update(p.values) + + for bytes_p in performative.proposal: + print(bytes_p) + + fipa_msg.propose.CopyFrom(performative) + + elif performative_id == "accept": + performative = fipa_pb2.FIPAMessage.Accept() + fipa_msg.accept.CopyFrom(performative) + elif performative_id == "match_accept": + performative = fipa_pb2.FIPAMessage.MatchAccept() + fipa_msg.match_accept.CopyFrom(performative) + elif performative_id == "decline": + performative = fipa_pb2.FIPAMessage.Decline() + fipa_msg.decline.CopyFrom(performative) + else: + raise ValueError("Performative not valid: {}".format(performative_id)) + + fipa_bytes = fipa_msg.SerializeToString() + msg_pb.body = fipa_bytes + + msg_bytes = msg_pb.SerializeToString() + + return msg_bytes + + def decode(self, obj: bytes) -> Message: + msg_pb = base_pb2.Message() + msg_pb.ParseFromString(obj) + + to = msg_pb.to + sender = msg_pb.sender + protocol_id = msg_pb.protocol_id + body_bytes = msg_pb.body + + fipa_pb = fipa_pb2.FIPAMessage() + fipa_pb.ParseFromString(body_bytes) + message_id = fipa_pb.message_id + dialogue_id = fipa_pb.dialogue_id + target = fipa_pb.target + + performative = fipa_pb.WhichOneof("performative") + performative_content = dict() + if performative == "cfp": + query = dict(fipa_pb.cfp.query) + performative_content["query"] = query + elif performative == "propose": + proposal = [dict(p) for p in fipa_pb.propose.proposal] + performative_content["proposal"] = proposal + elif performative == "accept": + pass + elif performative == "match_accept": + pass + elif performative == "decline": + pass + else: + raise ValueError("Performative not valid.") + + return FIPAMessage(to=to, sender=sender, message_id=message_id, dialogue_id=dialogue_id, target=target, + performative=performative, **performative_content) diff --git a/tac/agents/v1/protocols/simple/serialization.py b/tac/agents/v1/protocols/simple/serialization.py index de94f2d3..038d9a88 100644 --- a/tac/agents/v1/protocols/simple/serialization.py +++ b/tac/agents/v1/protocols/simple/serialization.py @@ -67,6 +67,7 @@ def decode(self, obj: bytes) -> Message: json_body = json_msg["body"] msg_type = SimpleMessage.Type(json_body["type"]) + body["type"] = msg_type if msg_type == SimpleMessage.Type.BYTES: content = base58.b58decode(json_body["content"].encode("utf-8")) body["content"] = content diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py new file mode 100644 index 00000000..ab45dd75 --- /dev/null +++ b/tests/test_messages/test_fipa.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the tests for the FIPA protocol.""" +from tac.agents.v1.protocols.fipa.serialization import FIPASerializer +from tac.agents.v1.protocols.simple.serialization import SimpleSerializer + +from tac.agents.v1.mail.messages import Message, SimpleMessage, FIPAMessage +from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer + + +def test_fipa_cfp_serialization(): + """Test that the serialization for the 'fipa' protocol works..""" + msg = FIPAMessage(to="receiver", sender="sender", message_id=0, dialogue_id=0, target=0, + performative=FIPAMessage.Performative.CFP, query={"foo": "bar"}) + msg_bytes = FIPASerializer().encode(msg) + actual_msg = FIPASerializer().decode(msg_bytes) + expected_msg = msg + + assert expected_msg == actual_msg diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index 73ecf435..eb932ce2 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -18,11 +18,9 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" +from tac.agents.v1.mail.messages import SimpleMessage from tac.agents.v1.protocols.simple.serialization import SimpleSerializer -from tac.agents.v1.mail.messages import Message, SimpleMessage -from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer - def test_simple_bytes_serialization(): """Test that the serialization for the 'simple' protocol works for the BYTES message.""" @@ -38,8 +36,8 @@ def test_simple_error_serialization(): """Test that the serialization for the 'simple' protocol works for the ERROR message.""" msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.ERROR, error_code=-1, error_msg="An error") - msg_bytes = DefaultJSONSerializer().encode(msg) - actual_msg = DefaultJSONSerializer().decode(msg_bytes) + msg_bytes = SimpleSerializer().encode(msg) + actual_msg = SimpleSerializer().decode(msg_bytes) expected_msg = msg assert expected_msg == actual_msg From 7da3a89dcae20051dd7bd7bbf67c2491d7e3d82b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 11:56:41 +0100 Subject: [PATCH 036/107] split 'Envelope' and 'Message'. --- tac/agents/v1/mail/base.py | 12 +- tac/agents/v1/mail/messages.py | 91 +++--------- tac/agents/v1/mail/oef.py | 11 +- tac/agents/v1/mail/protocol.py | 139 +++++++++++++----- tac/agents/v1/protocols/base.proto | 4 +- tac/agents/v1/protocols/base_pb2.py | 28 ++-- tac/agents/v1/protocols/fipa/serialization.py | 28 +--- .../v1/protocols/simple/serialization.py | 23 +-- tests/test_messages/test_base.py | 39 +++-- tests/test_messages/test_fipa.py | 20 ++- tests/test_messages/test_simple.py | 14 +- 11 files changed, 209 insertions(+), 200 deletions(-) diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py index d8b59ba3..2693ce47 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/agents/v1/mail/base.py @@ -25,7 +25,7 @@ from queue import Queue from typing import Optional -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope logger = logging.getLogger(__name__) @@ -50,7 +50,7 @@ def empty(self) -> bool: """ return self._queue.empty() - def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[Message]: + def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[Envelope]: """ Check for a message on the in queue. @@ -64,7 +64,7 @@ def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[M logger.debug("Incoming message type: type={}".format(type(msg))) return msg - def get_nowait(self) -> Optional[Message]: + def get_nowait(self) -> Optional[Envelope]: """ Check for a message on the in queue and wait for no time. @@ -94,7 +94,7 @@ def empty(self) -> bool: """ return self._queue.empty() - def put(self, item: Message) -> None: + def put(self, item: Envelope) -> None: """ Put an item into the queue. @@ -127,7 +127,7 @@ def is_established(self) -> bool: """Check if the connection is established.""" @abstractmethod - def send(self, msg: Message): + def send(self, msg: Envelope): """Send a message.""" @@ -154,6 +154,6 @@ def disconnect(self) -> None: """Disconnect.""" self._connection.disconnect() - def send(self, out: Message) -> None: + def send(self, out: Envelope) -> None: """Send.""" self.outbox.put(out) diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index c0439f1d..510577c9 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -19,6 +19,7 @@ # ------------------------------------------------------------------------------ """Protocol module v2.""" +from abc import abstractmethod from copy import copy from enum import Enum from typing import Optional, Any, Union, Dict @@ -35,62 +36,34 @@ class Message: - """The message class.""" - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - protocol_id: Optional[ProtocolId] = None, - body: Optional[Dict] = None, + protocol_id = "default" + + def __init__(self, body: Optional[Dict] = None, **kwargs): """ Initialize a Message object. - :param to: the public key of the receiver. - :param sender: the public key of the sender. - :param protocol_id: the protocol id. - "param body: a dictionary of + :param body: the dictionary of values to hold. """ - self._to = to - self._sender = sender - self._protocol_id = protocol_id self._body = copy(body) if body else {} # type: Dict[str, Any] self._body.update(kwargs) @property - def to(self) -> Address: - """Get public key of receiver.""" - return self._to - - @to.setter - def to(self, to: Address) -> None: - """Set public key of receiver.""" - self._to = to - - @property - def sender(self) -> Address: - """Get public key of sender.""" - return self._sender - - @sender.setter - def sender(self, sender: Address) -> None: - """Set public key of sender.""" - self._sender = sender - - @property - def protocol_id(self) -> Optional[ProtocolId]: - """Get protocol id.""" - return self._protocol_id - - @protocol_id.setter - def protocol_id(self, protocol_id: ProtocolId) -> None: - self._protocol_id = protocol_id - - @property - def body(self): + def body(self) -> Dict: + """ + The body of the message (in dictionary form) + :return: the body + """ return self._body @body.setter - def body(self, body: Dict[str, Any]): + def body(self, body: Dict): + """ + + :param body: + :return: + """ self._body = body def set(self, key: str, value: Any) -> None: @@ -121,10 +94,7 @@ def check_consistency(self) -> bool: def __eq__(self, other): return isinstance(other, Message) \ - and self.to == other.to \ - and self.sender == other.sender \ - and self.protocol_id == other.protocol_id \ - and self.body == other.body + and self.body == other.body class OEFMessage(Message): @@ -219,21 +189,17 @@ class ByteMessage(Message): protocol_id = "bytes" - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - message_id: Optional[int] = None, + def __init__(self, message_id: Optional[int] = None, dialogue_id: Optional[int] = None, content: bytes = b""): """ Initialize. - :param to: the public key of the receiver. - :param sender: the public key of the sender. :param message_id: the message id. :param dialogue_id: the dialogue id. :param content: the message content. """ - super().__init__(to=to, sender=sender, id=message_id, dialogue_id=dialogue_id, content=content) + super().__init__(id=message_id, dialogue_id=dialogue_id, content=content) class SimpleMessage(Message): @@ -247,18 +213,14 @@ class Type(Enum): BYTES = "bytes" ERROR = "error" - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - type: Optional[Type] = None, + def __init__(self, type: Optional[Type] = None, **kwargs): """ Initialize. - :param to: the public key of the receiver. - :param sender: the public key of the sender. :param type: the type. """ - super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=type, **kwargs) + super().__init__(type=type, **kwargs) class FIPAMessage(Message): @@ -275,9 +237,7 @@ class Performative(Enum): MATCH_ACCEPT = "match_accept" DECLINE = "decline" - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - message_id: Optional[int] = None, + def __init__(self, message_id: Optional[int] = None, dialogue_id: Optional[DialogueLabel] = None, target: Optional[int] = None, performative: Optional[Union[str, Performative]] = None, @@ -285,17 +245,12 @@ def __init__(self, to: Optional[Address] = None, """ Initialize. - :param to: the public key of the receiver. - :param sender: the public key of the sender. :param message_id: the message id. :param dialogue_id: the dialogue id. :param target: the message target. :param performative: the message performative. """ - super().__init__(to, - sender, - protocol_id=self.protocol_id, - id=message_id, + super().__init__(id=message_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative(performative), diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 3c4f22b8..b1af1443 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -32,7 +32,8 @@ from oef.proxy import OEFNetworkProxy from tac.agents.v1.mail.base import Connection, MailBox -from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, Message, ByteMessage +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, ByteMessage +from tac.agents.v1.mail.protocol import Envelope logger = logging.getLogger(__name__) @@ -246,7 +247,7 @@ def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> No origin=origin) self.in_queue.put(msg) - def send(self, msg: Message) -> None: + def send(self, msg: Envelope) -> None: """ Send message handler. @@ -264,7 +265,7 @@ def send(self, msg: Message) -> None: else: raise ValueError("Cannot send message.") - def send_oef_message(self, msg: Message) -> None: + def send_oef_message(self, msg: Envelope) -> None: """ Send oef message handler. @@ -301,7 +302,7 @@ def send_oef_message(self, msg: Message) -> None: else: raise ValueError("OEF request not recognized.") - def send_fipa_message(self, msg: Message) -> None: + def send_fipa_message(self, msg: Envelope) -> None: """ Send fipa message handler. @@ -392,7 +393,7 @@ def is_established(self) -> bool: """Get the connection status.""" return self.bridge.is_connected() - def _send(self, msg: Message) -> None: + def _send(self, msg: Envelope) -> None: """ Send messages. diff --git a/tac/agents/v1/mail/protocol.py b/tac/agents/v1/mail/protocol.py index 84114de4..bfa514d0 100644 --- a/tac/agents/v1/mail/protocol.py +++ b/tac/agents/v1/mail/protocol.py @@ -21,11 +21,11 @@ """Protocol module v2.""" import json from abc import abstractmethod, ABC -from typing import List +from typing import List, Optional from google.protobuf.struct_pb2 import Struct -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.messages import Message, Address, ProtocolId from tac.agents.v1.protocols import base_pb2 @@ -61,32 +61,20 @@ class DefaultProtobufSerializer(Serializer): """ Default Protobuf serializer. - It assumes that the Message contains a JSON-serializable body, - and uses the Protobuf serialization for the Message objects.""" + It assumes that the Message contains a JSON-serializable body.""" def encode(self, msg: Message) -> bytes: - - msg_pb = base_pb2.Message() - msg_pb.sender = msg.sender - msg_pb.to = msg.to - msg_pb.protocol_id = msg.protocol_id - body_json = Struct() body_json.update(msg.body) body_bytes = body_json.SerializeToString() - msg_pb.body = body_bytes - - msg_bytes = msg_pb.SerializeToString() - return msg_bytes + return body_bytes def decode(self, obj: bytes) -> Message: - msg_pb = base_pb2.Message() - msg_pb.ParseFromString(obj) - body_json = Struct() - body_json.ParseFromString(msg_pb.body) + body_json.ParseFromString(obj) + body = dict(body_json) - msg = Message(to=msg_pb.to, sender=msg_pb.sender, protocol_id=msg_pb.protocol_id, **body) + msg = Message(body=body) return msg @@ -94,27 +82,106 @@ class DefaultJSONSerializer(Serializer): """Default serialization in JSON for the Message object.""" def encode(self, msg: Message) -> bytes: - json_msg = { - "to": msg.to, - "sender": msg.sender, - "protocol_id": msg.protocol_id, - "body": msg.body - } - - bytes_msg = json.dumps(json_msg).encode("utf-8") + bytes_msg = json.dumps(msg.body).encode("utf-8") return bytes_msg def decode(self, obj: bytes) -> Message: json_msg = json.loads(obj.decode("utf-8")) + return Message(json_msg) - msg = Message( - to=json_msg["to"], - sender=json_msg["sender"], - protocol_id=json_msg["protocol_id"], - body=json_msg["body"] - ) - return msg +class Envelope: + """The message class.""" + + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, + protocol_id: Optional[ProtocolId] = None, + message: Optional[Message] = None): + """ + Initialize a Message object. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param protocol_id: the protocol id. + :param message: the protocol-specific message + """ + self._to = to + self._sender = sender + self._protocol_id = protocol_id + self._message = message + + @property + def to(self) -> Address: + """Get public key of receiver.""" + return self._to + + @to.setter + def to(self, to: Address) -> None: + """Set public key of receiver.""" + self._to = to + + @property + def sender(self) -> Address: + """Get public key of sender.""" + return self._sender + + @sender.setter + def sender(self, sender: Address) -> None: + """Set public key of sender.""" + self._sender = sender + + @property + def protocol_id(self) -> Optional[ProtocolId]: + """Get protocol id.""" + return self._protocol_id + + @protocol_id.setter + def protocol_id(self, protocol_id: ProtocolId) -> None: + """Set the protocol id.""" + self._protocol_id = protocol_id + + @property + def message(self) -> Message: + """Get the Message.""" + return self._message + + @message.setter + def message(self, message: Message) -> None: + """Set the message.""" + self._message = message + + def __eq__(self, other): + return isinstance(other, Envelope) \ + and self.to == other.to \ + and self.sender == other.sender \ + and self.protocol_id == other.protocol_id \ + and self._message == other._message + + def encode(self, encoder: Encoder) -> bytes: + envelope = self + envelope_pb = base_pb2.Envelope() + envelope_pb.to = envelope.to + envelope_pb.sender = envelope.sender + envelope_pb.protocol_id = envelope.protocol_id + message_bytes = encoder.encode(envelope.message) + envelope_pb.message = message_bytes + + envelope_bytes = envelope_pb.SerializeToString() + return envelope_bytes + + @classmethod + def decode(cls, envelope_bytes: bytes, decoder: Decoder) -> 'Envelope': + envelope_pb = base_pb2.Envelope() + envelope_pb.ParseFromString(envelope_bytes) + + to = envelope_pb.to + sender = envelope_pb.sender + protocol_id = envelope_pb.protocol_id + message_bytes = envelope_pb.message + message = decoder.decode(message_bytes) + + envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) + return envelope class Protocol: @@ -128,9 +195,9 @@ def __init__(self, serializer: Serializer): self.serializer = serializer @abstractmethod - def is_message_valid(self, msg: Message): + def is_message_valid(self, msg: Envelope): """Determine whether a message is valid.""" @abstractmethod - def is_trace_valid(self, trace: List[Message]) -> bool: + def is_trace_valid(self, trace: List[Envelope]) -> bool: """Determine whether a sequence of messages follows the protocol.""" diff --git a/tac/agents/v1/protocols/base.proto b/tac/agents/v1/protocols/base.proto index becc58f4..eaf5eebd 100644 --- a/tac/agents/v1/protocols/base.proto +++ b/tac/agents/v1/protocols/base.proto @@ -2,9 +2,9 @@ syntax = "proto3"; package fetch.aea; -message Message{ +message Envelope{ string to = 1; string sender = 2; string protocol_id = 3; - bytes body = 4; + bytes message = 4; } diff --git a/tac/agents/v1/protocols/base_pb2.py b/tac/agents/v1/protocols/base_pb2.py index d2655a3d..b379bfbc 100644 --- a/tac/agents/v1/protocols/base_pb2.py +++ b/tac/agents/v1/protocols/base_pb2.py @@ -19,43 +19,43 @@ name='base.proto', package='fetch.aea', syntax='proto3', - serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\"H\n\x07Message\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x62\x06proto3') + serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\"L\n\x08\x45nvelope\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\x0c\x62\x06proto3') ) _sym_db.RegisterFileDescriptor(DESCRIPTOR) -_MESSAGE = _descriptor.Descriptor( - name='Message', - full_name='fetch.aea.Message', +_ENVELOPE = _descriptor.Descriptor( + name='Envelope', + full_name='fetch.aea.Envelope', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='to', full_name='fetch.aea.Message.to', index=0, + name='to', full_name='fetch.aea.Envelope.to', index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( - name='sender', full_name='fetch.aea.Message.sender', index=1, + name='sender', full_name='fetch.aea.Envelope.sender', index=1, number=2, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( - name='protocol_id', full_name='fetch.aea.Message.protocol_id', index=2, + name='protocol_id', full_name='fetch.aea.Envelope.protocol_id', index=2, number=3, type=9, cpp_type=9, label=1, has_default_value=False, default_value=_b("").decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( - name='body', full_name='fetch.aea.Message.body', index=3, + name='message', full_name='fetch.aea.Envelope.message', index=3, number=4, type=12, cpp_type=9, label=1, has_default_value=False, default_value=_b(""), message_type=None, enum_type=None, containing_type=None, @@ -74,17 +74,17 @@ oneofs=[ ], serialized_start=25, - serialized_end=97, + serialized_end=101, ) -DESCRIPTOR.message_types_by_name['Message'] = _MESSAGE +DESCRIPTOR.message_types_by_name['Envelope'] = _ENVELOPE -Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), dict( - DESCRIPTOR = _MESSAGE, +Envelope = _reflection.GeneratedProtocolMessageType('Envelope', (_message.Message,), dict( + DESCRIPTOR = _ENVELOPE, __module__ = 'base_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.Message) + # @@protoc_insertion_point(class_scope:fetch.aea.Envelope) )) -_sym_db.RegisterMessage(Message) +_sym_db.RegisterMessage(Envelope) # @@protoc_insertion_point(module_scope) diff --git a/tac/agents/v1/protocols/fipa/serialization.py b/tac/agents/v1/protocols/fipa/serialization.py index 8a48fdf1..34b97d3e 100644 --- a/tac/agents/v1/protocols/fipa/serialization.py +++ b/tac/agents/v1/protocols/fipa/serialization.py @@ -23,8 +23,8 @@ from google.protobuf.struct_pb2 import Struct from oef.schema import Description -from tac.agents.v1.mail.messages import Message, FIPAMessage -from tac.agents.v1.mail.protocol import Serializer +from tac.agents.v1.mail.messages import FIPAMessage, Message +from tac.agents.v1.mail.protocol import Serializer, Envelope from tac.agents.v1.protocols import base_pb2 from tac.agents.v1.protocols.fipa import fipa_pb2 @@ -32,12 +32,6 @@ class FIPASerializer(Serializer): def encode(self, msg: Message) -> bytes: - assert msg.protocol_id == "fipa" - - msg_pb = base_pb2.Message() - msg_pb.sender = msg.sender - msg_pb.to = msg.to - msg_pb.protocol_id = msg.protocol_id fipa_msg = fipa_pb2.FIPAMessage() fipa_msg.message_id = msg.get("id") @@ -75,23 +69,11 @@ def encode(self, msg: Message) -> bytes: raise ValueError("Performative not valid: {}".format(performative_id)) fipa_bytes = fipa_msg.SerializeToString() - msg_pb.body = fipa_bytes - - msg_bytes = msg_pb.SerializeToString() - - return msg_bytes + return fipa_bytes def decode(self, obj: bytes) -> Message: - msg_pb = base_pb2.Message() - msg_pb.ParseFromString(obj) - - to = msg_pb.to - sender = msg_pb.sender - protocol_id = msg_pb.protocol_id - body_bytes = msg_pb.body - fipa_pb = fipa_pb2.FIPAMessage() - fipa_pb.ParseFromString(body_bytes) + fipa_pb.ParseFromString(obj) message_id = fipa_pb.message_id dialogue_id = fipa_pb.dialogue_id target = fipa_pb.target @@ -113,5 +95,5 @@ def decode(self, obj: bytes) -> Message: else: raise ValueError("Performative not valid.") - return FIPAMessage(to=to, sender=sender, message_id=message_id, dialogue_id=dialogue_id, target=target, + return FIPAMessage(message_id=message_id, dialogue_id=dialogue_id, target=target, performative=performative, **performative_content) diff --git a/tac/agents/v1/protocols/simple/serialization.py b/tac/agents/v1/protocols/simple/serialization.py index 038d9a88..1f626351 100644 --- a/tac/agents/v1/protocols/simple/serialization.py +++ b/tac/agents/v1/protocols/simple/serialization.py @@ -23,15 +23,14 @@ import base58 -from tac.agents.v1.mail.messages import Message, SimpleMessage -from tac.agents.v1.mail.protocol import Serializer +from tac.agents.v1.mail.messages import SimpleMessage, Message +from tac.agents.v1.mail.protocol import Serializer, Envelope class SimpleSerializer(Serializer): """Serializer for the 'simple' protocol.""" def encode(self, msg: Message) -> bytes: - assert msg.protocol_id == SimpleMessage.protocol_id body = {} @@ -47,25 +46,13 @@ def encode(self, msg: Message) -> bytes: else: raise ValueError("Type not recognized.") - json_msg = { - "to": msg.to, - "sender": msg.sender, - "protocol_id": msg.protocol_id, - "body": body - } - - bytes_msg = json.dumps(json_msg).encode("utf-8") + bytes_msg = json.dumps(body).encode("utf-8") return bytes_msg def decode(self, obj: bytes) -> Message: - json_msg = json.loads(obj.decode("utf-8")) - - to = json_msg["to"] - sender = json_msg["sender"] - protocol_id = json_msg["protocol_id"] + json_body = json.loads(obj.decode("utf-8")) body = {} - json_body = json_msg["body"] msg_type = SimpleMessage.Type(json_body["type"]) body["type"] = msg_type if msg_type == SimpleMessage.Type.BYTES: @@ -77,4 +64,4 @@ def decode(self, obj: bytes) -> Message: else: raise ValueError("Type not recognized.") - return Message(to=to, sender=sender, protocol_id=protocol_id, body=body) + return Message(body=body) diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index c8d65860..1317fde1 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -19,24 +19,33 @@ """This module contains the tests of the messages module.""" from tac.agents.v1.mail.messages import Message -from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer +from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer, Envelope -def test_default_protobuf_serialization(): - """Test that the default protobuf serialization works.""" - msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) - msg_bytes = DefaultProtobufSerializer().encode(msg) - actual_msg = DefaultProtobufSerializer().decode(msg_bytes) - expected_msg = msg +class TestDefaultSerializations: - assert expected_msg == actual_msg + @classmethod + def setup_class(cls): + cls.message = Message(content="hello") + cls.envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=cls.message) + def test_default_protobuf_serialization(self): + message = self.message + envelope = self.envelope -def test_default_json_serialization(): - """Test that the default json serialization works.""" - msg = Message(to="receiver", sender="sender", protocol_id="my_own_protocol", body={"content": "hello"}) - msg_bytes = DefaultJSONSerializer().encode(msg) - actual_msg = DefaultJSONSerializer().decode(msg_bytes) - expected_msg = msg + serializer = DefaultProtobufSerializer() + envelope_bytes = envelope.encode(serializer) + actual_envelope = Envelope.decode(envelope_bytes, serializer) + expected_envelope = envelope - assert expected_msg == actual_msg + assert expected_envelope == actual_envelope + + def test_default_json_serialization(self): + envelope = self.envelope + + serializer = DefaultJSONSerializer() + envelope_bytes = envelope.encode(serializer) + actual_envelope = Envelope.decode(envelope_bytes, serializer) + expected_envelope = envelope + + assert expected_envelope == actual_envelope diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py index ab45dd75..f070f1d9 100644 --- a/tests/test_messages/test_fipa.py +++ b/tests/test_messages/test_fipa.py @@ -21,16 +21,20 @@ from tac.agents.v1.protocols.fipa.serialization import FIPASerializer from tac.agents.v1.protocols.simple.serialization import SimpleSerializer -from tac.agents.v1.mail.messages import Message, SimpleMessage, FIPAMessage -from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer +from tac.agents.v1.mail.messages import SimpleMessage, FIPAMessage +from tac.agents.v1.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer, Envelope def test_fipa_cfp_serialization(): """Test that the serialization for the 'fipa' protocol works..""" - msg = FIPAMessage(to="receiver", sender="sender", message_id=0, dialogue_id=0, target=0, - performative=FIPAMessage.Performative.CFP, query={"foo": "bar"}) - msg_bytes = FIPASerializer().encode(msg) - actual_msg = FIPASerializer().decode(msg_bytes) - expected_msg = msg + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, + query={"foo": "bar"}) + envelope = Envelope(to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg) + serializer = FIPASerializer() - assert expected_msg == actual_msg + envelope_bytes = envelope.encode(serializer) + + actual_envelope = Envelope.decode(envelope_bytes, serializer) + expected_envelope = envelope + + assert expected_envelope == actual_envelope diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index eb932ce2..9d9f11b3 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -19,17 +19,21 @@ """This module contains the tests of the messages module.""" from tac.agents.v1.mail.messages import SimpleMessage +from tac.agents.v1.mail.protocol import Envelope from tac.agents.v1.protocols.simple.serialization import SimpleSerializer def test_simple_bytes_serialization(): """Test that the serialization for the 'simple' protocol works for the BYTES message.""" - msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.BYTES, content=b"hello") - msg_bytes = SimpleSerializer().encode(msg) - actual_msg = SimpleSerializer().decode(msg_bytes) - expected_msg = msg + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b"hello") + envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg) + serializer = SimpleSerializer() - assert expected_msg == actual_msg + envelope_bytes = envelope.encode(serializer) + actual_envelope = Envelope.decode(envelope_bytes, serializer) + expected_envelope = envelope + + assert expected_envelope == actual_envelope def test_simple_error_serialization(): From 42187ab55c79d403e485aba61b0ff3e2ca00ab72 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 12:23:25 +0100 Subject: [PATCH 037/107] fix OEF connector/channel. --- tac/agents/v1/mail/base.py | 15 ++++ tac/agents/v1/mail/messages.py | 12 +-- tac/agents/v1/mail/oef.py | 124 +++++++++++++++---------------- tests/test_messages/test_base.py | 1 - 4 files changed, 80 insertions(+), 72 deletions(-) diff --git a/tac/agents/v1/mail/base.py b/tac/agents/v1/mail/base.py index 2693ce47..c9e0bde0 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/agents/v1/mail/base.py @@ -25,6 +25,7 @@ from queue import Queue from typing import Optional +from tac.agents.v1.mail.messages import Address, ProtocolId, Message from tac.agents.v1.mail.protocol import Envelope logger = logging.getLogger(__name__) @@ -104,6 +105,20 @@ def put(self, item: Envelope) -> None: logger.debug("Put a message in the queue...") self._queue.put(item) + def put_message(self, to: Optional[Address] = None, sender: Optional[Address] = None, + protocol_id: Optional[ProtocolId] = None, message: Optional[Message] = None) -> None: + """ + Put a message in the outbox. + + :param to: the recipient of the message. + :param sender: the sender of the message. + :param protocol_id: the protocol id. + :param message: the content of the message. + :return: None + """ + envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) + self._queue.put(envelope) + class Connection: """Abstract definition of a connection.""" diff --git a/tac/agents/v1/mail/messages.py b/tac/agents/v1/mail/messages.py index 510577c9..566afc72 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/agents/v1/mail/messages.py @@ -115,18 +115,14 @@ class Type(Enum): DIALOGUE_ERROR = "dialogue_error" SEARCH_RESULT = "search_result" - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - oef_type: Optional[Type] = None, - **body): + def __init__(self, oef_type: Optional[Type] = None, + **kwargs): """ Initialize. - :param to: the public key of the receiver. - :param sender: the public key of the sender. - :param protocol_id: the protocol id. + :param oef_type: the type of OEF message. """ - super().__init__(to=to, sender=sender, protocol_id=self.protocol_id, type=oef_type, **body) + super().__init__(type=oef_type, **kwargs) def check_consistency(self) -> bool: """Check that the data is consistent.""" diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index b1af1443..7c3222cc 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -116,12 +116,11 @@ def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) :param content: the bytes content. :return: None """ - msg = ByteMessage(to=self.public_key, - sender=origin, - message_id=msg_id, + msg = ByteMessage(message_id=msg_id, dialogue_id=dialogue_id, content=content) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=ByteMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: """ @@ -134,14 +133,13 @@ def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: :param query: the query. :return: None """ - msg = FIPAMessage(to=self.public_key, - sender=origin, - message_id=msg_id, + msg = FIPAMessage(message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.CFP, query=query) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: """ @@ -154,14 +152,13 @@ def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, pr :param proposals: the proposals. :return: None """ - msg = FIPAMessage(to=self.public_key, - sender=origin, - message_id=msg_id, + msg = FIPAMessage(message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.PROPOSE, proposal=proposals) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: """ @@ -208,12 +205,9 @@ def on_search_result(self, search_id: int, agents: List[str]) -> None: :return: None """ self.mail_stats.search_end(search_id, len(agents)) - msg = OEFMessage(to=self.public_key, - sender=None, - oef_type=OEFMessage.Type.SEARCH_RESULT, - id=search_id, - agents=agents) - self.in_queue.put(msg) + msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=agents) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: """ @@ -223,12 +217,11 @@ def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: :param operation: the error operation. :return: None """ - msg = OEFMessage(to=self.public_key, - sender=None, - oef_type=OEFMessage.Type.OEF_ERROR, + msg = OEFMessage(oef_type=OEFMessage.Type.OEF_ERROR, id=answer_id, operation=operation) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: """ @@ -239,86 +232,91 @@ def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> No :param origin: the message sender. :return: None """ - msg = OEFMessage(to=self.public_key, - sender=None, - oef_type=OEFMessage.Type.DIALOGUE_ERROR, + msg = OEFMessage(oef_type=OEFMessage.Type.DIALOGUE_ERROR, id=answer_id, dialogue_id=dialogue_id, origin=origin) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + self.in_queue.put(envelope) - def send(self, msg: Envelope) -> None: + def send(self, envelope: Envelope) -> None: """ Send message handler. - :param msg: the message. + :param envelope: the message. :return: None """ - if msg.protocol_id == "oef": - self.send_oef_message(msg) - elif msg.protocol_id == "fipa": - self.send_fipa_message(msg) - elif msg.protocol_id == "bytes": - self.send_message(msg.get("id"), msg.get("dialogue_id"), msg.to, msg.get("content")) - elif msg.protocol_id == "default": - self.send_message(msg.get("id"), 0, msg.to, msg.get("content")) + if envelope.protocol_id == "oef": + self.send_oef_message(envelope) + elif envelope.protocol_id == "fipa": + self.send_fipa_message(envelope) + elif envelope.protocol_id == "bytes": + message_id = envelope.message.get("id") + dialogue_id = envelope.message.get("dialogue_id") + content = envelope.message.get("content") + self.send_message(message_id, dialogue_id, envelope.to, content) + elif envelope.protocol_id == "default": + message_id = envelope.message.get("id") + dialogue_id = 0 + content = envelope.message.get("content") + self.send_message(message_id, dialogue_id, envelope.to, content) else: raise ValueError("Cannot send message.") - def send_oef_message(self, msg: Envelope) -> None: + def send_oef_message(self, envelope: Envelope) -> None: """ Send oef message handler. - :param msg: the message. + :param envelope: the message. :return: None """ - oef_type = msg.get("type") + oef_type = envelope.message.get("type") if oef_type == OEFMessage.Type.REGISTER_SERVICE: - id = msg.get("id") - service_description = msg.get("service_description") - service_id = msg.get("service_id") + id = envelope.message.get("id") + service_description = envelope.message.get("service_description") + service_id = envelope.message.get("service_id") self.register_service(id, service_description, service_id) elif oef_type == OEFMessage.Type.REGISTER_AGENT: - id = msg.get("id") - agent_description = msg.get("agent_description") + id = envelope.message.get("id") + agent_description = envelope.message.get("agent_description") self.register_agent(id, agent_description) elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - id = msg.get("id") - service_description = msg.get("service_description") - service_id = msg.get("service_id") + id = envelope.message.get("id") + service_description = envelope.message.get("service_description") + service_id = envelope.message.get("service_id") self.unregister_service(id, service_description, service_id) elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - id = msg.get("id") + id = envelope.message.get("id") self.unregister_agent(id) elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - id = msg.get("id") - query = msg.get("query") + id = envelope.message.get("id") + query = envelope.message.get("query") self.search_agents(id, query) elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - id = msg.get("id") - query = msg.get("query") + id = envelope.message.get("id") + query = envelope.message.get("query") self.mail_stats.search_start(id) self.search_services(id, query) else: raise ValueError("OEF request not recognized.") - def send_fipa_message(self, msg: Envelope) -> None: + def send_fipa_message(self, envelope: Envelope) -> None: """ Send fipa message handler. - :param msg: the message. + :param envelope: the message. :return: None """ - id = msg.get("id") - dialogue_id = msg.get("dialogue_id") - destination = msg.to - target = msg.get("target") - performative = msg.get("performative") + id = envelope.message.get("id") + dialogue_id = envelope.message.get("dialogue_id") + destination = envelope.to + target = envelope.message.get("target") + performative = envelope.message.get("performative") if performative == FIPAMessage.Performative.CFP: - query = msg.get("query") + query = envelope.message.get("query") self.send_cfp(id, dialogue_id, destination, target, query) elif performative == FIPAMessage.Performative.PROPOSE: - proposal = msg.get("proposal") + proposal = envelope.message.get("proposal") self.send_propose(id, dialogue_id, destination, target, proposal) elif performative == FIPAMessage.Performative.ACCEPT: self.send_accept(id, dialogue_id, destination, target) @@ -356,7 +354,7 @@ def _fetch(self) -> None: while not self._stopped: try: msg = self.out_queue.get(block=True, timeout=1.0) - self._send(msg) + self.send(msg) except Empty: pass @@ -393,7 +391,7 @@ def is_established(self) -> bool: """Get the connection status.""" return self.bridge.is_connected() - def _send(self, msg: Envelope) -> None: + def send(self, msg: Envelope): """ Send messages. diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index 1317fde1..e6bacf77 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -30,7 +30,6 @@ def setup_class(cls): cls.envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=cls.message) def test_default_protobuf_serialization(self): - message = self.message envelope = self.envelope serializer = DefaultProtobufSerializer() From 93713d71befd53bcff3524ebe5f987230d456e86 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 13:32:48 +0100 Subject: [PATCH 038/107] fix 'agents/v1/base' subpackage. --- tac/agents/v1/base/actions.py | 17 ++-- tac/agents/v1/base/dialogues.py | 61 ++++++------- tac/agents/v1/base/handlers.py | 13 +-- tac/agents/v1/base/helpers.py | 15 ++-- tac/agents/v1/base/interfaces.py | 16 ++-- tac/agents/v1/base/negotiation_behaviours.py | 92 ++++++++++---------- tac/agents/v1/base/participant_agent.py | 4 +- tac/agents/v1/base/reactions.py | 64 +++++++------- 8 files changed, 145 insertions(+), 137 deletions(-) diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index a55886dc..f93c6bed 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -69,8 +69,9 @@ def request_state_update(self) -> None: :return: None """ msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - message = ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, message_id=0, dialogue_id=0, content=msg) - self.mailbox.outbox.put(message) + message = ByteMessage(message_id=0, dialogue_id=0, content=msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, + protocol_id=ByteMessage.protocol_id, message=message) class OEFActions(OEFActionInterface): @@ -107,8 +108,8 @@ def search_for_tac(self) -> None: search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_tac.add(search_id) - message = OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) - self.mailbox.outbox.put(message) + message = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message) def update_services(self) -> None: """ @@ -126,9 +127,9 @@ def unregister_service(self) -> None: :return: None """ if self.game_instance.goods_demanded_description is not None: - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) if self.game_instance.goods_supplied_description is not None: - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) def register_service(self) -> None: """ @@ -172,7 +173,7 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for sellers which match the demand of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_sellers.add(search_id) - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) if self.game_instance.strategy.is_searching_for_buyers: query = self.game_instance.build_services_query(is_searching_for_sellers=False) if query is None: @@ -182,7 +183,7 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for buyers which match the supply of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_buyers.add(search_id) - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) class DialogueActions(DialogueActionInterface): diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/v1/base/dialogues.py index 1e8ed08d..da9655d4 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/v1/base/dialogues.py @@ -29,7 +29,8 @@ import logging from typing import List, Any, Dict, Optional -from tac.agents.v1.mail.messages import Message, FIPAMessage +from tac.agents.v1.mail.messages import FIPAMessage +from tac.agents.v1.mail.protocol import Envelope Action = Any logger = logging.getLogger(__name__) @@ -98,9 +99,9 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: self._is_seller = is_seller self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk self._role = 'seller' if is_seller else 'buyer' - self._outgoing_messages = [] # type: List[Message] - self._outgoing_messages_controller = [] # type: List[Message] - self._incoming_messages = [] # type: List[Message] + self._outgoing_messages = [] # type: List[Envelope] + self._outgoing_messages_controller = [] # type: List[Envelope] + self._incoming_messages = [] # type: List[Envelope] @property def dialogue_label(self) -> DialogueLabel: @@ -122,7 +123,7 @@ def role(self) -> str: """Get role of agent in dialogue.""" return self._role - def outgoing_extend(self, messages: List[Message]) -> None: + def outgoing_extend(self, messages: List[Envelope]) -> None: """ Extend the list of messages which keeps track of outgoing messages. @@ -132,7 +133,7 @@ def outgoing_extend(self, messages: List[Message]) -> None: for message in messages: self._outgoing_messages.extend([message]) - def incoming_extend(self, messages: List[Message]) -> None: + def incoming_extend(self, messages: List[Envelope]) -> None: """ Extend the list of messages which keeps track of incoming messages. @@ -147,8 +148,8 @@ def is_expecting_propose(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.CFP + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_initial_accept(self) -> bool: @@ -157,8 +158,8 @@ def is_expecting_initial_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_matching_accept(self) -> bool: @@ -167,8 +168,8 @@ def is_expecting_matching_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.ACCEPT return result def is_expecting_cfp_decline(self) -> bool: @@ -177,8 +178,8 @@ def is_expecting_cfp_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.CFP + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_propose_decline(self) -> bool: @@ -187,8 +188,8 @@ def is_expecting_propose_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_accept_decline(self) -> bool: @@ -197,8 +198,8 @@ def is_expecting_accept_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] + result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.ACCEPT return result @@ -231,7 +232,7 @@ def dialogues_as_buyer(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent acts as a buyer.""" return self._dialogues_as_buyer - def is_permitted_for_new_dialogue(self, msg: Message, known_pbks: List[str]) -> bool: + def is_permitted_for_new_dialogue(self, msg: Envelope, known_pbks: List[str]) -> bool: """ Check whether an agent message is permitted for a new dialogue. @@ -246,9 +247,9 @@ def is_permitted_for_new_dialogue(self, msg: Message, known_pbks: List[str]) -> :return: a boolean indicating whether the message is permitted for a new dialogue """ protocol = msg.protocol_id - msg_id = msg.get("id") - target = msg.get("target") - performative = msg.get("performative") + msg_id = msg.message.get("id") + target = msg.message.get("target") + performative = msg.message.get("performative") result = protocol == "fipa"\ and performative == FIPAMessage.Performative.CFP \ @@ -257,7 +258,7 @@ def is_permitted_for_new_dialogue(self, msg: Message, known_pbks: List[str]) -> and (msg.sender in known_pbks) return result - def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> bool: + def is_belonging_to_registered_dialogue(self, msg: Envelope, agent_pbk: str) -> bool: """ Check whether an agent message is part of a registered dialogue. @@ -267,10 +268,10 @@ def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> b :return: boolean indicating whether the message belongs to a registered dialogue """ assert msg.protocol_id == "fipa" - dialogue_id = msg.get("dialogue_id") + dialogue_id = msg.message.get("dialogue_id") opponent = msg.sender - target = msg.get("target") - performative = msg.get("performative") + target = msg.message.get("target") + performative = msg.message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) result = False @@ -296,7 +297,7 @@ def is_belonging_to_registered_dialogue(self, msg: Message, agent_pbk: str) -> b result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: + def get_dialogue(self, msg: Envelope, agent_pbk: str) -> Dialogue: """ Retrieve dialogue. @@ -306,10 +307,10 @@ def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: :return: the dialogue """ assert msg.protocol_id == "fipa" - dialogue_id = msg.get("dialogue_id") + dialogue_id = msg.message.get("dialogue_id") opponent = msg.sender - target = msg.get("target") - performative = msg.get("performative") + target = msg.message.get("target") + performative = msg.message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: diff --git a/tac/agents/v1/base/handlers.py b/tac/agents/v1/base/handlers.py index 05023eee..b9c02ae2 100644 --- a/tac/agents/v1/base/handlers.py +++ b/tac/agents/v1/base/handlers.py @@ -34,7 +34,8 @@ from tac.agents.v1.base.game_instance import GameInstance, GamePhase from tac.agents.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message, OEFMessage +from tac.agents.v1.mail.messages import OEFMessage +from tac.agents.v1.mail.protocol import Envelope from tac.helpers.crypto import Crypto from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled @@ -59,7 +60,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, msg: Message) -> None: + def handle_dialogue_message(self, msg: Envelope) -> None: """ Handle messages from the other agents. @@ -94,7 +95,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan ControllerActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) ControllerReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_controller_message(self, msg: Message) -> None: + def handle_controller_message(self, msg: Envelope) -> None: """ Handle messages from the controller. @@ -105,7 +106,7 @@ def handle_controller_message(self, msg: Message) -> None: :return: None """ assert msg.protocol_id == "bytes" - response = Response.from_pb(msg.get("content"), msg.sender, self.crypto) + response = Response.from_pb(msg.message.get("content"), msg.sender, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: if msg.sender != self.game_instance.controller_pbk: @@ -150,7 +151,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan OEFActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) OEFReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name, rejoin) - def handle_oef_message(self, msg: Message) -> None: + def handle_oef_message(self, msg: Envelope) -> None: """ Handle messages from the oef. @@ -162,7 +163,7 @@ def handle_oef_message(self, msg: Message) -> None: """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) assert msg.protocol_id == "oef" - oef_type = msg.get("type") + oef_type = msg.message.get("type") if oef_type == OEFMessage.Type.SEARCH_RESULT: self.on_search_result(msg) elif oef_type == OEFMessage.Type.OEF_ERROR: diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py index c6e9f826..d42704cf 100644 --- a/tac/agents/v1/base/helpers.py +++ b/tac/agents/v1/base/helpers.py @@ -22,7 +22,8 @@ import logging from tac.agents.v1.base.dialogues import DialogueLabel -from tac.agents.v1.mail.messages import Message, OEFMessage, FIPAMessage +from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage +from tac.agents.v1.mail.protocol import Envelope from tac.helpers.crypto import Crypto from tac.platform.protocol import Response @@ -30,17 +31,17 @@ logger = logging.getLogger(__name__) -def is_oef_message(msg: Message) -> bool: +def is_oef_message(msg: Envelope) -> bool: """ Check whether a message is from the oef. :param msg: the message :return: boolean indicating whether or not the message is from the oef """ - return msg.protocol_id == "oef" and msg.get("type") in set(OEFMessage.Type) + return msg.protocol_id == "oef" and msg.message.get("type") in set(OEFMessage.Type) -def is_controller_message(msg: Message, crypto: Crypto) -> bool: +def is_controller_message(msg: Envelope, crypto: Crypto) -> bool: """ Check whether a message is from the controller. @@ -52,7 +53,7 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: return False try: - byte_content = msg.get("content") + byte_content = msg.message.get("content") sender_pbk = msg.sender Response.from_pb(byte_content, sender_pbk, crypto) except Exception as e: @@ -68,9 +69,9 @@ def is_controller_message(msg: Message, crypto: Crypto) -> bool: return True -def is_fipa_message(msg: Message) -> bool: +def is_fipa_message(msg: Envelope) -> bool: """Chcek whether a message is a FIPA message.""" - return msg.protocol_id == "fipa" and msg.get("performative") in set(FIPAMessage.Performative) + return msg.protocol_id == "fipa" and msg.message.get("performative") in set(FIPAMessage.Performative) def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: diff --git a/tac/agents/v1/base/interfaces.py b/tac/agents/v1/base/interfaces.py index 1c095c9d..2e5b830c 100644 --- a/tac/agents/v1/base/interfaces.py +++ b/tac/agents/v1/base/interfaces.py @@ -22,7 +22,7 @@ from abc import abstractmethod -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData @@ -30,7 +30,7 @@ class ControllerReactionInterface: """This interface contains the methods to react to events from the ControllerAgent.""" @abstractmethod - def on_dialogue_error(self, dialogue_error_msg: Message) -> None: + def on_dialogue_error(self, dialogue_error_msg: Envelope) -> None: """ Handle dialogue error event emitted by the controller. @@ -102,7 +102,7 @@ class OEFReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod - def on_search_result(self, search_result: Message) -> None: + def on_search_result(self, search_result: Envelope) -> None: """ Handle search results. @@ -112,7 +112,7 @@ def on_search_result(self, search_result: Message) -> None: """ @abstractmethod - def on_oef_error(self, oef_error: Message) -> None: + def on_oef_error(self, oef_error: Envelope) -> None: """ Handle an OEF error message. @@ -122,7 +122,7 @@ def on_oef_error(self, oef_error: Message) -> None: """ @abstractmethod - def on_dialogue_error(self, dialogue_error: Message) -> None: + def on_dialogue_error(self, dialogue_error: Envelope) -> None: """ Handle a dialogue error message. @@ -180,7 +180,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, msg: Message) -> None: + def on_new_dialogue(self, msg: Envelope) -> None: """ React to a message for a new dialogue. @@ -194,7 +194,7 @@ def on_new_dialogue(self, msg: Message) -> None: """ @abstractmethod - def on_existing_dialogue(self, msg: Message) -> None: + def on_existing_dialogue(self, msg: Envelope) -> None: """ React to a message of an existing dialogue. @@ -204,7 +204,7 @@ def on_existing_dialogue(self, msg: Message) -> None: """ @abstractmethod - def on_unidentified_dialogue(self, msg: Message) -> None: + def on_unidentified_dialogue(self, msg: Envelope) -> None: """ React to a message of an unidentified dialogue. diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index 982ab945..bb291dec 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -29,7 +29,8 @@ from tac.agents.v1.base.game_instance import GameInstance from tac.agents.v1.base.helpers import generate_transaction_id from tac.agents.v1.base.stats_manager import EndState -from tac.agents.v1.mail.messages import Message, FIPAMessage, ByteMessage +from tac.agents.v1.mail.messages import FIPAMessage, ByteMessage +from tac.agents.v1.mail.protocol import Envelope from tac.helpers.crypto import Crypto from tac.platform.protocol import Transaction @@ -70,15 +71,16 @@ def agent_name(self) -> str: """Get the agent name.""" return self._agent_name - def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Message: + def on_cfp(self, envelope: Envelope, dialogue: Dialogue) -> Envelope: """ Handle a CFP. - :param cfp: the CFP + :param envelope: the envelope containing the CFP :param dialogue: the dialogue :return: a Propose or a Decline """ + cfp = envelope.message assert cfp.protocol_id == "fipa" and cfp.get("performative") == FIPAMessage.Performative.CFP goods_description = self.game_instance.get_service_description(is_supply=dialogue.is_seller) new_msg_id = cfp.get("id") + 1 @@ -94,54 +96,55 @@ def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Message: logger.debug("[{}]: Current strategy does not generate proposal that satisfies CFP query.".format(self.agent_name)) if decline: - logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, cfp.sender, + logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, envelope.sender, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), - "origin": cfp.sender, + "origin": envelope.sender, "target": cfp.get("target") }))) - response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, message_id=new_msg_id, - dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id")) + response = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id"))) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) else: - transaction_id = generate_transaction_id(self.crypto.public_key, cfp.sender, dialogue.dialogue_label, dialogue.is_seller) + transaction_id = generate_transaction_id(self.crypto.public_key, envelope.sender, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=cfp.sender, + counterparty=envelope.sender, sender=self.crypto.public_key, crypto=self.crypto) self.game_instance.transaction_manager.add_pending_proposal(dialogue.dialogue_label, new_msg_id, transaction) - logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, cfp.sender, + logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, envelope.sender, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), - "origin": cfp.sender, + "origin": envelope.sender, "target": cfp.get("id"), "propose": proposal.values }))) - response = FIPAMessage(to=cfp.sender, sender=self.crypto.public_key, performative=FIPAMessage.Performative.PROPOSE, - message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) + response = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(performative=FIPAMessage.Performative.PROPOSE, message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal])) return response - def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: + def on_propose(self, envelope: Envelope, dialogue: Dialogue) -> Envelope: """ Handle a Propose. - :param propose: the Propose + :param envelope: the envelope containing the Propose :param dialogue: the dialogue :return: an Accept or a Decline """ + propose = envelope.message logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) assert propose.protocol_id == "fipa" and propose.get("performative") == FIPAMessage.Performative.PROPOSE proposal = propose.get("proposal")[0] - transaction_id = generate_transaction_id(self.crypto.public_key, propose.sender, dialogue.dialogue_label, dialogue.is_seller) + transaction_id = generate_transaction_id(self.crypto.public_key, envelope.sender, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=propose.sender, + counterparty=envelope.sender, sender=self.crypto.public_key, crypto=self.crypto) new_msg_id = propose.get("id") + 1 @@ -151,29 +154,28 @@ def on_propose(self, propose: Message, dialogue: Dialogue) -> Message: logger.debug("[{}]: Accepting propose (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance(dialogue.dialogue_label, new_msg_id, transaction) - result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, message_id=new_msg_id, - dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), - performative=FIPAMessage.Performative.ACCEPT) + result = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT)) else: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) - result = FIPAMessage(to=propose.sender, sender=self.crypto.public_key, message_id=new_msg_id, - dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), - performative=FIPAMessage.Performative.DECLINE) + result = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.DECLINE)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result - def on_decline(self, decline: Message, dialogue: Dialogue) -> None: + def on_decline(self, envelope: Envelope, dialogue: Dialogue) -> None: """ Handle a Decline. - :param decline: the decline + :param envelope: the envelope containing the Propose :param dialogue: the dialogue :return: None """ + decline = envelope.message assert decline.protocol_id == "fipa" and decline.get("performative") == FIPAMessage.Performative.DECLINE logger.debug("[{}]: on_decline: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), decline.sender, decline.get("target"))) + .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), envelope.sender, decline.get("target"))) target = decline.get("target") if target == 1: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) @@ -189,36 +191,38 @@ def on_decline(self, decline: Message, dialogue: Dialogue) -> None: return None - def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: + def on_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: """ Handle an Accept. - :param accept: the accept + :param envelope: the envelope containing the Accept or MatchAccept :param dialogue: the dialogue :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ + accept = envelope.message assert accept.protocol_id == "fipa" and accept.get("performative") == FIPAMessage.Performative.ACCEPT logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), accept.sender, accept.get("target"))) + .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), envelope.sender, accept.get("target"))) target = accept.get("target") if dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ and target in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label]: - results = self._on_match_accept(accept, dialogue) + results = self._on_match_accept(envelope, dialogue) else: - results = self._on_initial_accept(accept, dialogue) + results = self._on_initial_accept(envelope, dialogue) return results - def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: + def _on_initial_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: """ Handle an initial Accept. - :param accept: the accept + :param envelope: the envelope containing the Accept :param dialogue: the dialogue :return: a Decline or an Accept and a Transaction (in OutContainer """ + accept = envelope.message transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) new_msg_id = accept.get("id") + 1 results = [] @@ -229,34 +233,32 @@ def _on_initial_accept(self, accept: Message, dialogue: Dialogue) -> List[Messag self.game_instance.world_state.update_on_initial_accept(transaction) logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - results.append(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), - content=transaction.serialize())) - results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, - message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), - performative=FIPAMessage.Performative.MATCH_ACCEPT)) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), content=transaction.serialize()))) + results.append(Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT))) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) - results.append(FIPAMessage(to=accept.sender, sender=self.crypto.public_key, + results.append(FIPAMessage(to=envelope.sender, sender=self.crypto.public_key, message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results - def _on_match_accept(self, accept: Message, dialogue: Dialogue) -> List[Message]: + def _on_match_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: """ Handle a matching Accept. - :param accept: the accept + :param envelope: the envelope containing the MatchAccept :param dialogue: the dialogue :return: a Transaction """ + accept = envelope.message logger.debug("[{}]: on match accept".format(self.agent_name)) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, accept.get("target")) - results.append(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), - content=transaction.serialize())) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), content=transaction.serialize()))) return results diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/v1/base/participant_agent.py index 6613a9bc..cb1ac5b7 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/v1/base/participant_agent.py @@ -29,7 +29,7 @@ from tac.agents.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler from tac.agents.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message from tac.agents.v1.base.strategy import Strategy -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.gui.dashboards.agent import AgentDashboard @@ -103,7 +103,7 @@ def react(self) -> None: counter = 0 while (not self.mailbox.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.mailbox.inbox.get_nowait() # type: Optional[Message] + msg = self.mailbox.inbox.get_nowait() # type: Optional[Envelope] logger.debug("processing message of protocol={}".format(msg.protocol_id)) if msg is not None: if is_oef_message(msg): diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 8607488a..682f4394 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -39,7 +39,8 @@ from tac.agents.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.v1.base.stats_manager import EndState from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message, ByteMessage, FIPAMessage +from tac.agents.v1.mail.messages import ByteMessage, FIPAMessage +from tac.agents.v1.mail.protocol import Envelope from tac.helpers.crypto import Crypto from tac.helpers.misc import TAC_DEMAND_DATAMODEL_NAME from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ @@ -72,7 +73,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, dialogue_error_msg: Message) -> None: + def on_dialogue_error(self, dialogue_error_msg: Envelope) -> None: """ Handle dialogue error event emitted by the controller. @@ -183,8 +184,8 @@ def _request_state_update(self) -> None: :return: None """ msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.mailbox.outbox.put(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=0, dialogue_id=0, content=msg)) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) class OEFReactions(OEFReactionInterface): @@ -210,7 +211,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.agent_name = agent_name self.rejoin = rejoin - def on_search_result(self, search_result: Message) -> None: + def on_search_result(self, search_result: Envelope) -> None: """ Split the search results from the OEF. @@ -219,8 +220,8 @@ def on_search_result(self, search_result: Message) -> None: :return: None """ assert search_result.protocol_id == "oef" - search_id = search_result.get("id") - agents = search_result.get("agents") + search_id = search_result.message.get("id") + agents = search_result.message.get("agents") logger.debug("[{}]: on search result: {} {}".format(self.agent_name, search_id, agents)) if search_id in self.game_instance.search.ids_for_tac: self._on_controller_search_result(agents) @@ -231,7 +232,7 @@ def on_search_result(self, search_result: Message) -> None: else: logger.debug("[{}]: Unknown search id: search_id={}".format(self.agent_name, search_id)) - def on_oef_error(self, oef_error: Message) -> None: + def on_oef_error(self, oef_error: Envelope) -> None: """ Handle an OEF error message. @@ -240,9 +241,9 @@ def on_oef_error(self, oef_error: Message) -> None: :return: None """ logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.agent_name, oef_error.get("id"), oef_error.get("operation"))) + .format(self.agent_name, oef_error.message.get("id"), oef_error.message.get("operation"))) - def on_dialogue_error(self, dialogue_error: Message) -> None: + def on_dialogue_error(self, dialogue_error: Envelope) -> None: """ Handle a dialogue error message. @@ -251,7 +252,7 @@ def on_dialogue_error(self, dialogue_error: Message) -> None: :return: None """ logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) + .format(self.agent_name, dialogue_error.message.get("id"), dialogue_error.message.get("dialogue_id"), dialogue_error.message.get("origin"))) def _on_controller_search_result(self, agent_pbks: List[str]) -> None: """ @@ -302,12 +303,13 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel return for agent_pbk in agent_pbks: dialogue = self.game_instance.dialogues.create_self_initiated(agent_pbk, self.crypto.public_key, not is_searching_for_sellers) - cfp = FIPAMessage(to=agent_pbk, sender=self.crypto.public_key, message_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, + cfp = FIPAMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, target=STARTING_MESSAGE_TARGET, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) + envelope = Envelope(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), cfp.to, cfp.get("target"), services)) - dialogue.outgoing_extend([cfp]) - self.mailbox.outbox.put(cfp) + dialogue.outgoing_extend([envelope]) + self.mailbox.outbox.put(envelope) def _register_to_tac(self, controller_pbk: str) -> None: """ @@ -320,8 +322,8 @@ def _register_to_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() - self.mailbox.outbox.put(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=0, dialogue_id=0, content=msg)) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) def _rejoin_tac(self, controller_pbk: str) -> None: """ @@ -334,8 +336,8 @@ def _rejoin_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.mailbox.outbox.put(ByteMessage(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - message_id=0, dialogue_id=0, content=msg)) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) class DialogueReactions(DialogueReactionInterface): @@ -361,7 +363,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.dialogues = game_instance.dialogues self.negotiation_behaviour = FIPABehaviour(crypto, game_instance, agent_name) - def on_new_dialogue(self, msg: Message) -> None: + def on_new_dialogue(self, msg: Envelope) -> None: """ React to a new dialogue. @@ -369,16 +371,16 @@ def on_new_dialogue(self, msg: Message) -> None: :return: None """ - assert msg.protocol_id == "fipa" and msg.get("performative") == FIPAMessage.Performative.CFP - services = json.loads(msg.get("query").decode('utf-8')) + assert msg.protocol_id == "fipa" and msg.message.get("performative") == FIPAMessage.Performative.CFP + services = json.loads(msg.message.get("query").decode('utf-8')) is_seller = services['description'] == TAC_DEMAND_DATAMODEL_NAME - dialogue = self.dialogues.create_opponent_initiated(msg.sender, msg.get("dialogue_id"), is_seller) + dialogue = self.dialogues.create_opponent_initiated(msg.sender, msg.message.get("dialogue_id"), is_seller) logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format(self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) results = self._handle(msg, dialogue) for result in results: self.mailbox.outbox.put(result) - def on_existing_dialogue(self, msg: Message) -> None: + def on_existing_dialogue(self, msg: Envelope) -> None: """ React to an existing dialogue. @@ -392,7 +394,7 @@ def on_existing_dialogue(self, msg: Message) -> None: for result in results: self.mailbox.outbox.put(result) - def on_unidentified_dialogue(self, msg: Message) -> None: + def on_unidentified_dialogue(self, msg: Envelope) -> None: """ React to an unidentified dialogue. @@ -401,12 +403,12 @@ def on_unidentified_dialogue(self, msg: Message) -> None: :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - message_id = msg.get("id") if msg.get("id") is not None else 1 - result = ByteMessage(to=msg.sender, sender=self.crypto.public_key, message_id=message_id + 1, - dialogue_id=msg.get("dialogue_id"), content=b'This message belongs to an unidentified dialogue.') - self.mailbox.outbox.put(result) + message_id = msg.message.get("id") if msg.message.get("id") is not None else 1 + dialogue_id = msg.message.get("dialogue_id") if msg.message.get("dialogue_id") is not None else 1 + result = ByteMessage(message_id=message_id + 1, dialogue_id=dialogue_id, content=b'This message belongs to an unidentified dialogue.') + self.mailbox.outbox.put_message(to=msg.sender, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, message=result) - def _handle(self, msg: Message, dialogue: Dialogue) -> List[Message]: + def _handle(self, msg: Envelope, dialogue: Dialogue) -> List[Envelope]: """ Handle a message according to the defined behaviour. @@ -416,8 +418,8 @@ def _handle(self, msg: Message, dialogue: Dialogue) -> List[Message]: :return: a list of agent messages """ dialogue.incoming_extend([msg]) - results = [] # type: List[Message] - performative = msg.get("performative") + results = [] # type: List[Envelope] + performative = msg.message.get("performative") if performative == FIPAMessage.Performative.CFP: result = self.negotiation_behaviour.on_cfp(msg, dialogue) results = [result] From 93a917b29c49fd58ba2c2ae661749d8dce10e95c Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 13:37:52 +0100 Subject: [PATCH 039/107] update the controller subpackage with the new message module. --- tac/platform/controller/controller_agent.py | 4 +-- tac/platform/controller/handlers.py | 35 +++++++++++---------- tac/platform/controller/interfaces.py | 6 ++-- tac/platform/controller/reactions.py | 10 +++--- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/tac/platform/controller/controller_agent.py b/tac/platform/controller/controller_agent.py index fd889419..9a492b7c 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/platform/controller/controller_agent.py @@ -34,7 +34,7 @@ from tac.agents.v1.agent import Agent from tac.agents.v1.base.game_instance import GamePhase from tac.agents.v1.base.helpers import is_oef_message -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope from tac.agents.v1.mail.oef import OEFNetworkMailBox from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor from tac.platform.controller.handlers import OEFHandler, GameHandler, AgentMessageDispatcher @@ -141,7 +141,7 @@ def react(self) -> None: counter = 0 while (not self.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.inbox.get_nowait() # type: Optional[Message] + msg = self.inbox.get_nowait() # type: Optional[Envelope] if msg is not None: if is_oef_message(msg): self.oef_handler.handle_oef_message(msg) diff --git a/tac/platform/controller/handlers.py b/tac/platform/controller/handlers.py index e23b9709..1a988420 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/platform/controller/handlers.py @@ -43,7 +43,8 @@ from tac.agents.v1.agent import Liveness from tac.agents.v1.base.game_instance import GamePhase from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import ByteMessage, Message, OEFMessage +from tac.agents.v1.mail.messages import ByteMessage, OEFMessage +from tac.agents.v1.mail.protocol import Envelope from tac.gui.monitor import Monitor, NullMonitor from tac.helpers.crypto import Crypto from tac.helpers.misc import generate_good_pbk_to_name @@ -212,10 +213,10 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: # send the transaction confirmation. tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - self.controller_agent.outbox.put(ByteMessage(to=tx.public_key, sender=self.controller_agent.crypto.public_key, - message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) - self.controller_agent.outbox.put(ByteMessage(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, - message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) + self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) + self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) @@ -279,7 +280,7 @@ def __init__(self, controller_agent: 'ControllerAgent'): GetStateUpdate: GetStateUpdateHandler(controller_agent), } # type: Dict[Type[Request], RequestHandler] - def handle_agent_message(self, msg: Message) -> Response: + def handle_agent_message(self, msg: Envelope) -> Response: """ Dispatch the request to the right handler. @@ -290,11 +291,11 @@ def handle_agent_message(self, msg: Message) -> Response: :return: the response. """ assert msg.protocol_id == "bytes" - msg_id = msg.get("id") - dialogue_id = msg.get("dialogue_id") + msg_id = msg.message.get("id") + dialogue_id = msg.message.get("dialogue_id") sender = msg.sender - logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, msg.get("id"), dialogue_id, sender)) - request = self.decode(msg.get("content"), sender) + logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, msg.message.get("id"), dialogue_id, sender)) + request = self.decode(msg.message.get("content"), sender) handle_request = self.handlers.get(type(request), None) # type: RequestHandler if handle_request is None: logger.debug("[{}]: Unknown message: msg_id={}, dialogue_id={}, origin={}".format(self.controller_agent.name, msg_id, dialogue_id, sender)) @@ -440,15 +441,15 @@ def _send_game_data_to_agents(self) -> None: .format(self.agent_name, public_key, str(game_data_response))) self.game_data_per_participant[public_key] = game_data_response - self.mailbox.outbox.put(ByteMessage(to=public_key, sender=self.crypto.public_key, - message_id=1, dialogue_id=1, content=game_data_response.serialize())) + self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=1, dialogue_id=1, content=game_data_response.serialize())) def notify_competition_cancelled(self): """Notify agents that the TAC is cancelled.""" logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.agent_name)) for agent_pbk in self.registered_agents: - self.mailbox.outbox.put(ByteMessage(to=agent_pbk, sender=self.crypto.public_key, - message_id=1, dialogue_id=1, content=Cancelled(agent_pbk, self.crypto).serialize())) + self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, + message=ByteMessage(message_id=1, dialogue_id=1, content=Cancelled(agent_pbk, self.crypto).serialize())) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: @@ -486,7 +487,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, mailbox: MailBox, agent_n OEFActions.__init__(self, crypto, liveness, mailbox, agent_name) OEFReactions.__init__(self, crypto, liveness, mailbox, agent_name) - def handle_oef_message(self, msg: Message) -> None: + def handle_oef_message(self, msg: Envelope) -> None: """ Handle messages from the oef. @@ -498,9 +499,9 @@ def handle_oef_message(self, msg: Message) -> None: """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) assert msg.protocol_id == "oef" - if msg.get("type") == OEFMessage.Type.OEF_ERROR: + if msg.message.get("type") == OEFMessage.Type.OEF_ERROR: self.on_oef_error(msg) - elif msg.get("type") == OEFMessage.Type.DIALOGUE_ERROR: + elif msg.message.get("type") == OEFMessage.Type.DIALOGUE_ERROR: self.on_dialogue_error(msg) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/platform/controller/interfaces.py b/tac/platform/controller/interfaces.py index 20f78a7e..0890dbe6 100644 --- a/tac/platform/controller/interfaces.py +++ b/tac/platform/controller/interfaces.py @@ -28,14 +28,14 @@ from abc import abstractmethod -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope class OEFReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod - def on_oef_error(self, oef_error: Message) -> None: + def on_oef_error(self, oef_error: Envelope) -> None: """ Handle an OEF error message. @@ -45,7 +45,7 @@ def on_oef_error(self, oef_error: Message) -> None: """ @abstractmethod - def on_dialogue_error(self, dialogue_error: Message) -> None: + def on_dialogue_error(self, dialogue_error: Envelope) -> None: """ Handle a dialogue error message. diff --git a/tac/platform/controller/reactions.py b/tac/platform/controller/reactions.py index 2796d475..038d95cc 100644 --- a/tac/platform/controller/reactions.py +++ b/tac/platform/controller/reactions.py @@ -28,7 +28,7 @@ from tac.agents.v1.agent import Liveness from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message +from tac.agents.v1.mail.protocol import Envelope from tac.helpers.crypto import Crypto from tac.platform.controller.interfaces import OEFReactionInterface @@ -54,7 +54,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, mailbox: MailBox, agent_n self.mailbox = mailbox self.agent_name = agent_name - def on_oef_error(self, oef_error: Message) -> None: + def on_oef_error(self, oef_error: Envelope) -> None: """ Handle an OEF error message. @@ -63,9 +63,9 @@ def on_oef_error(self, oef_error: Message) -> None: :return: None """ logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.agent_name, oef_error.get("id"), oef_error.get("operation"))) + .format(self.agent_name, oef_error.message.get("id"), oef_error.message.get("operation"))) - def on_dialogue_error(self, dialogue_error: Message) -> None: + def on_dialogue_error(self, dialogue_error: Envelope) -> None: """ Handle a dialogue error message. @@ -74,4 +74,4 @@ def on_dialogue_error(self, dialogue_error: Message) -> None: :return: None """ logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) + .format(self.agent_name, dialogue_error.message.get("id"), dialogue_error.message.get("dialogue_id"), dialogue_error.message.get("origin"))) From 8e4209c3a2e71306d20d379bd2e2f538029d8fc1 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 14:16:10 +0100 Subject: [PATCH 040/107] fix misc bugs due to recent changes. --- sandbox/playground.py | 14 ++++++++------ tac/agents/v1/base/actions.py | 6 ++++-- tac/agents/v1/base/negotiation_behaviours.py | 6 ++---- tac/agents/v1/base/reactions.py | 2 +- tac/agents/v1/mail/oef.py | 14 ++++++-------- tac/platform/controller/actions.py | 4 ++-- tests/test_controller.py | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index 1e153e8e..e818242c 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -36,7 +36,8 @@ from tac.agents.v1.base.dialogues import Dialogue from tac.agents.v1.examples.baseline import BaselineAgent from tac.agents.v1.examples.strategy import BaselineStrategy -from tac.agents.v1.mail.messages import FIPAMessage, Message +from tac.agents.v1.mail.messages import FIPAMessage +from tac.agents.v1.mail.protocol import Envelope from tac.platform.protocol import GameData CUR_PATH = inspect.getfile(inspect.currentframe()) @@ -131,12 +132,13 @@ def launch_oef(): starting_message_id = 1 starting_message_target = 0 services = agent_one.game_instance.build_services_dict(is_supply=is_seller) # type: Dict - cfp = FIPAMessage(agent_two.crypto.public_key, - message_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, + cfp = FIPAMessage(message_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, target=starting_message_target, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) - dialogue.outgoing_extend([cfp]) - agent_one.outbox.out_queue.put(cfp) + envelope = Envelope(to=agent_two.crypto.public_key, sender=agent_one.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=cfp) + dialogue.outgoing_extend([envelope]) + agent_one.outbox.out_queue.put(envelope) # Send the messages in the outbox # agent_one.out_box.send_nowait() @@ -151,7 +153,7 @@ def launch_oef(): checks += 1 else: checks = 10 - msg = agent_two.inbox.get_nowait() # type: Optional[Message] + msg = agent_two.inbox.get_nowait() # type: Optional[Envelope] print("The msg is a CFP: {}".format(msg.get("performative") == FIPAMessage.Performative.CFP)) # Set the debugger diff --git a/tac/agents/v1/base/actions.py b/tac/agents/v1/base/actions.py index f93c6bed..e327eba1 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/v1/base/actions.py @@ -146,12 +146,14 @@ def register_service(self) -> None: logger.debug("[{}]: Updating service directory as seller with goods supplied.".format(self.agent_name)) goods_supplied_description = self.game_instance.get_service_description(is_supply=True) self.game_instance.goods_supplied_description = goods_supplied_description - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="")) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, + message=OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="")) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description - self.mailbox.outbox.put(OEFMessage(to=None, sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="")) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, + message=OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="")) def search_services(self) -> None: """ diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/v1/base/negotiation_behaviours.py index bb291dec..6492c014 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/v1/base/negotiation_behaviours.py @@ -239,10 +239,8 @@ def _on_initial_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Env message=FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT))) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) - results.append(FIPAMessage(to=envelope.sender, sender=self.crypto.public_key, - message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), - target=accept.get("id"), - performative=FIPAMessage.Performative.DECLINE)) + results.append(Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, + message=FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE))) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/v1/base/reactions.py index 682f4394..48a191ec 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/v1/base/reactions.py @@ -307,7 +307,7 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel target=STARTING_MESSAGE_TARGET, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) envelope = Envelope(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" - .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), cfp.to, cfp.get("target"), services)) + .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), envelope.to, cfp.get("target"), services)) dialogue.outgoing_extend([envelope]) self.mailbox.outbox.put(envelope) diff --git a/tac/agents/v1/mail/oef.py b/tac/agents/v1/mail/oef.py index 7c3222cc..7eb4fac1 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/agents/v1/mail/oef.py @@ -170,13 +170,12 @@ def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> :param target: the message target. :return: None """ - msg = FIPAMessage(to=self.public_key, - sender=origin, - message_id=msg_id, + msg = FIPAMessage(message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.ACCEPT) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: """ @@ -188,13 +187,12 @@ def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> :param target: the message target. :return: None """ - msg = FIPAMessage(to=self.public_key, - sender=origin, - message_id=msg_id, + msg = FIPAMessage(message_id=msg_id, dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.DECLINE) - self.in_queue.put(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + self.in_queue.put(envelope) def on_search_result(self, search_id: int, agents: List[str]) -> None: """ diff --git a/tac/platform/controller/actions.py b/tac/platform/controller/actions.py index 80bcbd5b..186cb73c 100644 --- a/tac/platform/controller/actions.py +++ b/tac/platform/controller/actions.py @@ -68,5 +68,5 @@ def register_tac(self) -> None: """ desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) - out = OEFMessage(sender=self.crypto.public_key, oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") - self.mailbox.outbox.put(out) + out = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out) diff --git a/tests/test_controller.py b/tests/test_controller.py index 38c684a4..2a72dc46 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -92,7 +92,7 @@ def setup_class(cls): cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, Register(crypto.public_key, crypto, 'agent_name').serialize()) - time.sleep(1.0) + time.sleep(5.0) job.join() cls.agent1.stop() From 4315cc43375a52eab80a32bbb7b3760fdaa7b981 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Mon, 19 Aug 2019 14:25:13 +0100 Subject: [PATCH 041/107] Refactor in preparation of AEA framework extraction. --- sandbox/playground.py | 8 +- scripts/launch.py | 4 +- tac/README.md | 5 +- tac/{helpers => aea}/__init__.py | 2 +- tac/{agents/v1 => aea}/agent.py | 4 +- tac/aea/crypto/__init__.py | 22 + tac/{helpers/crypto.py => aea/crypto/base.py} | 0 tac/aea/dialogue/__init__.py | 22 + tac/aea/dialogue/base.py | 224 ++++++++++ tac/{agents/v1 => aea}/mail/__init__.py | 0 tac/{agents/v1 => aea}/mail/base.py | 2 +- tac/{agents/v1 => aea}/mail/messages.py | 3 +- tac/{agents/v1 => aea}/mail/oef.py | 4 +- tac/aea/state/__init__.py | 22 + tac/aea/state/base.py | 35 ++ tac/agents/controller/README.md | 3 + .../controller/__init__.py | 0 .../controller/agent.py} | 14 +- .../controller/base}/actions.py | 10 +- .../controller/base}/handlers.py | 23 +- tac/agents/controller/base/helpers.py | 37 ++ .../controller/base}/interfaces.py | 2 +- .../controller/base}/reactions.py | 10 +- .../controller/base/states.py} | 415 +----------------- .../controller/base}/tac_parameters.py | 0 tac/agents/participant/README.md | 3 + tac/agents/{v1 => participant}/__init__.py | 0 .../agent.py} | 14 +- .../{v1 => participant}/base/__init__.py | 0 .../{v1 => participant}/base/actions.py | 13 +- .../{v1 => participant}/base/dialogues.py | 108 +---- .../{v1 => participant}/base/game_instance.py | 25 +- .../{v1 => participant}/base/handlers.py | 14 +- tac/agents/participant/base/helpers.py | 216 +++++++++ .../{v1 => participant}/base/interfaces.py | 2 +- .../base/negotiation_behaviours.py | 12 +- .../participant/base}/price_model.py | 0 .../{v1 => participant}/base/reactions.py | 21 +- tac/agents/participant/base/states.py | 298 +++++++++++++ .../{v1 => participant}/base/stats_manager.py | 2 +- .../{v1 => participant}/base/strategy.py | 2 +- .../base/transaction_manager.py | 2 +- .../{v1 => participant}/examples/__init__.py | 0 .../{v1 => participant}/examples/baseline.py | 6 +- .../{v1 => participant}/examples/strategy.py | 7 +- tac/agents/v1/README.md | 3 - tac/agents/v1/base/helpers.py | 107 ----- tac/gui/dashboards/agent.py | 6 +- tac/gui/dashboards/controller.py | 6 +- tac/gui/dashboards/helpers.py | 66 +++ tac/gui/dashboards/leaderboard.py | 6 +- tac/gui/monitor.py | 2 +- tac/platform/game/__init__.py | 21 + tac/platform/game/base.py | 193 ++++++++ .../misc.py => platform/game/helpers.py} | 193 +------- tac/platform/{ => game}/stats.py | 5 +- tac/platform/helpers.py | 51 --- tac/{helpers => platform}/oef_health_check.py | 0 tac/platform/protocol.py | 7 +- tac/platform/simulation.py | 10 +- tests/conftest.py | 2 +- tests/test_agent/test_agent_state.py | 4 +- tests/test_agent/test_misc.py | 4 +- tests/test_controller.py | 6 +- tests/test_crypto.py | 2 +- tests/test_game.py | 6 +- tests/test_mail.py | 4 +- tests/test_misc.py | 18 +- tests/test_protocol.py | 2 +- tests/test_simulation.py | 14 +- 70 files changed, 1354 insertions(+), 1000 deletions(-) rename tac/{helpers => aea}/__init__.py (92%) rename tac/{agents/v1 => aea}/agent.py (98%) create mode 100644 tac/aea/crypto/__init__.py rename tac/{helpers/crypto.py => aea/crypto/base.py} (100%) create mode 100644 tac/aea/dialogue/__init__.py create mode 100644 tac/aea/dialogue/base.py rename tac/{agents/v1 => aea}/mail/__init__.py (100%) rename tac/{agents/v1 => aea}/mail/base.py (98%) rename tac/{agents/v1 => aea}/mail/messages.py (99%) rename tac/{agents/v1 => aea}/mail/oef.py (99%) create mode 100644 tac/aea/state/__init__.py create mode 100644 tac/aea/state/base.py create mode 100644 tac/agents/controller/README.md rename tac/{platform => agents}/controller/__init__.py (100%) rename tac/{platform/controller/controller_agent.py => agents/controller/agent.py} (97%) rename tac/{platform/controller => agents/controller/base}/actions.py (90%) rename tac/{platform/controller => agents/controller/base}/handlers.py (97%) create mode 100644 tac/agents/controller/base/helpers.py rename tac/{platform/controller => agents/controller/base}/interfaces.py (97%) rename tac/{platform/controller => agents/controller/base}/reactions.py (91%) rename tac/{platform/game.py => agents/controller/base/states.py} (59%) rename tac/{platform/controller => agents/controller/base}/tac_parameters.py (100%) create mode 100644 tac/agents/participant/README.md rename tac/agents/{v1 => participant}/__init__.py (100%) rename tac/agents/{v1/base/participant_agent.py => participant/agent.py} (92%) rename tac/agents/{v1 => participant}/base/__init__.py (100%) rename tac/agents/{v1 => participant}/base/actions.py (96%) rename tac/agents/{v1 => participant}/base/dialogues.py (79%) rename tac/agents/{v1 => participant}/base/game_instance.py (95%) rename tac/agents/{v1 => participant}/base/handlers.py (93%) create mode 100644 tac/agents/participant/base/helpers.py rename tac/agents/{v1 => participant}/base/interfaces.py (99%) rename tac/agents/{v1 => participant}/base/negotiation_behaviours.py (97%) rename tac/{helpers => agents/participant/base}/price_model.py (100%) rename tac/agents/{v1 => participant}/base/reactions.py (96%) create mode 100644 tac/agents/participant/base/states.py rename tac/agents/{v1 => participant}/base/stats_manager.py (99%) rename tac/agents/{v1 => participant}/base/strategy.py (98%) rename tac/agents/{v1 => participant}/base/transaction_manager.py (99%) rename tac/agents/{v1 => participant}/examples/__init__.py (100%) rename tac/agents/{v1 => participant}/examples/baseline.py (96%) rename tac/agents/{v1 => participant}/examples/strategy.py (95%) delete mode 100644 tac/agents/v1/README.md delete mode 100644 tac/agents/v1/base/helpers.py create mode 100644 tac/gui/dashboards/helpers.py create mode 100644 tac/platform/game/__init__.py create mode 100644 tac/platform/game/base.py rename tac/{helpers/misc.py => platform/game/helpers.py} (57%) rename tac/platform/{ => game}/stats.py (98%) delete mode 100644 tac/platform/helpers.py rename tac/{helpers => platform}/oef_health_check.py (100%) diff --git a/sandbox/playground.py b/sandbox/playground.py index 1e153e8e..f03ebf63 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -33,10 +33,10 @@ from typing import Dict, Optional -from tac.agents.v1.base.dialogues import Dialogue -from tac.agents.v1.examples.baseline import BaselineAgent -from tac.agents.v1.examples.strategy import BaselineStrategy -from tac.agents.v1.mail.messages import FIPAMessage, Message +from tac.aea.mail.messages import FIPAMessage, Message +from tac.agents.participant.base.dialogues import Dialogue +from tac.agents.participant.examples.baseline import BaselineAgent +from tac.agents.participant.examples.strategy import BaselineStrategy from tac.platform.protocol import GameData CUR_PATH = inspect.getfile(inspect.currentframe()) diff --git a/scripts/launch.py b/scripts/launch.py index fafe2642..53ebf225 100644 --- a/scripts/launch.py +++ b/scripts/launch.py @@ -29,7 +29,7 @@ import docker -import tac.agents.v1.examples.baseline +from tac.agents.participant.examples.baseline import main as participant_agent_main CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") @@ -87,4 +87,4 @@ def wait_for_oef(): with Sandbox(): wait_for_oef() - tac.agents.v1.examples.baseline.main(name="my_agent", dashboard=True) + participant_agent_main(name="my_agent", dashboard=True) diff --git a/tac/README.md b/tac/README.md index 5c236b49..dfc87e93 100644 --- a/tac/README.md +++ b/tac/README.md @@ -3,7 +3,6 @@ Framework for the Trading Agents Competition ## Available modules -- `agents`: the agent framework and sample implementations; +- `agents`: the agent implementations; - `gui`: the gui modules; -- `helpers`: supporting modules; -- `platform`: the competition framework; \ No newline at end of file +- `platform`: the game modules; \ No newline at end of file diff --git a/tac/helpers/__init__.py b/tac/aea/__init__.py similarity index 92% rename from tac/helpers/__init__.py rename to tac/aea/__init__.py index 45b00496..4763e0c6 100644 --- a/tac/helpers/__init__.py +++ b/tac/aea/__init__.py @@ -18,4 +18,4 @@ # # ------------------------------------------------------------------------------ -"""The helpers module contains the helpers of the TAC package.""" +"""Contains the AEA package.""" diff --git a/tac/agents/v1/agent.py b/tac/aea/agent.py similarity index 98% rename from tac/agents/v1/agent.py rename to tac/aea/agent.py index 7e24ce3c..8e9ad0ab 100644 --- a/tac/agents/v1/agent.py +++ b/tac/aea/agent.py @@ -27,8 +27,8 @@ from enum import Enum from typing import Optional -from tac.agents.v1.mail.base import InBox, OutBox, MailBox -from tac.helpers.crypto import Crypto +from tac.aea.mail.base import InBox, OutBox, MailBox +from tac.aea.crypto.base import Crypto logger = logging.getLogger(__name__) diff --git a/tac/aea/crypto/__init__.py b/tac/aea/crypto/__init__.py new file mode 100644 index 00000000..fdfae79e --- /dev/null +++ b/tac/aea/crypto/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the crypto modules.""" diff --git a/tac/helpers/crypto.py b/tac/aea/crypto/base.py similarity index 100% rename from tac/helpers/crypto.py rename to tac/aea/crypto/base.py diff --git a/tac/aea/dialogue/__init__.py b/tac/aea/dialogue/__init__.py new file mode 100644 index 00000000..d4d42dda --- /dev/null +++ b/tac/aea/dialogue/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the dialogue modules.""" diff --git a/tac/aea/dialogue/base.py b/tac/aea/dialogue/base.py new file mode 100644 index 00000000..a63361c6 --- /dev/null +++ b/tac/aea/dialogue/base.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module contains the classes required for dialogue management. + +- DialogueLabel: The dialogue label class acts as an identifier for dialogues. +- Dialogue: The dialogue class maintains state of a dialogue and manages it. +- Dialogues: The dialogues class keeps track of all dialogues. +""" + +from abc import abstractmethod +from typing import Dict, List, TYPE_CHECKING + +if TYPE_CHECKING: + from tac.aea.mail.messages import Message + + +class DialogueLabel: + """The dialogue label class acts as an identifier for dialogues.""" + + def __init__(self, dialogue_id: int, dialogue_opponent_pbk: str, dialogue_starter_pbk: str) -> None: + """ + Initialize a dialogue label. + + :param dialogue_id: the id of the dialogue. + :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. + :param dialogue_starter_pbk: the pbk of the agent which started the dialogue. + + :return: None + """ + self._dialogue_id = dialogue_id + self._dialogue_opponent_pbk = dialogue_opponent_pbk + self._dialogue_starter_pbk = dialogue_starter_pbk + + @property + def dialogue_id(self) -> int: + """Get the dialogue id.""" + return self._dialogue_id + + @property + def dialogue_opponent_pbk(self) -> str: + """Get the public key of the dialogue opponent.""" + return self._dialogue_opponent_pbk + + @property + def dialogue_starter_pbk(self) -> str: + """Get the public key of the dialogue starter.""" + return self._dialogue_starter_pbk + + def __eq__(self, other) -> bool: + """Check for equality between two DialogueLabel objects.""" + if type(other) == DialogueLabel: + return self._dialogue_id == other.dialogue_id and self._dialogue_starter_pbk == other.dialogue_starter_pbk and self._dialogue_opponent_pbk == other.dialogue_opponent_pbk + else: + return False + + def __hash__(self) -> int: + """Turn object into hash.""" + return hash((self.dialogue_id, self.dialogue_opponent_pbk, self.dialogue_starter_pbk)) + + +class Dialogue: + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__(self, dialogue_label: DialogueLabel) -> None: + """ + Initialize a dialogue label. + + :param dialogue_label: the identifier of the dialogue + + :return: None + """ + self._dialogue_label = dialogue_label + self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk + self._outgoing_messages = [] # type: List['Message'] + self._outgoing_messages_controller = [] # type: List['Message'] + self._incoming_messages = [] # type: List['Message'] + + @property + def dialogue_label(self) -> DialogueLabel: + """Get the dialogue lable.""" + return self._dialogue_label + + @property + def is_self_initiated(self) -> bool: + """Check whether the agent initiated the dialogue.""" + return self._is_self_initiated + + def outgoing_extend(self, messages: List['Message']) -> None: + """ + Extend the list of messages which keeps track of outgoing messages. + + :param messages: a list of messages to be added + :return: None + """ + for message in messages: + self._outgoing_messages.extend([message]) + + def incoming_extend(self, messages: List['Message']) -> None: + """ + Extend the list of messages which keeps track of incoming messages. + + :param messages: a list of messages to be added + :return: None + """ + self._incoming_messages.extend(messages) + + +class Dialogues: + """The dialogues class keeps track of all dialogues.""" + + def __init__(self) -> None: + """ + Initialize dialogues. + + :return: None + """ + self._dialogues = {} # type: Dict[DialogueLabel, Dialogue] + self._dialogue_id = 0 + + @property + def dialogues(self) -> Dict[DialogueLabel, Dialogue]: + """Get dictionary of dialogues in which the agent is engaged in.""" + return self._dialogues + + @abstractmethod + def is_permitted_for_new_dialogue(self, msg: 'Message', known_pbks: List[str]) -> bool: + """ + Check whether an agent message is permitted for a new dialogue. + + :param msg: the agent message + :param known_pbks: the list of known public keys + + :return: a boolean indicating whether the message is permitted for a new dialogue + """ + + @abstractmethod + def is_belonging_to_registered_dialogue(self, msg: 'Message', agent_pbk: str) -> bool: + """ + Check whether an agent message is part of a registered dialogue. + + :param msg: the agent message + :param agent_pbk: the public key of the agent + + :return: boolean indicating whether the message belongs to a registered dialogue + """ + + @abstractmethod + def get_dialogue(self, msg: 'Message', agent_pbk: str) -> Dialogue: + """ + Retrieve dialogue. + + :param msg: the agent message + :param agent_pbk: the public key of the agent + + :return: the dialogue + """ + + def _next_dialogue_id(self) -> int: + """ + Increment the id and returns it. + + :return: the next id + """ + self._dialogue_id += 1 + return self._dialogue_id + + def create_self_initiated(self, dialogue_opponent_pbk: str, dialogue_starter_pbk: str) -> Dialogue: + """ + Create a self initiated dialogue. + + :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. + :param dialogue_starter_pbk: the pbk of the agent which started the dialogue + + :return: the created dialogue. + """ + dialogue_label = DialogueLabel(self._next_dialogue_id(), dialogue_opponent_pbk, dialogue_starter_pbk) + result = self._create(dialogue_label) + return result + + def create_opponent_initiated(self, dialogue_opponent_pbk: str, dialogue_id: int) -> Dialogue: + """ + Save an opponent initiated dialogue. + + :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. + :param dialogue_id: the id of the dialogue + + :return: the created dialogue + """ + dialogue_starter_pbk = dialogue_opponent_pbk + dialogue_label = DialogueLabel(dialogue_id, dialogue_opponent_pbk, dialogue_starter_pbk) + result = self._create(dialogue_label) + return result + + def _create(self, dialogue_label: DialogueLabel) -> Dialogue: + """ + Create a dialogue. + + :param dialogue_label: the dialogue label + + :return: the created dialogue + """ + assert dialogue_label not in self.dialogues + dialogue = Dialogue(dialogue_label) + self.dialogues.update({dialogue_label: dialogue}) + return dialogue diff --git a/tac/agents/v1/mail/__init__.py b/tac/aea/mail/__init__.py similarity index 100% rename from tac/agents/v1/mail/__init__.py rename to tac/aea/mail/__init__.py diff --git a/tac/agents/v1/mail/base.py b/tac/aea/mail/base.py similarity index 98% rename from tac/agents/v1/mail/base.py rename to tac/aea/mail/base.py index d8b59ba3..0f0e6e10 100644 --- a/tac/agents/v1/mail/base.py +++ b/tac/aea/mail/base.py @@ -25,7 +25,7 @@ from queue import Queue from typing import Optional -from tac.agents.v1.mail.messages import Message +from tac.aea.mail.messages import Message logger = logging.getLogger(__name__) diff --git a/tac/agents/v1/mail/messages.py b/tac/aea/mail/messages.py similarity index 99% rename from tac/agents/v1/mail/messages.py rename to tac/aea/mail/messages.py index 8502f3c8..3d43fcbe 100644 --- a/tac/agents/v1/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -27,8 +27,7 @@ from oef.query import Query from oef.schema import Description -# from tac.agents.v1.base.dialogues import DialogueLabel -DialogueLabel = int +from tac.aea.dialogue.base import DialogueLabel Address = str ProtocolId = str diff --git a/tac/agents/v1/mail/oef.py b/tac/aea/mail/oef.py similarity index 99% rename from tac/agents/v1/mail/oef.py rename to tac/aea/mail/oef.py index 48ef6608..77cd7deb 100644 --- a/tac/agents/v1/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -31,8 +31,8 @@ from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES from oef.proxy import OEFNetworkProxy -from tac.agents.v1.mail.base import Connection, MailBox -from tac.agents.v1.mail.messages import OEFMessage, FIPAMessage, Message, ByteMessage +from tac.aea.mail.base import Connection, MailBox +from tac.aea.mail.messages import OEFMessage, FIPAMessage, Message, ByteMessage logger = logging.getLogger(__name__) diff --git a/tac/aea/state/__init__.py b/tac/aea/state/__init__.py new file mode 100644 index 00000000..225dd473 --- /dev/null +++ b/tac/aea/state/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the state modules.""" diff --git a/tac/aea/state/base.py b/tac/aea/state/base.py new file mode 100644 index 00000000..d0177513 --- /dev/null +++ b/tac/aea/state/base.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the classes which define the states of an agent.""" + + +class AgentState: + """Represent the state of an agent during the game.""" + + def __init__(self): + """Initialize.""" + + +class WorldState: + """Represent the state of an agent during the game.""" + + def __init__(self): + """Initialize.""" diff --git a/tac/agents/controller/README.md b/tac/agents/controller/README.md new file mode 100644 index 00000000..6f2ca945 --- /dev/null +++ b/tac/agents/controller/README.md @@ -0,0 +1,3 @@ +### controller + +The controller agent is organising the TAC. diff --git a/tac/platform/controller/__init__.py b/tac/agents/controller/__init__.py similarity index 100% rename from tac/platform/controller/__init__.py rename to tac/agents/controller/__init__.py diff --git a/tac/platform/controller/controller_agent.py b/tac/agents/controller/agent.py similarity index 97% rename from tac/platform/controller/controller_agent.py rename to tac/agents/controller/agent.py index fd889419..992eb77e 100644 --- a/tac/platform/controller/controller_agent.py +++ b/tac/agents/controller/agent.py @@ -31,14 +31,14 @@ import dateutil -from tac.agents.v1.agent import Agent -from tac.agents.v1.base.game_instance import GamePhase -from tac.agents.v1.base.helpers import is_oef_message -from tac.agents.v1.mail.messages import Message -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.agent import Agent +from tac.aea.mail.messages import Message +from tac.aea.mail.oef import OEFNetworkMailBox +from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher +from tac.agents.controller.base.tac_parameters import TACParameters +from tac.agents.participant.base.helpers import is_oef_message +from tac.platform.game.base import GamePhase from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor -from tac.platform.controller.handlers import OEFHandler, GameHandler, AgentMessageDispatcher -from tac.platform.controller.tac_parameters import TACParameters if __name__ != "__main__": logger = logging.getLogger(__name__) diff --git a/tac/platform/controller/actions.py b/tac/agents/controller/base/actions.py similarity index 90% rename from tac/platform/controller/actions.py rename to tac/agents/controller/base/actions.py index 80bcbd5b..74f8e4bd 100644 --- a/tac/platform/controller/actions.py +++ b/tac/agents/controller/base/actions.py @@ -28,11 +28,11 @@ from oef.schema import Description, DataModel, AttributeSchema -from tac.agents.v1.agent import Liveness -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import OEFMessage -from tac.platform.controller.interfaces import OEFActionInterface -from tac.helpers.crypto import Crypto +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import OEFMessage +from tac.agents.controller.base.interfaces import OEFActionInterface logger = logging.getLogger(__name__) diff --git a/tac/platform/controller/handlers.py b/tac/agents/controller/base/handlers.py similarity index 97% rename from tac/platform/controller/handlers.py rename to tac/agents/controller/base/handlers.py index e23b9709..f1219e38 100644 --- a/tac/platform/controller/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -40,21 +40,20 @@ from collections import defaultdict from typing import Any, Dict, Optional, List, Set, Type, TYPE_CHECKING -from tac.agents.v1.agent import Liveness -from tac.agents.v1.base.game_instance import GamePhase -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import ByteMessage, Message, OEFMessage +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import ByteMessage, Message, OEFMessage +from tac.agents.controller.base.actions import OEFActions +from tac.agents.controller.base.helpers import generate_good_pbk_to_name +from tac.agents.controller.base.reactions import OEFReactions +from tac.agents.controller.base.states import Game +from tac.agents.controller.base.tac_parameters import TACParameters from tac.gui.monitor import Monitor, NullMonitor -from tac.helpers.crypto import Crypto -from tac.helpers.misc import generate_good_pbk_to_name -from tac.platform.controller.actions import OEFActions -from tac.platform.controller.reactions import OEFReactions -from tac.platform.controller.tac_parameters import TACParameters -from tac.platform.game import Game +from tac.platform.game.base import GamePhase +from tac.platform.game.stats import GameStats from tac.platform.protocol import Response, Request, Register, Unregister, Error, GameData, \ Transaction, TransactionConfirmation, ErrorCode, Cancelled, GetStateUpdate, StateUpdate -# from tac.platform.controller.controller_agent import ControllerAgent -from tac.platform.stats import GameStats if TYPE_CHECKING: from tac.platform.controller.controller_agent import ControllerAgent diff --git a/tac/agents/controller/base/helpers.py b/tac/agents/controller/base/helpers.py new file mode 100644 index 00000000..327a9407 --- /dev/null +++ b/tac/agents/controller/base/helpers.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the helpers methods for the controller agent.""" + +from typing import Dict +import math + + +def generate_good_pbk_to_name(nb_goods: int) -> Dict[str, str]: + """ + Generate public keys for things. + + :param nb_goods: the number of things. + :return: a dictionary mapping goods' public keys to names. + """ + max_number_of_digits = math.ceil(math.log10(nb_goods)) + string_format = 'tac_good_{:0' + str(max_number_of_digits) + '}' + return {string_format.format(i) + '_pbk': string_format.format(i) for i in range(nb_goods)} diff --git a/tac/platform/controller/interfaces.py b/tac/agents/controller/base/interfaces.py similarity index 97% rename from tac/platform/controller/interfaces.py rename to tac/agents/controller/base/interfaces.py index 20f78a7e..a9f86691 100644 --- a/tac/platform/controller/interfaces.py +++ b/tac/agents/controller/base/interfaces.py @@ -28,7 +28,7 @@ from abc import abstractmethod -from tac.agents.v1.mail.messages import Message +from tac.aea.mail.messages import Message class OEFReactionInterface: diff --git a/tac/platform/controller/reactions.py b/tac/agents/controller/base/reactions.py similarity index 91% rename from tac/platform/controller/reactions.py rename to tac/agents/controller/base/reactions.py index 2796d475..39b0f01b 100644 --- a/tac/platform/controller/reactions.py +++ b/tac/agents/controller/base/reactions.py @@ -26,11 +26,11 @@ import logging -from tac.agents.v1.agent import Liveness -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message -from tac.helpers.crypto import Crypto -from tac.platform.controller.interfaces import OEFReactionInterface +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import Message +from tac.agents.controller.base.interfaces import OEFReactionInterface logger = logging.getLogger(__name__) diff --git a/tac/platform/game.py b/tac/agents/controller/base/states.py similarity index 59% rename from tac/platform/game.py rename to tac/agents/controller/base/states.py index 34f78b4e..07b80d43 100644 --- a/tac/platform/game.py +++ b/tac/agents/controller/base/states.py @@ -18,27 +18,22 @@ # # ------------------------------------------------------------------------------ -"""This module contains all the classes to represent the TAC game. +"""This module contains classes to represent the TAC game. Classes: -- GameConfiguration: a class to hold the configuration of a game. Immutable. - GameInitialization: a class to hold the initialization of a game. Immutable. - Game: the class that manages an instance of a game (e.g. validate and settling transactions). -- AgentState: a class to hold the current state of an agent. -- GoodState: a class to hold the current state of a good. -- WorldState represent the state of the world from the perspective of the agent. """ -import copy import logging -import pprint from typing import List, Dict, Any -from tac.helpers.misc import generate_money_endowments, generate_good_endowments, generate_utility_params, \ - logarithmic_utility, generate_equilibrium_prices_and_holdings, determine_scaling_factor -from tac.helpers.price_model import GoodPriceModel -from tac.helpers.crypto import Crypto +from tac.aea.crypto.base import Crypto +from tac.agents.participant.base.states import AgentState +from tac.platform.game.base import GameConfiguration, GoodState +from tac.platform.game.helpers import generate_money_endowments, generate_good_endowments, generate_utility_params, \ + generate_equilibrium_prices_and_holdings, determine_scaling_factor from tac.platform.protocol import Transaction Endowment = List[int] # an element e_j is the endowment of good j. @@ -49,124 +44,6 @@ DEFAULT_PRICE = 0.0 -class GameConfiguration: - """Class containing the game configuration of a TAC instance.""" - - def __init__(self, - nb_agents: int, - nb_goods: int, - tx_fee: float, - agent_pbk_to_name: Dict[str, str], - good_pbk_to_name: Dict[str, str]): - """ - Instantiate a game configuration. - - :param nb_agents: the number of agents. - :param nb_goods: the number of goods. - :param tx_fee: the fee for a transaction. - :param agent_pbk_to_name: a dictionary mapping agent public keys to agent names (as strings). - :param good_pbk_to_name: a dictionary mapping good public keys to good names (as strings). - """ - self._nb_agents = nb_agents - self._nb_goods = nb_goods - self._tx_fee = tx_fee - self._agent_pbk_to_name = agent_pbk_to_name - self._good_pbk_to_name = good_pbk_to_name - - self._check_consistency() - - @property - def nb_agents(self) -> int: - """Agent number of a TAC instance.""" - return self._nb_agents - - @property - def nb_goods(self) -> int: - """Good number of a TAC instance.""" - return self._nb_goods - - @property - def tx_fee(self) -> float: - """Transaction fee for the TAC instance.""" - return self._tx_fee - - @property - def agent_pbk_to_name(self) -> Dict[str, str]: - """Map agent public keys to names.""" - return self._agent_pbk_to_name - - @property - def good_pbk_to_name(self) -> Dict[str, str]: - """Map good public keys to names.""" - return self._good_pbk_to_name - - @property - def agent_pbks(self) -> List[str]: - """List of agent public keys.""" - return list(self._agent_pbk_to_name.keys()) - - @property - def agent_names(self): - """List of agent names.""" - return list(self._agent_pbk_to_name.values()) - - @property - def good_pbks(self) -> List[str]: - """List of good public keys.""" - return list(self._good_pbk_to_name.keys()) - - @property - def good_names(self) -> List[str]: - """List of good names.""" - return list(self._good_pbk_to_name.values()) - - def _check_consistency(self): - """ - Check the consistency of the game configuration. - - :return: None - :raises: AssertionError: if some constraint is not satisfied. - """ - assert self.tx_fee >= 0, "Tx fee must be non-negative." - assert self.nb_agents > 1, "Must have at least two agents." - assert self.nb_goods > 1, "Must have at least two goods." - assert len(self.agent_pbks) == self.nb_agents, "There must be one public key for each agent." - assert len(set(self.agent_names)) == self.nb_agents, "Agents' names must be unique." - assert len(self.good_pbks) == self.nb_goods, "There must be one public key for each good." - assert len(set(self.good_names)) == self.nb_goods, "Goods' names must be unique." - - def to_dict(self) -> Dict[str, Any]: - """Get a dictionary from the object.""" - return { - "nb_agents": self.nb_agents, - "nb_goods": self.nb_goods, - "tx_fee": self.tx_fee, - "agent_pbk_to_name": self.agent_pbk_to_name, - "good_pbk_to_name": self.good_pbk_to_name - } - - @classmethod - def from_dict(cls, d: Dict[str, Any]) -> 'GameConfiguration': - """Instantiate an object from the dictionary.""" - obj = cls( - d["nb_agents"], - d["nb_goods"], - d["tx_fee"], - d["agent_pbk_to_name"], - d["good_pbk_to_name"] - ) - return obj - - def __eq__(self, other): - """Compare equality of two objects.""" - return isinstance(other, GameConfiguration) and \ - self.nb_agents == other.nb_agents and \ - self.nb_goods == other.nb_goods and \ - self.tx_fee == other.tx_fee and \ - self.agent_pbk_to_name == other.agent_pbk_to_name and \ - self.good_pbk_to_name == other.good_pbk_to_name - - class GameInitialization: """Class containing the game initialization of a TAC instance.""" @@ -650,283 +527,3 @@ def __eq__(self, other): return isinstance(other, Game) and \ self.configuration == other.configuration and \ self.transactions == other.transactions - - -class AgentState: - """Represent the state of an agent during the game.""" - - def __init__(self, money: float, endowment: Endowment, utility_params: UtilityParams): - """ - Instantiate an agent state object. - - :param money: the money of the agent in this state. - :param endowment: the endowment for every good. - :param utility_params: the utility params for every good. - """ - assert len(endowment) == len(utility_params) - self.balance = money - self._utility_params = copy.copy(utility_params) - self._current_holdings = copy.copy(endowment) - - @property - def current_holdings(self): - """Get current holding of each good.""" - return copy.copy(self._current_holdings) - - @property - def utility_params(self) -> UtilityParams: - """Get utility parameter for each good.""" - return copy.copy(self._utility_params) - - def get_score(self) -> float: - """ - Compute the score of the current state. - - The score is computed as the sum of all the utilities for the good holdings - with positive quantity plus the money left. - :return: the score. - """ - goods_score = logarithmic_utility(self.utility_params, self.current_holdings) - money_score = self.balance - score = goods_score + money_score - return score - - def get_score_diff_from_transaction(self, tx: Transaction, tx_fee: float) -> float: - """ - Simulate a transaction and get the resulting score (taking into account the fee). - - :param tx: a transaction object. - :return: the score. - """ - current_score = self.get_score() - new_state = self.apply([tx], tx_fee) - new_score = new_state.get_score() - return new_score - current_score - - def check_transaction_is_consistent(self, tx: Transaction, tx_fee: float) -> bool: - """ - Check if the transaction is consistent. - - E.g. check that the agent state has enough money if it is a buyer - or enough holdings if it is a seller. - :return: True if the transaction is legal wrt the current state, false otherwise. - """ - share_of_tx_fee = round(tx_fee / 2.0, 2) - if tx.is_sender_buyer: - # check if we have the money. - result = self.balance >= tx.amount + share_of_tx_fee - else: - # check if we have the goods. - result = True - for good_id, quantity in enumerate(tx.quantities_by_good_pbk.values()): - result = result and (self._current_holdings[good_id] >= quantity) - return result - - def apply(self, transactions: List[Transaction], tx_fee: float) -> 'AgentState': - """ - Apply a list of transactions to the current state. - - :param transactions: the sequence of transaction. - :return: the final state. - """ - new_state = copy.copy(self) - for tx in transactions: - new_state.update(tx, tx_fee) - - return new_state - - def update(self, tx: Transaction, tx_fee: float) -> None: - """ - Update the agent state from a transaction. - - :param tx: the transaction. - :param tx_fee: the transaction fee. - :return: None - """ - share_of_tx_fee = round(tx_fee / 2.0, 2) - if tx.is_sender_buyer: - diff = tx.amount + share_of_tx_fee - self.balance -= diff - else: - diff = tx.amount - share_of_tx_fee - self.balance += diff - - for good_id, quantity in enumerate(tx.quantities_by_good_pbk.values()): - quantity_delta = quantity if tx.is_sender_buyer else -quantity - self._current_holdings[good_id] += quantity_delta - - def __copy__(self): - """Copy the object.""" - return AgentState(self.balance, self.current_holdings, self.utility_params) - - def __str__(self): - """From object to string.""" - return "AgentState{}".format(pprint.pformat({ - "money": self.balance, - "utility_params": self.utility_params, - "current_holdings": self._current_holdings - })) - - def __eq__(self, other) -> bool: - """Compare equality of two instances of the class.""" - return isinstance(other, AgentState) and \ - self.balance == other.balance and \ - self.utility_params == other.utility_params and \ - self._current_holdings == other._current_holdings - - -class GoodState: - """Represent the state of a good during the game.""" - - def __init__(self, price: float) -> None: - """ - Instantiate an agent state object. - - :param price: price of the good in this state. - :return: None - """ - self.price = price - - self._check_consistency() - - def _check_consistency(self) -> None: - """ - Check the consistency of the good state. - - :return: None - :raises: AssertionError: if some constraint is not satisfied. - """ - assert self.price >= 0, "The price must be non-negative." - - -class WorldState: - """Represent the state of the world from the perspective of the agent.""" - - def __init__(self, opponent_pbks: List[str], - good_pbks: List[str], - initial_agent_state: AgentState) -> None: - """ - Instantiate an agent state object. - - :param opponent_pbks: the public keys of the opponents - :param good_pbks: the public keys of the goods - :param agent_state: the initial state of the agent - :return: None - """ - self.opponent_states = dict( - (agent_pbk, - AgentState( - self._expected_initial_money_amount(initial_agent_state.balance), - self._expected_good_endowments(initial_agent_state.current_holdings), - self._expected_utility_params(initial_agent_state.utility_params) - )) - for agent_pbk in opponent_pbks) # type: Dict[str, AgentState] - - self.good_price_models = dict( - (good_pbk, - GoodPriceModel()) - for good_pbk in good_pbks) - - def update_on_cfp(self, query) -> None: - """Update the world state when a new cfp is received.""" - pass - - def update_on_proposal(self, proposal) -> None: - """Update the world state when a new proposal is received.""" - pass - - def update_on_declined_propose(self, transaction: Transaction) -> None: - """ - Update the world state when a transaction (propose) is rejected. - - :param transaction: the transaction - :return: None - """ - self._from_transaction_update_price(transaction, is_accepted=False) - - def _from_transaction_update_price(self, transaction: Transaction, is_accepted: bool) -> None: - """ - Update the good price model based on a transaction. - - :param transaction: the transaction - :param is_accepted: whether the transaction is accepted or not - :return: None - """ - good_pbks = [] # type: List[str] - for good_pbk, quantity in transaction.quantities_by_good_pbk.items(): - if quantity > 0: - good_pbks += [good_pbk] * quantity - price = transaction.amount - price = price / len(good_pbks) - for good_pbk in list(set(good_pbks)): - self._update_price(good_pbk, price, is_accepted=is_accepted) - - def update_on_initial_accept(self, transaction: Transaction) -> None: - """ - Update the world state when a proposal is accepted. - - :param transaction: the transaction - :return: None - """ - self._from_transaction_update_price(transaction, is_accepted=True) - - def _expected_initial_money_amount(self, initial_money_amount: float) -> float: - """ - Compute expectation of the initial_money_amount of an opponent. - - :param initial_money_amount: the initial amount of money of the agent. - :return: the expected initial money amount of the opponent - """ - # Naiive expectation - expected_initial_money_amount = initial_money_amount - return expected_initial_money_amount - - def _expected_good_endowments(self, good_endowment: Endowment) -> Endowment: - """ - Compute expectation of the good endowment of an opponent. - - :param good_endowment: the good_endowment of the agent. - :return: the expected good endowment of the opponent - """ - # Naiive expectation - expected_good_endowment = good_endowment - return expected_good_endowment - - def _expected_utility_params(self, utility_params: UtilityParams) -> UtilityParams: - """ - Compute expectation of the utility params of an opponent. - - :param utility_params: the utility_params of the agent. - :return: the expected utility params of the opponent - """ - # Naiive expectation - expected_utility_params = utility_params - return expected_utility_params - - def expected_price(self, good_pbk: str, marginal_utility: float, is_seller: bool, share_of_tx_fee: float) -> float: - """ - Compute expectation of the price for the good given a constraint. - - :param good_pbk: the pbk of the good - :param marginal_utility: the marginal_utility from the good - :param is_seller: whether the agent is a seller or buyer - :param share_of_tx_fee: the share of the tx fee the agent pays - :return: the expected price - """ - constraint = round(marginal_utility + share_of_tx_fee, 1) if is_seller else round(marginal_utility - share_of_tx_fee, 1) - good_price_model = self.good_price_models[good_pbk] - expected_price = good_price_model.get_price_expectation(constraint, is_seller) - return expected_price - - def _update_price(self, good_pbk: str, price: float, is_accepted: bool) -> None: - """ - Update the price for the good based on an outcome. - - :param good_pbk: the pbk of the good - :param price: the price to which the outcome relates - :param is_accepted: boolean indicating the outcome - :return: None - """ - price = round(price, 1) - good_price_model = self.good_price_models[good_pbk] - good_price_model.update(is_accepted, price) diff --git a/tac/platform/controller/tac_parameters.py b/tac/agents/controller/base/tac_parameters.py similarity index 100% rename from tac/platform/controller/tac_parameters.py rename to tac/agents/controller/base/tac_parameters.py diff --git a/tac/agents/participant/README.md b/tac/agents/participant/README.md new file mode 100644 index 00000000..7e217aad --- /dev/null +++ b/tac/agents/participant/README.md @@ -0,0 +1,3 @@ +### participant + +The participant agent is able to compete in the TAC. diff --git a/tac/agents/v1/__init__.py b/tac/agents/participant/__init__.py similarity index 100% rename from tac/agents/v1/__init__.py rename to tac/agents/participant/__init__.py diff --git a/tac/agents/v1/base/participant_agent.py b/tac/agents/participant/agent.py similarity index 92% rename from tac/agents/v1/base/participant_agent.py rename to tac/agents/participant/agent.py index 6613a9bc..f9f2e87e 100644 --- a/tac/agents/v1/base/participant_agent.py +++ b/tac/agents/participant/agent.py @@ -24,13 +24,13 @@ import time from typing import Optional -from tac.agents.v1.agent import Agent -from tac.agents.v1.base.game_instance import GameInstance, GamePhase -from tac.agents.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler -from tac.agents.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message -from tac.agents.v1.base.strategy import Strategy -from tac.agents.v1.mail.messages import Message -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.agent import Agent +from tac.aea.mail.messages import Message +from tac.aea.mail.oef import OEFNetworkMailBox +from tac.agents.participant.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.base.handlers import DialogueHandler, ControllerHandler, OEFHandler +from tac.agents.participant.base.helpers import is_oef_message, is_controller_message, is_fipa_message +from tac.agents.participant.base.strategy import Strategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tac/agents/v1/base/__init__.py b/tac/agents/participant/base/__init__.py similarity index 100% rename from tac/agents/v1/base/__init__.py rename to tac/agents/participant/base/__init__.py diff --git a/tac/agents/v1/base/actions.py b/tac/agents/participant/base/actions.py similarity index 96% rename from tac/agents/v1/base/actions.py rename to tac/agents/participant/base/actions.py index a55886dc..40acbe4c 100644 --- a/tac/agents/v1/base/actions.py +++ b/tac/agents/participant/base/actions.py @@ -30,12 +30,13 @@ from oef.query import Query, Constraint, GtEq -from tac.agents.v1.agent import Liveness -from tac.agents.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface -from tac.agents.v1.base.game_instance import GameInstance -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import ByteMessage, OEFMessage -from tac.helpers.crypto import Crypto +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import ByteMessage, OEFMessage +from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface +from tac.agents.participant.base.game_instance import GameInstance + from tac.platform.protocol import GetStateUpdate logger = logging.getLogger(__name__) diff --git a/tac/agents/v1/base/dialogues.py b/tac/agents/participant/base/dialogues.py similarity index 79% rename from tac/agents/v1/base/dialogues.py rename to tac/agents/participant/base/dialogues.py index e6bc90e2..c5e01791 100644 --- a/tac/agents/v1/base/dialogues.py +++ b/tac/agents/participant/base/dialogues.py @@ -27,9 +27,13 @@ """ import logging -from typing import List, Any, Dict, Optional +from typing import Any, Dict, List, Optional + +from tac.aea.dialogue.base import DialogueLabel +from tac.aea.dialogue.base import Dialogue as BaseDialogue +from tac.aea.dialogue.base import Dialogues as BaseDialogues +from tac.aea.mail.messages import Message, FIPAMessage -from tac.agents.v1.mail.messages import Message, FIPAMessage Action = Any logger = logging.getLogger(__name__) @@ -38,51 +42,7 @@ STARTING_MESSAGE_TARGET = 0 -class DialogueLabel: - """The dialogue label class acts as an identifier for dialogues.""" - - def __init__(self, dialogue_id: int, dialogue_opponent_pbk: str, dialogue_starter_pbk: str) -> None: - """ - Initialize a dialogue label. - - :param dialogue_id: the id of the dialogue. - :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. - :param dialogue_starter_pbk: the pbk of the agent which started the dialogue. - - :return: None - """ - self._dialogue_id = dialogue_id - self._dialogue_opponent_pbk = dialogue_opponent_pbk - self._dialogue_starter_pbk = dialogue_starter_pbk - - @property - def dialogue_id(self) -> int: - """Get the dialogue id.""" - return self._dialogue_id - - @property - def dialogue_opponent_pbk(self) -> str: - """Get the public key of the dialogue opponent.""" - return self._dialogue_opponent_pbk - - @property - def dialogue_starter_pbk(self) -> str: - """Get the public key of the dialogue starter.""" - return self._dialogue_starter_pbk - - def __eq__(self, other) -> bool: - """Check for equality between two DialogueLabel objects.""" - if type(other) == DialogueLabel: - return self._dialogue_id == other.dialogue_id and self._dialogue_starter_pbk == other.dialogue_starter_pbk and self._dialogue_opponent_pbk == other.dialogue_opponent_pbk - else: - return False - - def __hash__(self) -> int: - """Turn object into hash.""" - return hash((self.dialogue_id, self.dialogue_opponent_pbk, self.dialogue_starter_pbk)) - - -class Dialogue: +class Dialogue(BaseDialogue): """The dialogue class maintains state of a dialogue and manages it.""" def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: @@ -94,53 +54,20 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: :return: None """ - self._dialogue_label = dialogue_label + super().__init__(dialogue_label=DialogueLabel) self._is_seller = is_seller - self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk self._role = 'seller' if is_seller else 'buyer' - self._outgoing_messages = [] # type: List[Message] - self._outgoing_messages_controller = [] # type: List[Message] - self._incoming_messages = [] # type: List[Message] - - @property - def dialogue_label(self) -> DialogueLabel: - """Get the dialogue lable.""" - return self._dialogue_label @property def is_seller(self) -> bool: """Check whether the agent acts as the seller in this dialogue.""" return self._is_seller - @property - def is_self_initiated(self) -> bool: - """Check whether the agent initiated the dialogue.""" - return self._is_self_initiated - @property def role(self) -> str: """Get role of agent in dialogue.""" return self._role - def outgoing_extend(self, messages: List[Message]) -> None: - """ - Extend the list of messages which keeps track of outgoing messages. - - :param messages: a list of messages to be added - :return: None - """ - for message in messages: - self._outgoing_messages.extend([message]) - - def incoming_extend(self, messages: List[Message]) -> None: - """ - Extend the list of messages which keeps track of incoming messages. - - :param messages: a list of messages to be added - :return: None - """ - self._incoming_messages.extend(messages) - def is_expecting_propose(self) -> bool: """ Check whether the dialogue is expecting a propose. @@ -202,7 +129,7 @@ def is_expecting_accept_decline(self) -> bool: return result -class Dialogues: +class Dialogues(BaseDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self) -> None: @@ -211,15 +138,9 @@ def __init__(self) -> None: :return: None """ - self._dialogues = {} # type: Dict[DialogueLabel, Dialogue] + super().__init__() self._dialogues_as_seller = {} # type: Dict[DialogueLabel, Dialogue] self._dialogues_as_buyer = {} # type: Dict[DialogueLabel, Dialogue] - self._dialogue_id = 0 - - @property - def dialogues(self) -> Dict[DialogueLabel, Dialogue]: - """Get dictionary of dialogues in which the agent is engaged in.""" - return self._dialogues @property def dialogues_as_seller(self) -> Dict[DialogueLabel, Dialogue]: @@ -338,15 +259,6 @@ def get_dialogue(self, msg: Message, agent_pbk: str) -> Dialogue: raise ValueError('Should have found dialogue.') return dialogue - def _next_dialogue_id(self) -> int: - """ - Increment the id and returns it. - - :return: the next id - """ - self._dialogue_id += 1 - return self._dialogue_id - def create_self_initiated(self, dialogue_opponent_pbk: str, dialogue_starter_pbk: str, is_seller: bool) -> Dialogue: """ Create a self initiated dialogue. diff --git a/tac/agents/v1/base/game_instance.py b/tac/agents/participant/base/game_instance.py similarity index 95% rename from tac/agents/v1/base/game_instance.py rename to tac/agents/participant/base/game_instance.py index 8c7d4acd..30be06d3 100644 --- a/tac/agents/v1/base/game_instance.py +++ b/tac/agents/participant/base/game_instance.py @@ -21,33 +21,24 @@ """This class manages the state and some services related to the TAC for an agent.""" import datetime -from enum import Enum import random from typing import List, Optional, Set, Tuple, Dict, Union from oef.query import Query from oef.schema import Description -from tac.agents.v1.mail.oef import MailStats -from tac.agents.v1.base.dialogues import Dialogues, Dialogue -from tac.agents.v1.base.transaction_manager import TransactionManager -from tac.agents.v1.base.strategy import Strategy -from tac.agents.v1.base.stats_manager import StatsManager +from tac.aea.mail.oef import MailStats +from tac.agents.participant.base.dialogues import Dialogues, Dialogue +from tac.agents.participant.base.helpers import build_dict, build_query, get_goods_quantities_description +from tac.agents.participant.base.states import AgentState, WorldState +from tac.agents.participant.base.stats_manager import StatsManager +from tac.agents.participant.base.strategy import Strategy +from tac.agents.participant.base.transaction_manager import TransactionManager from tac.gui.dashboards.agent import AgentDashboard -from tac.platform.game import AgentState, WorldState, GameConfiguration -from tac.helpers.misc import build_dict, build_query, get_goods_quantities_description +from tac.platform.game.base import GamePhase, GameConfiguration from tac.platform.protocol import GameData, StateUpdate, Transaction -class GamePhase(Enum): - """This class defines the TAC game stages.""" - - PRE_GAME = 'pre_game' - GAME_SETUP = 'game_setup' - GAME = 'game' - POST_GAME = 'post_game' - - class Search: """This class deals with the search state.""" diff --git a/tac/agents/v1/base/handlers.py b/tac/agents/participant/base/handlers.py similarity index 93% rename from tac/agents/v1/base/handlers.py rename to tac/agents/participant/base/handlers.py index 05023eee..878b7228 100644 --- a/tac/agents/v1/base/handlers.py +++ b/tac/agents/participant/base/handlers.py @@ -29,13 +29,13 @@ import logging from typing import Any -from tac.agents.v1.agent import Liveness -from tac.agents.v1.base.actions import DialogueActions, ControllerActions, OEFActions -from tac.agents.v1.base.game_instance import GameInstance, GamePhase -from tac.agents.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message, OEFMessage -from tac.helpers.crypto import Crypto +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import Message, OEFMessage +from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions +from tac.agents.participant.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.base.reactions import DialogueReactions, ControllerReactions, OEFReactions from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/base/helpers.py b/tac/agents/participant/base/helpers.py new file mode 100644 index 00000000..b80b5b20 --- /dev/null +++ b/tac/agents/participant/base/helpers.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains helper methods for base agent implementations.""" +import logging +from typing import Dict, List, Set, Union + +from tac.aea.crypto.base import Crypto +from tac.aea.dialogue.base import DialogueLabel +from tac.aea.mail.messages import Message, OEFMessage, FIPAMessage +from tac.platform.protocol import Response + +from oef.query import Query, Constraint, GtEq, Or +from oef.schema import AttributeSchema, DataModel, Description + + +logger = logging.getLogger(__name__) + + +logger = logging.getLogger("tac") +TAC_SUPPLY_DATAMODEL_NAME = "tac_supply" +TAC_DEMAND_DATAMODEL_NAME = "tac_demand" +QUANTITY_SHIFT = 1 # Any non-negative integer is fine. + + +def is_oef_message(msg: Message) -> bool: + """ + Check whether a message is from the oef. + + :param msg: the message + :return: boolean indicating whether or not the message is from the oef + """ + return msg.protocol_id == "oef" and msg.get("type") in set(OEFMessage.Type) + + +def is_controller_message(msg: Message, crypto: Crypto) -> bool: + """ + Check whether a message is from the controller. + + :param msg: the message + :param crypto: the crypto of the agent + :return: boolean indicating whether or not the message is from the controller + """ + if not msg.protocol_id == "bytes": + return False + + try: + byte_content = msg.get("content") + sender_pbk = msg.sender + Response.from_pb(byte_content, sender_pbk, crypto) + except Exception as e: + logger.debug("Not a Controller message: {}".format(str(e))) + # try: + # byte_content = msg.get("content") + # sender_pbk = msg.sender + # Response.from_pb(byte_content, sender_pbk, crypto) + # except: + # pass + return False + + return True + + +def is_fipa_message(msg: Message) -> bool: + """Chcek whether a message is a FIPA message.""" + return msg.protocol_id == "fipa" and msg.get("performative") in set(FIPAMessage.Performative) + + +def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: + """ + Make a transaction id. + + :param agent_pbk: the pbk of the agent. + :param opponent_pbk: the public key of the opponent. + :param dialogue_label: the dialogue label + :param agent_is_seller: boolean indicating if the agent is a seller + :return: a transaction id + """ + # the format is {buyer_pbk}_{seller_pbk}_{dialogue_id}_{dialogue_starter_pbk} + assert opponent_pbk == dialogue_label.dialogue_opponent_pbk + buyer_pbk, seller_pbk = (opponent_pbk, agent_pbk) if agent_is_seller else (agent_pbk, opponent_pbk) + transaction_id = "{}_{}_{}_{}".format(buyer_pbk, seller_pbk, dialogue_label.dialogue_id, dialogue_label.dialogue_starter_pbk) + return transaction_id + + +def dialogue_label_from_transaction_id(agent_pbk: str, transaction_id: str) -> DialogueLabel: + """ + Recover dialogue label from transaction id. + + :param agent_pbk: the pbk of the agent. + :param transaction_id: the transaction id + :return: a dialogue label + """ + buyer_pbk, seller_pbk, dialogue_id, dialogue_starter_pbk = transaction_id.split('_') + if agent_pbk == buyer_pbk: + dialogue_opponent_pbk = seller_pbk + else: + dialogue_opponent_pbk = buyer_pbk + dialogue_label = DialogueLabel(int(dialogue_id), dialogue_opponent_pbk, dialogue_starter_pbk) + return dialogue_label + + +def build_datamodel(good_pbks: List[str], is_supply: bool) -> DataModel: + """ + Build a data model for supply and demand (i.e. for offered or requested goods). + + :param good_pbks: the list of good public keys + :param is_supply: Boolean indicating whether it is a supply or demand data model + + :return: the data model. + """ + goods_quantities_attributes = [AttributeSchema(good_pbk, int, False) + for good_pbk in good_pbks] + price_attribute = AttributeSchema("price", float, False) + description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME + data_model = DataModel(description, goods_quantities_attributes + [price_attribute]) + return data_model + + +def get_goods_quantities_description(good_pbks: List[str], good_quantities: List[int], is_supply: bool) -> Description: + """ + Get the TAC description for supply or demand. + + That is, a description with the following structure: + >>> description = { + ... "tac_good_0": 1, + ... "tac_good_1": 0, + ... #... + ... + ... } + >>> + + where the keys indicate the good_pbk and the values the quantity. + + >>> desc = get_goods_quantities_description(['tac_good_0', 'tac_good_1', 'tac_good_2', 'tac_good_3'], [0, 0, 1, 2], True) + >>> desc.data_model.name == TAC_SUPPLY_DATAMODEL_NAME + True + >>> desc.values == { + ... "tac_good_0": 0, + ... "tac_good_1": 0, + ... "tac_good_2": 1, + ... "tac_good_3": 2} + ... + True + + :param good_pbks: the public keys of the goods. + :param good_quantities: the quantities per good. + :param is_supply: True if the description is indicating supply, False if it's indicating demand. + + :return: the description to advertise on the Service Directory. + """ + data_model = build_datamodel(good_pbks, is_supply=is_supply) + desc = Description({good_pbk: quantity for good_pbk, quantity in zip(good_pbks, good_quantities)}, + data_model=data_model) + return desc + + +def build_query(good_pbks: Set[str], is_searching_for_sellers: bool) -> Query: + """ + Build buyer or seller search query. + + Specifically, build the search query + - to look for sellers if the agent is a buyer, or + - to look for buyers if the agent is a seller. + + In particular, if the agent is a buyer and the demanded good public keys are {'tac_good_0', 'tac_good_2', 'tac_good_3'}, the resulting constraint expression is: + + tac_good_0 >= 1 OR tac_good_2 >= 1 OR tac_good_3 >= 1 + + That is, the OEF will return all the sellers that have at least one of the good in the query + (assuming that the sellers are registered with the data model specified). + + :param good_pbks: the good public keys to put in the query + :param is_searching_for_sellers: Boolean indicating whether the query is for sellers (supply) or buyers (demand). + + :return: the query + """ + data_model = None if good_pbks is None else build_datamodel(list(good_pbks), is_supply=is_searching_for_sellers) + constraints = [Constraint(good_pbk, GtEq(1)) for good_pbk in good_pbks] + + if len(good_pbks) > 1: + constraints = [Or(constraints)] + + query = Query(constraints, model=data_model) + return query + + +def build_dict(good_pbks: Set[str], is_supply: bool) -> Dict[str, Union[str, List]]: + """ + Build supply or demand services dictionary. + + :param good_pbks: the good public keys to put in the query + :param is_supply: Boolean indicating whether the services are for supply or demand. + + :return: the dictionary + """ + description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME + result = {'description': description, 'services': list(good_pbks)} + return result diff --git a/tac/agents/v1/base/interfaces.py b/tac/agents/participant/base/interfaces.py similarity index 99% rename from tac/agents/v1/base/interfaces.py rename to tac/agents/participant/base/interfaces.py index 1c095c9d..148e88f1 100644 --- a/tac/agents/v1/base/interfaces.py +++ b/tac/agents/participant/base/interfaces.py @@ -22,7 +22,7 @@ from abc import abstractmethod -from tac.agents.v1.mail.messages import Message +from tac.aea.mail.messages import Message from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData diff --git a/tac/agents/v1/base/negotiation_behaviours.py b/tac/agents/participant/base/negotiation_behaviours.py similarity index 97% rename from tac/agents/v1/base/negotiation_behaviours.py rename to tac/agents/participant/base/negotiation_behaviours.py index 6532c933..653a5cb2 100644 --- a/tac/agents/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/base/negotiation_behaviours.py @@ -25,12 +25,12 @@ import pprint from typing import List -from tac.agents.v1.base.dialogues import Dialogue -from tac.agents.v1.base.game_instance import GameInstance -from tac.agents.v1.base.helpers import generate_transaction_id -from tac.agents.v1.base.stats_manager import EndState -from tac.agents.v1.mail.messages import Message, FIPAMessage, ByteMessage -from tac.helpers.crypto import Crypto +from tac.aea.crypto.base import Crypto +from tac.aea.mail.messages import Message, FIPAMessage, ByteMessage +from tac.agents.participant.base.dialogues import Dialogue +from tac.agents.participant.base.game_instance import GameInstance +from tac.agents.participant.base.helpers import generate_transaction_id +from tac.agents.participant.base.stats_manager import EndState from tac.platform.protocol import Transaction logger = logging.getLogger(__name__) diff --git a/tac/helpers/price_model.py b/tac/agents/participant/base/price_model.py similarity index 100% rename from tac/helpers/price_model.py rename to tac/agents/participant/base/price_model.py diff --git a/tac/agents/v1/base/reactions.py b/tac/agents/participant/base/reactions.py similarity index 96% rename from tac/agents/v1/base/reactions.py rename to tac/agents/participant/base/reactions.py index d73b314a..86fce9d4 100644 --- a/tac/agents/v1/base/reactions.py +++ b/tac/agents/participant/base/reactions.py @@ -30,18 +30,17 @@ import logging from typing import List -from tac.agents.v1.agent import Liveness -from tac.agents.v1.base.dialogues import Dialogue -from tac.agents.v1.base.game_instance import GameInstance, GamePhase -from tac.agents.v1.base.helpers import dialogue_label_from_transaction_id -from tac.agents.v1.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ +from tac.aea.agent import Liveness +from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import Message, ByteMessage, FIPAMessage +from tac.agents.participant.base.dialogues import Dialogue +from tac.agents.participant.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME +from tac.agents.participant.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ DialogueReactionInterface -from tac.agents.v1.base.negotiation_behaviours import FIPABehaviour -from tac.agents.v1.base.stats_manager import EndState -from tac.agents.v1.mail.base import MailBox -from tac.agents.v1.mail.messages import Message, ByteMessage, FIPAMessage -from tac.helpers.crypto import Crypto -from tac.helpers.misc import TAC_DEMAND_DATAMODEL_NAME +from tac.agents.participant.base.negotiation_behaviours import FIPABehaviour +from tac.agents.participant.base.stats_manager import EndState from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ GetStateUpdate diff --git a/tac/agents/participant/base/states.py b/tac/agents/participant/base/states.py new file mode 100644 index 00000000..d00442c2 --- /dev/null +++ b/tac/agents/participant/base/states.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +""" +This module contains the classes which define the states of an agent. + +- AgentState: a class to hold the current state of an agent. +- GoodState: a class to hold the current state of a good. +- WorldState represent the state of the world from the perspective of the agent. +""" + +import copy +import pprint +from typing import Dict, List + +from tac.aea.state.base import AgentState as BaseAgentState +from tac.aea.state.base import WorldState as BaseWorldState +from tac.agents.participant.base.price_model import GoodPriceModel +from tac.platform.game.helpers import logarithmic_utility +from tac.platform.protocol import Transaction + +Endowment = List[int] # an element e_j is the endowment of good j. +UtilityParams = List[float] # an element u_j is the utility value of good j. + + +class AgentState(BaseAgentState): + """Represent the state of an agent during the game.""" + + def __init__(self, money: float, endowment: Endowment, utility_params: UtilityParams): + """ + Instantiate an agent state object. + + :param money: the money of the agent in this state. + :param endowment: the endowment for every good. + :param utility_params: the utility params for every good. + """ + super().__init__() + assert len(endowment) == len(utility_params) + self.balance = money + self._utility_params = copy.copy(utility_params) + self._current_holdings = copy.copy(endowment) + + @property + def current_holdings(self): + """Get current holding of each good.""" + return copy.copy(self._current_holdings) + + @property + def utility_params(self) -> UtilityParams: + """Get utility parameter for each good.""" + return copy.copy(self._utility_params) + + def get_score(self) -> float: + """ + Compute the score of the current state. + + The score is computed as the sum of all the utilities for the good holdings + with positive quantity plus the money left. + :return: the score. + """ + goods_score = logarithmic_utility(self.utility_params, self.current_holdings) + money_score = self.balance + score = goods_score + money_score + return score + + def get_score_diff_from_transaction(self, tx: Transaction, tx_fee: float) -> float: + """ + Simulate a transaction and get the resulting score (taking into account the fee). + + :param tx: a transaction object. + :return: the score. + """ + current_score = self.get_score() + new_state = self.apply([tx], tx_fee) + new_score = new_state.get_score() + return new_score - current_score + + def check_transaction_is_consistent(self, tx: Transaction, tx_fee: float) -> bool: + """ + Check if the transaction is consistent. + + E.g. check that the agent state has enough money if it is a buyer + or enough holdings if it is a seller. + :return: True if the transaction is legal wrt the current state, false otherwise. + """ + share_of_tx_fee = round(tx_fee / 2.0, 2) + if tx.is_sender_buyer: + # check if we have the money. + result = self.balance >= tx.amount + share_of_tx_fee + else: + # check if we have the goods. + result = True + for good_id, quantity in enumerate(tx.quantities_by_good_pbk.values()): + result = result and (self._current_holdings[good_id] >= quantity) + return result + + def apply(self, transactions: List[Transaction], tx_fee: float) -> 'AgentState': + """ + Apply a list of transactions to the current state. + + :param transactions: the sequence of transaction. + :return: the final state. + """ + new_state = copy.copy(self) + for tx in transactions: + new_state.update(tx, tx_fee) + + return new_state + + def update(self, tx: Transaction, tx_fee: float) -> None: + """ + Update the agent state from a transaction. + + :param tx: the transaction. + :param tx_fee: the transaction fee. + :return: None + """ + share_of_tx_fee = round(tx_fee / 2.0, 2) + if tx.is_sender_buyer: + diff = tx.amount + share_of_tx_fee + self.balance -= diff + else: + diff = tx.amount - share_of_tx_fee + self.balance += diff + + for good_id, quantity in enumerate(tx.quantities_by_good_pbk.values()): + quantity_delta = quantity if tx.is_sender_buyer else -quantity + self._current_holdings[good_id] += quantity_delta + + def __copy__(self): + """Copy the object.""" + return AgentState(self.balance, self.current_holdings, self.utility_params) + + def __str__(self): + """From object to string.""" + return "AgentState{}".format(pprint.pformat({ + "money": self.balance, + "utility_params": self.utility_params, + "current_holdings": self._current_holdings + })) + + def __eq__(self, other) -> bool: + """Compare equality of two instances of the class.""" + return isinstance(other, AgentState) and \ + self.balance == other.balance and \ + self.utility_params == other.utility_params and \ + self._current_holdings == other._current_holdings + + +class WorldState(BaseWorldState): + """Represent the state of the world from the perspective of the agent.""" + + def __init__(self, opponent_pbks: List[str], + good_pbks: List[str], + initial_agent_state: AgentState) -> None: + """ + Instantiate an agent state object. + + :param opponent_pbks: the public keys of the opponents + :param good_pbks: the public keys of the goods + :param agent_state: the initial state of the agent + :return: None + """ + super().__init__() + self.opponent_states = dict( + (agent_pbk, + AgentState( + self._expected_initial_money_amount(initial_agent_state.balance), + self._expected_good_endowments(initial_agent_state.current_holdings), + self._expected_utility_params(initial_agent_state.utility_params) + )) + for agent_pbk in opponent_pbks) # type: Dict[str, AgentState] + + self.good_price_models = dict( + (good_pbk, + GoodPriceModel()) + for good_pbk in good_pbks) + + def update_on_cfp(self, query) -> None: + """Update the world state when a new cfp is received.""" + pass + + def update_on_proposal(self, proposal) -> None: + """Update the world state when a new proposal is received.""" + pass + + def update_on_declined_propose(self, transaction: Transaction) -> None: + """ + Update the world state when a transaction (propose) is rejected. + + :param transaction: the transaction + :return: None + """ + self._from_transaction_update_price(transaction, is_accepted=False) + + def _from_transaction_update_price(self, transaction: Transaction, is_accepted: bool) -> None: + """ + Update the good price model based on a transaction. + + :param transaction: the transaction + :param is_accepted: whether the transaction is accepted or not + :return: None + """ + good_pbks = [] # type: List[str] + for good_pbk, quantity in transaction.quantities_by_good_pbk.items(): + if quantity > 0: + good_pbks += [good_pbk] * quantity + price = transaction.amount + price = price / len(good_pbks) + for good_pbk in list(set(good_pbks)): + self._update_price(good_pbk, price, is_accepted=is_accepted) + + def update_on_initial_accept(self, transaction: Transaction) -> None: + """ + Update the world state when a proposal is accepted. + + :param transaction: the transaction + :return: None + """ + self._from_transaction_update_price(transaction, is_accepted=True) + + def _expected_initial_money_amount(self, initial_money_amount: float) -> float: + """ + Compute expectation of the initial_money_amount of an opponent. + + :param initial_money_amount: the initial amount of money of the agent. + :return: the expected initial money amount of the opponent + """ + # Naiive expectation + expected_initial_money_amount = initial_money_amount + return expected_initial_money_amount + + def _expected_good_endowments(self, good_endowment: Endowment) -> Endowment: + """ + Compute expectation of the good endowment of an opponent. + + :param good_endowment: the good_endowment of the agent. + :return: the expected good endowment of the opponent + """ + # Naiive expectation + expected_good_endowment = good_endowment + return expected_good_endowment + + def _expected_utility_params(self, utility_params: UtilityParams) -> UtilityParams: + """ + Compute expectation of the utility params of an opponent. + + :param utility_params: the utility_params of the agent. + :return: the expected utility params of the opponent + """ + # Naiive expectation + expected_utility_params = utility_params + return expected_utility_params + + def expected_price(self, good_pbk: str, marginal_utility: float, is_seller: bool, share_of_tx_fee: float) -> float: + """ + Compute expectation of the price for the good given a constraint. + + :param good_pbk: the pbk of the good + :param marginal_utility: the marginal_utility from the good + :param is_seller: whether the agent is a seller or buyer + :param share_of_tx_fee: the share of the tx fee the agent pays + :return: the expected price + """ + constraint = round(marginal_utility + share_of_tx_fee, 1) if is_seller else round(marginal_utility - share_of_tx_fee, 1) + good_price_model = self.good_price_models[good_pbk] + expected_price = good_price_model.get_price_expectation(constraint, is_seller) + return expected_price + + def _update_price(self, good_pbk: str, price: float, is_accepted: bool) -> None: + """ + Update the price for the good based on an outcome. + + :param good_pbk: the pbk of the good + :param price: the price to which the outcome relates + :param is_accepted: boolean indicating the outcome + :return: None + """ + price = round(price, 1) + good_price_model = self.good_price_models[good_pbk] + good_price_model.update(is_accepted, price) diff --git a/tac/agents/v1/base/stats_manager.py b/tac/agents/participant/base/stats_manager.py similarity index 99% rename from tac/agents/v1/base/stats_manager.py rename to tac/agents/participant/base/stats_manager.py index b4a1f19c..eaf2eeef 100644 --- a/tac/agents/v1/base/stats_manager.py +++ b/tac/agents/participant/base/stats_manager.py @@ -27,7 +27,7 @@ import numpy as np -from tac.agents.v1.mail.oef import MailStats +from tac.aea.mail.oef import MailStats class EndState(Enum): diff --git a/tac/agents/v1/base/strategy.py b/tac/agents/participant/base/strategy.py similarity index 98% rename from tac/agents/v1/base/strategy.py rename to tac/agents/participant/base/strategy.py index a9478cce..8c0ddb98 100644 --- a/tac/agents/v1/base/strategy.py +++ b/tac/agents/participant/base/strategy.py @@ -26,7 +26,7 @@ from oef.schema import Description -from tac.platform.game import WorldState +from tac.agents.participant.base.states import WorldState class RegisterAs(Enum): diff --git a/tac/agents/v1/base/transaction_manager.py b/tac/agents/participant/base/transaction_manager.py similarity index 99% rename from tac/agents/v1/base/transaction_manager.py rename to tac/agents/participant/base/transaction_manager.py index 31918a58..62f9b009 100644 --- a/tac/agents/v1/base/transaction_manager.py +++ b/tac/agents/participant/base/transaction_manager.py @@ -25,7 +25,7 @@ from collections import defaultdict, deque from typing import Dict, Tuple, Deque -from tac.agents.v1.base.dialogues import DialogueLabel +from tac.agents.participant.base.dialogues import DialogueLabel from tac.platform.protocol import Transaction logger = logging.getLogger(__name__) diff --git a/tac/agents/v1/examples/__init__.py b/tac/agents/participant/examples/__init__.py similarity index 100% rename from tac/agents/v1/examples/__init__.py rename to tac/agents/participant/examples/__init__.py diff --git a/tac/agents/v1/examples/baseline.py b/tac/agents/participant/examples/baseline.py similarity index 96% rename from tac/agents/v1/examples/baseline.py rename to tac/agents/participant/examples/baseline.py index 37408215..ac03f823 100644 --- a/tac/agents/v1/examples/baseline.py +++ b/tac/agents/participant/examples/baseline.py @@ -25,9 +25,9 @@ import logging from typing import Optional -from tac.agents.v1.base.participant_agent import ParticipantAgent -from tac.agents.v1.base.strategy import Strategy, RegisterAs, SearchFor -from tac.agents.v1.examples.strategy import BaselineStrategy +from tac.agents.participant.agent import ParticipantAgent +from tac.agents.participant.base.strategy import Strategy, RegisterAs, SearchFor +from tac.agents.participant.examples.strategy import BaselineStrategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tac/agents/v1/examples/strategy.py b/tac/agents/participant/examples/strategy.py similarity index 95% rename from tac/agents/v1/examples/strategy.py rename to tac/agents/participant/examples/strategy.py index a843b4ec..0d071606 100644 --- a/tac/agents/v1/examples/strategy.py +++ b/tac/agents/participant/examples/strategy.py @@ -24,9 +24,10 @@ from oef.schema import Description -from tac.agents.v1.base.strategy import RegisterAs, SearchFor, Strategy -from tac.helpers.misc import get_goods_quantities_description, marginal_utility -from tac.platform.game import WorldState +from tac.agents.participant.base.helpers import get_goods_quantities_description +from tac.agents.participant.base.states import WorldState +from tac.agents.participant.base.strategy import RegisterAs, SearchFor, Strategy +from tac.platform.game.helpers import marginal_utility class BaselineStrategy(Strategy): diff --git a/tac/agents/v1/README.md b/tac/agents/v1/README.md deleted file mode 100644 index ae818c24..00000000 --- a/tac/agents/v1/README.md +++ /dev/null @@ -1,3 +0,0 @@ -### v1 - -The first (publicly released) iteration of our agents have an architecture whereby the agent main loop is separated from the event loop. diff --git a/tac/agents/v1/base/helpers.py b/tac/agents/v1/base/helpers.py deleted file mode 100644 index c6e9f826..00000000 --- a/tac/agents/v1/base/helpers.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains helper methods for base agent implementations.""" -import logging - -from tac.agents.v1.base.dialogues import DialogueLabel -from tac.agents.v1.mail.messages import Message, OEFMessage, FIPAMessage -from tac.helpers.crypto import Crypto -from tac.platform.protocol import Response - - -logger = logging.getLogger(__name__) - - -def is_oef_message(msg: Message) -> bool: - """ - Check whether a message is from the oef. - - :param msg: the message - :return: boolean indicating whether or not the message is from the oef - """ - return msg.protocol_id == "oef" and msg.get("type") in set(OEFMessage.Type) - - -def is_controller_message(msg: Message, crypto: Crypto) -> bool: - """ - Check whether a message is from the controller. - - :param msg: the message - :param crypto: the crypto of the agent - :return: boolean indicating whether or not the message is from the controller - """ - if not msg.protocol_id == "bytes": - return False - - try: - byte_content = msg.get("content") - sender_pbk = msg.sender - Response.from_pb(byte_content, sender_pbk, crypto) - except Exception as e: - logger.debug("Not a Controller message: {}".format(str(e))) - # try: - # byte_content = msg.get("content") - # sender_pbk = msg.sender - # Response.from_pb(byte_content, sender_pbk, crypto) - # except: - # pass - return False - - return True - - -def is_fipa_message(msg: Message) -> bool: - """Chcek whether a message is a FIPA message.""" - return msg.protocol_id == "fipa" and msg.get("performative") in set(FIPAMessage.Performative) - - -def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: - """ - Make a transaction id. - - :param agent_pbk: the pbk of the agent. - :param opponent_pbk: the public key of the opponent. - :param dialogue_label: the dialogue label - :param agent_is_seller: boolean indicating if the agent is a seller - :return: a transaction id - """ - # the format is {buyer_pbk}_{seller_pbk}_{dialogue_id}_{dialogue_starter_pbk} - assert opponent_pbk == dialogue_label.dialogue_opponent_pbk - buyer_pbk, seller_pbk = (opponent_pbk, agent_pbk) if agent_is_seller else (agent_pbk, opponent_pbk) - transaction_id = "{}_{}_{}_{}".format(buyer_pbk, seller_pbk, dialogue_label.dialogue_id, dialogue_label.dialogue_starter_pbk) - return transaction_id - - -def dialogue_label_from_transaction_id(agent_pbk: str, transaction_id: str) -> DialogueLabel: - """ - Recover dialogue label from transaction id. - - :param agent_pbk: the pbk of the agent. - :param transaction_id: the transaction id - :return: a dialogue label - """ - buyer_pbk, seller_pbk, dialogue_id, dialogue_starter_pbk = transaction_id.split('_') - if agent_pbk == buyer_pbk: - dialogue_opponent_pbk = seller_pbk - else: - dialogue_opponent_pbk = buyer_pbk - dialogue_label = DialogueLabel(int(dialogue_id), dialogue_opponent_pbk, dialogue_starter_pbk) - return dialogue_label diff --git a/tac/gui/dashboards/agent.py b/tac/gui/dashboards/agent.py index 45028f58..f0523cc4 100644 --- a/tac/gui/dashboards/agent.py +++ b/tac/gui/dashboards/agent.py @@ -26,11 +26,11 @@ import numpy as np +from tac.agents.participant.base.states import AgentState +from tac.agents.participant.base.stats_manager import StatsManager from tac.gui.dashboards.base import Dashboard -from tac.helpers.misc import generate_html_table_from_dict, escape_html -from tac.platform.game import AgentState +from tac.gui.dashboards.helpers import generate_html_table_from_dict, escape_html from tac.platform.protocol import Transaction -from tac.agents.v1.base.stats_manager import StatsManager CUR_PATH = inspect.getfile(inspect.currentframe()) CUR_DIR = os.path.dirname(CUR_PATH) diff --git a/tac/gui/dashboards/controller.py b/tac/gui/dashboards/controller.py index 5b7e331c..f772ff24 100644 --- a/tac/gui/dashboards/controller.py +++ b/tac/gui/dashboards/controller.py @@ -29,9 +29,9 @@ import numpy as np from tac.gui.dashboards.base import start_visdom_server, Dashboard -from tac.helpers.crypto import Crypto -from tac.platform.game import Game -from tac.platform.stats import GameStats +from tac.aea.crypto.base import Crypto +from tac.agents.controller.base.states import Game +from tac.platform.game.stats import GameStats DEFAULT_ENV_NAME = "tac_simulation_env_main" diff --git a/tac/gui/dashboards/helpers.py b/tac/gui/dashboards/helpers.py new file mode 100644 index 00000000..312e341f --- /dev/null +++ b/tac/gui/dashboards/helpers.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""A module containing helpers for dashboard.""" + +from typing import List, Dict + + +def generate_html_table_from_dict(dictionary: Dict[str, List[str]], title="") -> str: + """ + Generate a html table from a dictionary. + + :param dictionary: the dictionary + :param title: the title + :return: a html string + """ + style_tag = "" + html_head = "{}".format(style_tag) + title_tag = "

{}

".format(title) if title else "" + + table_head = "{}".format("".join(dictionary.keys())) + table_body = "" + for row in zip(*dictionary.values()): + table_row = "" + "".join(row) + "" + table_body += table_row + + table = "{}{}
".format(table_head, table_body) + + html_table = "" + html_head + title_tag + table + "" + + return html_table + + +def escape_html(string: str, quote=True) -> str: + """ + Replace special characters "&", "<" and ">" to HTML-safe sequences. + + :param string: the string + :param quote: If the optional flag quote is true (the default), the quotation mark characters, both double quote (") and single quote (') characters are also translated. + + :return: the escaped string + """ + string = string.replace("&", "&") # Must be done first! + string = string.replace("<", "<") + string = string.replace(">", ">") + if quote: + string = string.replace('"', """) + string = string.replace('\'', "'") + return string diff --git a/tac/gui/dashboards/leaderboard.py b/tac/gui/dashboards/leaderboard.py index 4afd21c4..becdf386 100644 --- a/tac/gui/dashboards/leaderboard.py +++ b/tac/gui/dashboards/leaderboard.py @@ -26,10 +26,10 @@ from collections import defaultdict from typing import Optional, Dict, List +from tac.aea.crypto.base import Crypto +from tac.agents.controller.base.states import Game from tac.gui.dashboards.base import start_visdom_server, Dashboard -from tac.helpers.crypto import Crypto -from tac.platform.game import Game -from tac.platform.stats import GameStats +from tac.platform.game.stats import GameStats DEFAULT_ENV_NAME = "tac_simulation_env_main" diff --git a/tac/gui/monitor.py b/tac/gui/monitor.py index 1418ddbb..da9f4215 100644 --- a/tac/gui/monitor.py +++ b/tac/gui/monitor.py @@ -25,7 +25,7 @@ from tac.gui.dashboards.controller import ControllerDashboard -from tac.platform.stats import GameStats +from tac.platform.game.stats import GameStats class Monitor(ABC): diff --git a/tac/platform/game/__init__.py b/tac/platform/game/__init__.py new file mode 100644 index 00000000..b3b98a70 --- /dev/null +++ b/tac/platform/game/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Contains the game specific modules.""" diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py new file mode 100644 index 00000000..8e3f15ed --- /dev/null +++ b/tac/platform/game/base.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains all the classes to represent the TAC game. + +Classes: + +- GameConfiguration: a class to hold the configuration of a game. Immutable. +- GameInitialization: a class to hold the initialization of a game. Immutable. +- Game: the class that manages an instance of a game (e.g. validate and settling transactions). +- AgentState: a class to hold the current state of an agent. +- GoodState: a class to hold the current state of a good. +- WorldState represent the state of the world from the perspective of the agent. +""" + +from enum import Enum +import logging +from typing import List, Dict, Any + +Endowment = List[int] # an element e_j is the endowment of good j. +UtilityParams = List[float] # an element u_j is the utility value of good j. + +logger = logging.getLogger(__name__) + +DEFAULT_PRICE = 0.0 + + +class GamePhase(Enum): + """This class defines the TAC game stages.""" + + PRE_GAME = 'pre_game' + GAME_SETUP = 'game_setup' + GAME = 'game' + POST_GAME = 'post_game' + + +class GameConfiguration: + """Class containing the game configuration of a TAC instance.""" + + def __init__(self, + nb_agents: int, + nb_goods: int, + tx_fee: float, + agent_pbk_to_name: Dict[str, str], + good_pbk_to_name: Dict[str, str]): + """ + Instantiate a game configuration. + + :param nb_agents: the number of agents. + :param nb_goods: the number of goods. + :param tx_fee: the fee for a transaction. + :param agent_pbk_to_name: a dictionary mapping agent public keys to agent names (as strings). + :param good_pbk_to_name: a dictionary mapping good public keys to good names (as strings). + """ + self._nb_agents = nb_agents + self._nb_goods = nb_goods + self._tx_fee = tx_fee + self._agent_pbk_to_name = agent_pbk_to_name + self._good_pbk_to_name = good_pbk_to_name + + self._check_consistency() + + @property + def nb_agents(self) -> int: + """Agent number of a TAC instance.""" + return self._nb_agents + + @property + def nb_goods(self) -> int: + """Good number of a TAC instance.""" + return self._nb_goods + + @property + def tx_fee(self) -> float: + """Transaction fee for the TAC instance.""" + return self._tx_fee + + @property + def agent_pbk_to_name(self) -> Dict[str, str]: + """Map agent public keys to names.""" + return self._agent_pbk_to_name + + @property + def good_pbk_to_name(self) -> Dict[str, str]: + """Map good public keys to names.""" + return self._good_pbk_to_name + + @property + def agent_pbks(self) -> List[str]: + """List of agent public keys.""" + return list(self._agent_pbk_to_name.keys()) + + @property + def agent_names(self): + """List of agent names.""" + return list(self._agent_pbk_to_name.values()) + + @property + def good_pbks(self) -> List[str]: + """List of good public keys.""" + return list(self._good_pbk_to_name.keys()) + + @property + def good_names(self) -> List[str]: + """List of good names.""" + return list(self._good_pbk_to_name.values()) + + def _check_consistency(self): + """ + Check the consistency of the game configuration. + + :return: None + :raises: AssertionError: if some constraint is not satisfied. + """ + assert self.tx_fee >= 0, "Tx fee must be non-negative." + assert self.nb_agents > 1, "Must have at least two agents." + assert self.nb_goods > 1, "Must have at least two goods." + assert len(self.agent_pbks) == self.nb_agents, "There must be one public key for each agent." + assert len(set(self.agent_names)) == self.nb_agents, "Agents' names must be unique." + assert len(self.good_pbks) == self.nb_goods, "There must be one public key for each good." + assert len(set(self.good_names)) == self.nb_goods, "Goods' names must be unique." + + def to_dict(self) -> Dict[str, Any]: + """Get a dictionary from the object.""" + return { + "nb_agents": self.nb_agents, + "nb_goods": self.nb_goods, + "tx_fee": self.tx_fee, + "agent_pbk_to_name": self.agent_pbk_to_name, + "good_pbk_to_name": self.good_pbk_to_name + } + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> 'GameConfiguration': + """Instantiate an object from the dictionary.""" + obj = cls( + d["nb_agents"], + d["nb_goods"], + d["tx_fee"], + d["agent_pbk_to_name"], + d["good_pbk_to_name"] + ) + return obj + + def __eq__(self, other): + """Compare equality of two objects.""" + return isinstance(other, GameConfiguration) and \ + self.nb_agents == other.nb_agents and \ + self.nb_goods == other.nb_goods and \ + self.tx_fee == other.tx_fee and \ + self.agent_pbk_to_name == other.agent_pbk_to_name and \ + self.good_pbk_to_name == other.good_pbk_to_name + + +class GoodState: + """Represent the state of a good during the game.""" + + def __init__(self, price: float) -> None: + """ + Instantiate an agent state object. + + :param price: price of the good in this state. + :return: None + """ + self.price = price + + self._check_consistency() + + def _check_consistency(self) -> None: + """ + Check the consistency of the good state. + + :return: None + :raises: AssertionError: if some constraint is not satisfied. + """ + assert self.price >= 0, "The price must be non-negative." diff --git a/tac/helpers/misc.py b/tac/platform/game/helpers.py similarity index 57% rename from tac/helpers/misc.py rename to tac/platform/game/helpers.py index 1a8d74ad..e9d52a00 100644 --- a/tac/helpers/misc.py +++ b/tac/platform/game/helpers.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - # ------------------------------------------------------------------------------ # # Copyright 2018-2019 Fetch.AI Limited @@ -18,16 +17,14 @@ # # ------------------------------------------------------------------------------ -"""A module containing miscellaneous methods and classes.""" +"""This module contains helpers for game.""" import logging import random -from typing import List, Set, Dict, Tuple, Union +from typing import List, Tuple import math import numpy as np -from oef.query import Query, Constraint, GtEq, Or -from oef.schema import AttributeSchema, DataModel, Description logger = logging.getLogger("tac") @@ -36,26 +33,6 @@ QUANTITY_SHIFT = 1 # Any non-negative integer is fine. -class TacError(Exception): - """General purpose exception to detect exception associated with the logic of the TAC application.""" - - -def generate_transaction_id(agent_pbk: str, origin: str, dialogue_id: int, agent_is_seller: bool) -> str: - """ - Generate a transaction id. - - :param agent_pbk: the pbk of the agent. - :param origin: the public key of the message sender. - :param dialogue_id: the dialogue id - :param agent_is_seller: boolean indicating if the agent is a seller - :return: a transaction id - """ - # the format is {buyer_pbk}_{seller_pbk}_{dialogue_id} - buyer_pbk, seller_pbk = (origin, agent_pbk) if agent_is_seller else (agent_pbk, origin) - transaction_id = "{}_{}_{}".format(buyer_pbk, seller_pbk, dialogue_id) - return transaction_id - - def determine_scaling_factor(money_endowment: int) -> float: """ Compute the scaling factor based on the money amount. @@ -211,155 +188,29 @@ def marginal_utility(utility_function_params: List[float], current_holdings: Lis return new_utility - current_utility -def build_datamodel(good_pbks: List[str], is_supply: bool) -> DataModel: +def make_agent_name(agent_id: int, is_world_modeling: bool, nb_agents: int) -> str: """ - Build a data model for supply and demand (i.e. for offered or requested goods). - - :param good_pbks: the list of good public keys - :param is_supply: Boolean indicating whether it is a supply or demand data model + Make the name for baseline agents from an integer identifier. - :return: the data model. - """ - goods_quantities_attributes = [AttributeSchema(good_pbk, int, False) - for good_pbk in good_pbks] - price_attribute = AttributeSchema("price", float, False) - description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME - data_model = DataModel(description, goods_quantities_attributes + [price_attribute]) - return data_model + E.g.: + >>> make_agent_name(2, False, 10) + 'tac_agent_2' + >>> make_agent_name(2, False, 100) + 'tac_agent_02' + >>> make_agent_name(2, False, 101) + 'tac_agent_002' -def get_goods_quantities_description(good_pbks: List[str], good_quantities: List[int], is_supply: bool) -> Description: + :param agent_id: the agent id. + :param is_world_modeling: the boolean indicated whether the baseline agent models the world around her or not. + :param nb_agents: the overall number of agents. + :return: the formatted name. + :return: the string associated to the integer id. """ - Get the TAC description for supply or demand. - - That is, a description with the following structure: - >>> description = { - ... "tac_good_0": 1, - ... "tac_good_1": 0, - ... #... - ... - ... } - >>> - - where the keys indicate the good_pbk and the values the quantity. - - >>> desc = get_goods_quantities_description(['tac_good_0', 'tac_good_1', 'tac_good_2', 'tac_good_3'], [0, 0, 1, 2], True) - >>> desc.data_model.name == TAC_SUPPLY_DATAMODEL_NAME - True - >>> desc.values == { - ... "tac_good_0": 0, - ... "tac_good_1": 0, - ... "tac_good_2": 1, - ... "tac_good_3": 2} - ... - True - - :param good_pbks: the public keys of the goods. - :param good_quantities: the quantities per good. - :param is_supply: True if the description is indicating supply, False if it's indicating demand. - - :return: the description to advertise on the Service Directory. - """ - data_model = build_datamodel(good_pbks, is_supply=is_supply) - desc = Description({good_pbk: quantity for good_pbk, quantity in zip(good_pbks, good_quantities)}, - data_model=data_model) - return desc - - -def build_query(good_pbks: Set[str], is_searching_for_sellers: bool) -> Query: - """ - Build buyer or seller search query. - - Specifically, build the search query - - to look for sellers if the agent is a buyer, or - - to look for buyers if the agent is a seller. - - In particular, if the agent is a buyer and the demanded good public keys are {'tac_good_0', 'tac_good_2', 'tac_good_3'}, the resulting constraint expression is: - - tac_good_0 >= 1 OR tac_good_2 >= 1 OR tac_good_3 >= 1 - - That is, the OEF will return all the sellers that have at least one of the good in the query - (assuming that the sellers are registered with the data model specified). - - :param good_pbks: the good public keys to put in the query - :param is_searching_for_sellers: Boolean indicating whether the query is for sellers (supply) or buyers (demand). - - :return: the query - """ - data_model = None if good_pbks is None else build_datamodel(list(good_pbks), is_supply=is_searching_for_sellers) - constraints = [Constraint(good_pbk, GtEq(1)) for good_pbk in good_pbks] - - if len(good_pbks) > 1: - constraints = [Or(constraints)] - - query = Query(constraints, model=data_model) - return query - - -def build_dict(good_pbks: Set[str], is_supply: bool) -> Dict[str, Union[str, List]]: - """ - Build supply or demand services dictionary. - - :param good_pbks: the good public keys to put in the query - :param is_supply: Boolean indicating whether the services are for supply or demand. - - :return: the dictionary - """ - description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME - result = {'description': description, 'services': list(good_pbks)} + max_number_of_digits = math.ceil(math.log10(nb_agents)) + if is_world_modeling: + string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}_wm" + else: + string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}" + result = string_format.format(agent_id) return result - - -def generate_good_pbk_to_name(nb_goods: int) -> Dict[str, str]: - """ - Generate public keys for things. - - :param nb_goods: the number of things. - :return: a dictionary mapping goods' public keys to names. - """ - max_number_of_digits = math.ceil(math.log10(nb_goods)) - string_format = 'tac_good_{:0' + str(max_number_of_digits) + '}' - return {string_format.format(i) + '_pbk': string_format.format(i) for i in range(nb_goods)} - - -def generate_html_table_from_dict(dictionary: Dict[str, List[str]], title="") -> str: - """ - Generate a html table from a dictionary. - - :param dictionary: the dictionary - :param title: the title - :return: a html string - """ - style_tag = "" - html_head = "{}".format(style_tag) - title_tag = "

{}

".format(title) if title else "" - - table_head = "{}".format("".join(dictionary.keys())) - table_body = "" - for row in zip(*dictionary.values()): - table_row = "" + "".join(row) + "" - table_body += table_row - - table = "{}{}
".format(table_head, table_body) - - html_table = "" + html_head + title_tag + table + "" - - return html_table - - -def escape_html(string: str, quote=True) -> str: - """ - Replace special characters "&", "<" and ">" to HTML-safe sequences. - - :param string: the string - :param quote: If the optional flag quote is true (the default), the quotation mark characters, both double quote (") and single quote (') characters are also translated. - - :return: the escaped string - """ - string = string.replace("&", "&") # Must be done first! - string = string.replace("<", "<") - string = string.replace(">", ">") - if quote: - string = string.replace('"', """) - string = string.replace('\'', "'") - return string diff --git a/tac/platform/stats.py b/tac/platform/game/stats.py similarity index 98% rename from tac/platform/stats.py rename to tac/platform/game/stats.py index 1e46e3cb..96e374ca 100644 --- a/tac/platform/stats.py +++ b/tac/platform/game/stats.py @@ -27,8 +27,9 @@ import os import pylab as plt -from tac.helpers.crypto import Crypto -from tac.platform.game import Game, AgentState +from tac.aea.crypto.base import Crypto +from tac.agents.controller.base.states import Game +from tac.agents.participant.base.states import AgentState matplotlib.use('agg') diff --git a/tac/platform/helpers.py b/tac/platform/helpers.py deleted file mode 100644 index 0cec0e6e..00000000 --- a/tac/platform/helpers.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains helpers for platform.""" - -import math - - -def make_agent_name(agent_id: int, is_world_modeling: bool, nb_agents: int) -> str: - """ - Make the name for baseline agents from an integer identifier. - - E.g.: - - >>> make_agent_name(2, False, 10) - 'tac_agent_2' - >>> make_agent_name(2, False, 100) - 'tac_agent_02' - >>> make_agent_name(2, False, 101) - 'tac_agent_002' - - :param agent_id: the agent id. - :param is_world_modeling: the boolean indicated whether the baseline agent models the world around her or not. - :param nb_agents: the overall number of agents. - :return: the formatted name. - :return: the string associated to the integer id. - """ - max_number_of_digits = math.ceil(math.log10(nb_agents)) - if is_world_modeling: - string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}_wm" - else: - string_format = "tac_agent_{:0" + str(max_number_of_digits) + "}" - result = string_format.format(agent_id) - return result diff --git a/tac/helpers/oef_health_check.py b/tac/platform/oef_health_check.py similarity index 100% rename from tac/helpers/oef_health_check.py rename to tac/platform/oef_health_check.py diff --git a/tac/platform/protocol.py b/tac/platform/protocol.py index c34d2c8e..b4662465 100644 --- a/tac/platform/protocol.py +++ b/tac/platform/protocol.py @@ -48,8 +48,7 @@ from google.protobuf.message import DecodeError -from tac.helpers.crypto import Crypto -from tac.helpers.misc import TacError +from tac.aea.crypto.base import Crypto import tac.tac_pb2 as tac_pb2 from oef.schema import Description @@ -87,6 +86,10 @@ def _make_str_str_pair(key: str, value: str) -> tac_pb2.StrStrPair: return pair +class TacError(Exception): + """General purpose exception to detect exception associated with the logic of the TAC application.""" + + class ErrorCode(Enum): """This class defines the error codes.""" diff --git a/tac/platform/simulation.py b/tac/platform/simulation.py index 9553e98a..0563d8ef 100644 --- a/tac/platform/simulation.py +++ b/tac/platform/simulation.py @@ -43,11 +43,11 @@ import dateutil -from tac.agents.v1.base.strategy import RegisterAs, SearchFor -from tac.agents.v1.examples.baseline import main as baseline_main -from tac.platform.controller.tac_parameters import TACParameters -from tac.platform.controller.controller_agent import main as controller_main -from tac.platform.helpers import make_agent_name +from tac.agents.controller.agent import main as controller_main +from tac.agents.controller.base.tac_parameters import TACParameters +from tac.agents.participant.base.strategy import RegisterAs, SearchFor +from tac.agents.participant.examples.baseline import main as baseline_main +from tac.platform.game.helpers import make_agent_name logger = logging.getLogger(__name__) diff --git a/tests/conftest.py b/tests/conftest.py index bddf9902..d8ba1465 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,7 @@ import docker import pytest from docker.models.containers import Container -from tac.helpers.oef_health_check import OEFHealthCheck +from tac.platform.oef_health_check import OEFHealthCheck logger = logging.getLogger(__name__) diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index de1d5c7a..551c5184 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -23,8 +23,8 @@ import time from threading import Thread -from tac.agents.v1.agent import Agent, AgentState -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.agent import Agent, AgentState +from tac.aea.mail.oef import OEFNetworkMailBox class TAgent(Agent): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index cdf95962..20b10fae 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -22,8 +22,8 @@ from threading import Timer from unittest.mock import MagicMock -from tac.agents.v1.agent import Agent -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.agent import Agent +from tac.aea.mail.oef import OEFNetworkMailBox class TAgent(Agent): diff --git a/tests/test_controller.py b/tests/test_controller.py index 38c684a4..bebd132e 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -29,9 +29,9 @@ from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 -from tac.helpers.crypto import Crypto -from tac.platform.controller.controller_agent import ControllerAgent -from tac.platform.controller.tac_parameters import TACParameters +from tac.aea.crypto.base import Crypto +from tac.agents.controller.agent import ControllerAgent +from tac.agents.controller.base.tac_parameters import TACParameters from tac.platform.protocol import Register logger = logging.getLogger(__name__) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 45822f94..9c6edc86 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -23,7 +23,7 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, \ load_pem_private_key -from tac.helpers.crypto import Crypto +from tac.aea.crypto.base import Crypto from .conftest import ROOT_DIR diff --git a/tests/test_game.py b/tests/test_game.py index 73574656..e2cd1b41 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -21,9 +21,11 @@ import pytest -from tac.platform.game import GameConfiguration, GameInitialization, Game, AgentState, GoodState +from tac.aea.crypto.base import Crypto +from tac.agents.controller.base.states import GameInitialization, Game +from tac.agents.participant.base.states import AgentState +from tac.platform.game.base import GameConfiguration, GoodState from tac.platform.protocol import Transaction -from tac.helpers.crypto import Crypto class TestGameConfiguration: diff --git a/tests/test_mail.py b/tests/test_mail.py index d5912365..08b9eae2 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,8 +20,8 @@ """This module contains tests for the mail module.""" import time -from tac.agents.v1.mail.messages import FIPAMessage, ByteMessage # OEFMessage -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.mail.messages import FIPAMessage, ByteMessage # OEFMessage +from tac.aea.mail.oef import OEFNetworkMailBox def test_example(network_node): diff --git a/tests/test_misc.py b/tests/test_misc.py index 76c40c7b..86f831bd 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -19,17 +19,17 @@ """This module contains miscellaneous tests.""" -from tac.helpers.misc import generate_transaction_id +# from tac.agents.participant.base.helpers import generate_transaction_id -def test_generate_transaction_id(): - """Test that the transaction id is correctly generated.""" - expected_result = "buyer_pbk_seller_pbk_12345" - actual_result = generate_transaction_id("buyer_pbk", "seller_pbk", 12345, False) +# def test_generate_transaction_id(): +# """Test that the transaction id is correctly generated.""" +# expected_result = "buyer_pbk_seller_pbk_12345" +# actual_result = generate_transaction_id("buyer_pbk", "seller_pbk", 12345, False) - assert actual_result == expected_result +# assert actual_result == expected_result - expected_result = "seller_pbk_buyer_pbk_12345" - actual_result = generate_transaction_id("buyer_pbk", "seller_pbk", 12345, True) +# expected_result = "seller_pbk_buyer_pbk_12345" +# actual_result = generate_transaction_id("buyer_pbk", "seller_pbk", 12345, True) - assert actual_result == expected_result +# assert actual_result == expected_result diff --git a/tests/test_protocol.py b/tests/test_protocol.py index fc5b0f12..80f28edf 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -21,9 +21,9 @@ import pytest +from tac.aea.crypto.base import Crypto from tac.platform.protocol import Register, Unregister, Transaction, TransactionConfirmation, Error, \ GameData, Request, Response, ErrorCode, Cancelled, GetStateUpdate, StateUpdate -from tac.helpers.crypto import Crypto class TestRequest: diff --git a/tests/test_simulation.py b/tests/test_simulation.py index dd848528..a1561f4b 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -26,14 +26,12 @@ import numpy as np import pytest -from tac.agents.v1.base.strategy import SearchFor, RegisterAs -from tac.agents.v1.examples.strategy import BaselineStrategy -from tac.platform.game import Game - -from tac.agents.v1.examples.baseline import BaselineAgent as BaselineAgentV1 - -from tac.platform.controller.controller_agent import ControllerAgent -from tac.platform.controller.tac_parameters import TACParameters +from tac.agents.controller.agent import ControllerAgent +from tac.agents.controller.base.states import Game +from tac.agents.controller.base.tac_parameters import TACParameters +from tac.agents.participant.base.strategy import SearchFor, RegisterAs +from tac.agents.participant.examples.baseline import BaselineAgent as BaselineAgentV1 +from tac.agents.participant.examples.strategy import BaselineStrategy def _init_baseline_agents(n: int, version: str, oef_addr: str, oef_port: int) -> List[BaselineAgentV1]: From a127c6349b421ab6655d7c15a5e364a027376020 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Mon, 19 Aug 2019 14:53:28 +0100 Subject: [PATCH 042/107] Minor fixes to last pr --- tac/aea/mail/messages.py | 4 ++-- tac/agents/participant/base/dialogues.py | 4 ++-- tac/agents/participant/base/states.py | 4 ++-- tests/test_controller.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index 3d43fcbe..5355537d 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -27,7 +27,7 @@ from oef.query import Query from oef.schema import Description -from tac.aea.dialogue.base import DialogueLabel +# from tac.aea.dialogue.base import DialogueLabel Address = str ProtocolId = str @@ -255,7 +255,7 @@ class Performative(Enum): def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, message_id: Optional[int] = None, - dialogue_id: Optional[DialogueLabel] = None, + dialogue_id: Optional[int] = None, target: Optional[int] = None, performative: Optional[Union[str, Performative]] = None, **body): diff --git a/tac/agents/participant/base/dialogues.py b/tac/agents/participant/base/dialogues.py index c5e01791..f14ba193 100644 --- a/tac/agents/participant/base/dialogues.py +++ b/tac/agents/participant/base/dialogues.py @@ -54,7 +54,7 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: :return: None """ - super().__init__(dialogue_label=DialogueLabel) + BaseDialogue.__init__(self, dialogue_label=DialogueLabel) self._is_seller = is_seller self._role = 'seller' if is_seller else 'buyer' @@ -138,7 +138,7 @@ def __init__(self) -> None: :return: None """ - super().__init__() + BaseDialogues.__init__(self) self._dialogues_as_seller = {} # type: Dict[DialogueLabel, Dialogue] self._dialogues_as_buyer = {} # type: Dict[DialogueLabel, Dialogue] diff --git a/tac/agents/participant/base/states.py b/tac/agents/participant/base/states.py index d00442c2..e4f16827 100644 --- a/tac/agents/participant/base/states.py +++ b/tac/agents/participant/base/states.py @@ -51,7 +51,7 @@ def __init__(self, money: float, endowment: Endowment, utility_params: UtilityPa :param endowment: the endowment for every good. :param utility_params: the utility params for every good. """ - super().__init__() + BaseAgentState.__init__(self) assert len(endowment) == len(utility_params) self.balance = money self._utility_params = copy.copy(utility_params) @@ -178,7 +178,7 @@ def __init__(self, opponent_pbks: List[str], :param agent_state: the initial state of the agent :return: None """ - super().__init__() + BaseWorldState.__init__(self) self.opponent_states = dict( (agent_pbk, AgentState( diff --git a/tests/test_controller.py b/tests/test_controller.py index bebd132e..04133332 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -92,7 +92,7 @@ def setup_class(cls): cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, Register(crypto.public_key, crypto, 'agent_name').serialize()) - time.sleep(1.0) + time.sleep(10.0) job.join() cls.agent1.stop() From 24907ee41ebb9bfe4f5f09dbca8d1d7c57d6ff7e Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 15:30:31 +0100 Subject: [PATCH 043/107] do minor fixes. --- sandbox/run_iterated_games.py | 2 +- scripts/launch_alt.py | 2 +- tac/agents/participant/base/dialogues.py | 2 +- templates/v1/advanced.py | 6 +++--- templates/v1/basic.py | 6 +++--- templates/v1/expert.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sandbox/run_iterated_games.py b/sandbox/run_iterated_games.py index 9cb23d10..f02dc8a6 100644 --- a/sandbox/run_iterated_games.py +++ b/sandbox/run_iterated_games.py @@ -35,7 +35,7 @@ from collections import defaultdict from typing import List, Dict, Any -from tac.platform.stats import GameStats +from tac.platform.game.stats import GameStats OUR_DIRECTORY = os.path.dirname(inspect.getfile(inspect.currentframe())) ROOT_DIR = os.path.join(OUR_DIRECTORY, "..") diff --git a/scripts/launch_alt.py b/scripts/launch_alt.py index f194b3b3..195de148 100644 --- a/scripts/launch_alt.py +++ b/scripts/launch_alt.py @@ -32,8 +32,8 @@ import docker import tac +from tac.platform.oef_health_check import OEFHealthCheck from tac.platform.simulation import parse_arguments, build_simulation_parameters -from tac.helpers.oef_health_check import OEFHealthCheck CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") diff --git a/tac/agents/participant/base/dialogues.py b/tac/agents/participant/base/dialogues.py index f14ba193..5c6ca818 100644 --- a/tac/agents/participant/base/dialogues.py +++ b/tac/agents/participant/base/dialogues.py @@ -54,7 +54,7 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: :return: None """ - BaseDialogue.__init__(self, dialogue_label=DialogueLabel) + BaseDialogue.__init__(self, dialogue_label=dialogue_label) self._is_seller = is_seller self._role = 'seller' if is_seller else 'buyer' diff --git a/templates/v1/advanced.py b/templates/v1/advanced.py index 4cd5a0a3..b745fdbc 100644 --- a/templates/v1/advanced.py +++ b/templates/v1/advanced.py @@ -27,10 +27,10 @@ from oef.schema import Description +from tac.aea.state.base import WorldState +from tac.agents.participant.base.strategy import RegisterAs, SearchFor, Strategy +from tac.agents.participant.examples.baseline import BaselineAgent from tac.gui.dashboards.agent import AgentDashboard -from tac.platform.game import WorldState -from tac.agents.v1.base.strategy import Strategy, RegisterAs, SearchFor -from tac.agents.v1.examples.baseline import BaselineAgent logger = logging.getLogger(__name__) diff --git a/templates/v1/basic.py b/templates/v1/basic.py index e01f4bfa..abcf7e07 100644 --- a/templates/v1/basic.py +++ b/templates/v1/basic.py @@ -24,9 +24,9 @@ import argparse import logging -from tac.agents.v1.base.strategy import RegisterAs, SearchFor -from tac.agents.v1.examples.baseline import BaselineAgent -from tac.agents.v1.examples.strategy import BaselineStrategy +from tac.agents.participant.base.strategy import SearchFor, RegisterAs +from tac.agents.participant.examples.baseline import BaselineAgent +from tac.agents.participant.examples.strategy import BaselineStrategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/templates/v1/expert.py b/templates/v1/expert.py index 33166729..b2f4055f 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -25,8 +25,8 @@ import logging from typing import Optional -from tac.agents.v1.agent import Agent -from tac.agents.v1.mail.oef import OEFNetworkMailBox +from tac.aea.agent import Agent +from tac.aea.mail.oef import OEFNetworkMailBox logger = logging.getLogger(__name__) From 35cb4f96ed3faa394f532cb14065686be1b49644 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 16:01:16 +0100 Subject: [PATCH 044/107] temp --- tac/aea/helpers/local_node.py | 314 ++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 tac/aea/helpers/local_node.py diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py new file mode 100644 index 00000000..6d938fb2 --- /dev/null +++ b/tac/aea/helpers/local_node.py @@ -0,0 +1,314 @@ +import asyncio +import logging +from collections import defaultdict +from threading import Thread +from typing import Dict, List, Tuple, Optional + +from oef import agent_pb2, uri +from oef.core import OEFProxy +from oef.messages import BaseMessage, AgentMessage, OEFErrorOperation, OEFErrorMessage, SearchResult, \ + DialogueErrorMessage, PROPOSE_TYPES, Message, CFP, Propose, Accept, Decline, CFP_TYPES +from oef.proxy import OEFConnectionError +from oef.query import Query +from oef.schema import Description + +Envelope = None + +logger = logging.getLogger(__name__) + + +class LocalNode: + """A light-weight local implementation of a OEF Node.""" + + def __init__(self, loop=None): + """ + Initialize a local (i.e. non-networked) implementation of an OEF Node + """ + self.agents = dict() # type: Dict[str, Description] + self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] + self.loop = asyncio.get_event_loop() if loop is None else loop + self._lock = asyncio.Lock() + self._thread = Thread(target=self.run) + + self._read_queue = asyncio.Queue() # type: asyncio.Queue + self._queues = {} # type: Dict[str, asyncio.Queue] + + def __enter__(self): + self._thread.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + + def connect(self, public_key: str) -> Optional[Tuple[asyncio.Queue, asyncio.Queue]]: + """ + Connect a public key to the node. + + :param public_key: the public key of the agent. + :return: an asynchronous queue, that constitutes the communication channel. + """ + if public_key in self._queues: + return None + + queue = asyncio.Queue() + self._queues[public_key] = queue + return self._read_queue, queue + + def _process_messages(self) -> None: + """ + Main event loop to process the incoming messages. + + :return: ``None`` + """ + while True: + try: + data = await self._read_queue.get() # type: Tuple[str, BaseMessage] + except asyncio.CancelledError: + logger.debug("Local Node: loop cancelled.") + break + + public_key, msg = data + assert isinstance(msg, AgentMessage) + self._send_agent_message(public_key, msg) + + def run(self) -> None: + """ + Run the node, i.e. start processing the messages. + + :return: ``None`` + """ + self._process_messages() + + def stop(self) -> None: + """ + Stop the execution of the node. + + :return: ``None`` + """ + pass + + def register_agent(self, public_key: str, agent_description: Description) -> None: + """ + Register an agent in the agent directory of the node. + + :param public_key: the public key of the agent to be registered. + :param agent_description: the description of the agent to be registered. + :return: ``None`` + """ + self.loop.run_until_complete(self._lock.acquire()) + self.agents[public_key] = agent_description + self._lock.release() + + def register_service(self, public_key: str, service_description: Description): + """ + Register a service agent in the service directory of the node. + + :param public_key: the public key of the service agent to be registered. + :param service_description: the description of the service agent to be registered. + :return: ``None`` + """ + self.loop.run_until_complete(self._lock.acquire()) + self.services[public_key].append(service_description) + self._lock.release() + + def register_service_wide(self, public_key: str, service_description: Description): + self.register_service(public_key, service_description) + + def unregister_agent(self, public_key: str, msg_id: int) -> None: + """ + Unregister an agent. + + :param public_key: the public key of the agent to be unregistered. + :param msg_id: the message id of the request. + :return: ``None`` + """ + self.loop.run_until_complete(self._lock.acquire()) + if public_key not in self.agents: + msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_DESCRIPTION) + self._send(public_key, msg.to_pb()) + else: + self.agents.pop(public_key) + self._lock.release() + + def unregister_service(self, public_key: str, msg_id: int, service_description: Description) -> None: + """ + Unregister a service agent. + + :param public_key: the public key of the service agent to be unregistered. + :param msg_id: the message id of the request. + :param service_description: the description of the service agent to be unregistered. + :return: ``None`` + """ + self.loop.run_until_complete(self._lock.acquire()) + + if public_key not in self.services: + msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_SERVICE) + self._send(public_key, msg.to_pb()) + else: + self.services[public_key].remove(service_description) + if len(self.services[public_key]) == 0: + self.services.pop(public_key) + self._lock.release() + + def search_agents(self, public_key: str, search_id: int, query: Query) -> None: + """ + Search the agents in the local Agent Directory, and send back the result. + The provided query will be checked with every instance of the Agent Directory. + + :param public_key: the source of the search request. + :param search_id: the search identifier associated with the search request. + :param query: the query that constitutes the search. + :return: ``None`` + """ + + result = [] + for agent_public_key, description in self.agents.items(): + if query.check(description): + result.append(agent_public_key) + + msg = SearchResult(search_id, sorted(set(result))) + self._send(public_key, msg.to_pb()) + + def search_services(self, public_key: str, search_id: int, query: Query) -> None: + """ + Search the agents in the local Service Directory, and send back the result. + The provided query will be checked with every instance of the Agent Directory. + + :param public_key: the source of the search request. + :param search_id: the search identifier associated with the search request. + :param query: the query that constitutes the search. + :return: ``None`` + """ + + result = [] + for agent_public_key, descriptions in self.services.items(): + for description in descriptions: + if query.check(description): + result.append(agent_public_key) + + msg = SearchResult(search_id, sorted(set(result))) + self._send(public_key, msg.to_pb()) + + def _send_agent_message(self, origin: str, msg: AgentMessage) -> None: + """ + Send an :class:`~oef.messages.AgentMessage`. + + :param origin: the public key of the sender agent. + :param msg: the message. + :return: ``None`` + """ + e = msg.to_pb() + destination = e.send_message.destination + + if destination not in self._queues: + msg = DialogueErrorMessage(msg.msg_id, e.send_message.dialogue_id, destination) + self._send(origin, msg.to_pb()) + return + + new_msg = agent_pb2.Server.AgentMessage() + new_msg.answer_id = msg.msg_id + new_msg.content.origin = origin + new_msg.content.dialogue_id = e.send_message.dialogue_id + + payload = e.send_message.WhichOneof("payload") + if payload == "content": + new_msg.content.content = e.send_message.content + elif payload == "fipa": + new_msg.content.fipa.CopyFrom(e.send_message.fipa) + + self._queues[destination].put_nowait(new_msg.SerializeToString()) + + def _send(self, public_key: str, msg): + self._queues[public_key].put_nowait(msg.SerializeToString()) + + +class OEFLocalProxy(OEFProxy): + """ + Proxy to the functionality of the OEF. + It allows the interaction between agents, but not the search functionality. + It is useful for local testing. + """ + + def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.AbstractEventLoop = None): + """ + Initialize a OEF proxy for a local OEF Node (that is, :class:`~oef.proxy.OEFLocalProxy.LocalNode` + + :param public_key: the public key used in the protocols. + :param local_node: the Local OEF Node object. This reference must be the same across the agents of interest. + :param loop: the event loop. + """ + + super().__init__(public_key, loop) + self.local_node = local_node + self._connection = None + self._read_queue = None + self._write_queue = None + + def register_agent(self, msg_id: int, agent_description: Description) -> None: + self.local_node.register_agent(self.public_key, agent_description) + + def register_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: + self.local_node.register_service(self.public_key, service_description) + + def search_agents(self, search_id: int, query: Query) -> None: + self.local_node.search_agents(self.public_key, search_id, query) + + def search_services(self, search_id: int, query: Query) -> None: + self.local_node.search_services(self.public_key, search_id, query) + + def search_services_wide(self, msg_id: int, query: Query) -> None: + raise NotImplementedError + + def unregister_agent(self, msg_id: int) -> None: + self.local_node.unregister_agent(self.public_key, msg_id) + + def unregister_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: + self.local_node.unregister_service(self.public_key, msg_id, service_description) + + def send_message(self, msg_id: int, dialogue_id: int, destination: str, msg: bytes): + msg = Message(msg_id, dialogue_id, destination, msg, uri.Context()) + self._send(msg) + + def send_cfp(self, msg_id: int, dialogue_id: int, destination: str, target: int, query: CFP_TYPES, context=uri.Context()) -> None: + msg = CFP(msg_id, dialogue_id, destination, target, query, context) + self._send(msg) + + def send_propose(self, msg_id: int, dialogue_id: int, destination: str, target: int, proposals: PROPOSE_TYPES, context=uri.Context()) -> None: + msg = Propose(msg_id, dialogue_id, destination, target, proposals, context) + self._send(msg) + + def send_accept(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: + msg = Accept(msg_id, dialogue_id, destination, target, context) + self._send(msg) + + def send_decline(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: + msg = Decline(msg_id, dialogue_id, destination, target, context) + self._send(msg) + + async def connect(self) -> bool: + if self._connection is not None: + return True + + self._connection = self.local_node.connect(self.public_key) + if self._connection is None: + return False + self._write_queue, self._read_queue = self._connection + return True + + async def _receive(self) -> bytes: + if not self.is_connected(): + raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") + data = await self._read_queue.get() + return data + + def _send(self, msg: BaseMessage) -> None: + if not self.is_connected(): + raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") + self._write_queue.put_nowait((self.public_key, msg)) + + async def stop(self): + self._connection = None + self._read_queue = None + self._write_queue = None + + def is_connected(self) -> bool: + return self._connection is not None \ No newline at end of file From 32b55f43c9fe6e9b7e5c5875faed6b997e334c42 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 16:03:55 +0100 Subject: [PATCH 045/107] fix test for 'simple' protocol serialization. --- tests/test_messages/test_simple.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index 9d9f11b3..ac4725f1 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -38,10 +38,12 @@ def test_simple_bytes_serialization(): def test_simple_error_serialization(): """Test that the serialization for the 'simple' protocol works for the ERROR message.""" - msg = SimpleMessage(to="receiver", sender="sender", type=SimpleMessage.Type.ERROR, - error_code=-1, error_msg="An error") - msg_bytes = SimpleSerializer().encode(msg) - actual_msg = SimpleSerializer().decode(msg_bytes) - expected_msg = msg + msg = SimpleMessage(type=SimpleMessage.Type.ERROR, error_code=-1, error_msg="An error") + envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg) + serializer = SimpleSerializer() - assert expected_msg == actual_msg + envelope_bytes = envelope.encode(serializer) + actual_envelope = Envelope.decode(envelope_bytes, serializer) + expected_envelope = envelope + + assert expected_envelope == actual_envelope From c3e20ff63c8cd6d6636f6910733c865855d1b284 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 19:47:08 +0100 Subject: [PATCH 046/107] add local node implementation. --- tac/aea/helpers/local_node.py | 136 +++++++++++++++++++--------------- tac/aea/mail/oef.py | 8 +- tests/test_localnode.py | 75 +++++++++++++++++++ 3 files changed, 157 insertions(+), 62 deletions(-) create mode 100644 tests/test_localnode.py diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index 6d938fb2..13298a2c 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -1,5 +1,8 @@ import asyncio import logging +import queue +import threading +from asyncio import AbstractEventLoop, Queue from collections import defaultdict from threading import Thread from typing import Dict, List, Tuple, Optional @@ -12,34 +15,37 @@ from oef.query import Query from oef.schema import Description -Envelope = None - logger = logging.getLogger(__name__) class LocalNode: """A light-weight local implementation of a OEF Node.""" - def __init__(self, loop=None): + def __init__(self): """ Initialize a local (i.e. non-networked) implementation of an OEF Node """ self.agents = dict() # type: Dict[str, Description] self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] - self.loop = asyncio.get_event_loop() if loop is None else loop - self._lock = asyncio.Lock() - self._thread = Thread(target=self.run) + self._lock = threading.Lock() - self._read_queue = asyncio.Queue() # type: asyncio.Queue + self._loop = asyncio.new_event_loop() + self._task = None # type: Optional[asyncio.Task] + self._stopped = True # type: bool + self._thread = None # type: Optional[Thread] + + self._read_queue = Queue(loop=self._loop) # type: Queue self._queues = {} # type: Dict[str, asyncio.Queue] + self._loops = {} # type: Dict[str, AbstractEventLoop] def __enter__(self): - self._thread.start() + self.start() + return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop() - def connect(self, public_key: str) -> Optional[Tuple[asyncio.Queue, asyncio.Queue]]: + def connect(self, public_key: str, loop: AbstractEventLoop) -> Optional[Tuple[Queue, Queue]]: """ Connect a public key to the node. @@ -49,23 +55,23 @@ def connect(self, public_key: str) -> Optional[Tuple[asyncio.Queue, asyncio.Queu if public_key in self._queues: return None - queue = asyncio.Queue() - self._queues[public_key] = queue - return self._read_queue, queue + q = Queue(loop=loop) + self._queues[public_key] = q + self._loops[public_key] = loop + return self._read_queue, q - def _process_messages(self) -> None: + async def _process_messages(self) -> None: """ Main event loop to process the incoming messages. - :return: ``None`` + :return: None """ - while True: + while not self._stopped: try: data = await self._read_queue.get() # type: Tuple[str, BaseMessage] except asyncio.CancelledError: logger.debug("Local Node: loop cancelled.") break - public_key, msg = data assert isinstance(msg, AgentMessage) self._send_agent_message(public_key, msg) @@ -74,17 +80,30 @@ def run(self) -> None: """ Run the node, i.e. start processing the messages. - :return: ``None`` + :return: None """ - self._process_messages() + self._stopped = False + self._task = asyncio.ensure_future(self._process_messages(), loop=self._loop) + self._loop.run_until_complete(self._task) + + def start(self): + """Start the node in its own thread.""" + self._thread = Thread(target=self.run) + self._thread.start() def stop(self) -> None: """ Stop the execution of the node. - :return: ``None`` + :return: None """ - pass + self._stopped = True + + if self._task and not self._task.cancelled(): + self._loop.call_soon_threadsafe(self._task.cancel) + + if self._thread: + self._thread.join() def register_agent(self, public_key: str, agent_description: Description) -> None: """ @@ -92,11 +111,10 @@ def register_agent(self, public_key: str, agent_description: Description) -> Non :param public_key: the public key of the agent to be registered. :param agent_description: the description of the agent to be registered. - :return: ``None`` + :return: None """ - self.loop.run_until_complete(self._lock.acquire()) - self.agents[public_key] = agent_description - self._lock.release() + with self._lock: + self.agents[public_key] = agent_description def register_service(self, public_key: str, service_description: Description): """ @@ -104,14 +122,13 @@ def register_service(self, public_key: str, service_description: Description): :param public_key: the public key of the service agent to be registered. :param service_description: the description of the service agent to be registered. - :return: ``None`` + :return: None """ - self.loop.run_until_complete(self._lock.acquire()) - self.services[public_key].append(service_description) - self._lock.release() + with self._lock: + self.services[public_key].append(service_description) def register_service_wide(self, public_key: str, service_description: Description): - self.register_service(public_key, service_description) + raise NotImplementedError def unregister_agent(self, public_key: str, msg_id: int) -> None: """ @@ -119,15 +136,14 @@ def unregister_agent(self, public_key: str, msg_id: int) -> None: :param public_key: the public key of the agent to be unregistered. :param msg_id: the message id of the request. - :return: ``None`` + :return: None """ - self.loop.run_until_complete(self._lock.acquire()) - if public_key not in self.agents: - msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_DESCRIPTION) - self._send(public_key, msg.to_pb()) - else: - self.agents.pop(public_key) - self._lock.release() + with self._lock: + if public_key not in self.agents: + msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_DESCRIPTION) + self._send(public_key, msg.to_pb()) + else: + self.agents.pop(public_key) def unregister_service(self, public_key: str, msg_id: int, service_description: Description) -> None: """ @@ -136,18 +152,16 @@ def unregister_service(self, public_key: str, msg_id: int, service_description: :param public_key: the public key of the service agent to be unregistered. :param msg_id: the message id of the request. :param service_description: the description of the service agent to be unregistered. - :return: ``None`` + :return: None """ - self.loop.run_until_complete(self._lock.acquire()) - - if public_key not in self.services: - msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_SERVICE) - self._send(public_key, msg.to_pb()) - else: - self.services[public_key].remove(service_description) - if len(self.services[public_key]) == 0: - self.services.pop(public_key) - self._lock.release() + with self._lock: + if public_key not in self.services: + msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_SERVICE) + self._send(public_key, msg.to_pb()) + else: + self.services[public_key].remove(service_description) + if len(self.services[public_key]) == 0: + self.services.pop(public_key) def search_agents(self, public_key: str, search_id: int, query: Query) -> None: """ @@ -157,7 +171,7 @@ def search_agents(self, public_key: str, search_id: int, query: Query) -> None: :param public_key: the source of the search request. :param search_id: the search identifier associated with the search request. :param query: the query that constitutes the search. - :return: ``None`` + :return: None """ result = [] @@ -176,7 +190,7 @@ def search_services(self, public_key: str, search_id: int, query: Query) -> None :param public_key: the source of the search request. :param search_id: the search identifier associated with the search request. :param query: the query that constitutes the search. - :return: ``None`` + :return: None """ result = [] @@ -194,7 +208,7 @@ def _send_agent_message(self, origin: str, msg: AgentMessage) -> None: :param origin: the public key of the sender agent. :param msg: the message. - :return: ``None`` + :return: None """ e = msg.to_pb() destination = e.send_message.destination @@ -215,10 +229,12 @@ def _send_agent_message(self, origin: str, msg: AgentMessage) -> None: elif payload == "fipa": new_msg.content.fipa.CopyFrom(e.send_message.fipa) - self._queues[destination].put_nowait(new_msg.SerializeToString()) + self._send(destination, new_msg) def _send(self, public_key: str, msg): - self._queues[public_key].put_nowait(msg.SerializeToString()) + loop = self._loops[public_key] + loop.call_soon_threadsafe(self._queues[public_key].put_nowait, msg.SerializeToString()) + # self._queues[public_key].put_nowait(msg.SerializeToString()) class OEFLocalProxy(OEFProxy): @@ -239,9 +255,9 @@ def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.Abstrac super().__init__(public_key, loop) self.local_node = local_node - self._connection = None - self._read_queue = None - self._write_queue = None + self._connection = None # type: Optional[Tuple[queue.Queue, queue.Queue]] + self._read_queue = None # type: Optional[Queue] + self._write_queue = None # type: Optional[Queue] def register_agent(self, msg_id: int, agent_description: Description) -> None: self.local_node.register_agent(self.public_key, agent_description) @@ -264,8 +280,8 @@ def unregister_agent(self, msg_id: int) -> None: def unregister_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: self.local_node.unregister_service(self.public_key, msg_id, service_description) - def send_message(self, msg_id: int, dialogue_id: int, destination: str, msg: bytes): - msg = Message(msg_id, dialogue_id, destination, msg, uri.Context()) + def send_message(self, msg_id: int, dialogue_id: int, destination: str, msg: bytes, context=uri.Context()): + msg = Message(msg_id, dialogue_id, destination, msg, context) self._send(msg) def send_cfp(self, msg_id: int, dialogue_id: int, destination: str, target: int, query: CFP_TYPES, context=uri.Context()) -> None: @@ -288,7 +304,7 @@ async def connect(self) -> bool: if self._connection is not None: return True - self._connection = self.local_node.connect(self.public_key) + self._connection = self.local_node.connect(self.public_key, self._loop) if self._connection is None: return False self._write_queue, self._read_queue = self._connection @@ -311,4 +327,4 @@ async def stop(self): self._write_queue = None def is_connected(self) -> bool: - return self._connection is not None \ No newline at end of file + return self._connection is not None diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 77cd7deb..ece70a02 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -97,6 +97,10 @@ def __init__(self, oef_proxy: OEFProxy, in_queue: Queue): self.in_queue = in_queue self.mail_stats = MailStats() + @property + def loop(self) -> asyncio.AbstractEventLoop: + return self._loop + def is_connected(self) -> bool: """Get connected status.""" return self._oef_proxy.is_connected() @@ -378,10 +382,10 @@ def disconnect(self) -> None: :return: None """ + self._stopped = True if self.bridge.is_active(): - self.bridge.stop() + self.bridge.loop.call_soon_threadsafe(self.bridge.stop) - self._stopped = True self.in_thread.join() self.out_thread.join() self.in_thread = Thread(target=self.bridge.run) diff --git a/tests/test_localnode.py b/tests/test_localnode.py new file mode 100644 index 00000000..00c6db70 --- /dev/null +++ b/tests/test_localnode.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the tests of the local OEF node implementation.""" +import asyncio +import time + +from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy +from tac.aea.mail.messages import ByteMessage, FIPAMessage +from tac.aea.mail.oef import OEFMailBox + + +def test_connection(): + """Test that two mailbox can connect to the node.""" + with LocalNode() as node: + mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) + mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) + + mailbox1.connect() + mailbox2.connect() + + mailbox1.disconnect() + mailbox2.disconnect() + + +def test_communication(): + """Test that two mailbox can communicate through the node.""" + with LocalNode() as node: + mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) + mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) + + mailbox1.connect() + mailbox2.connect() + + mailbox1.send(ByteMessage("mailbox2", "mailbox1", message_id=0, dialogue_id=0, content=b"hello")) + mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None)) + mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[])) + mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT)) + mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE)) + + time.sleep(5.0) + + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.get("content") == b"hello" + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.CFP + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.PROPOSE + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.ACCEPT + msg = mailbox2.inbox.get(block=True, timeout=1.0) + assert msg.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.DECLINE + + mailbox1.disconnect() + mailbox2.disconnect() From 44b7fb245d24e8b3e17deabbb6166f481b3c7bab Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 21:30:15 +0100 Subject: [PATCH 047/107] Address PR comments. Change the name of 'msg' Envelope variable into 'envelope' in several places. Do other misc changes. --- tac/aea/mail/base.py | 2 +- tac/aea/mail/oef.py | 4 +- tac/aea/mail/protocol.py | 2 +- tac/agents/controller/base/handlers.py | 34 +++++++------- tac/agents/participant/base/dialogues.py | 45 +++++++++--------- tac/agents/participant/base/handlers.py | 42 ++++++++--------- tac/agents/participant/base/helpers.py | 20 ++++---- tac/agents/participant/base/interfaces.py | 16 +++---- tac/agents/participant/base/reactions.py | 57 ++++++++++++----------- 9 files changed, 111 insertions(+), 111 deletions(-) diff --git a/tac/aea/mail/base.py b/tac/aea/mail/base.py index 28bf9306..333bf86e 100644 --- a/tac/aea/mail/base.py +++ b/tac/aea/mail/base.py @@ -143,7 +143,7 @@ def is_established(self) -> bool: """Check if the connection is established.""" @abstractmethod - def send(self, msg: Envelope): + def send(self, envelope: Envelope): """Send a message.""" diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index edb05792..511991c8 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -391,13 +391,13 @@ def is_established(self) -> bool: """Get the connection status.""" return self.bridge.is_connected() - def send(self, msg: Envelope): + def send(self, envelope: Envelope): """ Send messages. :return: None """ - self.bridge.send(msg) + self.bridge.send(envelope) class OEFMailBox(MailBox): diff --git a/tac/aea/mail/protocol.py b/tac/aea/mail/protocol.py index 99cdcbbd..678f458c 100644 --- a/tac/aea/mail/protocol.py +++ b/tac/aea/mail/protocol.py @@ -227,7 +227,7 @@ def __init__(self, serializer: Serializer): self.serializer = serializer @abstractmethod - def is_message_valid(self, msg: Envelope): + def is_message_valid(self, envelope: Envelope): """Determine whether a message is valid.""" @abstractmethod diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 61897e53..2027f26e 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -279,26 +279,26 @@ def __init__(self, controller_agent: 'ControllerAgent'): GetStateUpdate: GetStateUpdateHandler(controller_agent), } # type: Dict[Type[Request], RequestHandler] - def handle_agent_message(self, msg: Envelope) -> Response: + def handle_agent_message(self, envelope: Envelope) -> Response: """ Dispatch the request to the right handler. If no handler is found for the provided type of request, return an "invalid request" error. If something bad happen, return a "generic" error. - :param msg: the request to handle + :param envelope: the request to handle :return: the response. """ - assert msg.protocol_id == "bytes" - msg_id = msg.message.get("id") - dialogue_id = msg.message.get("dialogue_id") - sender = msg.sender - logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, msg.message.get("id"), dialogue_id, sender)) - request = self.decode(msg.message.get("content"), sender) + assert envelope.protocol_id == "bytes" + msg_id = envelope.message.get("id") + dialogue_id = envelope.message.get("dialogue_id") + sender = envelope.sender + logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, envelope.message.get("id"), dialogue_id, sender)) + request = self.decode(envelope.message.get("content"), sender) handle_request = self.handlers.get(type(request), None) # type: RequestHandler if handle_request is None: logger.debug("[{}]: Unknown message: msg_id={}, dialogue_id={}, origin={}".format(self.controller_agent.name, msg_id, dialogue_id, sender)) - return Error(msg.sender, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) + return Error(envelope.sender, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) try: return handle_request(request) except Exception as e: @@ -486,21 +486,21 @@ def __init__(self, crypto: Crypto, liveness: Liveness, mailbox: MailBox, agent_n OEFActions.__init__(self, crypto, liveness, mailbox, agent_name) OEFReactions.__init__(self, crypto, liveness, mailbox, agent_name) - def handle_oef_message(self, msg: Envelope) -> None: + def handle_oef_message(self, envelope: Envelope) -> None: """ Handle messages from the oef. The oef does not expect a response for any of these messages. - :param msg: the OEF message + :param envelope: the OEF message :return: None """ - logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) - assert msg.protocol_id == "oef" - if msg.message.get("type") == OEFMessage.Type.OEF_ERROR: - self.on_oef_error(msg) - elif msg.message.get("type") == OEFMessage.Type.DIALOGUE_ERROR: - self.on_dialogue_error(msg) + logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(envelope))) + assert envelope.protocol_id == "oef" + if envelope.message.get("type") == OEFMessage.Type.OEF_ERROR: + self.on_oef_error(envelope) + elif envelope.message.get("type") == OEFMessage.Type.DIALOGUE_ERROR: + self.on_dialogue_error(envelope) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/agents/participant/base/dialogues.py b/tac/agents/participant/base/dialogues.py index 77c633d1..c2f2f706 100644 --- a/tac/agents/participant/base/dialogues.py +++ b/tac/agents/participant/base/dialogues.py @@ -84,8 +84,7 @@ def outgoing_extend(self, messages: List[Envelope]) -> None: :param messages: a list of messages to be added :return: None """ - for message in messages: - self._outgoing_messages.extend([message]) + self._outgoing_messages.extend(messages) def incoming_extend(self, messages: List[Envelope]) -> None: """ @@ -180,7 +179,7 @@ def dialogues_as_buyer(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent acts as a buyer.""" return self._dialogues_as_buyer - def is_permitted_for_new_dialogue(self, msg: Envelope, known_pbks: List[str]) -> bool: + def is_permitted_for_new_dialogue(self, envelope: Envelope, known_pbks: List[str]) -> bool: """ Check whether an agent message is permitted for a new dialogue. @@ -189,37 +188,37 @@ def is_permitted_for_new_dialogue(self, msg: Envelope, known_pbks: List[str]) -> - have the correct msg id and message target, and - be from a known public key. - :param msg: the agent message + :param envelope: the agent message :param known_pbks: the list of known public keys :return: a boolean indicating whether the message is permitted for a new dialogue """ - protocol = msg.protocol_id - msg_id = msg.message.get("id") - target = msg.message.get("target") - performative = msg.message.get("performative") + protocol = envelope.protocol_id + msg_id = envelope.message.get("id") + target = envelope.message.get("target") + performative = envelope.message.get("performative") result = protocol == "fipa"\ and performative == FIPAMessage.Performative.CFP \ and msg_id == STARTING_MESSAGE_ID\ and target == STARTING_MESSAGE_TARGET \ - and (msg.sender in known_pbks) + and (envelope.sender in known_pbks) return result - def is_belonging_to_registered_dialogue(self, msg: Envelope, agent_pbk: str) -> bool: + def is_belonging_to_registered_dialogue(self, envelope: Envelope, agent_pbk: str) -> bool: """ Check whether an agent message is part of a registered dialogue. - :param msg: the agent message + :param envelope: the agent message :param agent_pbk: the public key of the agent :return: boolean indicating whether the message belongs to a registered dialogue """ - assert msg.protocol_id == "fipa" - dialogue_id = msg.message.get("dialogue_id") - opponent = msg.sender - target = msg.message.get("target") - performative = msg.message.get("performative") + assert envelope.protocol_id == "fipa" + dialogue_id = envelope.message.get("dialogue_id") + opponent = envelope.sender + target = envelope.message.get("target") + performative = envelope.message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) result = False @@ -246,20 +245,20 @@ def is_belonging_to_registered_dialogue(self, msg: Envelope, agent_pbk: str) -> result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, msg: Envelope, agent_pbk: str) -> Dialogue: + def get_dialogue(self, envelope: Envelope, agent_pbk: str) -> Dialogue: """ Retrieve dialogue. - :param msg: the agent message + :param envelope: the agent message :param agent_pbk: the public key of the agent :return: the dialogue """ - assert msg.protocol_id == "fipa" - dialogue_id = msg.message.get("dialogue_id") - opponent = msg.sender - target = msg.message.get("target") - performative = msg.message.get("performative") + assert envelope.protocol_id == "fipa" + dialogue_id = envelope.message.get("dialogue_id") + opponent = envelope.sender + target = envelope.message.get("target") + performative = envelope.message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: diff --git a/tac/agents/participant/base/handlers.py b/tac/agents/participant/base/handlers.py index 52a3ada7..2f11d68f 100644 --- a/tac/agents/participant/base/handlers.py +++ b/tac/agents/participant/base/handlers.py @@ -60,23 +60,23 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, msg: Envelope) -> None: + def handle_dialogue_message(self, envelope: Envelope) -> None: """ Handle messages from the other agents. The agents expect a response. - :param msg: the agent message + :param envelope: the agent message :return: None """ - logger.debug("Handling Dialogue message. type={}".format(type(msg))) - if self.dialogues.is_belonging_to_registered_dialogue(msg, self.crypto.public_key): - self.on_existing_dialogue(msg) - elif self.dialogues.is_permitted_for_new_dialogue(msg, self.game_instance.game_configuration.agent_pbks): - self.on_new_dialogue(msg) + logger.debug("Handling Dialogue message. type={}".format(type(envelope))) + if self.dialogues.is_belonging_to_registered_dialogue(envelope, self.crypto.public_key): + self.on_existing_dialogue(envelope) + elif self.dialogues.is_permitted_for_new_dialogue(envelope, self.game_instance.game_configuration.agent_pbks): + self.on_new_dialogue(envelope) else: - self.on_unidentified_dialogue(msg) + self.on_unidentified_dialogue(envelope) class ControllerHandler(ControllerActions, ControllerReactions): @@ -95,21 +95,21 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan ControllerActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) ControllerReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_controller_message(self, msg: Envelope) -> None: + def handle_controller_message(self, envelope: Envelope) -> None: """ Handle messages from the controller. The controller does not expect a response for any of these messages. - :param msg: the controller message + :param envelope: the controller message :return: None """ - assert msg.protocol_id == "bytes" - response = Response.from_pb(msg.message.get("content"), msg.sender, self.crypto) + assert envelope.protocol_id == "bytes" + response = Response.from_pb(envelope.message.get("content"), envelope.sender, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: - if msg.sender != self.game_instance.controller_pbk: + if envelope.sender != self.game_instance.controller_pbk: raise ValueError("The sender of the message is not the controller agent we registered with.") if isinstance(response, Error): @@ -151,24 +151,24 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan OEFActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) OEFReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name, rejoin) - def handle_oef_message(self, msg: Envelope) -> None: + def handle_oef_message(self, envelope: Envelope) -> None: """ Handle messages from the oef. The oef does not expect a response for any of these messages. - :param msg: the OEF message + :param envelope: the OEF message :return: None """ - logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(msg))) - assert msg.protocol_id == "oef" - oef_type = msg.message.get("type") + logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(envelope))) + assert envelope.protocol_id == "oef" + oef_type = envelope.message.get("type") if oef_type == OEFMessage.Type.SEARCH_RESULT: - self.on_search_result(msg) + self.on_search_result(envelope) elif oef_type == OEFMessage.Type.OEF_ERROR: - self.on_oef_error(msg) + self.on_oef_error(envelope) elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: - self.on_dialogue_error(msg) + self.on_dialogue_error(envelope) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/agents/participant/base/helpers.py b/tac/agents/participant/base/helpers.py index 56fe0d1d..8dbe2d69 100644 --- a/tac/agents/participant/base/helpers.py +++ b/tac/agents/participant/base/helpers.py @@ -39,30 +39,30 @@ QUANTITY_SHIFT = 1 # Any non-negative integer is fine. -def is_oef_message(msg: Envelope) -> bool: +def is_oef_message(envelope: Envelope) -> bool: """ Check whether a message is from the oef. - :param msg: the message + :param envelope: the message :return: boolean indicating whether or not the message is from the oef """ - return msg.protocol_id == "oef" and msg.message.get("type") in set(OEFMessage.Type) + return envelope.protocol_id == "oef" and envelope.message.get("type") in set(OEFMessage.Type) -def is_controller_message(msg: Envelope, crypto: Crypto) -> bool: +def is_controller_message(envelope: Envelope, crypto: Crypto) -> bool: """ Check whether a message is from the controller. - :param msg: the message + :param envelope: the message :param crypto: the crypto of the agent :return: boolean indicating whether or not the message is from the controller """ - if not msg.protocol_id == "bytes": + if not envelope.protocol_id == "bytes": return False try: - byte_content = msg.message.get("content") - sender_pbk = msg.sender + byte_content = envelope.message.get("content") + sender_pbk = envelope.sender Response.from_pb(byte_content, sender_pbk, crypto) except Exception as e: logger.debug("Not a Controller message: {}".format(str(e))) @@ -77,9 +77,9 @@ def is_controller_message(msg: Envelope, crypto: Crypto) -> bool: return True -def is_fipa_message(msg: Envelope) -> bool: +def is_fipa_message(envelope: Envelope) -> bool: """Chcek whether a message is a FIPA message.""" - return msg.protocol_id == "fipa" and msg.message.get("performative") in set(FIPAMessage.Performative) + return envelope.protocol_id == "fipa" and envelope.message.get("performative") in set(FIPAMessage.Performative) def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: diff --git a/tac/agents/participant/base/interfaces.py b/tac/agents/participant/base/interfaces.py index 08799945..707ae78a 100644 --- a/tac/agents/participant/base/interfaces.py +++ b/tac/agents/participant/base/interfaces.py @@ -30,11 +30,11 @@ class ControllerReactionInterface: """This interface contains the methods to react to events from the ControllerAgent.""" @abstractmethod - def on_dialogue_error(self, dialogue_error_msg: Envelope) -> None: + def on_dialogue_error(self, envelope: Envelope) -> None: """ Handle dialogue error event emitted by the controller. - :param dialogue_error_msg: the dialogue error message + :param envelope: the dialogue error message :return: None """ @@ -180,7 +180,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, msg: Envelope) -> None: + def on_new_dialogue(self, envelope: Envelope) -> None: """ React to a message for a new dialogue. @@ -188,27 +188,27 @@ def on_new_dialogue(self, msg: Envelope) -> None: - the protocol rules that messages must follow; - how the agent behaves in this dialogue. - :param msg: the agent message + :param envelope: the agent message :return: None """ @abstractmethod - def on_existing_dialogue(self, msg: Envelope) -> None: + def on_existing_dialogue(self, envelope: Envelope) -> None: """ React to a message of an existing dialogue. - :param msg: the agent message + :param envelope: the agent message :return: None """ @abstractmethod - def on_unidentified_dialogue(self, msg: Envelope) -> None: + def on_unidentified_dialogue(self, envelope: Envelope) -> None: """ React to a message of an unidentified dialogue. - :param msg: the agent message + :param envelope: the agent message :return: None """ diff --git a/tac/agents/participant/base/reactions.py b/tac/agents/participant/base/reactions.py index 078e924d..12e18ea6 100644 --- a/tac/agents/participant/base/reactions.py +++ b/tac/agents/participant/base/reactions.py @@ -72,17 +72,18 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, dialogue_error_msg: Envelope) -> None: + def on_dialogue_error(self, envelope: Envelope) -> None: """ Handle dialogue error event emitted by the controller. - :param dialogue_error_msg: the dialogue error message + :param envelope: the dialogue error message :return: None """ - assert dialogue_error_msg.protocol_id == "oef" + assert envelope.protocol_id == "oef" + dialogue_error = envelope.message logger.warning("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error_msg.get("id"), dialogue_error_msg.get("dialogue_id"), dialogue_error_msg.get("origin"))) + .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) def on_start(self, game_data: GameData) -> None: """ @@ -362,75 +363,75 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.dialogues = game_instance.dialogues self.negotiation_behaviour = FIPABehaviour(crypto, game_instance, agent_name) - def on_new_dialogue(self, msg: Envelope) -> None: + def on_new_dialogue(self, envelope: Envelope) -> None: """ React to a new dialogue. - :param msg: the agent message + :param envelope: the agent message :return: None """ - assert msg.protocol_id == "fipa" and msg.message.get("performative") == FIPAMessage.Performative.CFP - services = json.loads(msg.message.get("query").decode('utf-8')) + assert envelope.protocol_id == "fipa" and envelope.message.get("performative") == FIPAMessage.Performative.CFP + services = json.loads(envelope.message.get("query").decode('utf-8')) is_seller = services['description'] == TAC_DEMAND_DATAMODEL_NAME - dialogue = self.dialogues.create_opponent_initiated(msg.sender, msg.message.get("dialogue_id"), is_seller) + dialogue = self.dialogues.create_opponent_initiated(envelope.sender, envelope.message.get("dialogue_id"), is_seller) logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format(self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) - results = self._handle(msg, dialogue) + results = self._handle(envelope, dialogue) for result in results: self.mailbox.outbox.put(result) - def on_existing_dialogue(self, msg: Envelope) -> None: + def on_existing_dialogue(self, envelope: Envelope) -> None: """ React to an existing dialogue. - :param msg: the agent message + :param envelope: the agent message :return: None """ - dialogue = self.dialogues.get_dialogue(msg, self.crypto.public_key) + dialogue = self.dialogues.get_dialogue(envelope, self.crypto.public_key) - results = self._handle(msg, dialogue) + results = self._handle(envelope, dialogue) for result in results: self.mailbox.outbox.put(result) - def on_unidentified_dialogue(self, msg: Envelope) -> None: + def on_unidentified_dialogue(self, envelope: Envelope) -> None: """ React to an unidentified dialogue. - :param msg: agent message + :param envelope: agent message :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - message_id = msg.message.get("id") if msg.message.get("id") is not None else 1 - dialogue_id = msg.message.get("dialogue_id") if msg.message.get("dialogue_id") is not None else 1 + message_id = envelope.message.get("id") if envelope.message.get("id") is not None else 1 + dialogue_id = envelope.message.get("dialogue_id") if envelope.message.get("dialogue_id") is not None else 1 result = ByteMessage(message_id=message_id + 1, dialogue_id=dialogue_id, content=b'This message belongs to an unidentified dialogue.') - self.mailbox.outbox.put_message(to=msg.sender, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, message=result) + self.mailbox.outbox.put_message(to=envelope.sender, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, message=result) - def _handle(self, msg: Envelope, dialogue: Dialogue) -> List[Envelope]: + def _handle(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: """ Handle a message according to the defined behaviour. - :param msg: the agent message + :param envelope: the agent message :param dialogue: the dialogue :return: a list of agent messages """ - dialogue.incoming_extend([msg]) + dialogue.incoming_extend([envelope]) results = [] # type: List[Envelope] - performative = msg.message.get("performative") + performative = envelope.message.get("performative") if performative == FIPAMessage.Performative.CFP: - result = self.negotiation_behaviour.on_cfp(msg, dialogue) + result = self.negotiation_behaviour.on_cfp(envelope, dialogue) results = [result] elif performative == FIPAMessage.Performative.PROPOSE: - result = self.negotiation_behaviour.on_propose(msg, dialogue) + result = self.negotiation_behaviour.on_propose(envelope, dialogue) results = [result] elif performative == FIPAMessage.Performative.ACCEPT: - results = self.negotiation_behaviour.on_accept(msg, dialogue) + results = self.negotiation_behaviour.on_accept(envelope, dialogue) elif performative == FIPAMessage.Performative.MATCH_ACCEPT: - results = self.negotiation_behaviour.on_match_accept(msg, dialogue) + results = self.negotiation_behaviour.on_match_accept(envelope, dialogue) elif performative == FIPAMessage.Performative.DECLINE: - self.negotiation_behaviour.on_decline(msg, dialogue) + self.negotiation_behaviour.on_decline(envelope, dialogue) results = [] else: raise ValueError("Performative not supported: {}".format(str(performative))) From 7873831ceff45eb4026aad4c125e1348f6dd0008 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 22:32:10 +0100 Subject: [PATCH 048/107] fix local node communication. --- tac/aea/helpers/local_node.py | 14 +++++++------- tests/test_localnode.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index 13298a2c..87dcfc0a 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -29,12 +29,12 @@ def __init__(self): self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._lock = threading.Lock() - self._loop = asyncio.new_event_loop() + self.loop = asyncio.new_event_loop() self._task = None # type: Optional[asyncio.Task] self._stopped = True # type: bool self._thread = None # type: Optional[Thread] - self._read_queue = Queue(loop=self._loop) # type: Queue + self._read_queue = Queue(loop=self.loop) # type: Queue self._queues = {} # type: Dict[str, asyncio.Queue] self._loops = {} # type: Dict[str, AbstractEventLoop] @@ -74,6 +74,7 @@ async def _process_messages(self) -> None: break public_key, msg = data assert isinstance(msg, AgentMessage) + logger.debug("Processing message from {}: {}".format(public_key, msg)) self._send_agent_message(public_key, msg) def run(self) -> None: @@ -83,8 +84,8 @@ def run(self) -> None: :return: None """ self._stopped = False - self._task = asyncio.ensure_future(self._process_messages(), loop=self._loop) - self._loop.run_until_complete(self._task) + self._task = asyncio.ensure_future(self._process_messages(), loop=self.loop) + self.loop.run_until_complete(self._task) def start(self): """Start the node in its own thread.""" @@ -100,7 +101,7 @@ def stop(self) -> None: self._stopped = True if self._task and not self._task.cancelled(): - self._loop.call_soon_threadsafe(self._task.cancel) + self.loop.call_soon_threadsafe(self._task.cancel) if self._thread: self._thread.join() @@ -234,7 +235,6 @@ def _send_agent_message(self, origin: str, msg: AgentMessage) -> None: def _send(self, public_key: str, msg): loop = self._loops[public_key] loop.call_soon_threadsafe(self._queues[public_key].put_nowait, msg.SerializeToString()) - # self._queues[public_key].put_nowait(msg.SerializeToString()) class OEFLocalProxy(OEFProxy): @@ -319,7 +319,7 @@ async def _receive(self) -> bytes: def _send(self, msg: BaseMessage) -> None: if not self.is_connected(): raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") - self._write_queue.put_nowait((self.public_key, msg)) + self.local_node.loop.call_soon_threadsafe(self._write_queue.put_nowait, (self.public_key, msg)) async def stop(self): self._connection = None diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 00c6db70..ede8e6f0 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -54,7 +54,7 @@ def test_communication(): mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT)) mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE)) - time.sleep(5.0) + time.sleep(1.0) msg = mailbox2.inbox.get(block=True, timeout=1.0) assert msg.get("content") == b"hello" From 0bac94c36f1d2dfc4c528d7f5106bac9745d8cfa Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 19 Aug 2019 22:58:12 +0100 Subject: [PATCH 049/107] fix docstrings. --- tac/aea/helpers/local_node.py | 56 ++++++++++++++++++++++++++++++----- tac/aea/mail/oef.py | 1 + 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index 87dcfc0a..5dc9cc40 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -1,3 +1,25 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Naive implementation of the OEF Node features.""" + import asyncio import logging import queue @@ -22,9 +44,7 @@ class LocalNode: """A light-weight local implementation of a OEF Node.""" def __init__(self): - """ - Initialize a local (i.e. non-networked) implementation of an OEF Node - """ + """Initialize a local (i.e. non-networked) implementation of an OEF Node.""" self.agents = dict() # type: Dict[str, Description] self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._lock = threading.Lock() @@ -39,10 +59,12 @@ def __init__(self): self._loops = {} # type: Dict[str, AbstractEventLoop] def __enter__(self): + """Start the OEF Node.""" self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): + """Stop the OEF Node.""" self.stop() def connect(self, public_key: str, loop: AbstractEventLoop) -> Optional[Tuple[Queue, Queue]]: @@ -62,7 +84,7 @@ def connect(self, public_key: str, loop: AbstractEventLoop) -> Optional[Tuple[Qu async def _process_messages(self) -> None: """ - Main event loop to process the incoming messages. + Process the incoming messages. :return: None """ @@ -129,6 +151,7 @@ def register_service(self, public_key: str, service_description: Description): self.services[public_key].append(service_description) def register_service_wide(self, public_key: str, service_description: Description): + """Register service wide.""" raise NotImplementedError def unregister_agent(self, public_key: str, msg_id: int) -> None: @@ -167,6 +190,7 @@ def unregister_service(self, public_key: str, msg_id: int, service_description: def search_agents(self, public_key: str, search_id: int, query: Query) -> None: """ Search the agents in the local Agent Directory, and send back the result. + The provided query will be checked with every instance of the Agent Directory. :param public_key: the source of the search request. @@ -174,7 +198,6 @@ def search_agents(self, public_key: str, search_id: int, query: Query) -> None: :param query: the query that constitutes the search. :return: None """ - result = [] for agent_public_key, description in self.agents.items(): if query.check(description): @@ -186,6 +209,7 @@ def search_agents(self, public_key: str, search_id: int, query: Query) -> None: def search_services(self, public_key: str, search_id: int, query: Query) -> None: """ Search the agents in the local Service Directory, and send back the result. + The provided query will be checked with every instance of the Agent Directory. :param public_key: the source of the search request. @@ -193,7 +217,6 @@ def search_services(self, public_key: str, search_id: int, query: Query) -> None :param query: the query that constitutes the search. :return: None """ - result = [] for agent_public_key, descriptions in self.services.items(): for description in descriptions: @@ -240,19 +263,19 @@ def _send(self, public_key: str, msg): class OEFLocalProxy(OEFProxy): """ Proxy to the functionality of the OEF. + It allows the interaction between agents, but not the search functionality. It is useful for local testing. """ def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.AbstractEventLoop = None): """ - Initialize a OEF proxy for a local OEF Node (that is, :class:`~oef.proxy.OEFLocalProxy.LocalNode` + Initialize a OEF proxy for a local OEF Node (that is, :class:`~oef.proxy.OEFLocalProxy.LocalNode`. :param public_key: the public key used in the protocols. :param local_node: the Local OEF Node object. This reference must be the same across the agents of interest. :param loop: the event loop. """ - super().__init__(public_key, loop) self.local_node = local_node self._connection = None # type: Optional[Tuple[queue.Queue, queue.Queue]] @@ -260,47 +283,60 @@ def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.Abstrac self._write_queue = None # type: Optional[Queue] def register_agent(self, msg_id: int, agent_description: Description) -> None: + """Register an agent.""" self.local_node.register_agent(self.public_key, agent_description) def register_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: + """Register a service.""" self.local_node.register_service(self.public_key, service_description) def search_agents(self, search_id: int, query: Query) -> None: + """Search agents.""" self.local_node.search_agents(self.public_key, search_id, query) def search_services(self, search_id: int, query: Query) -> None: + """Search services.""" self.local_node.search_services(self.public_key, search_id, query) def search_services_wide(self, msg_id: int, query: Query) -> None: + """Search wide.""" raise NotImplementedError def unregister_agent(self, msg_id: int) -> None: + """Unregister an agent.""" self.local_node.unregister_agent(self.public_key, msg_id) def unregister_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: + """Unregister a service.""" self.local_node.unregister_service(self.public_key, msg_id, service_description) def send_message(self, msg_id: int, dialogue_id: int, destination: str, msg: bytes, context=uri.Context()): + """Send a simple message.""" msg = Message(msg_id, dialogue_id, destination, msg, context) self._send(msg) def send_cfp(self, msg_id: int, dialogue_id: int, destination: str, target: int, query: CFP_TYPES, context=uri.Context()) -> None: + """Send a CFP.""" msg = CFP(msg_id, dialogue_id, destination, target, query, context) self._send(msg) def send_propose(self, msg_id: int, dialogue_id: int, destination: str, target: int, proposals: PROPOSE_TYPES, context=uri.Context()) -> None: + """Send a propose.""" msg = Propose(msg_id, dialogue_id, destination, target, proposals, context) self._send(msg) def send_accept(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: + """Send an accept.""" msg = Accept(msg_id, dialogue_id, destination, target, context) self._send(msg) def send_decline(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: + """Send a decline.""" msg = Decline(msg_id, dialogue_id, destination, target, context) self._send(msg) async def connect(self) -> bool: + """Connect the proxy.""" if self._connection is not None: return True @@ -311,20 +347,24 @@ async def connect(self) -> bool: return True async def _receive(self) -> bytes: + """Receive a message.""" if not self.is_connected(): raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") data = await self._read_queue.get() return data def _send(self, msg: BaseMessage) -> None: + """Send a message.""" if not self.is_connected(): raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") self.local_node.loop.call_soon_threadsafe(self._write_queue.put_nowait, (self.public_key, msg)) async def stop(self): + """Tear down the connection.""" self._connection = None self._read_queue = None self._write_queue = None def is_connected(self) -> bool: + """Return True if the proxy is connected, False otherwise.""" return self._connection is not None diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index ece70a02..caad88b2 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -99,6 +99,7 @@ def __init__(self, oef_proxy: OEFProxy, in_queue: Queue): @property def loop(self) -> asyncio.AbstractEventLoop: + """Get the event loop.""" return self._loop def is_connected(self) -> bool: From 785a73b39a240a532cfff9197fb35298e56b854a Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Tue, 20 Aug 2019 16:09:46 +0100 Subject: [PATCH 050/107] Split v1 and v2 participant agent --- docs/reference/api/tac.aea.crypto.rst | 22 ++++ docs/reference/api/tac.aea.dialogue.rst | 22 ++++ docs/reference/api/tac.aea.mail.rst | 38 ++++++ docs/reference/api/tac.aea.rst | 32 +++++ docs/reference/api/tac.aea.state.rst | 22 ++++ docs/reference/api/tac.agents.controller.rst | 22 ++++ .../api/tac.agents.participant.base.rst | 118 +++++++++++++++++ .../api/tac.agents.participant.examples.rst | 30 +++++ docs/reference/api/tac.agents.participant.rst | 30 +++++ docs/reference/api/tac.agents.rst | 3 +- docs/reference/api/tac.agents.v1.base.rst | 110 ---------------- docs/reference/api/tac.agents.v1.examples.rst | 30 ----- docs/reference/api/tac.agents.v1.rst | 38 ------ docs/reference/api/tac.gui.dashboards.rst | 8 ++ docs/reference/api/tac.helpers.rst | 38 ------ docs/reference/api/tac.platform.game.rst | 38 ++++++ docs/reference/api/tac.platform.rst | 29 ++-- docs/reference/api/tac.rst | 2 +- docs/sections/baseline_agent.rst | 86 ++++++------ docs/sections/develop_agent.rst | 26 ++-- docs/sections/negotiation_guide.ipynb | 10 +- scripts/launch.py | 2 +- tac/aea/agent.py | 15 ++- tac/agents/controller/agent.py | 2 +- tac/agents/controller/base/states.py | 2 +- tac/agents/participant/__init__.py | 3 +- tac/agents/participant/v1/__init__.py | 22 ++++ tac/agents/participant/{ => v1}/agent.py | 8 +- .../participant/{ => v1}/base/__init__.py | 0 .../participant/{ => v1}/base/actions.py | 4 +- .../participant/{ => v1}/base/dialogues.py | 0 .../{ => v1}/base/game_instance.py | 12 +- .../participant/{ => v1}/base/handlers.py | 6 +- .../participant/{ => v1}/base/helpers.py | 0 .../participant/{ => v1}/base/interfaces.py | 0 .../{ => v1}/base/negotiation_behaviours.py | 8 +- .../participant/{ => v1}/base/price_model.py | 0 .../participant/{ => v1}/base/reactions.py | 12 +- .../participant/{ => v1}/base/states.py | 2 +- .../{ => v1}/base/stats_manager.py | 0 .../participant/{ => v1}/base/strategy.py | 2 +- .../{ => v1}/base/transaction_manager.py | 2 +- .../participant/{ => v1}/examples/__init__.py | 0 .../participant/{ => v1}/examples/baseline.py | 6 +- .../participant/{ => v1}/examples/strategy.py | 6 +- tac/agents/participant/v2/__init__.py | 22 ++++ tac/agents/participant/v2/agent.py | 124 ++++++++++++++++++ tac/agents/participant/v2/skills/__init__.py | 22 ++++ .../v2/skills/fipa_negotiation/AUTHORS.rst | 7 + .../v2/skills/fipa_negotiation/__init__.py | 22 ++++ .../v2/skills/fipa_negotiation/__version__.py | 28 ++++ .../fipa_negotiation/behaviours/__init__.py | 22 ++++ .../fipa_negotiation/behaviours/base.py | 21 +++ .../fipa_negotiation/handlers/__init__.py | 22 ++++ .../skills/fipa_negotiation/handlers/base.py | 21 +++ .../serialization/__init__.py | 22 ++++ .../fipa_negotiation/serialization/base.py | 21 +++ tac/gui/dashboards/agent.py | 4 +- tac/platform/game/stats.py | 2 +- tac/platform/simulation.py | 4 +- tests/test_game.py | 2 +- tests/test_simulation.py | 6 +- 62 files changed, 893 insertions(+), 347 deletions(-) create mode 100644 docs/reference/api/tac.aea.crypto.rst create mode 100644 docs/reference/api/tac.aea.dialogue.rst create mode 100644 docs/reference/api/tac.aea.mail.rst create mode 100644 docs/reference/api/tac.aea.rst create mode 100644 docs/reference/api/tac.aea.state.rst create mode 100644 docs/reference/api/tac.agents.controller.rst create mode 100644 docs/reference/api/tac.agents.participant.base.rst create mode 100644 docs/reference/api/tac.agents.participant.examples.rst create mode 100644 docs/reference/api/tac.agents.participant.rst delete mode 100644 docs/reference/api/tac.agents.v1.base.rst delete mode 100644 docs/reference/api/tac.agents.v1.examples.rst delete mode 100644 docs/reference/api/tac.agents.v1.rst delete mode 100644 docs/reference/api/tac.helpers.rst create mode 100644 docs/reference/api/tac.platform.game.rst create mode 100644 tac/agents/participant/v1/__init__.py rename tac/agents/participant/{ => v1}/agent.py (94%) rename tac/agents/participant/{ => v1}/base/__init__.py (100%) rename tac/agents/participant/{ => v1}/base/actions.py (98%) rename tac/agents/participant/{ => v1}/base/dialogues.py (100%) rename tac/agents/participant/{ => v1}/base/game_instance.py (96%) rename tac/agents/participant/{ => v1}/base/handlers.py (96%) rename tac/agents/participant/{ => v1}/base/helpers.py (100%) rename tac/agents/participant/{ => v1}/base/interfaces.py (100%) rename tac/agents/participant/{ => v1}/base/negotiation_behaviours.py (98%) rename tac/agents/participant/{ => v1}/base/price_model.py (100%) rename tac/agents/participant/{ => v1}/base/reactions.py (97%) rename tac/agents/participant/{ => v1}/base/states.py (99%) rename tac/agents/participant/{ => v1}/base/stats_manager.py (100%) rename tac/agents/participant/{ => v1}/base/strategy.py (98%) rename tac/agents/participant/{ => v1}/base/transaction_manager.py (99%) rename tac/agents/participant/{ => v1}/examples/__init__.py (100%) rename tac/agents/participant/{ => v1}/examples/baseline.py (96%) rename tac/agents/participant/{ => v1}/examples/strategy.py (95%) create mode 100644 tac/agents/participant/v2/__init__.py create mode 100644 tac/agents/participant/v2/agent.py create mode 100644 tac/agents/participant/v2/skills/__init__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/AUTHORS.rst create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/__init__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/__version__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/behaviours/__init__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/behaviours/base.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/handlers/__init__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/handlers/base.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/serialization/__init__.py create mode 100644 tac/agents/participant/v2/skills/fipa_negotiation/serialization/base.py diff --git a/docs/reference/api/tac.aea.crypto.rst b/docs/reference/api/tac.aea.crypto.rst new file mode 100644 index 00000000..d2409952 --- /dev/null +++ b/docs/reference/api/tac.aea.crypto.rst @@ -0,0 +1,22 @@ +tac.aea.crypto package +====================== + +Submodules +---------- + +tac.aea.crypto.base module +-------------------------- + +.. automodule:: tac.aea.crypto.base + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.aea.crypto + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.aea.dialogue.rst b/docs/reference/api/tac.aea.dialogue.rst new file mode 100644 index 00000000..9ed60d3a --- /dev/null +++ b/docs/reference/api/tac.aea.dialogue.rst @@ -0,0 +1,22 @@ +tac.aea.dialogue package +======================== + +Submodules +---------- + +tac.aea.dialogue.base module +---------------------------- + +.. automodule:: tac.aea.dialogue.base + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.aea.dialogue + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.aea.mail.rst b/docs/reference/api/tac.aea.mail.rst new file mode 100644 index 00000000..f24ec578 --- /dev/null +++ b/docs/reference/api/tac.aea.mail.rst @@ -0,0 +1,38 @@ +tac.aea.mail package +==================== + +Submodules +---------- + +tac.aea.mail.base module +------------------------ + +.. automodule:: tac.aea.mail.base + :members: + :undoc-members: + :show-inheritance: + +tac.aea.mail.messages module +---------------------------- + +.. automodule:: tac.aea.mail.messages + :members: + :undoc-members: + :show-inheritance: + +tac.aea.mail.oef module +----------------------- + +.. automodule:: tac.aea.mail.oef + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.aea.mail + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.aea.rst b/docs/reference/api/tac.aea.rst new file mode 100644 index 00000000..1350e936 --- /dev/null +++ b/docs/reference/api/tac.aea.rst @@ -0,0 +1,32 @@ +tac.aea package +=============== + +Subpackages +----------- + +.. toctree:: + + tac.aea.crypto + tac.aea.dialogue + tac.aea.mail + tac.aea.state + +Submodules +---------- + +tac.aea.agent module +-------------------- + +.. automodule:: tac.aea.agent + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.aea + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.aea.state.rst b/docs/reference/api/tac.aea.state.rst new file mode 100644 index 00000000..ccb8ca8f --- /dev/null +++ b/docs/reference/api/tac.aea.state.rst @@ -0,0 +1,22 @@ +tac.aea.state package +===================== + +Submodules +---------- + +tac.aea.state.base module +------------------------- + +.. automodule:: tac.aea.state.base + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.aea.state + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.agents.controller.rst b/docs/reference/api/tac.agents.controller.rst new file mode 100644 index 00000000..a36cb463 --- /dev/null +++ b/docs/reference/api/tac.agents.controller.rst @@ -0,0 +1,22 @@ +tac.agents.controller package +============================= + +Submodules +---------- + +tac.agents.controller.agent module +---------------------------------- + +.. automodule:: tac.agents.controller.agent + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.agents.controller + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.base.rst b/docs/reference/api/tac.agents.participant.base.rst new file mode 100644 index 00000000..90f7b64b --- /dev/null +++ b/docs/reference/api/tac.agents.participant.base.rst @@ -0,0 +1,118 @@ +tac.agents.participant.base package +=================================== + +Submodules +---------- + +tac.agents.participant.base.actions module +------------------------------------------ + +.. automodule:: tac.agents.participant.base.actions + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.dialogues module +-------------------------------------------- + +.. automodule:: tac.agents.participant.base.dialogues + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.game\_instance module +------------------------------------------------- + +.. automodule:: tac.agents.participant.base.game_instance + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.handlers module +------------------------------------------- + +.. automodule:: tac.agents.participant.base.handlers + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.helpers module +------------------------------------------ + +.. automodule:: tac.agents.participant.base.helpers + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.interfaces module +--------------------------------------------- + +.. automodule:: tac.agents.participant.base.interfaces + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.negotiation\_behaviours module +---------------------------------------------------------- + +.. automodule:: tac.agents.participant.base.negotiation_behaviours + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.price\_model module +----------------------------------------------- + +.. automodule:: tac.agents.participant.base.price_model + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.reactions module +-------------------------------------------- + +.. automodule:: tac.agents.participant.base.reactions + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.states module +----------------------------------------- + +.. automodule:: tac.agents.participant.base.states + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.stats\_manager module +------------------------------------------------- + +.. automodule:: tac.agents.participant.base.stats_manager + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.strategy module +------------------------------------------- + +.. automodule:: tac.agents.participant.base.strategy + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.base.transaction\_manager module +------------------------------------------------------- + +.. automodule:: tac.agents.participant.base.transaction_manager + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.agents.participant.base + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.examples.rst b/docs/reference/api/tac.agents.participant.examples.rst new file mode 100644 index 00000000..1e439f9f --- /dev/null +++ b/docs/reference/api/tac.agents.participant.examples.rst @@ -0,0 +1,30 @@ +tac.agents.participant.examples package +======================================= + +Submodules +---------- + +tac.agents.participant.examples.baseline module +----------------------------------------------- + +.. automodule:: tac.agents.participant.examples.baseline + :members: + :undoc-members: + :show-inheritance: + +tac.agents.participant.examples.strategy module +----------------------------------------------- + +.. automodule:: tac.agents.participant.examples.strategy + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.agents.participant.examples + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.rst b/docs/reference/api/tac.agents.participant.rst new file mode 100644 index 00000000..db447ad9 --- /dev/null +++ b/docs/reference/api/tac.agents.participant.rst @@ -0,0 +1,30 @@ +tac.agents.participant package +============================== + +Subpackages +----------- + +.. toctree:: + + tac.agents.participant.base + tac.agents.participant.examples + +Submodules +---------- + +tac.agents.participant.agent module +----------------------------------- + +.. automodule:: tac.agents.participant.agent + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.agents.participant + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.agents.rst b/docs/reference/api/tac.agents.rst index 5ba0eec5..5823815d 100644 --- a/docs/reference/api/tac.agents.rst +++ b/docs/reference/api/tac.agents.rst @@ -6,7 +6,8 @@ Subpackages .. toctree:: - tac.agents.v1 + tac.agents.controller + tac.agents.participant Module contents --------------- diff --git a/docs/reference/api/tac.agents.v1.base.rst b/docs/reference/api/tac.agents.v1.base.rst deleted file mode 100644 index 6e7f277d..00000000 --- a/docs/reference/api/tac.agents.v1.base.rst +++ /dev/null @@ -1,110 +0,0 @@ -tac.agents.v1.base package -========================== - -Submodules ----------- - -tac.agents.v1.base.actions module ---------------------------------- - -.. automodule:: tac.agents.v1.base.actions - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.dialogues module ------------------------------------ - -.. automodule:: tac.agents.v1.base.dialogues - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.game\_instance module ----------------------------------------- - -.. automodule:: tac.agents.v1.base.game_instance - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.handlers module ----------------------------------- - -.. automodule:: tac.agents.v1.base.handlers - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.helpers module ---------------------------------- - -.. automodule:: tac.agents.v1.base.helpers - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.interfaces module ------------------------------------- - -.. automodule:: tac.agents.v1.base.interfaces - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.negotiation\_behaviours module -------------------------------------------------- - -.. automodule:: tac.agents.v1.base.negotiation_behaviours - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.participant\_agent module --------------------------------------------- - -.. automodule:: tac.agents.v1.base.participant_agent - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.reactions module ------------------------------------ - -.. automodule:: tac.agents.v1.base.reactions - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.stats\_manager module ----------------------------------------- - -.. automodule:: tac.agents.v1.base.stats_manager - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.strategy module ----------------------------------- - -.. automodule:: tac.agents.v1.base.strategy - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.base.transaction\_manager module ----------------------------------------------- - -.. automodule:: tac.agents.v1.base.transaction_manager - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.v1.base - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.v1.examples.rst b/docs/reference/api/tac.agents.v1.examples.rst deleted file mode 100644 index 955ad67d..00000000 --- a/docs/reference/api/tac.agents.v1.examples.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.agents.v1.examples package -============================== - -Submodules ----------- - -tac.agents.v1.examples.baseline module --------------------------------------- - -.. automodule:: tac.agents.v1.examples.baseline - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.examples.strategy module --------------------------------------- - -.. automodule:: tac.agents.v1.examples.strategy - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.v1.examples - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.v1.rst b/docs/reference/api/tac.agents.v1.rst deleted file mode 100644 index 3d9baf85..00000000 --- a/docs/reference/api/tac.agents.v1.rst +++ /dev/null @@ -1,38 +0,0 @@ -tac.agents.v1 package -===================== - -Subpackages ------------ - -.. toctree:: - - tac.agents.v1.base - tac.agents.v1.examples - -Submodules ----------- - -tac.agents.v1.agent module --------------------------- - -.. automodule:: tac.agents.v1.agent - :members: - :undoc-members: - :show-inheritance: - -tac.agents.v1.mail module -------------------------- - -.. automodule:: tac.agents.v1.mail - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.v1 - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.dashboards.rst b/docs/reference/api/tac.gui.dashboards.rst index fe6c0387..104380af 100644 --- a/docs/reference/api/tac.gui.dashboards.rst +++ b/docs/reference/api/tac.gui.dashboards.rst @@ -28,6 +28,14 @@ tac.gui.dashboards.controller module :undoc-members: :show-inheritance: +tac.gui.dashboards.helpers module +--------------------------------- + +.. automodule:: tac.gui.dashboards.helpers + :members: + :undoc-members: + :show-inheritance: + tac.gui.dashboards.leaderboard module ------------------------------------- diff --git a/docs/reference/api/tac.helpers.rst b/docs/reference/api/tac.helpers.rst deleted file mode 100644 index 95a0c657..00000000 --- a/docs/reference/api/tac.helpers.rst +++ /dev/null @@ -1,38 +0,0 @@ -tac.helpers package -=================== - -Submodules ----------- - -tac.helpers.crypto module -------------------------- - -.. automodule:: tac.helpers.crypto - :members: - :undoc-members: - :show-inheritance: - -tac.helpers.misc module ------------------------ - -.. automodule:: tac.helpers.misc - :members: - :undoc-members: - :show-inheritance: - -tac.helpers.price\_model module -------------------------------- - -.. automodule:: tac.helpers.price_model - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.helpers - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.platform.game.rst b/docs/reference/api/tac.platform.game.rst new file mode 100644 index 00000000..8794ef93 --- /dev/null +++ b/docs/reference/api/tac.platform.game.rst @@ -0,0 +1,38 @@ +tac.platform.game package +========================= + +Submodules +---------- + +tac.platform.game.base module +----------------------------- + +.. automodule:: tac.platform.game.base + :members: + :undoc-members: + :show-inheritance: + +tac.platform.game.helpers module +-------------------------------- + +.. automodule:: tac.platform.game.helpers + :members: + :undoc-members: + :show-inheritance: + +tac.platform.game.stats module +------------------------------ + +.. automodule:: tac.platform.game.stats + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: tac.platform.game + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/api/tac.platform.rst b/docs/reference/api/tac.platform.rst index 21a6387a..24642943 100644 --- a/docs/reference/api/tac.platform.rst +++ b/docs/reference/api/tac.platform.rst @@ -1,21 +1,20 @@ tac.platform package ==================== -Submodules ----------- +Subpackages +----------- -tac.platform.controller module ------------------------------- +.. toctree:: -.. automodule:: tac.platform.controller - :members: - :undoc-members: - :show-inheritance: + tac.platform.game + +Submodules +---------- -tac.platform.game module ------------------------- +tac.platform.oef\_health\_check module +-------------------------------------- -.. automodule:: tac.platform.game +.. automodule:: tac.platform.oef_health_check :members: :undoc-members: :show-inheritance: @@ -36,14 +35,6 @@ tac.platform.simulation module :undoc-members: :show-inheritance: -tac.platform.stats module -------------------------- - -.. automodule:: tac.platform.stats - :members: - :undoc-members: - :show-inheritance: - Module contents --------------- diff --git a/docs/reference/api/tac.rst b/docs/reference/api/tac.rst index 95943422..affa375f 100644 --- a/docs/reference/api/tac.rst +++ b/docs/reference/api/tac.rst @@ -6,9 +6,9 @@ Subpackages .. toctree:: + tac.aea tac.agents tac.gui - tac.helpers tac.platform Submodules diff --git a/docs/sections/baseline_agent.rst b/docs/sections/baseline_agent.rst index 210a4889..6427a89d 100644 --- a/docs/sections/baseline_agent.rst +++ b/docs/sections/baseline_agent.rst @@ -3,91 +3,91 @@ Baseline Agent v1 ================= -In this section, we describe the baseline agent v1 :class:`~tac.agents.v1.examples.baseline.BaselineAgent`. This agent inherits from :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` and implements the :class:`~tac.agents.v1.examples.strategy.BaselineStrategy`. :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` is a TAC specific implementation of a generic :class:`~tac.agents.v1.agent.Agent`. +In this section, we describe the baseline agent v1 :class:`~tac.agents.participant.examples.baseline.BaselineAgent`. This agent inherits from :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` and implements the :class:`~tac.agents.participant.examples.strategy.BaselineStrategy`. :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` is a TAC specific implementation of a generic :class:`~tac.agents.participant.agent.Agent`. Main Loop and Event Loop ------------------------ -A generic :class:`~tac.agents.v1.agent.Agent` can be started via :meth:`~tac.agents.v1.agent.Agent.start`. This starts the :class:`~tac.agents.v1.mail.MailBox` and a main loop implemented in :meth:`~tac.agents.v1.agent.Agent._run_main_loop`. +A generic :class:`~tac.agents.participant.agent.Agent` can be started via :meth:`~tac.agents.participant.agent.Agent.start`. This starts the :class:`~tac.agents.participant.mail.MailBox` and a main loop implemented in :meth:`~tac.agents.participant.agent.Agent._run_main_loop`. -The mailbox is responsible for handling incoming and outgoing messages. The :class:`~tac.agents.v1.mail.InBox` enqueues incoming messages on an :attr:`~tac.agents.v1.mail.MailBox.in_queue` for later processing, the :class:`~tac.agents.v1.mail.OutBox` picks messages from the :attr:`~tac.agents.v1.mail.MailBox.out_queue` and sends them to the OEF. +The mailbox is responsible for handling incoming and outgoing messages. The :class:`~tac.agents.participant.mail.InBox` enqueues incoming messages on an :attr:`~tac.agents.participant.mail.MailBox.in_queue` for later processing, the :class:`~tac.agents.participant.mail.OutBox` picks messages from the :attr:`~tac.agents.participant.mail.MailBox.out_queue` and sends them to the OEF. Before the execution of the main loop, the framework will call the user's implementation of the -:meth:`~tac.agents.v1.agent.Agent.setup` method, to let the initialization of the resources needed to the agent. +:meth:`~tac.agents.participant.agent.Agent.setup` method, to let the initialization of the resources needed to the agent. Upon exit, the framework will call the user's implementation of the -:meth:`~tac.agents.v1.agent.Agent.teardown` method, to let the release of the initialized resources. +:meth:`~tac.agents.participant.agent.Agent.teardown` method, to let the release of the initialized resources. At any moment, the execution state of the agent can be inspected by reading the -:meth:`~tac.agents.v1.agent.Agent.agent_state` property. +:meth:`~tac.agents.participant.agent.Agent.agent_state` property. -The main loop deals with processing enqueued events/messages. It has the methods :meth:`~tac.agents.v1.agent.Agent.act` and :meth:`~tac.agents.v1.agent.Agent.react` which handle the active and reactive agent behaviours. +The main loop deals with processing enqueued events/messages. It has the methods :meth:`~tac.agents.participant.agent.Agent.act` and :meth:`~tac.agents.participant.agent.Agent.react` which handle the active and reactive agent behaviours. Actions and Reactions --------------------- -The v1 architecture distinguishes between `actions` and `reactions`. Actions are scheduled behaviours by the agent whereas reactions are behaviours which the agent makes in response to individual messages it receives. +The participant architecture distinguishes between `actions` and `reactions`. Actions are scheduled behaviours by the agent whereas reactions are behaviours which the agent makes in response to individual messages it receives. -We split both actions and reactions into three domains: :class:`~tac.agents.v1.base.actions.ControllerActions` and :class:`~tac.agents.v1.base.reactions.ControllerReactions`, :class:`~tac.agents.v1.base.actions.OEFActions` and :class:`~tac.agents.v1.base.reactions.OEFReactions` and :class:`~tac.agents.v1.base.actions.DialogueActions` and :class:`~tac.agents.v1.base.reactions.DialogueReactions`. Dialogues are agent to agent communications and maintained in :class:`~tac.agents.v1.base.dialogues.Dialogues`. +We split both actions and reactions into three domains: :class:`~tac.agents.participant.base.actions.ControllerActions` and :class:`~tac.agents.participant.base.reactions.ControllerReactions`, :class:`~tac.agents.participant.base.actions.OEFActions` and :class:`~tac.agents.participant.base.reactions.OEFReactions` and :class:`~tac.agents.participant.base.actions.DialogueActions` and :class:`~tac.agents.participant.base.reactions.DialogueReactions`. Dialogues are agent to agent communications and maintained in :class:`~tac.agents.participant.base.dialogues.Dialogues`. Actions ^^^^^^^ -The :class:`~tac.agents.v1.base.actions.ControllerActions` class includes the methods: +The :class:`~tac.agents.participant.base.actions.ControllerActions` class includes the methods: -- :meth:`~tac.agents.v1.base.actions.ControllerActions.request_state_update` to request the current agent state. This method is not utilised by :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent`. +- :meth:`~tac.agents.participant.base.actions.ControllerActions.request_state_update` to request the current agent state. This method is not utilised by :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent`. -The :class:`~tac.agents.v1.base.actions.OEFActions` class includes the methods: +The :class:`~tac.agents.participant.base.actions.OEFActions` class includes the methods: -- :meth:`~tac.agents.v1.base.actions.OEFActions.search_for_tac` to search for the active :class:`~tac.platform.controller.ControllerAgent`; -- :meth:`~tac.agents.v1.base.actions.OEFActions.update_services` to :meth:`~tac.agents.v1.base.actions.OEFActions.unregister_service` and :meth:`~tac.agents.v1.base.actions.OEFActions.register_service` on the OEF where the registration behaviour is specified via :class:`~tac.agents.v1.base.strategy.RegisterAs` in the :class:`~tac.agents.v1.base.strategy.Strategy`; -- :meth:`~tac.agents.v1.base.actions.OEFActions.search_services` to search for services on the OEF where the search behaviour is specified via :class:`~tac.agents.v1.base.strategy.SearchFor` in the :class:`~tac.agents.v1.base.strategy.Strategy`. +- :meth:`~tac.agents.participant.base.actions.OEFActions.search_for_tac` to search for the active :class:`~tac.platform.controller.ControllerAgent`; +- :meth:`~tac.agents.participant.base.actions.OEFActions.update_services` to :meth:`~tac.agents.participant.base.actions.OEFActions.unregister_service` and :meth:`~tac.agents.participant.base.actions.OEFActions.register_service` on the OEF where the registration behaviour is specified via :class:`~tac.agents.participant.base.strategy.RegisterAs` in the :class:`~tac.agents.participant.base.strategy.Strategy`; +- :meth:`~tac.agents.participant.base.actions.OEFActions.search_services` to search for services on the OEF where the search behaviour is specified via :class:`~tac.agents.participant.base.strategy.SearchFor` in the :class:`~tac.agents.participant.base.strategy.Strategy`. -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` does not implement any methods in :class:`~tac.agents.v1.base.actions.DialogueActions`. This is because all dialogue related methods are reactions to events. In particular, the search for services (:meth:`~tac.agents.v1.base.actions.OEFActions.search_services`) initiates a chain of reactions leading to a dialogue. +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` does not implement any methods in :class:`~tac.agents.participant.base.actions.DialogueActions`. This is because all dialogue related methods are reactions to events. In particular, the search for services (:meth:`~tac.agents.participant.base.actions.OEFActions.search_services`) initiates a chain of reactions leading to a dialogue. Reactions ^^^^^^^^^ -The :class:`~tac.agents.v1.base.reactions.ControllerReactions` class includes the methods: +The :class:`~tac.agents.participant.base.reactions.ControllerReactions` class includes the methods: -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_start` which handles the 'start' event emitted by the controller; -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_transaction_confirmed` which handles the 'on transaction confirmed' event emitted by the controller; -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_state_update` which handles the 'on state update' event emitted by the controller; -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_cancelled` which handles the cancellation of the competition from the TAC controller; -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_tac_error` which handles the 'on tac error' event emitted by the controller; -- :meth:`~tac.agents.v1.base.reactions.ControllerReactions.on_dialogue_error` which handles the 'dialogue error' event emitted by the controller. +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_start` which handles the 'start' event emitted by the controller; +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_transaction_confirmed` which handles the 'on transaction confirmed' event emitted by the controller; +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_state_update` which handles the 'on state update' event emitted by the controller; +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_cancelled` which handles the cancellation of the competition from the TAC controller; +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_tac_error` which handles the 'on tac error' event emitted by the controller; +- :meth:`~tac.agents.participant.base.reactions.ControllerReactions.on_dialogue_error` which handles the 'dialogue error' event emitted by the controller. -The :class:`~tac.agents.v1.base.reactions.OEFReactions` class includes the methods: +The :class:`~tac.agents.participant.base.reactions.OEFReactions` class includes the methods: -- :meth:`~tac.agents.v1.base.reactions.OEFReactions.on_search_result` which handles the OEF search results; -- :meth:`~tac.agents.v1.base.reactions.OEFReactions.on_oef_error` which handles the OEF error message; -- :meth:`~tac.agents.v1.base.reactions.OEFReactions.on_dialogue_error` which handles the dialogue error message. +- :meth:`~tac.agents.participant.base.reactions.OEFReactions.on_search_result` which handles the OEF search results; +- :meth:`~tac.agents.participant.base.reactions.OEFReactions.on_oef_error` which handles the OEF error message; +- :meth:`~tac.agents.participant.base.reactions.OEFReactions.on_dialogue_error` which handles the dialogue error message. -The :class:`~tac.agents.v1.base.reactions.DialogueReactions` class includes the methods: +The :class:`~tac.agents.participant.base.reactions.DialogueReactions` class includes the methods: -- :meth:`~tac.agents.v1.base.reactions.DialogueReactions.on_new_dialogue` which handles reaction to a new dialogue; -- :meth:`~tac.agents.v1.base.reactions.DialogueReactions.on_existing_dialogue` which handles reaction to an existing dialogue; -- :meth:`~tac.agents.v1.base.reactions.DialogueReactions.on_unidentified_dialogue` which handles reaction to an unidentified dialogue. +- :meth:`~tac.agents.participant.base.reactions.DialogueReactions.on_new_dialogue` which handles reaction to a new dialogue; +- :meth:`~tac.agents.participant.base.reactions.DialogueReactions.on_existing_dialogue` which handles reaction to an existing dialogue; +- :meth:`~tac.agents.participant.base.reactions.DialogueReactions.on_unidentified_dialogue` which handles reaction to an unidentified dialogue. -The message level handling of a negotiation dialogue is performed in :class:`~tac.agents.v1.base.negotiation_behaviours.FIPABehaviour`. +The message level handling of a negotiation dialogue is performed in :class:`~tac.agents.participant.base.negotiation_behaviours.FIPABehaviour`. Handlers -------- -The three types of handlers :class:`~tac.agents.v1.base.handlers.ControllerHandler`, :class:`~tac.agents.v1.base.handlers.OEFHandler` and :class:`~tac.agents.v1.base.handlers.DialogueHandler` inherit from the actions and reactions of their specific type. They are resonsible for handling the implemented behaviours. +The three types of handlers :class:`~tac.agents.participant.base.handlers.ControllerHandler`, :class:`~tac.agents.participant.base.handlers.OEFHandler` and :class:`~tac.agents.participant.base.handlers.DialogueHandler` inherit from the actions and reactions of their specific type. They are resonsible for handling the implemented behaviours. Strategy -------- -The strategy of a :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` has to implement is defined via an interface :class:`~tac.agents.v1.base.strategy.Strategy`. We also provide a sample implementation of a strategy called :class:`~tac.agents.v1.examples.strategy.BaselineStrategy` and utilised by the :class:`~tac.agents.v1.examples.baseline.BaselineAgent`. +The strategy of a :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` has to implement is defined via an interface :class:`~tac.agents.participant.base.strategy.Strategy`. We also provide a sample implementation of a strategy called :class:`~tac.agents.participant.examples.strategy.BaselineStrategy` and utilised by the :class:`~tac.agents.participant.examples.baseline.BaselineAgent`. -The `advanced.py` template can be used to build a :class:`~tac.agents.v1.examples.baseline.BaselineAgent` with a custom strategy. +The `advanced.py` template can be used to build a :class:`~tac.agents.participant.examples.baseline.BaselineAgent` with a custom strategy. We have implemented a basic model of a :class:`~tac.platform.game.WorldState` which can be used and extended to enrich an agents strategy. @@ -95,37 +95,37 @@ We have implemented a basic model of a :class:`~tac.platform.game.WorldState` wh Agent State and World State --------------------------- -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` keeps track of its state via :class:`~tac.platform.game.AgentState` and it can keep track of its environment via :class:`~tac.platform.game.WorldState`. +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` keeps track of its state via :class:`~tac.platform.game.AgentState` and it can keep track of its environment via :class:`~tac.platform.game.WorldState`. Controller Registration ----------------------- -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` initiates the registration with the controller via :meth:`~tac.agents.v1.base.actions.OEFActions.search_for_tac`. +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` initiates the registration with the controller via :meth:`~tac.agents.participant.base.actions.OEFActions.search_for_tac`. Services (/Goods) Registration ------------------------------ -Once the game has started, the :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` can register on the OEF's Service Directory either as a *seller*, as a *buyer* or both. To be specific, the agent can either register the goods it is willing to sell, the goods it is willing to buy or both. The registration options are available in :class:`~tac.agents.v1.base.strategy.RegisterAs`. The registration and unregistering of services is handled via the OEF action :meth:`~tac.agents.v1.base.actions.OEFActions.update_services`. +Once the game has started, the :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` can register on the OEF's Service Directory either as a *seller*, as a *buyer* or both. To be specific, the agent can either register the goods it is willing to sell, the goods it is willing to buy or both. The registration options are available in :class:`~tac.agents.participant.base.strategy.RegisterAs`. The registration and unregistering of services is handled via the OEF action :meth:`~tac.agents.participant.base.actions.OEFActions.update_services`. Services (/Goods) Search ------------------------ -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` can search for the demand and supply registered by other agents on the OEF's Service Directory. The search options are available in :class:`~tac.agents.v1.base.strategy.SearchFor`. The search is handled via the OEF action :meth:`~tac.agents.v1.base.actions.OEFActions.search_services`. +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` can search for the demand and supply registered by other agents on the OEF's Service Directory. The search options are available in :class:`~tac.agents.participant.base.strategy.SearchFor`. The search is handled via the OEF action :meth:`~tac.agents.participant.base.actions.OEFActions.search_services`. Negotiation ------------ -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` implements the FIPA negotiation protocol in :class:`~tac.agents.v1.base.negotiation_behaviours.FIPABehaviour`. A FIPA negotiation starts with a call for proposal (:class:`~oef.messages.CFP`) which contains a :class:`~oef.query.Query` referencing the services which are demanded or supplied by the sending agent. The receiving agent then responds, if it implements the FIPA negotiation protocol, with a suitable proposal (:class:`~oef.messages.Propose`) which contains a list of :class:`~oef.schema.Description` objects (think individual proposals). The first agent responds to the proposal with either a :class:`~oef.messages.Decline` or an :class:`~oef.messages.Accept`. Assuming the agent accepts, it will also send the :class:`~tac.platform.protocol.Transaction` to the :class:`~tac.platform.controller.ControllerAgent`. Finally, the second agent can close the negotiation by responding with a matching :class:`~oef.messages.Accept` and a submission of the :class:`~tac.platform.protocol.Transaction` to the :class:`~tac.platform.controller.ControllerAgent`. The controller only settles a transaction if it receives matching transactions from each one of the two trading parties referenced in the transaction. +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` implements the FIPA negotiation protocol in :class:`~tac.agents.participant.base.negotiation_behaviours.FIPABehaviour`. A FIPA negotiation starts with a call for proposal (:class:`~oef.messages.CFP`) which contains a :class:`~oef.query.Query` referencing the services which are demanded or supplied by the sending agent. The receiving agent then responds, if it implements the FIPA negotiation protocol, with a suitable proposal (:class:`~oef.messages.Propose`) which contains a list of :class:`~oef.schema.Description` objects (think individual proposals). The first agent responds to the proposal with either a :class:`~oef.messages.Decline` or an :class:`~oef.messages.Accept`. Assuming the agent accepts, it will also send the :class:`~tac.platform.protocol.Transaction` to the :class:`~tac.platform.controller.ControllerAgent`. Finally, the second agent can close the negotiation by responding with a matching :class:`~oef.messages.Accept` and a submission of the :class:`~tac.platform.protocol.Transaction` to the :class:`~tac.platform.controller.ControllerAgent`. The controller only settles a transaction if it receives matching transactions from each one of the two trading parties referenced in the transaction. .. mermaid:: ../diagrams/fipa_negotiation_1.mmd :align: center :caption: A successful FIPA negotiation between two agents. -Trade can break down at various stages in the negotiation due to the :class:`~tac.agents.v1.base.strategy.Strategy` employed by the agents: +Trade can break down at various stages in the negotiation due to the :class:`~tac.agents.participant.base.strategy.Strategy` employed by the agents: .. mermaid:: ../diagrams/fipa_negotiation_2.mmd :align: center @@ -143,7 +143,7 @@ Trade can break down at various stages in the negotiation due to the :class:`~ta Agent Speed ----------- -There are two parameters of the :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` which affect the agent speed directly. First, the `agent_timeout` parameter specifies the duration in (fractions of) seconds for which the :class:`~tac.agents.v1.agent.Agent` times out between :meth:`~tac.agents.v1.agent.Agent.act` and :meth:`~tac.agents.v1.agent.Agent.react`. Lowering this parameter increases the speed at which the agent loop spins. Second, the `services_interval` parameter specifies the length of the interval at which the agent updates its services on the OEF and searches for services on the OEF. Lowering this parameter leads to more frequent updates and searches and therefore higher number of negotiations initiated by the agent. +There are two parameters of the :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` which affect the agent speed directly. First, the `agent_timeout` parameter specifies the duration in (fractions of) seconds for which the :class:`~tac.agents.participant.agent.Agent` times out between :meth:`~tac.agents.participant.agent.Agent.act` and :meth:`~tac.agents.participant.agent.Agent.react`. Lowering this parameter increases the speed at which the agent loop spins. Second, the `services_interval` parameter specifies the length of the interval at which the agent updates its services on the OEF and searches for services on the OEF. Lowering this parameter leads to more frequent updates and searches and therefore higher number of negotiations initiated by the agent. -There is a further parameter of the :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` which affects the agent speed indirectly: the `max_reactions` parameter sets an upper bound on the number of messages which are processed by the :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` during each call to :meth:`~tac.agents.v1.agent.Agent.react`. Lowering this number slows down the reactive behaviour of the agent relative to the active behaviour of the agent. +There is a further parameter of the :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` which affects the agent speed indirectly: the `max_reactions` parameter sets an upper bound on the number of messages which are processed by the :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` during each call to :meth:`~tac.agents.participant.agent.Agent.react`. Lowering this number slows down the reactive behaviour of the agent relative to the active behaviour of the agent. diff --git a/docs/sections/develop_agent.rst b/docs/sections/develop_agent.rst index 763c5aae..e57a627a 100644 --- a/docs/sections/develop_agent.rst +++ b/docs/sections/develop_agent.rst @@ -25,9 +25,9 @@ This lets you explore the agent and mailbox interface. Basic: Tuning the Agent's Parameters ------------------------------------ -We have developed a :class:`~tac.agents.v1.examples.baseline.BaselineAgent` and a :class:`~tac.agents.v1.examples.strategy.BaselineStrategy` for you. You can run this agent via the `basic template`_. +We have developed a :class:`~tac.agents.participant.examples.baseline.BaselineAgent` and a :class:`~tac.agents.participant.examples.strategy.BaselineStrategy` for you. You can run this agent via the `basic template`_. -.. _basic template: https://github.com/fetchai/agents-tac/blob/master/templates/v1/basic.py +.. _basic template: https://github.com/fetchai/agents-tac/blob/master/templates/participant/basic.py By tuning the parameters the agent's trading performance can be improved. @@ -55,24 +55,24 @@ Advanced: Changing the Agent's Strategy --------------------------------------- -An intermediate approach to developing your own agent is to adjust the strategy of your agent whilst still relying on our :class:`~tac.agents.v1.examples.baseline.BaselineAgent` implementation. This way you can focus on the decision making component of the agent relevant for the TAC. +An intermediate approach to developing your own agent is to adjust the strategy of your agent whilst still relying on our :class:`~tac.agents.participant.examples.baseline.BaselineAgent` implementation. This way you can focus on the decision making component of the agent relevant for the TAC. -The strategy interface is defined in :class:`~tac.agents.v1.base.strategy.Strategy`. It defines the following methods: +The strategy interface is defined in :class:`~tac.agents.participant.base.strategy.Strategy`. It defines the following methods: -- :meth:`~tac.agents.v1.base.strategy.Strategy.supplied_good_quantities` to specify the list of quantities which are supplied by the agent. -- :meth:`~tac.agents.v1.base.strategy.Strategy.supplied_good_pbks` to specify the set of good public keys which are supplied by the agent. -- :meth:`~tac.agents.v1.base.strategy.Strategy.demanded_good_quantities` to specify the list of quantities which are demanded by the agent. -- :meth:`~tac.agents.v1.base.strategy.Strategy.demanded_good_pbks` to specify the set of good public keys which are demanded by the agent. -- :meth:`~tac.agents.v1.base.strategy.Strategy.get_proposals` to specify the proposals from the agent in the role of seller/buyer. +- :meth:`~tac.agents.participant.base.strategy.Strategy.supplied_good_quantities` to specify the list of quantities which are supplied by the agent. +- :meth:`~tac.agents.participant.base.strategy.Strategy.supplied_good_pbks` to specify the set of good public keys which are supplied by the agent. +- :meth:`~tac.agents.participant.base.strategy.Strategy.demanded_good_quantities` to specify the list of quantities which are demanded by the agent. +- :meth:`~tac.agents.participant.base.strategy.Strategy.demanded_good_pbks` to specify the set of good public keys which are demanded by the agent. +- :meth:`~tac.agents.participant.base.strategy.Strategy.get_proposals` to specify the proposals from the agent in the role of seller/buyer. -The :meth:`~tac.agents.v1.base.strategy.Strategy.supplied_good_quantities` and :meth:`~tac.agents.v1.base.strategy.Strategy.demanded_good_quantities` methods are used to :meth:`~tac.agents.v1.base.game_instance.GameInstance.get_service_description`. The service descriptions thus generated are used for registration on the OEF. Changing these methods therefore directly affects what an agent registers and what services/goods of the agent other agents can therefore find. +The :meth:`~tac.agents.participant.base.strategy.Strategy.supplied_good_quantities` and :meth:`~tac.agents.participant.base.strategy.Strategy.demanded_good_quantities` methods are used to :meth:`~tac.agents.participant.base.game_instance.GameInstance.get_service_description`. The service descriptions thus generated are used for registration on the OEF. Changing these methods therefore directly affects what an agent registers and what services/goods of the agent other agents can therefore find. -The :meth:`~tac.agents.v1.base.strategy.Strategy.supplied_good_pbks` and :meth:`~tac.agents.v1.base.strategy.Strategy.demanded_good_pbks` methods are used to :meth:`~tac.agents.v1.base.game_instance.GameInstance.build_services_query`. The service queries thus generated are used to search for services/goods on the OEF. Changing these methods therefore directly affects what an agent searches on the OEF. +The :meth:`~tac.agents.participant.base.strategy.Strategy.supplied_good_pbks` and :meth:`~tac.agents.participant.base.strategy.Strategy.demanded_good_pbks` methods are used to :meth:`~tac.agents.participant.base.game_instance.GameInstance.build_services_query`. The service queries thus generated are used to search for services/goods on the OEF. Changing these methods therefore directly affects what an agent searches on the OEF. -The :meth:`~tac.agents.v1.base.strategy.Strategy.get_proposals` method is used to generate proposals. Changing this method directly affects what an agent proposes. Of particular relevance here is the price at which an agent proposes to sell\buy the goods referenced in the proposal. +The :meth:`~tac.agents.participant.base.strategy.Strategy.get_proposals` method is used to generate proposals. Changing this method directly affects what an agent proposes. Of particular relevance here is the price at which an agent proposes to sell\buy the goods referenced in the proposal. Expert: Start from Scratch -------------------------- -The :class:`~tac.agents.v1.base.participant_agent.ParticipantAgent` is one possible implementation of an agent campable of competing in the TAC. You can build your own implementation by starting from scratch entirely or building on top of our basic :class:`~tac.agents.v1.agent.Agent`. We are excited to see what you will build! +The :class:`~tac.agents.participant.base.participant_agent.ParticipantAgent` is one possible implementation of an agent campable of competing in the TAC. You can build your own implementation by starting from scratch entirely or building on top of our basic :class:`~tac.agents.participant.agent.Agent`. We are excited to see what you will build! diff --git a/docs/sections/negotiation_guide.ipynb b/docs/sections/negotiation_guide.ipynb index af79234c..9f3b3fcf 100644 --- a/docs/sections/negotiation_guide.ipynb +++ b/docs/sections/negotiation_guide.ipynb @@ -68,8 +68,8 @@ }, "outputs": [], "source": [ - "from tac.agents.v1.examples.baseline import BaselineAgent\n", - "from tac.agents.v1.examples.strategy import BaselineStrategy\n", + "from tac.agents.participant.examples.baseline import BaselineAgent\n", + "from tac.agents.participant.examples.strategy import BaselineStrategy\n", "\n", "strategy = BaselineStrategy()\n", "agent = BaselineAgent(name=\"tac_agent\", oef_addr=\"127.0.0.1\", oef_port=10000, strategy=strategy)" @@ -175,7 +175,7 @@ }, "outputs": [], "source": [ - "from tac.agents.v1.examples.strategy import BaselineStrategy\n", + "from tac.agents.participant.examples.strategy import BaselineStrategy\n", "baseline_strategy = BaselineStrategy()\n", "\n", "current_holdings = [2, 3, 4, 1]\n", @@ -266,7 +266,7 @@ "outputs": [], "source": [ "\n", - "from tac.agents.v1.examples.strategy import BaselineStrategy\n", + "from tac.agents.participant.examples.strategy import BaselineStrategy\n", "baseline_strategy = BaselineStrategy()\n", "\n", "good_pbks = [\"tac_good_0_pbk\", \"tac_good_1_pbk\", \"tac_good_2_pbk\", \"tac_good_3_pbk\"]\n", @@ -351,7 +351,7 @@ }, "outputs": [], "source": [ - "from tac.agents.v1.examples.strategy import BaselineStrategy\n", + "from tac.agents.participant.examples.strategy import BaselineStrategy\n", "baseline_strategy = BaselineStrategy()\n", "proposals = baseline_strategy.get_proposals(\n", " good_pbks=[\"tac_good_0_pbk\", \"tac_good_1_pbk\"],\n", diff --git a/scripts/launch.py b/scripts/launch.py index 53ebf225..eadd70ae 100644 --- a/scripts/launch.py +++ b/scripts/launch.py @@ -29,7 +29,7 @@ import docker -from tac.agents.participant.examples.baseline import main as participant_agent_main +from tac.agents.participant.v1.examples.baseline import main as participant_agent_main CUR_PATH = inspect.getfile(inspect.currentframe()) ROOT_DIR = os.path.join(os.path.dirname(CUR_PATH), "..") diff --git a/tac/aea/agent.py b/tac/aea/agent.py index 8e9ad0ab..c7735a15 100644 --- a/tac/aea/agent.py +++ b/tac/aea/agent.py @@ -25,7 +25,7 @@ from abc import abstractmethod from enum import Enum -from typing import Optional +from typing import Dict, Optional from tac.aea.mail.base import InBox, OutBox, MailBox from tac.aea.crypto.base import Crypto @@ -80,6 +80,9 @@ def __init__(self, name: str, self._liveness = Liveness() self._timeout = timeout + self._handlers = {} + self._behaviours = {} + self.debug = debug self.mailbox = None # type: Optional[MailBox] @@ -109,6 +112,16 @@ def liveness(self) -> Liveness: """Get the liveness.""" return self._liveness + @property + def handlers(self) -> Dict[str, object]: + """Get the registered handlers.""" + return self._behaviours + + @property + def behaviours(self) -> Dict[str, object]: + """Get the registered behaviours.""" + return self._behaviours + @property def agent_state(self) -> AgentState: """ diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 368c4fc5..6c2e49fc 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -36,7 +36,7 @@ from tac.aea.mail.protocol import Envelope from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters -from tac.agents.participant.base.helpers import is_oef_message +from tac.agents.participant.v1.base.helpers import is_oef_message from tac.platform.game.base import GamePhase from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index 07b80d43..c7065a57 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -30,7 +30,7 @@ from typing import List, Dict, Any from tac.aea.crypto.base import Crypto -from tac.agents.participant.base.states import AgentState +from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState from tac.platform.game.helpers import generate_money_endowments, generate_good_endowments, generate_utility_params, \ generate_equilibrium_prices_and_holdings, determine_scaling_factor diff --git a/tac/agents/participant/__init__.py b/tac/agents/participant/__init__.py index 0ba73156..c2189f40 100644 --- a/tac/agents/participant/__init__.py +++ b/tac/agents/participant/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ @@ -19,4 +18,4 @@ # # ------------------------------------------------------------------------------ -"""This module contains the modules of the agent architecture for TAC.""" +"""Contains the TAC participant packages.""" diff --git a/tac/agents/participant/v1/__init__.py b/tac/agents/participant/v1/__init__.py new file mode 100644 index 00000000..5efc6f51 --- /dev/null +++ b/tac/agents/participant/v1/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the modules of the participant agent v1.""" diff --git a/tac/agents/participant/agent.py b/tac/agents/participant/v1/agent.py similarity index 94% rename from tac/agents/participant/agent.py rename to tac/agents/participant/v1/agent.py index 43d54595..f0629e5c 100644 --- a/tac/agents/participant/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -27,10 +27,10 @@ from tac.aea.agent import Agent from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.handlers import DialogueHandler, ControllerHandler, OEFHandler -from tac.agents.participant.base.helpers import is_oef_message, is_controller_message, is_fipa_message -from tac.agents.participant.base.strategy import Strategy +from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.v1.base.handlers import DialogueHandler, ControllerHandler, OEFHandler +from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message +from tac.agents.participant.v1.base.strategy import Strategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/base/__init__.py b/tac/agents/participant/v1/base/__init__.py similarity index 100% rename from tac/agents/participant/base/__init__.py rename to tac/agents/participant/v1/base/__init__.py diff --git a/tac/agents/participant/base/actions.py b/tac/agents/participant/v1/base/actions.py similarity index 98% rename from tac/agents/participant/base/actions.py rename to tac/agents/participant/v1/base/actions.py index cfdcb91d..0f2d1162 100644 --- a/tac/agents/participant/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -34,8 +34,8 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import ByteMessage, OEFMessage -from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface -from tac.agents.participant.base.game_instance import GameInstance +from tac.agents.participant.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface +from tac.agents.participant.v1.base.game_instance import GameInstance from tac.platform.protocol import GetStateUpdate diff --git a/tac/agents/participant/base/dialogues.py b/tac/agents/participant/v1/base/dialogues.py similarity index 100% rename from tac/agents/participant/base/dialogues.py rename to tac/agents/participant/v1/base/dialogues.py diff --git a/tac/agents/participant/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py similarity index 96% rename from tac/agents/participant/base/game_instance.py rename to tac/agents/participant/v1/base/game_instance.py index 30be06d3..7cc2ad7c 100644 --- a/tac/agents/participant/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -28,12 +28,12 @@ from oef.schema import Description from tac.aea.mail.oef import MailStats -from tac.agents.participant.base.dialogues import Dialogues, Dialogue -from tac.agents.participant.base.helpers import build_dict, build_query, get_goods_quantities_description -from tac.agents.participant.base.states import AgentState, WorldState -from tac.agents.participant.base.stats_manager import StatsManager -from tac.agents.participant.base.strategy import Strategy -from tac.agents.participant.base.transaction_manager import TransactionManager +from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue +from tac.agents.participant.v1.base.helpers import build_dict, build_query, get_goods_quantities_description +from tac.agents.participant.v1.base.states import AgentState, WorldState +from tac.agents.participant.v1.base.stats_manager import StatsManager +from tac.agents.participant.v1.base.strategy import Strategy +from tac.agents.participant.v1.base.transaction_manager import TransactionManager from tac.gui.dashboards.agent import AgentDashboard from tac.platform.game.base import GamePhase, GameConfiguration from tac.platform.protocol import GameData, StateUpdate, Transaction diff --git a/tac/agents/participant/base/handlers.py b/tac/agents/participant/v1/base/handlers.py similarity index 96% rename from tac/agents/participant/base/handlers.py rename to tac/agents/participant/v1/base/handlers.py index 2f11d68f..0bfda8ca 100644 --- a/tac/agents/participant/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -34,9 +34,9 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage -from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.reactions import DialogueReactions, ControllerReactions, OEFReactions +from tac.agents.participant.v1.base.actions import DialogueActions, ControllerActions, OEFActions +from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/base/helpers.py b/tac/agents/participant/v1/base/helpers.py similarity index 100% rename from tac/agents/participant/base/helpers.py rename to tac/agents/participant/v1/base/helpers.py diff --git a/tac/agents/participant/base/interfaces.py b/tac/agents/participant/v1/base/interfaces.py similarity index 100% rename from tac/agents/participant/base/interfaces.py rename to tac/agents/participant/v1/base/interfaces.py diff --git a/tac/agents/participant/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py similarity index 98% rename from tac/agents/participant/base/negotiation_behaviours.py rename to tac/agents/participant/v1/base/negotiation_behaviours.py index 86547029..80615a5f 100644 --- a/tac/agents/participant/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -28,10 +28,10 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.messages import FIPAMessage, ByteMessage from tac.aea.mail.protocol import Envelope -from tac.agents.participant.base.dialogues import Dialogue -from tac.agents.participant.base.game_instance import GameInstance -from tac.agents.participant.base.helpers import generate_transaction_id -from tac.agents.participant.base.stats_manager import EndState +from tac.agents.participant.v1.base.dialogues import Dialogue +from tac.agents.participant.v1.base.game_instance import GameInstance +from tac.agents.participant.v1.base.helpers import generate_transaction_id +from tac.agents.participant.v1.base.stats_manager import EndState from tac.platform.protocol import Transaction logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/base/price_model.py b/tac/agents/participant/v1/base/price_model.py similarity index 100% rename from tac/agents/participant/base/price_model.py rename to tac/agents/participant/v1/base/price_model.py diff --git a/tac/agents/participant/base/reactions.py b/tac/agents/participant/v1/base/reactions.py similarity index 97% rename from tac/agents/participant/base/reactions.py rename to tac/agents/participant/v1/base/reactions.py index 12e18ea6..16febef5 100644 --- a/tac/agents/participant/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -34,14 +34,14 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import ByteMessage, FIPAMessage -from tac.agents.participant.base.dialogues import Dialogue -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME -from tac.agents.participant.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ +from tac.agents.participant.v1.base.dialogues import Dialogue +from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.v1.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME +from tac.agents.participant.v1.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ DialogueReactionInterface from tac.aea.mail.protocol import Envelope -from tac.agents.participant.base.negotiation_behaviours import FIPABehaviour -from tac.agents.participant.base.stats_manager import EndState +from tac.agents.participant.v1.base.negotiation_behaviours import FIPABehaviour +from tac.agents.participant.v1.base.stats_manager import EndState from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ GetStateUpdate diff --git a/tac/agents/participant/base/states.py b/tac/agents/participant/v1/base/states.py similarity index 99% rename from tac/agents/participant/base/states.py rename to tac/agents/participant/v1/base/states.py index e4f16827..f6585665 100644 --- a/tac/agents/participant/base/states.py +++ b/tac/agents/participant/v1/base/states.py @@ -32,7 +32,7 @@ from tac.aea.state.base import AgentState as BaseAgentState from tac.aea.state.base import WorldState as BaseWorldState -from tac.agents.participant.base.price_model import GoodPriceModel +from tac.agents.participant.v1.base.price_model import GoodPriceModel from tac.platform.game.helpers import logarithmic_utility from tac.platform.protocol import Transaction diff --git a/tac/agents/participant/base/stats_manager.py b/tac/agents/participant/v1/base/stats_manager.py similarity index 100% rename from tac/agents/participant/base/stats_manager.py rename to tac/agents/participant/v1/base/stats_manager.py diff --git a/tac/agents/participant/base/strategy.py b/tac/agents/participant/v1/base/strategy.py similarity index 98% rename from tac/agents/participant/base/strategy.py rename to tac/agents/participant/v1/base/strategy.py index 8c0ddb98..f3d855cf 100644 --- a/tac/agents/participant/base/strategy.py +++ b/tac/agents/participant/v1/base/strategy.py @@ -26,7 +26,7 @@ from oef.schema import Description -from tac.agents.participant.base.states import WorldState +from tac.agents.participant.v1.base.states import WorldState class RegisterAs(Enum): diff --git a/tac/agents/participant/base/transaction_manager.py b/tac/agents/participant/v1/base/transaction_manager.py similarity index 99% rename from tac/agents/participant/base/transaction_manager.py rename to tac/agents/participant/v1/base/transaction_manager.py index 62f9b009..2c033f83 100644 --- a/tac/agents/participant/base/transaction_manager.py +++ b/tac/agents/participant/v1/base/transaction_manager.py @@ -25,7 +25,7 @@ from collections import defaultdict, deque from typing import Dict, Tuple, Deque -from tac.agents.participant.base.dialogues import DialogueLabel +from tac.agents.participant.v1.base.dialogues import DialogueLabel from tac.platform.protocol import Transaction logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/examples/__init__.py b/tac/agents/participant/v1/examples/__init__.py similarity index 100% rename from tac/agents/participant/examples/__init__.py rename to tac/agents/participant/v1/examples/__init__.py diff --git a/tac/agents/participant/examples/baseline.py b/tac/agents/participant/v1/examples/baseline.py similarity index 96% rename from tac/agents/participant/examples/baseline.py rename to tac/agents/participant/v1/examples/baseline.py index ac03f823..5376cad7 100644 --- a/tac/agents/participant/examples/baseline.py +++ b/tac/agents/participant/v1/examples/baseline.py @@ -25,9 +25,9 @@ import logging from typing import Optional -from tac.agents.participant.agent import ParticipantAgent -from tac.agents.participant.base.strategy import Strategy, RegisterAs, SearchFor -from tac.agents.participant.examples.strategy import BaselineStrategy +from tac.agents.participant.v1.agent import ParticipantAgent +from tac.agents.participant.v1.base.strategy import Strategy, RegisterAs, SearchFor +from tac.agents.participant.v1.examples.strategy import BaselineStrategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/examples/strategy.py b/tac/agents/participant/v1/examples/strategy.py similarity index 95% rename from tac/agents/participant/examples/strategy.py rename to tac/agents/participant/v1/examples/strategy.py index 0d071606..2bb3a85d 100644 --- a/tac/agents/participant/examples/strategy.py +++ b/tac/agents/participant/v1/examples/strategy.py @@ -24,9 +24,9 @@ from oef.schema import Description -from tac.agents.participant.base.helpers import get_goods_quantities_description -from tac.agents.participant.base.states import WorldState -from tac.agents.participant.base.strategy import RegisterAs, SearchFor, Strategy +from tac.agents.participant.v1.base.helpers import get_goods_quantities_description +from tac.agents.participant.v1.base.states import WorldState +from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor, Strategy from tac.platform.game.helpers import marginal_utility diff --git a/tac/agents/participant/v2/__init__.py b/tac/agents/participant/v2/__init__.py new file mode 100644 index 00000000..b64f96ef --- /dev/null +++ b/tac/agents/participant/v2/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the modules of the participant agent v2.""" diff --git a/tac/agents/participant/v2/agent.py b/tac/agents/participant/v2/agent.py new file mode 100644 index 00000000..ff66ab62 --- /dev/null +++ b/tac/agents/participant/v2/agent.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains a base implementation of an agent for TAC.""" + +import logging +import time +from typing import Optional + +from tac.aea.agent import Agent +from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.protocol import Envelope +from tac.gui.dashboards.agent import AgentDashboard + +logger = logging.getLogger(__name__) + + +class ParticipantAgent(Agent): + """The participant agent class implements a base agent for TAC.""" + + def __init__(self, name: str, + oef_addr: str, + oef_port: int, + dashboard: Optional[AgentDashboard] = None, + private_key_pem: Optional[str] = None, + agent_timeout: Optional[float] = 1.0, + debug: bool = False): + """ + Initialize a participant agent. + + :param name: the name of the agent. + :param oef_addr: the TCP/IP address of the OEF node. + :param oef_port: the TCP/IP port of the OEF node. + :param agent_timeout: the time in (fractions of) seconds to time out an agent between act and react. + :param dashboard: a Visdom dashboard to visualize agent statistics during the competition. + :param private_key_pem: the path to a private key in PEM format. + :param debug: if True, run the agent in debug mode. + """ + super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) + self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + + def act(self) -> None: + """ + Perform the agent's actions. + + :return: None + """ + for behaviour in self.behaviours.values(): + behaviour.run() + + def react(self) -> None: + """ + React to incoming events. + + :return: None + """ + counter = 0 + while (not self.mailbox.inbox.empty() and counter < self.max_reactions): + counter += 1 + msg = self.mailbox.inbox.get_nowait() # type: Optional[Envelope] + logger.debug("processing message of protocol={}".format(msg.protocol_id)) + handler = self.handlers[msg.protocol_id] + handler.handle(msg) + + def update(self) -> None: + """ + Update the state of the agent. + + :return: None + """ + self.game_instance.transaction_manager.cleanup_pending_transactions() + + def stop(self) -> None: + """ + Stop the agent. + + :return: None + """ + super().stop() + self.game_instance.stop() + + def start(self, rejoin: bool = False) -> None: + """ + Start the agent. + + :return: None + """ + try: + self.oef_handler.rejoin = rejoin + super().start() + self.oef_handler.rejoin = False + return + except Exception as e: + logger.exception(e) + logger.debug("Stopping the agent...") + self.stop() + + # here only if an error occurred + logger.debug("Trying to rejoin in 5 seconds...") + time.sleep(5.0) + self.start(rejoin=True) + + def setup(self) -> None: + """Set up the agent.""" + + def teardown(self) -> None: + """Tear down the agent.""" diff --git a/tac/agents/participant/v2/skills/__init__.py b/tac/agents/participant/v2/skills/__init__.py new file mode 100644 index 00000000..0fcc1fe3 --- /dev/null +++ b/tac/agents/participant/v2/skills/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the skills of the agent.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/AUTHORS.rst b/tac/agents/participant/v2/skills/fipa_negotiation/AUTHORS.rst new file mode 100644 index 00000000..d6d743f4 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/AUTHORS.rst @@ -0,0 +1,7 @@ +Authors +======= + +This is the official list of Fetch.AI authors for copyright purposes. + +* Marco Favorito `@MarcoFavorito `_ +* David Minarsch `@DavidMinarsch `_ diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/__init__.py b/tac/agents/participant/v2/skills/fipa_negotiation/__init__.py new file mode 100644 index 00000000..d3d36356 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the fipa_negotiation skill.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/__version__.py b/tac/agents/participant/v2/skills/fipa_negotiation/__version__.py new file mode 100644 index 00000000..ef251f39 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/__version__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Specifies the version of the skill.""" + +__title__ = 'fipa_negotiation_v1' +__description__ = 'FIPA Negotiation' +__version__ = '0.0.1' +__author__ = 'Fetch.AI Limited' +__license__ = 'Apache 2.0' +__copyright__ = '2019 Fetch.AI Limited' diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/__init__.py b/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/__init__.py new file mode 100644 index 00000000..d3d36356 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the fipa_negotiation skill.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/base.py b/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/base.py new file mode 100644 index 00000000..e0dd17d2 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/behaviours/base.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Classes defining behaviours.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/handlers/__init__.py b/tac/agents/participant/v2/skills/fipa_negotiation/handlers/__init__.py new file mode 100644 index 00000000..d3d36356 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/handlers/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the fipa_negotiation skill.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/handlers/base.py b/tac/agents/participant/v2/skills/fipa_negotiation/handlers/base.py new file mode 100644 index 00000000..6261fb1f --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/handlers/base.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Classes defining handlers.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/serialization/__init__.py b/tac/agents/participant/v2/skills/fipa_negotiation/serialization/__init__.py new file mode 100644 index 00000000..d3d36356 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/serialization/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the fipa_negotiation skill.""" diff --git a/tac/agents/participant/v2/skills/fipa_negotiation/serialization/base.py b/tac/agents/participant/v2/skills/fipa_negotiation/serialization/base.py new file mode 100644 index 00000000..a2b12132 --- /dev/null +++ b/tac/agents/participant/v2/skills/fipa_negotiation/serialization/base.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Classes defining serialization.""" diff --git a/tac/gui/dashboards/agent.py b/tac/gui/dashboards/agent.py index f0523cc4..751ff2a5 100644 --- a/tac/gui/dashboards/agent.py +++ b/tac/gui/dashboards/agent.py @@ -26,8 +26,8 @@ import numpy as np -from tac.agents.participant.base.states import AgentState -from tac.agents.participant.base.stats_manager import StatsManager +from tac.agents.participant.v1.base.states import AgentState +from tac.agents.participant.v1.base.stats_manager import StatsManager from tac.gui.dashboards.base import Dashboard from tac.gui.dashboards.helpers import generate_html_table_from_dict, escape_html from tac.platform.protocol import Transaction diff --git a/tac/platform/game/stats.py b/tac/platform/game/stats.py index 96e374ca..90c40f6b 100644 --- a/tac/platform/game/stats.py +++ b/tac/platform/game/stats.py @@ -29,7 +29,7 @@ from tac.aea.crypto.base import Crypto from tac.agents.controller.base.states import Game -from tac.agents.participant.base.states import AgentState +from tac.agents.participant.v1.base.states import AgentState matplotlib.use('agg') diff --git a/tac/platform/simulation.py b/tac/platform/simulation.py index 0563d8ef..50407b62 100644 --- a/tac/platform/simulation.py +++ b/tac/platform/simulation.py @@ -45,8 +45,8 @@ from tac.agents.controller.agent import main as controller_main from tac.agents.controller.base.tac_parameters import TACParameters -from tac.agents.participant.base.strategy import RegisterAs, SearchFor -from tac.agents.participant.examples.baseline import main as baseline_main +from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor +from tac.agents.participant.v1.examples.baseline import main as baseline_main from tac.platform.game.helpers import make_agent_name logger = logging.getLogger(__name__) diff --git a/tests/test_game.py b/tests/test_game.py index e2cd1b41..cfa45ee2 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -23,7 +23,7 @@ from tac.aea.crypto.base import Crypto from tac.agents.controller.base.states import GameInitialization, Game -from tac.agents.participant.base.states import AgentState +from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState from tac.platform.protocol import Transaction diff --git a/tests/test_simulation.py b/tests/test_simulation.py index a1561f4b..314cb281 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -29,9 +29,9 @@ from tac.agents.controller.agent import ControllerAgent from tac.agents.controller.base.states import Game from tac.agents.controller.base.tac_parameters import TACParameters -from tac.agents.participant.base.strategy import SearchFor, RegisterAs -from tac.agents.participant.examples.baseline import BaselineAgent as BaselineAgentV1 -from tac.agents.participant.examples.strategy import BaselineStrategy +from tac.agents.participant.v1.base.strategy import SearchFor, RegisterAs +from tac.agents.participant.v1.examples.baseline import BaselineAgent as BaselineAgentV1 +from tac.agents.participant.v1.examples.strategy import BaselineStrategy def _init_baseline_agents(n: int, version: str, oef_addr: str, oef_port: int) -> List[BaselineAgentV1]: From ddc943403dc910a3611cfb0624abc9c00d3946b8 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Tue, 20 Aug 2019 16:24:57 +0100 Subject: [PATCH 051/107] Add oef local mail box --- tac/aea/mail/oef.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 17a744cc..f5e4ca0a 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -31,7 +31,7 @@ from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES from oef.proxy import OEFNetworkProxy - +from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy from tac.aea.mail.protocol import Envelope from tac.aea.mail.base import Connection, MailBox from tac.aea.mail.messages import OEFMessage, FIPAMessage, ByteMessage @@ -435,3 +435,17 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): :param oef_port: the oef port. """ super().__init__(OEFNetworkProxy(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop())) + + +class OEFLocalMailBox(OEFMailBox): + """The OEF local mail box.""" + + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): + """ + Initialize. + + :param public_key: the public key of the agent. + :param oef_addr: the OEF address. + :param oef_port: the oef port. + """ + super().__init__(OEFLocalProxy(public_key, LocalNode(), loop=asyncio.new_event_loop())) From 8942097deb4637ac020ea1b41bacb8d61348787d Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 20 Aug 2019 17:27:19 +0100 Subject: [PATCH 052/107] fix envelope message from 'Message' object to raw bytes. The change implied to rewrite many parts regarding the messaging system. Also, all the protocols are forced to use the serialization, since now the inbox and outbox only accept 'Envelope' with only raw bytes. --- sandbox/playground.py | 7 +- tac/aea/agent.py | 6 +- tac/aea/mail/base.py | 5 +- tac/aea/mail/messages.py | 12 +- tac/aea/mail/oef.py | 95 +++++----- tac/aea/mail/protocol.py | 40 +++-- .../protocols/{simple => default}/__init__.py | 0 .../{simple => default}/serialization.py | 2 +- tac/aea/protocols/fipa/fipa.proto | 10 +- tac/aea/protocols/fipa/fipa_pb2.py | 92 ++++++++-- tac/aea/protocols/fipa/serialization.py | 57 ++++-- tac/aea/protocols/oef/serialization.py | 162 ++++++++++++++++++ tac/agents/controller/base/actions.py | 5 +- tac/agents/controller/base/handlers.py | 44 +++-- tac/agents/participant/agent.py | 23 +-- tac/agents/participant/base/actions.py | 46 +++-- tac/agents/participant/base/dialogues.py | 78 ++++----- tac/agents/participant/base/handlers.py | 39 +++-- tac/agents/participant/base/helpers.py | 29 +--- tac/agents/participant/base/interfaces.py | 13 +- .../base/negotiation_behaviours.py | 131 +++++++------- tac/agents/participant/base/reactions.py | 102 +++++------ tests/test_mail.py | 68 +++++--- tests/test_messages/test_base.py | 30 ++-- tests/test_messages/test_fipa.py | 42 ++++- tests/test_messages/test_simple.py | 29 ++-- 26 files changed, 762 insertions(+), 405 deletions(-) rename tac/aea/protocols/{simple => default}/__init__.py (100%) rename tac/aea/protocols/{simple => default}/serialization.py (97%) create mode 100644 tac/aea/protocols/oef/serialization.py diff --git a/sandbox/playground.py b/sandbox/playground.py index 664a700d..6b6f867b 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -34,6 +34,7 @@ from tac.aea.mail.messages import FIPAMessage from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.dialogues import Dialogue from tac.agents.participant.examples.baseline import BaselineAgent from tac.agents.participant.examples.strategy import BaselineStrategy @@ -134,9 +135,9 @@ def launch_oef(): cfp = FIPAMessage(message_id=starting_message_id, dialogue_id=dialogue.dialogue_label.dialogue_id, target=starting_message_target, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) - envelope = Envelope(to=agent_two.crypto.public_key, sender=agent_one.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=cfp) - dialogue.outgoing_extend([envelope]) + cfp_bytes = FIPASerializer().encode(cfp) + envelope = Envelope(to=agent_two.crypto.public_key, sender=agent_one.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp_bytes) + dialogue.outgoing_extend([cfp]) agent_one.outbox.out_queue.put(envelope) # Send the messages in the outbox diff --git a/tac/aea/agent.py b/tac/aea/agent.py index 8e9ad0ab..d0a814e2 100644 --- a/tac/aea/agent.py +++ b/tac/aea/agent.py @@ -25,7 +25,9 @@ from abc import abstractmethod from enum import Enum -from typing import Optional +from typing import Optional, Dict + +from tac.aea.mail.messages import ProtocolId from tac.aea.mail.base import InBox, OutBox, MailBox from tac.aea.crypto.base import Crypto @@ -83,6 +85,8 @@ def __init__(self, name: str, self.debug = debug self.mailbox = None # type: Optional[MailBox] + self._handlers = {} # type: Dict[ProtocolId, Handler] + self.behaviours = {} @property def inbox(self) -> Optional[InBox]: diff --git a/tac/aea/mail/base.py b/tac/aea/mail/base.py index 333bf86e..11339548 100644 --- a/tac/aea/mail/base.py +++ b/tac/aea/mail/base.py @@ -26,8 +26,7 @@ from typing import Optional from tac.aea.mail.messages import Address, ProtocolId, Message -from tac.aea.mail.protocol import Envelope - +from tac.aea.mail.protocol import Envelope, Encoder logger = logging.getLogger(__name__) @@ -107,7 +106,7 @@ def put(self, item: Envelope) -> None: self._queue.put(item) def put_message(self, to: Optional[Address] = None, sender: Optional[Address] = None, - protocol_id: Optional[ProtocolId] = None, message: Optional[Message] = None) -> None: + protocol_id: Optional[ProtocolId] = None, message: bytes = b"") -> None: """ Put a message in the outbox. diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index 3de5d502..f094ddf4 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -36,8 +36,6 @@ class Message: """This class implements a message.""" - protocol_id = "default" - def __init__(self, body: Optional[Dict] = None, **kwargs): """ @@ -117,6 +115,9 @@ class Type(Enum): DIALOGUE_ERROR = "dialogue_error" SEARCH_RESULT = "search_result" + def __str__(self): + return self.value + def __init__(self, oef_type: Optional[Type] = None, **kwargs): """ @@ -124,7 +125,7 @@ def __init__(self, oef_type: Optional[Type] = None, :param oef_type: the type of OEF message. """ - super().__init__(type=oef_type, **kwargs) + super().__init__(type=str(oef_type), **kwargs) def check_consistency(self) -> bool: """Check that the data is consistent.""" @@ -203,7 +204,7 @@ def __init__(self, message_id: Optional[int] = None, class SimpleMessage(Message): """The Simple message class.""" - protocol_id = "simple" + protocol_id = "default" class Type(Enum): """Simple message types.""" @@ -235,6 +236,9 @@ class Performative(Enum): MATCH_ACCEPT = "match_accept" DECLINE = "decline" + def __str__(self): + return self.value + def __init__(self, message_id: Optional[int] = None, dialogue_id: Optional[int] = None, target: Optional[int] = None, diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 511991c8..82854797 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -30,11 +30,14 @@ from oef.core import OEFProxy from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES from oef.proxy import OEFNetworkProxy +from oef.schema import Description - -from tac.aea.mail.protocol import Envelope from tac.aea.mail.base import Connection, MailBox -from tac.aea.mail.messages import OEFMessage, FIPAMessage, ByteMessage +from tac.aea.mail.messages import OEFMessage, FIPAMessage, SimpleMessage +from tac.aea.mail.protocol import Envelope, DefaultJSONSerializer +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer +from tac.aea.protocols.oef.serialization import OEFSerializer logger = logging.getLogger(__name__) @@ -117,10 +120,7 @@ def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) :param content: the bytes content. :return: None """ - msg = ByteMessage(message_id=msg_id, - dialogue_id=dialogue_id, - content=content) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=ByteMessage.protocol_id, message=msg) + envelope = Envelope.decode(content) self.in_queue.put(envelope) def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: @@ -139,7 +139,8 @@ def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: target=target, performative=FIPAMessage.Performative.CFP, query=query) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: @@ -158,7 +159,8 @@ def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, pr target=target, performative=FIPAMessage.Performative.PROPOSE, proposal=proposals) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: @@ -176,7 +178,8 @@ def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> dialogue_id=dialogue_id, target=target, performative=performative) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: @@ -193,7 +196,8 @@ def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> dialogue_id=dialogue_id, target=target, performative=FIPAMessage.Performative.DECLINE) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_search_result(self, search_id: int, agents: List[str]) -> None: @@ -206,7 +210,8 @@ def on_search_result(self, search_id: int, agents: List[str]) -> None: """ self.mail_stats.search_end(search_id, len(agents)) msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=agents) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: @@ -220,7 +225,8 @@ def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: msg = OEFMessage(oef_type=OEFMessage.Type.OEF_ERROR, id=answer_id, operation=operation) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: @@ -236,7 +242,8 @@ def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> No id=answer_id, dialogue_id=dialogue_id, origin=origin) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) self.in_queue.put(envelope) def send(self, envelope: Envelope) -> None: @@ -251,15 +258,9 @@ def send(self, envelope: Envelope) -> None: elif envelope.protocol_id == "fipa": self.send_fipa_message(envelope) elif envelope.protocol_id == "bytes": - message_id = envelope.message.get("id") - dialogue_id = envelope.message.get("dialogue_id") - content = envelope.message.get("content") - self.send_message(message_id, dialogue_id, envelope.to, content) + self.send_bytes(envelope) elif envelope.protocol_id == "default": - message_id = envelope.message.get("id") - dialogue_id = 0 - content = envelope.message.get("content") - self.send_message(message_id, dialogue_id, envelope.to, content) + self.send_default(envelope) else: raise ValueError("Cannot send message.") @@ -270,31 +271,32 @@ def send_oef_message(self, envelope: Envelope) -> None: :param envelope: the message. :return: None """ - oef_type = envelope.message.get("type") + oef_message = OEFSerializer().decode(envelope.message) + oef_type = OEFMessage.Type(oef_message.get("type")) if oef_type == OEFMessage.Type.REGISTER_SERVICE: - id = envelope.message.get("id") - service_description = envelope.message.get("service_description") - service_id = envelope.message.get("service_id") + id = oef_message.get("id") + service_description = oef_message.get("service_description") + service_id = oef_message.get("service_id") self.register_service(id, service_description, service_id) elif oef_type == OEFMessage.Type.REGISTER_AGENT: - id = envelope.message.get("id") - agent_description = envelope.message.get("agent_description") + id = oef_message.get("id") + agent_description = oef_message.get("agent_description") self.register_agent(id, agent_description) elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - id = envelope.message.get("id") - service_description = envelope.message.get("service_description") - service_id = envelope.message.get("service_id") + id = oef_message.get("id") + service_description = oef_message.get("service_description") + service_id = oef_message.get("service_id") self.unregister_service(id, service_description, service_id) elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - id = envelope.message.get("id") + id = oef_message.get("id") self.unregister_agent(id) elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - id = envelope.message.get("id") - query = envelope.message.get("query") + id = oef_message.get("id") + query = oef_message.get("query") self.search_agents(id, query) elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - id = envelope.message.get("id") - query = envelope.message.get("query") + id = oef_message.get("id") + query = oef_message.get("query") self.mail_stats.search_start(id) self.search_services(id, query) else: @@ -307,16 +309,17 @@ def send_fipa_message(self, envelope: Envelope) -> None: :param envelope: the message. :return: None """ - id = envelope.message.get("id") - dialogue_id = envelope.message.get("dialogue_id") + fipa_message = FIPASerializer().decode(envelope.message) + id = fipa_message.get("id") + dialogue_id = fipa_message.get("dialogue_id") destination = envelope.to - target = envelope.message.get("target") - performative = envelope.message.get("performative") + target = fipa_message.get("target") + performative = FIPAMessage.Performative(fipa_message.get("performative")) if performative == FIPAMessage.Performative.CFP: - query = envelope.message.get("query") + query = fipa_message.get("query") self.send_cfp(id, dialogue_id, destination, target, query) elif performative == FIPAMessage.Performative.PROPOSE: - proposal = envelope.message.get("proposal") + proposal = fipa_message.get("proposal") self.send_propose(id, dialogue_id, destination, target, proposal) elif performative == FIPAMessage.Performative.ACCEPT: self.send_accept(id, dialogue_id, destination, target) @@ -327,6 +330,14 @@ def send_fipa_message(self, envelope: Envelope) -> None: else: raise ValueError("OEF FIPA message not recognized.") + def send_bytes(self, envelope: Envelope): + """Send a 'bytes' message.""" + self.send_message(0, 0, envelope.to, envelope.encode()) + + def send_default(self, envelope: Envelope): + """Send a 'default' message.""" + self.send_message(0, 0, envelope.to, envelope.encode()) + class OEFConnection(Connection): """The OEFConnection connects the to the mailbox.""" diff --git a/tac/aea/mail/protocol.py b/tac/aea/mail/protocol.py index 678f458c..1a36dc33 100644 --- a/tac/aea/mail/protocol.py +++ b/tac/aea/mail/protocol.py @@ -113,7 +113,7 @@ class Envelope: def __init__(self, to: Optional[Address] = None, sender: Optional[Address] = None, protocol_id: Optional[ProtocolId] = None, - message: Optional[Message] = None): + message: Optional[bytes] = b""): """ Initialize a Message object. @@ -126,6 +126,12 @@ def __init__(self, to: Optional[Address] = None, self._sender = sender self._protocol_id = protocol_id self._message = message + assert type(self._to) == str or self._to is None + try: + if self._to is not None and type(self._to) == str: + self._to.encode('utf-8') + except Exception: + assert False @property def to(self) -> Address: @@ -158,12 +164,12 @@ def protocol_id(self, protocol_id: ProtocolId) -> None: self._protocol_id = protocol_id @property - def message(self) -> Message: + def message(self) -> bytes: """Get the Message.""" return self._message @message.setter - def message(self, message: Message) -> None: + def message(self, message: bytes) -> None: """Set the message.""" self._message = message @@ -175,41 +181,41 @@ def __eq__(self, other): and self.protocol_id == other.protocol_id \ and self._message == other._message - def encode(self, encoder: Encoder) -> bytes: + def encode(self) -> bytes: """ Encode the envelope. - :param encoder: the encoder to use in order to encode the body of the envelope. :return: the encoded envelope. """ envelope = self envelope_pb = base_pb2.Envelope() - envelope_pb.to = envelope.to - envelope_pb.sender = envelope.sender - envelope_pb.protocol_id = envelope.protocol_id - message_bytes = encoder.encode(envelope.message) - envelope_pb.message = message_bytes + if envelope.to is not None: + envelope_pb.to = envelope.to + if envelope.sender is not None: + envelope_pb.sender = envelope.sender + if envelope.protocol_id is not None: + envelope_pb.protocol_id = envelope.protocol_id + if envelope.message is not None: + envelope_pb.message = envelope.message envelope_bytes = envelope_pb.SerializeToString() return envelope_bytes @classmethod - def decode(cls, envelope_bytes: bytes, decoder: Decoder) -> 'Envelope': + def decode(cls, envelope_bytes: bytes) -> 'Envelope': """ Decode the envelope. :param envelope_bytes: the bytes to be decoded. - :param decoder: the decoder to use in order to decode the body of the envelope. :return: the decoded envelope. """ envelope_pb = base_pb2.Envelope() envelope_pb.ParseFromString(envelope_bytes) - to = envelope_pb.to - sender = envelope_pb.sender - protocol_id = envelope_pb.protocol_id - message_bytes = envelope_pb.message - message = decoder.decode(message_bytes) + to = envelope_pb.to if envelope_pb.to else None + sender = envelope_pb.sender if envelope_pb.sender else None + protocol_id = envelope_pb.protocol_id if envelope_pb.protocol_id else None + message = envelope_pb.message if envelope_pb.message else None envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) return envelope diff --git a/tac/aea/protocols/simple/__init__.py b/tac/aea/protocols/default/__init__.py similarity index 100% rename from tac/aea/protocols/simple/__init__.py rename to tac/aea/protocols/default/__init__.py diff --git a/tac/aea/protocols/simple/serialization.py b/tac/aea/protocols/default/serialization.py similarity index 97% rename from tac/aea/protocols/simple/serialization.py rename to tac/aea/protocols/default/serialization.py index 514852f7..c49834a4 100644 --- a/tac/aea/protocols/simple/serialization.py +++ b/tac/aea/protocols/default/serialization.py @@ -28,7 +28,7 @@ class SimpleSerializer(Serializer): - """Serialization for the 'simple' protocol.""" + """Serialization for the 'default' protocol.""" def encode(self, msg: Message) -> bytes: """Encode a 'Simple' message into bytes.""" diff --git a/tac/aea/protocols/fipa/fipa.proto b/tac/aea/protocols/fipa/fipa.proto index 7624bd8a..3f06c8f1 100644 --- a/tac/aea/protocols/fipa/fipa.proto +++ b/tac/aea/protocols/fipa/fipa.proto @@ -7,10 +7,16 @@ import "google/protobuf/struct.proto"; message FIPAMessage{ message CFP{ - google.protobuf.Struct query = 1; + message Nothing { + } + oneof query{ + google.protobuf.Struct json = 1; + bytes bytes = 2; + Nothing nothing = 3; + } } message Propose{ - repeated google.protobuf.Struct proposal = 4; + repeated bytes proposal = 4; } message Accept{} message MatchAccept{} diff --git a/tac/aea/protocols/fipa/fipa_pb2.py b/tac/aea/protocols/fipa/fipa_pb2.py index a589dc96..2251b1fa 100644 --- a/tac/aea/protocols/fipa/fipa_pb2.py +++ b/tac/aea/protocols/fipa/fipa_pb2.py @@ -20,7 +20,7 @@ name='fipa.proto', package='fetch.aea.fipa', syntax='proto3', - serialized_pb=_b('\n\nfipa.proto\x12\x0e\x66\x65tch.aea.fipa\x1a\x1cgoogle/protobuf/struct.proto\"\xf6\x03\n\x0b\x46IPAMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12\x13\n\x0b\x64ialogue_id\x18\x02 \x01(\x05\x12\x0e\n\x06target\x18\x03 \x01(\x05\x12.\n\x03\x63\x66p\x18\x04 \x01(\x0b\x32\x1f.fetch.aea.fipa.FIPAMessage.CFPH\x00\x12\x36\n\x07propose\x18\x05 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.ProposeH\x00\x12\x34\n\x06\x61\x63\x63\x65pt\x18\x06 \x01(\x0b\x32\".fetch.aea.fipa.FIPAMessage.AcceptH\x00\x12?\n\x0cmatch_accept\x18\x07 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.MatchAcceptH\x00\x12\x36\n\x07\x64\x65\x63line\x18\x08 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.DeclineH\x00\x1a-\n\x03\x43\x46P\x12&\n\x05query\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x1a\x34\n\x07Propose\x12)\n\x08proposal\x18\x04 \x03(\x0b\x32\x17.google.protobuf.Struct\x1a\x08\n\x06\x41\x63\x63\x65pt\x1a\r\n\x0bMatchAccept\x1a\t\n\x07\x44\x65\x63lineB\x0e\n\x0cperformativeb\x06proto3') + serialized_pb=_b('\n\nfipa.proto\x12\x0e\x66\x65tch.aea.fipa\x1a\x1cgoogle/protobuf/struct.proto\"\xc0\x04\n\x0b\x46IPAMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12\x13\n\x0b\x64ialogue_id\x18\x02 \x01(\x05\x12\x0e\n\x06target\x18\x03 \x01(\x05\x12.\n\x03\x63\x66p\x18\x04 \x01(\x0b\x32\x1f.fetch.aea.fipa.FIPAMessage.CFPH\x00\x12\x36\n\x07propose\x18\x05 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.ProposeH\x00\x12\x34\n\x06\x61\x63\x63\x65pt\x18\x06 \x01(\x0b\x32\".fetch.aea.fipa.FIPAMessage.AcceptH\x00\x12?\n\x0cmatch_accept\x18\x07 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.MatchAcceptH\x00\x12\x36\n\x07\x64\x65\x63line\x18\x08 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.DeclineH\x00\x1a\x8f\x01\n\x03\x43\x46P\x12\'\n\x04json\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12\x0f\n\x05\x62ytes\x18\x02 \x01(\x0cH\x00\x12:\n\x07nothing\x18\x03 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.CFP.NothingH\x00\x1a\t\n\x07NothingB\x07\n\x05query\x1a\x1b\n\x07Propose\x12\x10\n\x08proposal\x18\x04 \x03(\x0c\x1a\x08\n\x06\x41\x63\x63\x65pt\x1a\r\n\x0bMatchAccept\x1a\t\n\x07\x44\x65\x63lineB\x0e\n\x0cperformativeb\x06proto3') , dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -28,6 +28,29 @@ +_FIPAMESSAGE_CFP_NOTHING = _descriptor.Descriptor( + name='Nothing', + full_name='fetch.aea.fipa.FIPAMessage.CFP.Nothing', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=538, + serialized_end=547, +) + _FIPAMESSAGE_CFP = _descriptor.Descriptor( name='CFP', full_name='fetch.aea.fipa.FIPAMessage.CFP', @@ -36,16 +59,30 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name='query', full_name='fetch.aea.fipa.FIPAMessage.CFP.query', index=0, + name='json', full_name='fetch.aea.fipa.FIPAMessage.CFP.json', index=0, number=1, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), + _descriptor.FieldDescriptor( + name='bytes', full_name='fetch.aea.fipa.FIPAMessage.CFP.bytes', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='nothing', full_name='fetch.aea.fipa.FIPAMessage.CFP.nothing', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), ], extensions=[ ], - nested_types=[], + nested_types=[_FIPAMESSAGE_CFP_NOTHING, ], enum_types=[ ], options=None, @@ -53,9 +90,12 @@ syntax='proto3', extension_ranges=[], oneofs=[ + _descriptor.OneofDescriptor( + name='query', full_name='fetch.aea.fipa.FIPAMessage.CFP.query', + index=0, containing_type=None, fields=[]), ], - serialized_start=412, - serialized_end=457, + serialized_start=413, + serialized_end=556, ) _FIPAMESSAGE_PROPOSE = _descriptor.Descriptor( @@ -67,7 +107,7 @@ fields=[ _descriptor.FieldDescriptor( name='proposal', full_name='fetch.aea.fipa.FIPAMessage.Propose.proposal', index=0, - number=4, type=11, cpp_type=10, label=3, + number=4, type=12, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, @@ -84,8 +124,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=459, - serialized_end=511, + serialized_start=558, + serialized_end=585, ) _FIPAMESSAGE_ACCEPT = _descriptor.Descriptor( @@ -107,8 +147,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=513, - serialized_end=521, + serialized_start=587, + serialized_end=595, ) _FIPAMESSAGE_MATCHACCEPT = _descriptor.Descriptor( @@ -130,8 +170,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=523, - serialized_end=536, + serialized_start=597, + serialized_end=610, ) _FIPAMESSAGE_DECLINE = _descriptor.Descriptor( @@ -153,8 +193,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=538, - serialized_end=547, + serialized_start=612, + serialized_end=621, ) _FIPAMESSAGE = _descriptor.Descriptor( @@ -236,12 +276,22 @@ index=0, containing_type=None, fields=[]), ], serialized_start=61, - serialized_end=563, + serialized_end=637, ) -_FIPAMESSAGE_CFP.fields_by_name['query'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_FIPAMESSAGE_CFP_NOTHING.containing_type = _FIPAMESSAGE_CFP +_FIPAMESSAGE_CFP.fields_by_name['json'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_FIPAMESSAGE_CFP.fields_by_name['nothing'].message_type = _FIPAMESSAGE_CFP_NOTHING _FIPAMESSAGE_CFP.containing_type = _FIPAMESSAGE -_FIPAMESSAGE_PROPOSE.fields_by_name['proposal'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( + _FIPAMESSAGE_CFP.fields_by_name['json']) +_FIPAMESSAGE_CFP.fields_by_name['json'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] +_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( + _FIPAMESSAGE_CFP.fields_by_name['bytes']) +_FIPAMESSAGE_CFP.fields_by_name['bytes'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] +_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( + _FIPAMESSAGE_CFP.fields_by_name['nothing']) +_FIPAMESSAGE_CFP.fields_by_name['nothing'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] _FIPAMESSAGE_PROPOSE.containing_type = _FIPAMESSAGE _FIPAMESSAGE_ACCEPT.containing_type = _FIPAMESSAGE _FIPAMESSAGE_MATCHACCEPT.containing_type = _FIPAMESSAGE @@ -271,6 +321,13 @@ FIPAMessage = _reflection.GeneratedProtocolMessageType('FIPAMessage', (_message.Message,), dict( CFP = _reflection.GeneratedProtocolMessageType('CFP', (_message.Message,), dict( + + Nothing = _reflection.GeneratedProtocolMessageType('Nothing', (_message.Message,), dict( + DESCRIPTOR = _FIPAMESSAGE_CFP_NOTHING, + __module__ = 'fipa_pb2' + # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.CFP.Nothing) + )) + , DESCRIPTOR = _FIPAMESSAGE_CFP, __module__ = 'fipa_pb2' # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.CFP) @@ -310,6 +367,7 @@ )) _sym_db.RegisterMessage(FIPAMessage) _sym_db.RegisterMessage(FIPAMessage.CFP) +_sym_db.RegisterMessage(FIPAMessage.CFP.Nothing) _sym_db.RegisterMessage(FIPAMessage.Propose) _sym_db.RegisterMessage(FIPAMessage.Accept) _sym_db.RegisterMessage(FIPAMessage.MatchAccept) diff --git a/tac/aea/protocols/fipa/serialization.py b/tac/aea/protocols/fipa/serialization.py index d0fa0839..502725d5 100644 --- a/tac/aea/protocols/fipa/serialization.py +++ b/tac/aea/protocols/fipa/serialization.py @@ -19,6 +19,8 @@ # ------------------------------------------------------------------------------ """Serialization for the FIPA protocol.""" +from google.protobuf.struct_pb2 import Struct +from oef import data_model_instance_pb2 from oef.schema import Description from tac.aea.mail.messages import Message, FIPAMessage @@ -39,19 +41,22 @@ def encode(self, msg: Message) -> bytes: performative_id = msg.get("performative").value if performative_id == "cfp": performative = fipa_pb2.FIPAMessage.CFP() - performative.query.update(msg.get("query")) + query = msg.get("query") + if query is None: + nothing = fipa_pb2.FIPAMessage.CFP.Nothing() + performative.nothing.CopyFrom(nothing) + elif type(query) == dict: + performative.json.update(query) + elif type(query) == bytes: + performative.bytes = query + else: + raise ValueError("Query type not supported: {}".format(type(query))) fipa_msg.cfp.CopyFrom(performative) elif performative_id == "propose": performative = fipa_pb2.FIPAMessage.Propose() proposal = msg.get("proposal") - for p in proposal: - p: Description - new_struct = performative.proposal.add() - new_struct.update(p.values) - - for bytes_p in performative.proposal: - print(bytes_p) - + p_array_bytes = [p.to_pb().SerializeToString() for p in proposal] + performative.proposal.extend(p_array_bytes) fipa_msg.propose.CopyFrom(performative) elif performative_id == "accept": @@ -78,21 +83,37 @@ def decode(self, obj: bytes) -> Message: target = fipa_pb.target performative = fipa_pb.WhichOneof("performative") + performative_id = FIPAMessage.Performative(str(performative)) performative_content = dict() - if performative == "cfp": - query = dict(fipa_pb.cfp.query) + if performative_id == FIPAMessage.Performative.CFP: + query_type = fipa_pb.cfp.WhichOneof("query") + if query_type == "nothing": + query = None + elif query_type == "json": + query_pb = Struct() + query_pb.update(fipa_pb.cfp.json) + query = dict(query_pb) + elif query_type == "bytes": + query = fipa_pb.cfp.bytes + else: + raise ValueError("Query type not recognized.") + performative_content["query"] = query - elif performative == "propose": - proposal = [dict(p) for p in fipa_pb.propose.proposal] - performative_content["proposal"] = proposal - elif performative == "accept": + elif performative_id == FIPAMessage.Performative.PROPOSE: + descriptions = [] + for p_bytes in fipa_pb.propose.proposal: + p_pb = data_model_instance_pb2.Instance() + p_pb.ParseFromString(p_bytes) + descriptions.append(Description.from_pb(p_pb)) + performative_content["proposal"] = descriptions + elif performative_id == FIPAMessage.Performative.ACCEPT: pass - elif performative == "match_accept": + elif performative_id == FIPAMessage.Performative.MATCH_ACCEPT: pass - elif performative == "decline": + elif performative_id == FIPAMessage.Performative.DECLINE: pass else: - raise ValueError("Performative not valid.") + raise ValueError("Performative not valid: {}.".format(performative)) return FIPAMessage(message_id=message_id, dialogue_id=dialogue_id, target=target, performative=performative, **performative_content) diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py new file mode 100644 index 00000000..5cfeb28f --- /dev/null +++ b/tac/aea/protocols/oef/serialization.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Serialization for the FIPA protocol.""" +import copy +import json +import pickle +import pprint +from typing import Dict + +import base58 +from google.protobuf import json_format +from google.protobuf.json_format import MessageToJson +from oef import query_pb2, dap_interface_pb2 +from oef.messages import OEFErrorOperation, CFP_TYPES +from oef.query import Query, ConstraintExpr, And, Or, Not, Constraint +from oef.schema import Description, DataModel + +from tac.aea.mail.messages import Message, OEFMessage +from tac.aea.mail.protocol import Serializer + + +class ConstraintWrapper: + """Make the constraint object pickable.""" + + def __init__(self, c: ConstraintExpr): + """Wrap the constraint object.""" + self.c = c + + def to_json(self) -> Dict: + result = {} + if isinstance(self.c, And) or isinstance(self.c, Or): + wraps = [ConstraintWrapper(subc).to_json() for subc in self.c.constraints] + key = "and" if isinstance(self.c, And) else "or" if isinstance(self.c, Or) else "" + result[key] = wraps + elif isinstance(self.c, Not): + wrap = ConstraintWrapper(self.c.constraint).to_json() + result["not"] = wrap + elif isinstance(self.c, Constraint): + result["attribute_name"] = self.c.attribute_name + result["constraint_type"] = base58.b58encode(pickle.dumps(self.c.constraint)).decode("utf-8") + else: + raise ValueError("ConstraintExpr not recognized.") + + return result + + @classmethod + def from_json(cls, d: Dict): + if "and" in d: + return And([ConstraintWrapper.from_json(subc) for subc in d["and"]]) + elif "or" in d: + return Or([ConstraintWrapper.from_json(subc) for subc in d["or"]]) + elif "not" in d: + return Not(ConstraintWrapper.from_json(d["not"])) + else: + constraint_type = pickle.loads(base58.b58decode(d["constraint_type"])) + return Constraint(d["attribute_name"], constraint_type) + + +class QueryWrapper: + """Make the query object pickable.""" + + def __init__(self, q: Query): + self.q = q + + def to_json(self) -> Dict: + result = {} + if self.q.model: + result["data_model"] = base58.b58encode(self.q.model.to_pb().SerializeToString()).decode("utf-8") + else: + result["data_model"] = None + result["constraints"] = [ConstraintWrapper(c).to_json() for c in self.q.constraints] + return result + + @classmethod + def from_json(self, d: Dict): + if d["data_model"]: + data_model_pb = dap_interface_pb2.ValueMessage.DataModel() + data_model_pb.ParseFromString(base58.b58decode(d["data_model"])) + data_model = DataModel.from_pb(data_model_pb) + else: + data_model = None + + constraints = [ConstraintWrapper.from_json(c) for c in d["constraints"]] + return Query(constraints, data_model) + + +class OEFSerializer(Serializer): + """Serialization for the FIPA protocol.""" + + def encode(self, msg: Message) -> bytes: + oef_type = OEFMessage.Type(msg.get("type")) + new_body = copy.copy(msg.body) + + if oef_type in {OEFMessage.Type.REGISTER_SERVICE, OEFMessage.Type.UNREGISTER_SERVICE}: + service_description = msg.body["service_description"] # type: Description + service_description_pb = service_description.to_pb() + service_description_json = MessageToJson(service_description_pb) + new_body["service_description"] = service_description_json + elif oef_type in {OEFMessage.Type.REGISTER_AGENT}: + agent_description = msg.body["agent_description"] # type: Description + agent_description_pb = agent_description.to_pb() + agent_description_json = MessageToJson(agent_description_pb) + new_body["agent_description"] = agent_description_json + elif oef_type in {OEFMessage.Type.SEARCH_SERVICES, OEFMessage.Type.SEARCH_AGENTS}: + query = msg.body["query"] # type: Query + new_body["query"] = QueryWrapper(query).to_json() + elif oef_type in {OEFMessage.Type.SEARCH_RESULT}: + # we need this cast because the "agents" field might contains + # the Protobuf type "RepeatedScalarContainer", which is not JSON serializable. + new_body["agents"] = list(msg.body["agents"]) + elif oef_type in {OEFMessage.Type.OEF_ERROR}: + operation = msg.body["operation"] + new_body["operation"] = str(operation) + + oef_message_bytes = json.dumps(new_body).encode("utf-8") + return oef_message_bytes + + def decode(self, obj: bytes) -> Message: + json_msg = json.loads(obj.decode("utf-8")) + oef_type = OEFMessage.Type(json_msg["type"]) + new_body = copy.copy(json_msg) + new_body["type"] = oef_type + + if oef_type in {OEFMessage.Type.REGISTER_SERVICE, OEFMessage.Type.UNREGISTER_SERVICE}: + service_description_json = json_msg["service_description"] + service_description_pb = json_format.Parse(service_description_json, query_pb2.Query.Instance()) + service_description = Description.from_pb(service_description_pb) + new_body["service_description"] = service_description + elif oef_type in {OEFMessage.Type.REGISTER_AGENT}: + agent_description_json = json_msg["agent_description"] + agent_description_pb = json_format.Parse(agent_description_json, query_pb2.Query.Instance()) + agent_description = Description.from_pb(agent_description_pb) + new_body["agent_description"] = agent_description + elif oef_type in {OEFMessage.Type.SEARCH_SERVICES, OEFMessage.Type.SEARCH_AGENTS}: + query = QueryWrapper.from_json(json_msg["query"]) + new_body["query"] = query + elif oef_type in {OEFMessage.Type.SEARCH_RESULT}: + new_body["agents"] = list(json_msg["agents"]) + elif oef_type in {OEFMessage.Type.OEF_ERROR}: + operation = json_msg["operation"] + new_body["operation"] = OEFErrorOperation(operation) + + oef_message = Message(body=new_body) + return oef_message diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index fb32e5f0..38d90080 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -32,6 +32,8 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage +from tac.aea.mail.protocol import DefaultJSONSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.interfaces import OEFActionInterface logger = logging.getLogger(__name__) @@ -69,4 +71,5 @@ def register_tac(self) -> None: desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) out = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out) + out_bytes = OEFSerializer().encode(out) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out_bytes) diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 2027f26e..246790ac 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -43,8 +43,10 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import ByteMessage, OEFMessage +from tac.aea.mail.messages import OEFMessage, SimpleMessage from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.actions import OEFActions from tac.agents.controller.base.helpers import generate_good_pbk_to_name from tac.agents.controller.base.reactions import OEFReactions @@ -166,7 +168,7 @@ def handle(self, tx: Transaction) -> Optional[Response]: If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. - :param request: the transaction request. + :param tx: the transaction request. :return: an Error response if an error occurred, else None (no response to send back). """ logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, tx)) @@ -212,10 +214,14 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: # send the transaction confirmation. tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) - self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=1, dialogue_id=1, content=tx_confirmation.serialize())) + + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tx_confirmation.serialize()) + msg_bytes = SimpleSerializer().encode(msg) + self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tx_confirmation.serialize()) + msg_bytes = SimpleSerializer().encode(msg) + self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) @@ -289,15 +295,14 @@ def handle_agent_message(self, envelope: Envelope) -> Response: :param envelope: the request to handle :return: the response. """ - assert envelope.protocol_id == "bytes" - msg_id = envelope.message.get("id") - dialogue_id = envelope.message.get("dialogue_id") + assert envelope.protocol_id == "default" + msg = SimpleSerializer().decode(envelope.message) sender = envelope.sender - logger.debug("[{}] on_message: msg_id={}, dialogue_id={}, origin={}" .format(self.controller_agent.name, envelope.message.get("id"), dialogue_id, sender)) - request = self.decode(envelope.message.get("content"), sender) + logger.debug("[{}] on_message: origin={}" .format(self.controller_agent.name, sender)) + request = self.decode(msg.get("content"), sender) handle_request = self.handlers.get(type(request), None) # type: RequestHandler if handle_request is None: - logger.debug("[{}]: Unknown message: msg_id={}, dialogue_id={}, origin={}".format(self.controller_agent.name, msg_id, dialogue_id, sender)) + logger.debug("[{}]: Unknown message from {}".format(self.controller_agent.name, sender)) return Error(envelope.sender, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) try: return handle_request(request) @@ -440,15 +445,17 @@ def _send_game_data_to_agents(self) -> None: .format(self.agent_name, public_key, str(game_data_response))) self.game_data_per_participant[public_key] = game_data_response - self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=1, dialogue_id=1, content=game_data_response.serialize())) + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=game_data_response.serialize()) + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) def notify_competition_cancelled(self): """Notify agents that the TAC is cancelled.""" logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.agent_name)) for agent_pbk in self.registered_agents: - self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=1, dialogue_id=1, content=Cancelled(agent_pbk, self.crypto).serialize())) + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=Cancelled(agent_pbk, self.crypto).serialize()) + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: @@ -498,9 +505,10 @@ def handle_oef_message(self, envelope: Envelope) -> None: """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(envelope))) assert envelope.protocol_id == "oef" - if envelope.message.get("type") == OEFMessage.Type.OEF_ERROR: + msg = OEFSerializer().decode(envelope.message) + if msg.get("type") == OEFMessage.Type.OEF_ERROR: self.on_oef_error(envelope) - elif envelope.message.get("type") == OEFMessage.Type.DIALOGUE_ERROR: + elif msg.get("type") == OEFMessage.Type.DIALOGUE_ERROR: self.on_dialogue_error(envelope) else: logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) diff --git a/tac/agents/participant/agent.py b/tac/agents/participant/agent.py index 43d54595..18fd6148 100644 --- a/tac/agents/participant/agent.py +++ b/tac/agents/participant/agent.py @@ -27,6 +27,7 @@ from tac.aea.agent import Agent from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.game_instance import GameInstance, GamePhase from tac.agents.participant.base.handlers import DialogueHandler, ControllerHandler, OEFHandler from tac.agents.participant.base.helpers import is_oef_message, is_controller_message, is_fipa_message @@ -103,17 +104,19 @@ def react(self) -> None: counter = 0 while (not self.mailbox.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.mailbox.inbox.get_nowait() # type: Optional[Envelope] - logger.debug("processing message of protocol={}".format(msg.protocol_id)) - if msg is not None: - if is_oef_message(msg): - self.oef_handler.handle_oef_message(msg) - elif is_controller_message(msg, self.crypto): - self.controller_handler.handle_controller_message(msg) - elif is_fipa_message(msg): - self.dialogue_handler.handle_dialogue_message(msg) + envelope = self.mailbox.inbox.get_nowait() # type: Optional[Envelope] + logger.debug("processing message of protocol={}".format(envelope.protocol_id)) + + if envelope is not None: + if is_oef_message(envelope): + self.oef_handler.handle_oef_message(envelope) + elif is_controller_message(envelope): + self.controller_handler.handle_controller_message(envelope) + elif is_fipa_message(envelope): + fipa_message = FIPASerializer().decode(envelope.message) # type: # Message + self.dialogue_handler.handle_dialogue_message(fipa_message, envelope.sender) else: - logger.warning("Message type not recognized: sender={}".format(msg.sender)) + logger.warning("Message type not recognized: sender={}".format(envelope.sender)) def update(self) -> None: """ diff --git a/tac/agents/participant/base/actions.py b/tac/agents/participant/base/actions.py index cfdcb91d..645606e5 100644 --- a/tac/agents/participant/base/actions.py +++ b/tac/agents/participant/base/actions.py @@ -33,10 +33,12 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import ByteMessage, OEFMessage -from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, DialogueActionInterface +from tac.aea.mail.messages import OEFMessage, SimpleMessage +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.base.game_instance import GameInstance - +from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, \ + DialogueActionInterface from tac.platform.protocol import GetStateUpdate logger = logging.getLogger(__name__) @@ -69,10 +71,11 @@ def request_state_update(self) -> None: :return: None """ - msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - message = ByteMessage(message_id=0, dialogue_id=0, content=msg) + tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) + msg_bytes = SimpleSerializer().encode(msg) self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - protocol_id=ByteMessage.protocol_id, message=message) + protocol_id=SimpleMessage.protocol_id, message=msg_bytes) class OEFActions(OEFActionInterface): @@ -110,7 +113,8 @@ def search_for_tac(self) -> None: self.game_instance.search.ids_for_tac.add(search_id) message = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message) + message_bytes = OEFSerializer().encode(message) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message_bytes) def update_services(self) -> None: """ @@ -128,9 +132,13 @@ def unregister_service(self) -> None: :return: None """ if self.game_instance.goods_demanded_description is not None: - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="")) + msg = OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="") + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.goods_supplied_description is not None: - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="")) + msg = OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="") + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) def register_service(self) -> None: """ @@ -147,14 +155,16 @@ def register_service(self) -> None: logger.debug("[{}]: Updating service directory as seller with goods supplied.".format(self.agent_name)) goods_supplied_description = self.game_instance.get_service_description(is_supply=True) self.game_instance.goods_supplied_description = goods_supplied_description - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, - message=OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="")) + msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="") + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, - message=OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="")) + msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="") + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) def search_services(self) -> None: """ @@ -176,7 +186,10 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for sellers which match the demand of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_sellers.add(search_id) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) + + msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.strategy.is_searching_for_buyers: query = self.game_instance.build_services_query(is_searching_for_sellers=False) if query is None: @@ -186,7 +199,10 @@ def search_services(self) -> None: logger.debug("[{}]: Searching for buyers which match the supply of the agent.".format(self.agent_name)) search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_buyers.add(search_id) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query)) + + msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) class DialogueActions(DialogueActionInterface): diff --git a/tac/agents/participant/base/dialogues.py b/tac/agents/participant/base/dialogues.py index c2f2f706..8ed50680 100644 --- a/tac/agents/participant/base/dialogues.py +++ b/tac/agents/participant/base/dialogues.py @@ -32,7 +32,7 @@ from tac.aea.dialogue.base import DialogueLabel from tac.aea.dialogue.base import Dialogue as BaseDialogue from tac.aea.dialogue.base import Dialogues as BaseDialogues -from tac.aea.mail.messages import FIPAMessage +from tac.aea.mail.messages import FIPAMessage, Message, Address from tac.aea.mail.protocol import Envelope @@ -58,9 +58,9 @@ def __init__(self, dialogue_label: DialogueLabel, is_seller: bool) -> None: BaseDialogue.__init__(self, dialogue_label=dialogue_label) self._is_seller = is_seller self._role = 'seller' if is_seller else 'buyer' - self._outgoing_messages = [] # type: List[Envelope] - self._outgoing_messages_controller = [] # type: List[Envelope] - self._incoming_messages = [] # type: List[Envelope] + self._outgoing_messages = [] # type: List[Message] + self._outgoing_messages_controller = [] # type: List[Message] + self._incoming_messages = [] # type: List[Message] @property def dialogue_label(self) -> DialogueLabel: @@ -77,7 +77,7 @@ def role(self) -> str: """Get role of agent in dialogue.""" return self._role - def outgoing_extend(self, messages: List[Envelope]) -> None: + def outgoing_extend(self, messages: List[Message]) -> None: """ Extend the list of messages which keeps track of outgoing messages. @@ -86,7 +86,7 @@ def outgoing_extend(self, messages: List[Envelope]) -> None: """ self._outgoing_messages.extend(messages) - def incoming_extend(self, messages: List[Envelope]) -> None: + def incoming_extend(self, messages: List[Message]) -> None: """ Extend the list of messages which keeps track of incoming messages. @@ -101,8 +101,8 @@ def is_expecting_propose(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.CFP + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_initial_accept(self) -> bool: @@ -111,8 +111,8 @@ def is_expecting_initial_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.PROPOSE + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_matching_accept(self) -> bool: @@ -121,8 +121,8 @@ def is_expecting_matching_accept(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.ACCEPT + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT return result def is_expecting_cfp_decline(self) -> bool: @@ -131,8 +131,8 @@ def is_expecting_cfp_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.CFP + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.CFP return result def is_expecting_propose_decline(self) -> bool: @@ -141,8 +141,8 @@ def is_expecting_propose_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.PROPOSE + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.PROPOSE return result def is_expecting_accept_decline(self) -> bool: @@ -151,8 +151,8 @@ def is_expecting_accept_decline(self) -> bool: :return: True if yes, False otherwise. """ - last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Envelope] - result = (last_sent_message is not None) and last_sent_message.protocol_id == "fipa" and last_sent_message.message.get("performative") == FIPAMessage.Performative.ACCEPT + last_sent_message = self._outgoing_messages[-1] if len(self._outgoing_messages) > 0 else None # type: Optional[Message] + result = (last_sent_message is not None) and last_sent_message.get("performative") == FIPAMessage.Performative.ACCEPT return result @@ -179,7 +179,7 @@ def dialogues_as_buyer(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent acts as a buyer.""" return self._dialogues_as_buyer - def is_permitted_for_new_dialogue(self, envelope: Envelope, known_pbks: List[str]) -> bool: + def is_permitted_for_new_dialogue(self, message: Message, known_pbks: List[str], sender: Address) -> bool: """ Check whether an agent message is permitted for a new dialogue. @@ -188,37 +188,34 @@ def is_permitted_for_new_dialogue(self, envelope: Envelope, known_pbks: List[str - have the correct msg id and message target, and - be from a known public key. - :param envelope: the agent message + :param message: the agent message :param known_pbks: the list of known public keys :return: a boolean indicating whether the message is permitted for a new dialogue """ - protocol = envelope.protocol_id - msg_id = envelope.message.get("id") - target = envelope.message.get("target") - performative = envelope.message.get("performative") + msg_id = message.get("id") + target = message.get("target") + performative = message.get("performative") - result = protocol == "fipa"\ - and performative == FIPAMessage.Performative.CFP \ + result = performative == FIPAMessage.Performative.CFP \ and msg_id == STARTING_MESSAGE_ID\ and target == STARTING_MESSAGE_TARGET \ - and (envelope.sender in known_pbks) + and (sender in known_pbks) return result - def is_belonging_to_registered_dialogue(self, envelope: Envelope, agent_pbk: str) -> bool: + def is_belonging_to_registered_dialogue(self, message: Message, agent_pbk: str, sender: Address) -> bool: """ Check whether an agent message is part of a registered dialogue. - :param envelope: the agent message + :param message: the agent message :param agent_pbk: the public key of the agent :return: boolean indicating whether the message belongs to a registered dialogue """ - assert envelope.protocol_id == "fipa" - dialogue_id = envelope.message.get("dialogue_id") - opponent = envelope.sender - target = envelope.message.get("target") - performative = envelope.message.get("performative") + dialogue_id = message.get("dialogue_id") + opponent = sender + target = message.get("target") + performative = message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) result = False @@ -245,20 +242,19 @@ def is_belonging_to_registered_dialogue(self, envelope: Envelope, agent_pbk: str result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, envelope: Envelope, agent_pbk: str) -> Dialogue: + def get_dialogue(self, message: Message, sender: Address, agent_pbk: str) -> Dialogue: """ Retrieve dialogue. - :param envelope: the agent message + :param message: the agent message :param agent_pbk: the public key of the agent :return: the dialogue """ - assert envelope.protocol_id == "fipa" - dialogue_id = envelope.message.get("dialogue_id") - opponent = envelope.sender - target = envelope.message.get("target") - performative = envelope.message.get("performative") + dialogue_id = message.get("dialogue_id") + opponent = sender + target = message.get("target") + performative = message.get("performative") self_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, agent_pbk) other_initiated_dialogue_label = DialogueLabel(dialogue_id, opponent, opponent) if performative == FIPAMessage.Performative.PROPOSE and target == 1 and self_initiated_dialogue_label in self.dialogues: diff --git a/tac/agents/participant/base/handlers.py b/tac/agents/participant/base/handlers.py index 2f11d68f..7814f026 100644 --- a/tac/agents/participant/base/handlers.py +++ b/tac/agents/participant/base/handlers.py @@ -29,11 +29,13 @@ import logging from typing import Any -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.protocol import Envelope, DefaultJSONSerializer from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage +from tac.aea.mail.messages import OEFMessage, Message, Address +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.base.game_instance import GameInstance, GamePhase from tac.agents.participant.base.reactions import DialogueReactions, ControllerReactions, OEFReactions @@ -60,23 +62,24 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, envelope: Envelope) -> None: + def handle_dialogue_message(self, message: Message, sender: Address) -> None: """ Handle messages from the other agents. The agents expect a response. - :param envelope: the agent message + :param message: the agent message. + :param sender: the sender. :return: None """ - logger.debug("Handling Dialogue message. type={}".format(type(envelope))) - if self.dialogues.is_belonging_to_registered_dialogue(envelope, self.crypto.public_key): - self.on_existing_dialogue(envelope) - elif self.dialogues.is_permitted_for_new_dialogue(envelope, self.game_instance.game_configuration.agent_pbks): - self.on_new_dialogue(envelope) + logger.debug("Handling Dialogue message. type={}".format(type(message.get("performative")))) + if self.dialogues.is_belonging_to_registered_dialogue(message, self.crypto.public_key, sender): + self.on_existing_dialogue(message, sender) + elif self.dialogues.is_permitted_for_new_dialogue(message, self.game_instance.game_configuration.agent_pbks, sender): + self.on_new_dialogue(message, sender) else: - self.on_unidentified_dialogue(envelope) + self.on_unidentified_dialogue(message, sender) class ControllerHandler(ControllerActions, ControllerReactions): @@ -105,8 +108,9 @@ def handle_controller_message(self, envelope: Envelope) -> None: :return: None """ - assert envelope.protocol_id == "bytes" - response = Response.from_pb(envelope.message.get("content"), envelope.sender, self.crypto) + assert envelope.protocol_id == "default" + msg = SimpleSerializer().decode(envelope.message) + response = Response.from_pb(msg.get("content"), envelope.sender, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: if envelope.sender != self.game_instance.controller_pbk: @@ -163,12 +167,13 @@ def handle_oef_message(self, envelope: Envelope) -> None: """ logger.debug("[{}]: Handling OEF message. type={}".format(self.agent_name, type(envelope))) assert envelope.protocol_id == "oef" - oef_type = envelope.message.get("type") + oef_message = OEFSerializer().decode(envelope.message) + oef_type = oef_message.get("type") if oef_type == OEFMessage.Type.SEARCH_RESULT: - self.on_search_result(envelope) + self.on_search_result(oef_message) elif oef_type == OEFMessage.Type.OEF_ERROR: - self.on_oef_error(envelope) + self.on_oef_error(oef_message) elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: - self.on_dialogue_error(envelope) + self.on_dialogue_error(oef_message) else: - logger.warning("[{}]: OEF Message type not recognized.".format(self.agent_name)) + logger.warning("[{}]: OEF Message type not recognized: {}.".format(self.agent_name, oef_type)) diff --git a/tac/agents/participant/base/helpers.py b/tac/agents/participant/base/helpers.py index 8dbe2d69..2cd26bfd 100644 --- a/tac/agents/participant/base/helpers.py +++ b/tac/agents/participant/base/helpers.py @@ -25,11 +25,8 @@ from oef.query import Query, Constraint, GtEq, Or from oef.schema import AttributeSchema, DataModel, Description -from tac.aea.crypto.base import Crypto from tac.aea.dialogue.base import DialogueLabel -from tac.aea.mail.messages import OEFMessage, FIPAMessage from tac.aea.mail.protocol import Envelope -from tac.platform.protocol import Response logger = logging.getLogger(__name__) @@ -46,40 +43,22 @@ def is_oef_message(envelope: Envelope) -> bool: :param envelope: the message :return: boolean indicating whether or not the message is from the oef """ - return envelope.protocol_id == "oef" and envelope.message.get("type") in set(OEFMessage.Type) + return envelope.protocol_id == "oef" -def is_controller_message(envelope: Envelope, crypto: Crypto) -> bool: +def is_controller_message(envelope: Envelope) -> bool: """ Check whether a message is from the controller. :param envelope: the message - :param crypto: the crypto of the agent :return: boolean indicating whether or not the message is from the controller """ - if not envelope.protocol_id == "bytes": - return False - - try: - byte_content = envelope.message.get("content") - sender_pbk = envelope.sender - Response.from_pb(byte_content, sender_pbk, crypto) - except Exception as e: - logger.debug("Not a Controller message: {}".format(str(e))) - # try: - # byte_content = msg.get("content") - # sender_pbk = msg.sender - # Response.from_pb(byte_content, sender_pbk, crypto) - # except: - # pass - return False - - return True + return envelope.protocol_id == "default" def is_fipa_message(envelope: Envelope) -> bool: """Chcek whether a message is a FIPA message.""" - return envelope.protocol_id == "fipa" and envelope.message.get("performative") in set(FIPAMessage.Performative) + return envelope.protocol_id == "fipa" def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: diff --git a/tac/agents/participant/base/interfaces.py b/tac/agents/participant/base/interfaces.py index 707ae78a..654a4ae3 100644 --- a/tac/agents/participant/base/interfaces.py +++ b/tac/agents/participant/base/interfaces.py @@ -22,6 +22,7 @@ from abc import abstractmethod +from tac.aea.mail.messages import Message from tac.aea.mail.protocol import Envelope from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData @@ -180,7 +181,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, envelope: Envelope) -> None: + def on_new_dialogue(self, message: Message) -> None: """ React to a message for a new dialogue. @@ -188,27 +189,27 @@ def on_new_dialogue(self, envelope: Envelope) -> None: - the protocol rules that messages must follow; - how the agent behaves in this dialogue. - :param envelope: the agent message + :param message: the agent message :return: None """ @abstractmethod - def on_existing_dialogue(self, envelope: Envelope) -> None: + def on_existing_dialogue(self, message: Message) -> None: """ React to a message of an existing dialogue. - :param envelope: the agent message + :param message: the agent message :return: None """ @abstractmethod - def on_unidentified_dialogue(self, envelope: Envelope) -> None: + def on_unidentified_dialogue(self, message: Message) -> None: """ React to a message of an unidentified dialogue. - :param envelope: the agent message + :param message: the agent message :return: None """ diff --git a/tac/agents/participant/base/negotiation_behaviours.py b/tac/agents/participant/base/negotiation_behaviours.py index 86547029..c59d1525 100644 --- a/tac/agents/participant/base/negotiation_behaviours.py +++ b/tac/agents/participant/base/negotiation_behaviours.py @@ -26,8 +26,10 @@ from typing import List from tac.aea.crypto.base import Crypto -from tac.aea.mail.messages import FIPAMessage, ByteMessage +from tac.aea.mail.messages import FIPAMessage, Message, SimpleMessage from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.dialogues import Dialogue from tac.agents.participant.base.game_instance import GameInstance from tac.agents.participant.base.helpers import generate_transaction_id @@ -71,17 +73,16 @@ def agent_name(self) -> str: """Get the agent name.""" return self._agent_name - def on_cfp(self, envelope: Envelope, dialogue: Dialogue) -> Envelope: + def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Envelope: """ Handle a CFP. - :param envelope: the envelope containing the CFP + :param cfp: the message containing the CFP :param dialogue: the dialogue :return: a Propose or a Decline """ - cfp = envelope.message - assert cfp.protocol_id == "fipa" and cfp.get("performative") == FIPAMessage.Performative.CFP + assert cfp.get("performative") == FIPAMessage.Performative.CFP goods_description = self.game_instance.get_service_description(is_supply=dialogue.is_seller) new_msg_id = cfp.get("id") + 1 decline = False @@ -96,86 +97,93 @@ def on_cfp(self, envelope: Envelope, dialogue: Dialogue) -> Envelope: logger.debug("[{}]: Current strategy does not generate proposal that satisfies CFP query.".format(self.agent_name)) if decline: - logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, envelope.sender, + logger.debug("[{}]: sending to {} a Decline{}".format(self.agent_name, dialogue.dialogue_label.dialogue_opponent_pbk, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), - "origin": envelope.sender, + "origin": dialogue.dialogue_label.dialogue_opponent_pbk, "target": cfp.get("target") }))) - response = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id"))) + msg = FIPAMessage(message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), performative=FIPAMessage.Performative.DECLINE, target=cfp.get("id")) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + response = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) else: - transaction_id = generate_transaction_id(self.crypto.public_key, envelope.sender, dialogue.dialogue_label, dialogue.is_seller) + transaction_id = generate_transaction_id(self.crypto.public_key, dialogue.dialogue_label.dialogue_opponent_pbk, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=envelope.sender, + counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, crypto=self.crypto) self.game_instance.transaction_manager.add_pending_proposal(dialogue.dialogue_label, new_msg_id, transaction) - logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, envelope.sender, + logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, dialogue.dialogue_label.dialogue_opponent_pbk, pprint.pformat({ "msg_id": new_msg_id, "dialogue_id": cfp.get("dialogue_id"), - "origin": envelope.sender, + "origin": dialogue.dialogue_label.dialogue_opponent_pbk, "target": cfp.get("id"), "propose": proposal.values }))) - response = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(performative=FIPAMessage.Performative.PROPOSE, message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal])) + msg = FIPAMessage(performative=FIPAMessage.Performative.PROPOSE, message_id=new_msg_id, dialogue_id=cfp.get("dialogue_id"), target=cfp.get("id"), proposal=[proposal]) + dialogue.outgoing_extend([msg]) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + response = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) return response - def on_propose(self, envelope: Envelope, dialogue: Dialogue) -> Envelope: + def on_propose(self, propose: Message, dialogue: Dialogue) -> Envelope: """ Handle a Propose. - :param envelope: the envelope containing the Propose + :param propose: the message containing the Propose :param dialogue: the dialogue :return: an Accept or a Decline """ - propose = envelope.message logger.debug("[{}]: on propose as {}.".format(self.agent_name, dialogue.role)) - assert propose.protocol_id == "fipa" and propose.get("performative") == FIPAMessage.Performative.PROPOSE + assert propose.get("performative") == FIPAMessage.Performative.PROPOSE proposal = propose.get("proposal")[0] - transaction_id = generate_transaction_id(self.crypto.public_key, envelope.sender, dialogue.dialogue_label, dialogue.is_seller) + transaction_id = generate_transaction_id(self.crypto.public_key, dialogue.dialogue_label.dialogue_opponent_pbk, dialogue.dialogue_label, dialogue.is_seller) transaction = Transaction.from_proposal(proposal=proposal, transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, - counterparty=envelope.sender, + counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, crypto=self.crypto) new_msg_id = propose.get("id") + 1 - is_profitable_transaction, message = self.game_instance.is_profitable_transaction(transaction, dialogue) - logger.debug(message) + is_profitable_transaction, propose_log_msg = self.game_instance.is_profitable_transaction(transaction, dialogue) + logger.debug(propose_log_msg) if is_profitable_transaction: logger.debug("[{}]: Accepting propose (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) self.game_instance.transaction_manager.add_pending_initial_acceptance(dialogue.dialogue_label, new_msg_id, transaction) - result = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT)) + msg = FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.ACCEPT) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + result = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) else: logger.debug("[{}]: Declining propose (as {})".format(self.agent_name, dialogue.role)) - result = Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.DECLINE)) + msg = FIPAMessage(message_id=new_msg_id, dialogue_id=propose.get("dialogue_id"), target=propose.get("id"), performative=FIPAMessage.Performative.DECLINE) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + result = Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_PROPOSE, dialogue.is_self_initiated) return result - def on_decline(self, envelope: Envelope, dialogue: Dialogue) -> None: + def on_decline(self, decline: Message, dialogue: Dialogue) -> None: """ Handle a Decline. - :param envelope: the envelope containing the Decline + :param decline: the message containing the Decline :param dialogue: the dialogue :return: None """ - decline = envelope.message - assert decline.protocol_id == "fipa" and decline.get("performative") == FIPAMessage.Performative.DECLINE + assert decline.get("performative") == FIPAMessage.Performative.DECLINE logger.debug("[{}]: on_decline: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), envelope.sender, decline.get("target"))) + .format(self.agent_name, decline.get("id"), decline.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, decline.get("target"))) target = decline.get("target") if target == 1: self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_CFP, dialogue.is_self_initiated) @@ -191,59 +199,66 @@ def on_decline(self, envelope: Envelope, dialogue: Dialogue) -> None: return None - def on_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: + def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle an Accept. - :param envelope: the envelope containing the Accept + :param accept: the message containing the Accept :param dialogue: the dialogue :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ - accept = envelope.message - assert envelope.protocol_id == "fipa" \ - and accept.get("performative") == FIPAMessage.Performative.ACCEPT \ - and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ - and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] + assert accept.get("performative") == FIPAMessage.Performative.ACCEPT \ + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ + and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), envelope.sender, accept.get("target"))) + .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, accept.get("target"))) new_msg_id = accept.get("id") + 1 results = [] transaction = self.game_instance.transaction_manager.pop_pending_proposal(dialogue.dialogue_label, accept.get("target")) - is_profitable_transaction, message = self.game_instance.is_profitable_transaction(transaction, dialogue) - logger.debug(message) + is_profitable_transaction, accept_log_msg = self.game_instance.is_profitable_transaction(transaction, dialogue) + logger.debug(accept_log_msg) if is_profitable_transaction: if self.game_instance.strategy.is_world_modeling: self.game_instance.world_state.update_on_initial_accept(transaction) logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=accept.get("dialogue_id"), content=transaction.serialize()))) - results.append(Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT))) + + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=transaction.serialize()) + msg_bytes = SimpleSerializer().encode(msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) + dialogue.outgoing_extend([msg]) + + msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + results.append(Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) else: logger.debug("[{}]: Decline the accept (as {}).".format(self.agent_name, dialogue.role)) - results.append(Envelope(to=envelope.sender, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, - message=FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE))) + + msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.DECLINE) + dialogue.outgoing_extend([msg]) + msg_bytes = FIPASerializer().encode(msg) + results.append(Envelope(to=dialogue.dialogue_label.dialogue_opponent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) self.game_instance.stats_manager.add_dialogue_endstate(EndState.DECLINED_ACCEPT, dialogue.is_self_initiated) return results - def on_match_accept(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: + def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle a matching Accept. - :param envelope: the envelope containing the MatchAccept + :param match_accept: the envelope containing the MatchAccept :param dialogue: the dialogue :return: a Transaction """ - match_accept = envelope.message - assert envelope.protocol_id == "fipa" \ - and match_accept.get("performative") == FIPAMessage.Performative.MATCH_ACCEPT \ - and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ - and match_accept.get("target") in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label] + assert match_accept.get("performative") == FIPAMessage.Performative.MATCH_ACCEPT \ + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ + and match_accept.get("target") in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label] logger.debug("[{}]: on_match_accept: msg_id={}, dialogue_id={}, origin={}, target={}" - .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), envelope.sender, match_accept.get("target"))) + .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, match_accept.get("target"))) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, match_accept.get("target")) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=match_accept.get("dialogue_id"), content=transaction.serialize()))) + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=transaction.serialize()) + msg_bytes = SimpleSerializer().encode(msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) + dialogue.outgoing_extend([msg]) return results diff --git a/tac/agents/participant/base/reactions.py b/tac/agents/participant/base/reactions.py index 12e18ea6..52d2431a 100644 --- a/tac/agents/participant/base/reactions.py +++ b/tac/agents/participant/base/reactions.py @@ -33,13 +33,15 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import ByteMessage, FIPAMessage +from tac.aea.mail.messages import FIPAMessage, Address, Message, SimpleMessage +from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.dialogues import Dialogue from tac.agents.participant.base.game_instance import GameInstance, GamePhase from tac.agents.participant.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME from tac.agents.participant.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ DialogueReactionInterface -from tac.aea.mail.protocol import Envelope from tac.agents.participant.base.negotiation_behaviours import FIPABehaviour from tac.agents.participant.base.stats_manager import EndState from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ @@ -72,16 +74,15 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, envelope: Envelope) -> None: + def on_dialogue_error(self, message: Message) -> None: """ Handle dialogue error event emitted by the controller. - :param envelope: the dialogue error message + :param message: the dialogue error message :return: None """ - assert envelope.protocol_id == "oef" - dialogue_error = envelope.message + dialogue_error = message logger.warning("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) @@ -183,9 +184,10 @@ def _request_state_update(self) -> None: :return: None """ - msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) + tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) class OEFReactions(OEFReactionInterface): @@ -211,7 +213,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.agent_name = agent_name self.rejoin = rejoin - def on_search_result(self, search_result: Envelope) -> None: + def on_search_result(self, search_result: Message) -> None: """ Split the search results from the OEF. @@ -219,9 +221,8 @@ def on_search_result(self, search_result: Envelope) -> None: :return: None """ - assert search_result.protocol_id == "oef" - search_id = search_result.message.get("id") - agents = search_result.message.get("agents") + search_id = search_result.get("id") + agents = search_result.get("agents") logger.debug("[{}]: on search result: {} {}".format(self.agent_name, search_id, agents)) if search_id in self.game_instance.search.ids_for_tac: self._on_controller_search_result(agents) @@ -232,7 +233,7 @@ def on_search_result(self, search_result: Envelope) -> None: else: logger.debug("[{}]: Unknown search id: search_id={}".format(self.agent_name, search_id)) - def on_oef_error(self, oef_error: Envelope) -> None: + def on_oef_error(self, oef_error: Message) -> None: """ Handle an OEF error message. @@ -241,9 +242,9 @@ def on_oef_error(self, oef_error: Envelope) -> None: :return: None """ logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.agent_name, oef_error.message.get("id"), oef_error.message.get("operation"))) + .format(self.agent_name, oef_error.get("id"), oef_error.get("operation"))) - def on_dialogue_error(self, dialogue_error: Envelope) -> None: + def on_dialogue_error(self, dialogue_error: Message) -> None: """ Handle a dialogue error message. @@ -252,7 +253,7 @@ def on_dialogue_error(self, dialogue_error: Envelope) -> None: :return: None """ logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.message.get("id"), dialogue_error.message.get("dialogue_id"), dialogue_error.message.get("origin"))) + .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) def _on_controller_search_result(self, agent_pbks: List[str]) -> None: """ @@ -305,10 +306,11 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel dialogue = self.game_instance.dialogues.create_self_initiated(agent_pbk, self.crypto.public_key, not is_searching_for_sellers) cfp = FIPAMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, target=STARTING_MESSAGE_TARGET, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) - envelope = Envelope(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp) + cfp_bytes = FIPASerializer().encode(cfp) + envelope = Envelope(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp_bytes) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), envelope.to, cfp.get("target"), services)) - dialogue.outgoing_extend([envelope]) + dialogue.outgoing_extend([cfp]) self.mailbox.outbox.put(envelope) def _register_to_tac(self, controller_pbk: str) -> None: @@ -321,9 +323,10 @@ def _register_to_tac(self, controller_pbk: str) -> None: """ self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP - msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) + tac_msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) def _rejoin_tac(self, controller_pbk: str) -> None: """ @@ -335,9 +338,10 @@ def _rejoin_tac(self, controller_pbk: str) -> None: """ self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP - msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, - message=ByteMessage(message_id=0, dialogue_id=0, content=msg)) + tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) class DialogueReactions(DialogueReactionInterface): @@ -363,24 +367,24 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.dialogues = game_instance.dialogues self.negotiation_behaviour = FIPABehaviour(crypto, game_instance, agent_name) - def on_new_dialogue(self, envelope: Envelope) -> None: + def on_new_dialogue(self, message: Message, sender: Address) -> None: """ React to a new dialogue. - :param envelope: the agent message + :param message: the agent message :return: None """ - assert envelope.protocol_id == "fipa" and envelope.message.get("performative") == FIPAMessage.Performative.CFP - services = json.loads(envelope.message.get("query").decode('utf-8')) + assert message.get("performative") == FIPAMessage.Performative.CFP + services = json.loads(message.get("query").decode('utf-8')) is_seller = services['description'] == TAC_DEMAND_DATAMODEL_NAME - dialogue = self.dialogues.create_opponent_initiated(envelope.sender, envelope.message.get("dialogue_id"), is_seller) + dialogue = self.dialogues.create_opponent_initiated(sender, message.get("dialogue_id"), is_seller) logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format(self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) - results = self._handle(envelope, dialogue) + results = self._handle(message, dialogue) for result in results: self.mailbox.outbox.put(result) - def on_existing_dialogue(self, envelope: Envelope) -> None: + def on_existing_dialogue(self, message: Message, sender: Address) -> None: """ React to an existing dialogue. @@ -388,53 +392,51 @@ def on_existing_dialogue(self, envelope: Envelope) -> None: :return: None """ - dialogue = self.dialogues.get_dialogue(envelope, self.crypto.public_key) + dialogue = self.dialogues.get_dialogue(message, sender, self.crypto.public_key) - results = self._handle(envelope, dialogue) + results = self._handle(message, dialogue) for result in results: self.mailbox.outbox.put(result) - def on_unidentified_dialogue(self, envelope: Envelope) -> None: + def on_unidentified_dialogue(self, message: Message, sender: Address) -> None: """ React to an unidentified dialogue. - :param envelope: agent message + :param message: agent message :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - message_id = envelope.message.get("id") if envelope.message.get("id") is not None else 1 - dialogue_id = envelope.message.get("dialogue_id") if envelope.message.get("dialogue_id") is not None else 1 - result = ByteMessage(message_id=message_id + 1, dialogue_id=dialogue_id, content=b'This message belongs to an unidentified dialogue.') - self.mailbox.outbox.put_message(to=envelope.sender, sender=self.crypto.public_key, protocol_id=ByteMessage.protocol_id, message=result) + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b'This message belongs to an unidentified dialogue.') + msg_bytes = SimpleSerializer().encode(msg) + self.mailbox.outbox.put_message(to=sender, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) - def _handle(self, envelope: Envelope, dialogue: Dialogue) -> List[Envelope]: + def _handle(self, message: Message, dialogue: Dialogue) -> List[Envelope]: """ Handle a message according to the defined behaviour. - :param envelope: the agent message + :param message: the agent message :param dialogue: the dialogue :return: a list of agent messages """ - dialogue.incoming_extend([envelope]) + dialogue.incoming_extend([message]) results = [] # type: List[Envelope] - performative = envelope.message.get("performative") + performative = message.get("performative") if performative == FIPAMessage.Performative.CFP: - result = self.negotiation_behaviour.on_cfp(envelope, dialogue) + result = self.negotiation_behaviour.on_cfp(message, dialogue) results = [result] elif performative == FIPAMessage.Performative.PROPOSE: - result = self.negotiation_behaviour.on_propose(envelope, dialogue) + result = self.negotiation_behaviour.on_propose(message, dialogue) results = [result] elif performative == FIPAMessage.Performative.ACCEPT: - results = self.negotiation_behaviour.on_accept(envelope, dialogue) + results = self.negotiation_behaviour.on_accept(message, dialogue) elif performative == FIPAMessage.Performative.MATCH_ACCEPT: - results = self.negotiation_behaviour.on_match_accept(envelope, dialogue) + results = self.negotiation_behaviour.on_match_accept(message, dialogue) elif performative == FIPAMessage.Performative.DECLINE: - self.negotiation_behaviour.on_decline(envelope, dialogue) + self.negotiation_behaviour.on_decline(message, dialogue) results = [] else: raise ValueError("Performative not supported: {}".format(str(performative))) - dialogue.outgoing_extend(results) return results diff --git a/tests/test_mail.py b/tests/test_mail.py index 6a6b30b2..f1bb8ce9 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,9 +20,11 @@ """This module contains tests for the mail module.""" import time -from tac.aea.mail.messages import FIPAMessage, ByteMessage # OEFMessage +from tac.aea.mail.messages import FIPAMessage, ByteMessage, SimpleMessage # OEFMessage from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer def test_example(network_node): @@ -33,33 +35,47 @@ def test_example(network_node): mailbox1.connect() mailbox2.connect() - msg = ByteMessage(message_id=0, dialogue_id=0, content=b"hello") - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=ByteMessage.protocol_id, message=msg)) - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg)) - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg)) - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg)) - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg)) + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b"hello") + msg_bytes = SimpleSerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) - time.sleep(10.0) + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query=None) + msg_bytes = FIPASerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) - msg = mailbox2.inbox.get(block=True, timeout=2.0) - assert msg.message.get("content") == b"hello" - msg = mailbox2.inbox.get(block=True, timeout=2.0) - assert msg.protocol_id == "fipa" - assert msg.message.get("performative") == FIPAMessage.Performative.CFP - msg = mailbox2.inbox.get(block=True, timeout=2.0) - assert msg.protocol_id == "fipa" - assert msg.message.get("performative") == FIPAMessage.Performative.PROPOSE - msg = mailbox2.inbox.get(block=True, timeout=2.0) - assert msg.protocol_id == "fipa" - assert msg.message.get("performative") == FIPAMessage.Performative.ACCEPT - msg = mailbox2.inbox.get(block=True, timeout=2.0) - assert msg.protocol_id == "fipa" - assert msg.message.get("performative") == FIPAMessage.Performative.DECLINE + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=[]) + msg_bytes = FIPASerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.ACCEPT) + msg_bytes = FIPASerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.DECLINE) + msg_bytes = FIPASerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + + time.sleep(5.0) + + envelope = mailbox2.inbox.get(block=True, timeout=2.0) + msg = SimpleSerializer().decode(envelope.message) + assert msg.get("content") == b"hello" + envelope = mailbox2.inbox.get(block=True, timeout=2.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.CFP + envelope = mailbox2.inbox.get(block=True, timeout=2.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.PROPOSE + envelope = mailbox2.inbox.get(block=True, timeout=2.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.ACCEPT + envelope = mailbox2.inbox.get(block=True, timeout=2.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.DECLINE mailbox1.disconnect() mailbox2.disconnect() diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index 1a5bd6a9..7d33e401 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -29,26 +29,32 @@ class TestDefaultSerializations: def setup_class(cls): """Set up the use case.""" cls.message = Message(content="hello") - cls.envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=cls.message) def test_default_protobuf_serialization(self): """Test that the default Protobuf serialization works.""" - envelope = self.envelope - serializer = DefaultProtobufSerializer() - envelope_bytes = envelope.encode(serializer) - actual_envelope = Envelope.decode(envelope_bytes, serializer) - expected_envelope = envelope + message_bytes = DefaultProtobufSerializer().encode(self.message) + envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) + envelope_bytes = envelope.encode() + expected_envelope = Envelope.decode(envelope_bytes) + actual_envelope = envelope assert expected_envelope == actual_envelope + expected_msg = DefaultProtobufSerializer().decode(expected_envelope.message) + actual_msg = self.message + assert expected_msg == actual_msg + def test_default_json_serialization(self): """Test that the default JSON serialization works.""" - envelope = self.envelope - - serializer = DefaultJSONSerializer() - envelope_bytes = envelope.encode(serializer) - actual_envelope = Envelope.decode(envelope_bytes, serializer) - expected_envelope = envelope + message_bytes = DefaultJSONSerializer().encode(self.message) + envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) + envelope_bytes = envelope.encode() + expected_envelope = Envelope.decode(envelope_bytes) + actual_envelope = envelope assert expected_envelope == actual_envelope + + expected_msg = DefaultJSONSerializer().decode(expected_envelope.message) + actual_msg = self.message + assert expected_msg == actual_msg diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py index a88c2057..f7cfc6bc 100644 --- a/tests/test_messages/test_fipa.py +++ b/tests/test_messages/test_fipa.py @@ -18,6 +18,8 @@ # ------------------------------------------------------------------------------ """This module contains the tests for the FIPA protocol.""" +from oef.schema import Description, DataModel, AttributeSchema + from tac.aea.mail.messages import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer @@ -26,14 +28,40 @@ def test_fipa_cfp_serialization(): """Test that the serialization for the 'fipa' protocol works..""" - msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, - query={"foo": "bar"}) - envelope = Envelope(to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg) - serializer = FIPASerializer() - - envelope_bytes = envelope.encode(serializer) + msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query={"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, serializer) + actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope + assert expected_envelope == actual_envelope + actual_msg = FIPASerializer().decode(actual_envelope.message) + expected_msg = msg + assert expected_msg == actual_msg + + +def test_fipa_propose_serialization(): + """Test that the serialization for the 'fipa' protocol works..""" + + proposal = [ + Description({"foo1": 1, "bar1": 2}), # DataModel("dm_bar", [AttributeSchema("foo1", int, True), AttributeSchema("bar1", int, True)])) + Description({"foo2": 1, "bar2": 2}), + ] + msg = FIPAMessage(message_id=0, dialogue_id=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 diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index d4ee34d1..94729400 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -18,32 +18,39 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" + from tac.aea.mail.messages import SimpleMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.simple.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import SimpleSerializer def test_simple_bytes_serialization(): """Test that the serialization for the 'simple' protocol works for the BYTES message.""" msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b"hello") - envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg) - serializer = SimpleSerializer() + msg_bytes = SimpleSerializer().encode(msg) + envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) + envelope_bytes = envelope.encode() - envelope_bytes = envelope.encode(serializer) - actual_envelope = Envelope.decode(envelope_bytes, serializer) + actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope - assert expected_envelope == actual_envelope + actual_msg = SimpleSerializer().decode(actual_envelope.message) + expected_msg = msg + assert expected_msg == actual_msg + def test_simple_error_serialization(): """Test that the serialization for the 'simple' protocol works for the ERROR message.""" msg = SimpleMessage(type=SimpleMessage.Type.ERROR, error_code=-1, error_msg="An error") - envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg) - serializer = SimpleSerializer() + msg_bytes = SimpleSerializer().encode(msg) + envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) + envelope_bytes = envelope.encode() - envelope_bytes = envelope.encode(serializer) - actual_envelope = Envelope.decode(envelope_bytes, serializer) + actual_envelope = Envelope.decode(envelope_bytes) expected_envelope = envelope - assert expected_envelope == actual_envelope + + actual_msg = SimpleSerializer().decode(actual_envelope.message) + expected_msg = msg + assert expected_msg == actual_msg From b3b2644e94297f4405fb18a4607aabec705e8dc8 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 10:49:00 +0100 Subject: [PATCH 053/107] fix controller tests. --- tests/test_controller.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/test_controller.py b/tests/test_controller.py index 04133332..56775124 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -26,6 +26,9 @@ import time from threading import Thread +from tac.aea.mail.messages import SimpleMessage +from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import SimpleSerializer from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 @@ -54,14 +57,10 @@ def test_no_registered_agents(self): """Test no agent is registered.""" job = Thread(target=self.controller_agent.start) job.start() - # process = multiprocessing.Process(target=self.controller_agent.start) - # process.start() - time.sleep(1.0) + job.join(10.0) + assert not job.is_alive() assert len(self.controller_agent.game_handler.registered_agents) == 0 self.controller_agent.stop() - # process.join() - job.join() - # process.terminate() class TestCompetitionStopsTooFewAgentRegistered: @@ -90,7 +89,11 @@ def setup_class(cls): agent_job = Thread(target=cls.agent1.run) agent_job.start() - cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, Register(crypto.public_key, crypto, 'agent_name').serialize()) + tac_msg = Register(crypto.public_key, crypto, 'agent_name').serialize() + msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) + msg_bytes = SimpleSerializer().encode(msg) + envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, envelope.encode()) time.sleep(10.0) From 5977de0ec9c1525830a6d5e990470752ac0e2a63 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 11:16:42 +0100 Subject: [PATCH 054/107] Remove duplicate attribute. --- tac/aea/agent.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tac/aea/agent.py b/tac/aea/agent.py index 9a7a01c1..9e578bf1 100644 --- a/tac/aea/agent.py +++ b/tac/aea/agent.py @@ -81,14 +81,12 @@ def __init__(self, name: str, self._liveness = Liveness() self._timeout = timeout - self._handlers = {} - self._behaviours = {} + self._handlers = {} # type: Dict[ProtocolId, Handler] + self._behaviours = {} # type: Dict[ProtocolId, Behaviour] self.debug = debug self.mailbox = None # type: Optional[MailBox] - self._handlers = {} # type: Dict[ProtocolId, Handler] - self.behaviours = {} @property def inbox(self) -> Optional[InBox]: From 38d53d1da53a767fd1e07fc88b3b22a0b66d3e77 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 11:12:49 +0100 Subject: [PATCH 055/107] address PR comments. - change from "simple" prefix to "default" prefix in class names for the "default" protocol. - Put a default "to" field for envelopes containing OEF requests. - Remove some 'magic numbers'. --- tac/aea/mail/messages.py | 2 +- tac/aea/mail/oef.py | 22 +++++++------- tac/aea/mail/protocol.py | 4 +-- tac/aea/protocols/default/serialization.py | 20 ++++++------- tac/aea/protocols/oef/serialization.py | 7 +++-- tac/agents/controller/base/actions.py | 6 ++-- tac/agents/controller/base/handlers.py | 29 +++++++++---------- tac/agents/participant/v1/base/actions.py | 26 ++++++++--------- tac/agents/participant/v1/base/handlers.py | 6 ++-- .../v1/base/negotiation_behaviours.py | 16 +++++----- tac/agents/participant/v1/base/reactions.py | 28 +++++++++--------- tests/test_controller.py | 10 +++---- tests/test_mail.py | 12 ++++---- tests/test_messages/test_base.py | 10 +++---- tests/test_messages/test_simple.py | 16 +++++----- 15 files changed, 108 insertions(+), 106 deletions(-) diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index f094ddf4..91d41fd7 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -201,7 +201,7 @@ def __init__(self, message_id: Optional[int] = None, super().__init__(id=message_id, dialogue_id=dialogue_id, content=content) -class SimpleMessage(Message): +class DefaultMessage(Message): """The Simple message class.""" protocol_id = "default" diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 40221402..ad15bb37 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -30,19 +30,21 @@ from oef.core import OEFProxy from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES from oef.proxy import OEFNetworkProxy -from oef.schema import Description from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy from tac.aea.mail.base import Connection, MailBox -from tac.aea.mail.messages import OEFMessage, FIPAMessage, SimpleMessage -from tac.aea.mail.protocol import Envelope, DefaultJSONSerializer -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.mail.messages import OEFMessage, FIPAMessage +from tac.aea.mail.protocol import Envelope from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer logger = logging.getLogger(__name__) +STUB_MESSSAGE_ID = 0 +STUB_DIALOGUE_ID = 0 + + class MailStats(object): """The MailStats class tracks statistics on messages processed by MailBox.""" @@ -264,9 +266,9 @@ def send(self, envelope: Envelope) -> None: elif envelope.protocol_id == "fipa": self.send_fipa_message(envelope) elif envelope.protocol_id == "bytes": - self.send_bytes(envelope) + self.send_bytes_message(envelope) elif envelope.protocol_id == "default": - self.send_default(envelope) + self.send_default_message(envelope) else: raise ValueError("Cannot send message.") @@ -336,13 +338,13 @@ def send_fipa_message(self, envelope: Envelope) -> None: else: raise ValueError("OEF FIPA message not recognized.") - def send_bytes(self, envelope: Envelope): + def send_bytes_message(self, envelope: Envelope): """Send a 'bytes' message.""" - self.send_message(0, 0, envelope.to, envelope.encode()) + self.send_message(STUB_MESSSAGE_ID, STUB_DIALOGUE_ID, envelope.to, envelope.encode()) - def send_default(self, envelope: Envelope): + def send_default_message(self, envelope: Envelope): """Send a 'default' message.""" - self.send_message(0, 0, envelope.to, envelope.encode()) + self.send_message(STUB_MESSSAGE_ID, STUB_DIALOGUE_ID, envelope.to, envelope.encode()) class OEFConnection(Connection): diff --git a/tac/aea/mail/protocol.py b/tac/aea/mail/protocol.py index 1a36dc33..bcb7c1db 100644 --- a/tac/aea/mail/protocol.py +++ b/tac/aea/mail/protocol.py @@ -59,7 +59,7 @@ class Serializer(Encoder, Decoder, ABC): """The implementations of this class defines a serialization layer for a protocol.""" -class DefaultProtobufSerializer(Serializer): +class ProtobufSerializer(Serializer): """ Default Protobuf serializer. @@ -83,7 +83,7 @@ def decode(self, obj: bytes) -> Message: return msg -class DefaultJSONSerializer(Serializer): +class JSONSerializer(Serializer): """Default serialization in JSON for the Message object.""" def encode(self, msg: Message) -> bytes: diff --git a/tac/aea/protocols/default/serialization.py b/tac/aea/protocols/default/serialization.py index c49834a4..bffb0f5a 100644 --- a/tac/aea/protocols/default/serialization.py +++ b/tac/aea/protocols/default/serialization.py @@ -23,24 +23,24 @@ import base58 -from tac.aea.mail.messages import Message, SimpleMessage +from tac.aea.mail.messages import Message, DefaultMessage from tac.aea.mail.protocol import Serializer -class SimpleSerializer(Serializer): +class DefaultSerializer(Serializer): """Serialization for the 'default' protocol.""" def encode(self, msg: Message) -> bytes: - """Encode a 'Simple' message into bytes.""" + """Encode a 'default' message into bytes.""" body = {} msg_type = msg.get("type") - assert msg_type in set(SimpleMessage.Type) + assert msg_type in set(DefaultMessage.Type) body["type"] = str(msg_type.value) - if msg_type == SimpleMessage.Type.BYTES: + if msg_type == DefaultMessage.Type.BYTES: body["content"] = base58.b58encode(msg.get("content")).decode("utf-8") - elif msg_type == SimpleMessage.Type.ERROR: + elif msg_type == DefaultMessage.Type.ERROR: body["error_code"] = msg.get("error_code") body["error_msg"] = msg.get("error_msg") else: @@ -50,16 +50,16 @@ def encode(self, msg: Message) -> bytes: return bytes_msg def decode(self, obj: bytes) -> Message: - """Decode bytes into a 'Simple' message.""" + """Decode bytes into a 'default' message.""" json_body = json.loads(obj.decode("utf-8")) body = {} - msg_type = SimpleMessage.Type(json_body["type"]) + msg_type = DefaultMessage.Type(json_body["type"]) body["type"] = msg_type - if msg_type == SimpleMessage.Type.BYTES: + if msg_type == DefaultMessage.Type.BYTES: content = base58.b58decode(json_body["content"].encode("utf-8")) body["content"] = content - elif msg_type == SimpleMessage.Type.ERROR: + elif msg_type == DefaultMessage.Type.ERROR: body["error_code"] = json_body["error_code"] body["error_msg"] = json_body["error_msg"] else: diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py index 5cfeb28f..6475637b 100644 --- a/tac/aea/protocols/oef/serialization.py +++ b/tac/aea/protocols/oef/serialization.py @@ -22,14 +22,13 @@ import copy import json import pickle -import pprint from typing import Dict import base58 from google.protobuf import json_format from google.protobuf.json_format import MessageToJson from oef import query_pb2, dap_interface_pb2 -from oef.messages import OEFErrorOperation, CFP_TYPES +from oef.messages import OEFErrorOperation from oef.query import Query, ConstraintExpr, And, Or, Not, Constraint from oef.schema import Description, DataModel @@ -37,6 +36,10 @@ from tac.aea.mail.protocol import Serializer +"""default 'to' field for OEF envelopes.""" +DEFAULT_OEF = "oef" + + class ConstraintWrapper: """Make the constraint object pickable.""" diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index 38d90080..61663a7a 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -32,8 +32,8 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage -from tac.aea.mail.protocol import DefaultJSONSerializer -from tac.aea.protocols.oef.serialization import OEFSerializer +from tac.aea.mail.protocol import JSONSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.controller.base.interfaces import OEFActionInterface logger = logging.getLogger(__name__) @@ -72,4 +72,4 @@ def register_tac(self) -> None: logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) out = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") out_bytes = OEFSerializer().encode(out) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out_bytes) diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 246790ac..17d6ca96 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -43,9 +43,9 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, SimpleMessage +from tac.aea.mail.messages import OEFMessage, DefaultMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.actions import OEFActions from tac.agents.controller.base.helpers import generate_good_pbk_to_name @@ -215,13 +215,10 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: # send the transaction confirmation. tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tx_confirmation.serialize()) - msg_bytes = SimpleSerializer().encode(msg) - self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) - - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tx_confirmation.serialize()) - msg_bytes = SimpleSerializer().encode(msg) - self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tx_confirmation.serialize()) + msg_bytes = DefaultSerializer().encode(msg) + self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) @@ -296,7 +293,7 @@ def handle_agent_message(self, envelope: Envelope) -> Response: :return: the response. """ assert envelope.protocol_id == "default" - msg = SimpleSerializer().decode(envelope.message) + msg = DefaultSerializer().decode(envelope.message) sender = envelope.sender logger.debug("[{}] on_message: origin={}" .format(self.controller_agent.name, sender)) request = self.decode(msg.get("content"), sender) @@ -445,17 +442,17 @@ def _send_game_data_to_agents(self) -> None: .format(self.agent_name, public_key, str(game_data_response))) self.game_data_per_participant[public_key] = game_data_response - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=game_data_response.serialize()) - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=game_data_response.serialize()) + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) def notify_competition_cancelled(self): """Notify agents that the TAC is cancelled.""" logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.agent_name)) for agent_pbk in self.registered_agents: - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=Cancelled(agent_pbk, self.crypto).serialize()) - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=Cancelled(agent_pbk, self.crypto).serialize()) + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index 645606e5..b89528ad 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -33,9 +33,9 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, SimpleMessage -from tac.aea.protocols.default.serialization import SimpleSerializer -from tac.aea.protocols.oef.serialization import OEFSerializer +from tac.aea.mail.messages import OEFMessage, DefaultMessage +from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.participant.base.game_instance import GameInstance from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, \ DialogueActionInterface @@ -72,10 +72,10 @@ def request_state_update(self) -> None: :return: None """ tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) - msg_bytes = SimpleSerializer().encode(msg) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) + msg_bytes = DefaultSerializer().encode(msg) self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + protocol_id=DefaultMessage.protocol_id, message=msg_bytes) class OEFActions(OEFActionInterface): @@ -114,7 +114,7 @@ def search_for_tac(self) -> None: message = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) message_bytes = OEFSerializer().encode(message) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message_bytes) def update_services(self) -> None: """ @@ -134,11 +134,11 @@ def unregister_service(self) -> None: if self.game_instance.goods_demanded_description is not None: msg = OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_demanded_description, service_id="") msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.goods_supplied_description is not None: msg = OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, id=1, service_description=self.game_instance.goods_supplied_description, service_id="") msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) def register_service(self) -> None: """ @@ -157,14 +157,14 @@ def register_service(self) -> None: self.game_instance.goods_supplied_description = goods_supplied_description msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_supplied_description, service_id="") msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.strategy.is_registering_as_buyer: logger.debug("[{}]: Updating service directory as buyer with goods demanded.".format(self.agent_name)) goods_demanded_description = self.game_instance.get_service_description(is_supply=False) self.game_instance.goods_demanded_description = goods_demanded_description msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=goods_demanded_description, service_id="") msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) def search_services(self) -> None: """ @@ -189,7 +189,7 @@ def search_services(self) -> None: msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) if self.game_instance.strategy.is_searching_for_buyers: query = self.game_instance.build_services_query(is_searching_for_sellers=False) if query is None: @@ -202,7 +202,7 @@ def search_services(self) -> None: msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) msg_bytes = OEFSerializer().encode(msg) - self.mailbox.outbox.put_message(to=None, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) class DialogueActions(DialogueActionInterface): diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index 7814f026..b39a0660 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -29,12 +29,12 @@ import logging from typing import Any -from tac.aea.mail.protocol import Envelope, DefaultJSONSerializer +from tac.aea.mail.protocol import Envelope, JSONSerializer from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage, Message, Address -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.base.game_instance import GameInstance, GamePhase @@ -109,7 +109,7 @@ def handle_controller_message(self, envelope: Envelope) -> None: :return: None """ assert envelope.protocol_id == "default" - msg = SimpleSerializer().decode(envelope.message) + msg = DefaultSerializer().decode(envelope.message) response = Response.from_pb(msg.get("content"), envelope.sender, self.crypto) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) try: diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index c59d1525..82ffb339 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -26,9 +26,9 @@ from typing import List from tac.aea.crypto.base import Crypto -from tac.aea.mail.messages import FIPAMessage, Message, SimpleMessage +from tac.aea.mail.messages import FIPAMessage, Message, DefaultMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.dialogues import Dialogue from tac.agents.participant.base.game_instance import GameInstance @@ -223,9 +223,9 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=transaction.serialize()) - msg_bytes = SimpleSerializer().encode(msg) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=transaction.serialize()) + msg_bytes = DefaultSerializer().encode(msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) dialogue.outgoing_extend([msg]) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT) @@ -257,8 +257,8 @@ def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Env .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, match_accept.get("target"))) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, match_accept.get("target")) - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=transaction.serialize()) - msg_bytes = SimpleSerializer().encode(msg) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=transaction.serialize()) + msg_bytes = DefaultSerializer().encode(msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) dialogue.outgoing_extend([msg]) return results diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 52d2431a..78347e07 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -33,9 +33,9 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import FIPAMessage, Address, Message, SimpleMessage +from tac.aea.mail.messages import FIPAMessage, Address, Message, DefaultMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.base.dialogues import Dialogue from tac.agents.participant.base.game_instance import GameInstance, GamePhase @@ -185,9 +185,9 @@ def _request_state_update(self) -> None: :return: None """ tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) class OEFReactions(OEFReactionInterface): @@ -324,9 +324,9 @@ def _register_to_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP tac_msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) def _rejoin_tac(self, controller_pbk: str) -> None: """ @@ -339,9 +339,9 @@ def _rejoin_tac(self, controller_pbk: str) -> None: self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) class DialogueReactions(DialogueReactionInterface): @@ -407,9 +407,9 @@ def on_unidentified_dialogue(self, message: Message, sender: Address) -> None: :return: None """ logger.debug("[{}]: Unidentified dialogue.".format(self.agent_name)) - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b'This message belongs to an unidentified dialogue.') - msg_bytes = SimpleSerializer().encode(msg) - self.mailbox.outbox.put_message(to=sender, sender=self.crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b'This message belongs to an unidentified dialogue.') + msg_bytes = DefaultSerializer().encode(msg) + self.mailbox.outbox.put_message(to=sender, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) def _handle(self, message: Message, dialogue: Dialogue) -> List[Envelope]: """ diff --git a/tests/test_controller.py b/tests/test_controller.py index 56775124..ff743aa7 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -26,9 +26,9 @@ import time from threading import Thread -from tac.aea.mail.messages import SimpleMessage +from tac.aea.mail.messages import DefaultMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 @@ -90,9 +90,9 @@ def setup_class(cls): agent_job.start() tac_msg = Register(crypto.public_key, crypto, 'agent_name').serialize() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=tac_msg) - msg_bytes = SimpleSerializer().encode(msg) - envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=SimpleMessage.protocol_id, message=msg_bytes) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) + msg_bytes = DefaultSerializer().encode(msg) + envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, envelope.encode()) time.sleep(10.0) diff --git a/tests/test_mail.py b/tests/test_mail.py index f1bb8ce9..1cd4d1d8 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,10 +20,10 @@ """This module contains tests for the mail module.""" import time -from tac.aea.mail.messages import FIPAMessage, ByteMessage, SimpleMessage # OEFMessage +from tac.aea.mail.messages import FIPAMessage, ByteMessage, DefaultMessage # OEFMessage from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer @@ -35,9 +35,9 @@ def test_example(network_node): mailbox1.connect() mailbox2.connect() - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b"hello") - msg_bytes = SimpleSerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=SimpleMessage.protocol_id, message=msg_bytes)) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) + mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query=None) msg_bytes = FIPASerializer().encode(msg) @@ -58,7 +58,7 @@ def test_example(network_node): time.sleep(5.0) envelope = mailbox2.inbox.get(block=True, timeout=2.0) - msg = SimpleSerializer().decode(envelope.message) + msg = DefaultSerializer().decode(envelope.message) assert msg.get("content") == b"hello" envelope = mailbox2.inbox.get(block=True, timeout=2.0) msg = FIPASerializer().decode(envelope.message) diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index 7d33e401..38e497fd 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -19,7 +19,7 @@ """This module contains the tests of the messages module.""" from tac.aea.mail.messages import Message -from tac.aea.mail.protocol import DefaultProtobufSerializer, DefaultJSONSerializer, Envelope +from tac.aea.mail.protocol import ProtobufSerializer, JSONSerializer, Envelope class TestDefaultSerializations: @@ -33,7 +33,7 @@ def setup_class(cls): def test_default_protobuf_serialization(self): """Test that the default Protobuf serialization works.""" - message_bytes = DefaultProtobufSerializer().encode(self.message) + message_bytes = ProtobufSerializer().encode(self.message) envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) envelope_bytes = envelope.encode() @@ -41,13 +41,13 @@ def test_default_protobuf_serialization(self): actual_envelope = envelope assert expected_envelope == actual_envelope - expected_msg = DefaultProtobufSerializer().decode(expected_envelope.message) + expected_msg = ProtobufSerializer().decode(expected_envelope.message) actual_msg = self.message assert expected_msg == actual_msg def test_default_json_serialization(self): """Test that the default JSON serialization works.""" - message_bytes = DefaultJSONSerializer().encode(self.message) + message_bytes = JSONSerializer().encode(self.message) envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) envelope_bytes = envelope.encode() @@ -55,6 +55,6 @@ def test_default_json_serialization(self): actual_envelope = envelope assert expected_envelope == actual_envelope - expected_msg = DefaultJSONSerializer().decode(expected_envelope.message) + expected_msg = JSONSerializer().decode(expected_envelope.message) actual_msg = self.message assert expected_msg == actual_msg diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index 94729400..bbcbc681 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -19,15 +19,15 @@ """This module contains the tests of the messages module.""" -from tac.aea.mail.messages import SimpleMessage +from tac.aea.mail.messages import DefaultMessage from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import SimpleSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer def test_simple_bytes_serialization(): """Test that the serialization for the 'simple' protocol works for the BYTES message.""" - msg = SimpleMessage(type=SimpleMessage.Type.BYTES, content=b"hello") - msg_bytes = SimpleSerializer().encode(msg) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) envelope_bytes = envelope.encode() @@ -35,15 +35,15 @@ def test_simple_bytes_serialization(): expected_envelope = envelope assert expected_envelope == actual_envelope - actual_msg = SimpleSerializer().decode(actual_envelope.message) + actual_msg = DefaultSerializer().decode(actual_envelope.message) expected_msg = msg assert expected_msg == actual_msg def test_simple_error_serialization(): """Test that the serialization for the 'simple' protocol works for the ERROR message.""" - msg = SimpleMessage(type=SimpleMessage.Type.ERROR, error_code=-1, error_msg="An error") - msg_bytes = SimpleSerializer().encode(msg) + msg = DefaultMessage(type=DefaultMessage.Type.ERROR, error_code=-1, error_msg="An error") + msg_bytes = DefaultSerializer().encode(msg) envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) envelope_bytes = envelope.encode() @@ -51,6 +51,6 @@ def test_simple_error_serialization(): expected_envelope = envelope assert expected_envelope == actual_envelope - actual_msg = SimpleSerializer().decode(actual_envelope.message) + actual_msg = DefaultSerializer().decode(actual_envelope.message) expected_msg = msg assert expected_msg == actual_msg From 61db1ba0c7c3462ccdf081dd3ab25610acec3e91 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 11:36:45 +0100 Subject: [PATCH 056/107] address other PR comments. --- tac/aea/mail/oef.py | 2 ++ tac/agents/participant/v1/agent.py | 14 +++++++------- tac/agents/participant/v1/base/handlers.py | 17 +++++++++-------- .../v1/base/negotiation_behaviours.py | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index ad15bb37..49d8596e 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -128,6 +128,8 @@ def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) :param content: the bytes content. :return: None """ + # We are not using the 'origin' parameter because 'content' contains a serialized instance of 'Envelope', + # hence it already contains the address of the sender. envelope = Envelope.decode(content) self.in_queue.put(envelope) diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 18fd6148..8d575ecc 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -28,11 +28,12 @@ from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.handlers import DialogueHandler, ControllerHandler, OEFHandler -from tac.agents.participant.base.helpers import is_oef_message, is_controller_message, is_fipa_message -from tac.agents.participant.base.strategy import Strategy +from tac.agents.participant.v1.base.game_instance import GameInstance +from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler +from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message +from tac.agents.participant.v1.base.strategy import Strategy from tac.gui.dashboards.agent import AgentDashboard +from tac.platform.game.base import GamePhase logger = logging.getLogger(__name__) @@ -105,16 +106,15 @@ def react(self) -> None: while (not self.mailbox.inbox.empty() and counter < self.max_reactions): counter += 1 envelope = self.mailbox.inbox.get_nowait() # type: Optional[Envelope] - logger.debug("processing message of protocol={}".format(envelope.protocol_id)) if envelope is not None: + logger.debug("processing message of protocol={}".format(envelope.protocol_id)) if is_oef_message(envelope): self.oef_handler.handle_oef_message(envelope) elif is_controller_message(envelope): self.controller_handler.handle_controller_message(envelope) elif is_fipa_message(envelope): - fipa_message = FIPASerializer().decode(envelope.message) # type: # Message - self.dialogue_handler.handle_dialogue_message(fipa_message, envelope.sender) + self.dialogue_handler.handle_dialogue_message(envelope) else: logger.warning("Message type not recognized: sender={}".format(envelope.sender)) diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index b39a0660..a11b7987 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -35,6 +35,7 @@ from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage, Message, Address from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.base.game_instance import GameInstance, GamePhase @@ -62,24 +63,24 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan DialogueActions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) DialogueReactions.__init__(self, crypto, liveness, game_instance, mailbox, agent_name) - def handle_dialogue_message(self, message: Message, sender: Address) -> None: + def handle_dialogue_message(self, envelope: Envelope) -> None: """ Handle messages from the other agents. The agents expect a response. - :param message: the agent message. - :param sender: the sender. + :param envelope: the envelope. :return: None """ + message = FIPASerializer().decode(envelope.message) # type: # Message logger.debug("Handling Dialogue message. type={}".format(type(message.get("performative")))) - if self.dialogues.is_belonging_to_registered_dialogue(message, self.crypto.public_key, sender): - self.on_existing_dialogue(message, sender) - elif self.dialogues.is_permitted_for_new_dialogue(message, self.game_instance.game_configuration.agent_pbks, sender): - self.on_new_dialogue(message, sender) + if self.dialogues.is_belonging_to_registered_dialogue(message, self.crypto.public_key, envelope.sender): + self.on_existing_dialogue(message, envelope.sender) + elif self.dialogues.is_permitted_for_new_dialogue(message, self.game_instance.game_configuration.agent_pbks, envelope.sender): + self.on_new_dialogue(message, envelope.sender) else: - self.on_unidentified_dialogue(message, sender) + self.on_unidentified_dialogue(message, envelope.sender) class ControllerHandler(ControllerActions, ControllerReactions): diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index 82ffb339..5ea4e457 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -224,9 +224,9 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=transaction.serialize()) + dialogue.outgoing_extend([msg]) msg_bytes = DefaultSerializer().encode(msg) results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) - dialogue.outgoing_extend([msg]) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT) dialogue.outgoing_extend([msg]) From 4a3beda1c48ac668fb68f1fa528ef2e312f55227 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 11:40:07 +0100 Subject: [PATCH 057/107] Clarify some instance variable names. --- tac/agents/participant/v1/base/reactions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 78347e07..73ffca33 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -380,9 +380,9 @@ def on_new_dialogue(self, message: Message, sender: Address) -> None: is_seller = services['description'] == TAC_DEMAND_DATAMODEL_NAME dialogue = self.dialogues.create_opponent_initiated(sender, message.get("dialogue_id"), is_seller) logger.debug("[{}]: saving dialogue (as {}): dialogue_id={}".format(self.agent_name, dialogue.role, dialogue.dialogue_label.dialogue_id)) - results = self._handle(message, dialogue) - for result in results: - self.mailbox.outbox.put(result) + envelopes = self._handle(message, dialogue) + for envelope in envelopes: + self.mailbox.outbox.put(envelope) def on_existing_dialogue(self, message: Message, sender: Address) -> None: """ @@ -394,9 +394,9 @@ def on_existing_dialogue(self, message: Message, sender: Address) -> None: """ dialogue = self.dialogues.get_dialogue(message, sender, self.crypto.public_key) - results = self._handle(message, dialogue) - for result in results: - self.mailbox.outbox.put(result) + envelopes = self._handle(message, dialogue) + for envelope in envelopes: + self.mailbox.outbox.put(envelope) def on_unidentified_dialogue(self, message: Message, sender: Address) -> None: """ From 838e55f3680f741f6422fe74cfb1d0ce14bcd708 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 11:55:41 +0100 Subject: [PATCH 058/107] fix import statements from 'participant.base' to 'participant.v1.base'. --- sandbox/playground.py | 5 ++--- tac/agents/participant/v1/base/actions.py | 4 ++-- tac/agents/participant/v1/base/handlers.py | 11 ++++++----- .../participant/v1/base/negotiation_behaviours.py | 8 ++++---- tac/agents/participant/v1/base/reactions.py | 12 ++++++------ templates/v1/advanced.py | 2 +- templates/v1/basic.py | 6 +++--- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index 6b6f867b..bdeaf9a6 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -35,9 +35,8 @@ from tac.aea.mail.messages import FIPAMessage from tac.aea.mail.protocol import Envelope from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.agents.participant.base.dialogues import Dialogue -from tac.agents.participant.examples.baseline import BaselineAgent -from tac.agents.participant.examples.strategy import BaselineStrategy +from tac.agents.participant.v1.examples.baseline import BaselineAgent +from tac.agents.participant.v1.examples.strategy import BaselineStrategy from tac.platform.protocol import GameData CUR_PATH = inspect.getfile(inspect.currentframe()) diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index b89528ad..345f2d09 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -36,8 +36,8 @@ from tac.aea.mail.messages import OEFMessage, DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF -from tac.agents.participant.base.game_instance import GameInstance -from tac.agents.participant.base.interfaces import ControllerActionInterface, OEFActionInterface, \ +from tac.agents.participant.v1.base.game_instance import GameInstance +from tac.agents.participant.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, \ DialogueActionInterface from tac.platform.protocol import GetStateUpdate diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index a11b7987..f6db6803 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -29,17 +29,18 @@ import logging from typing import Any -from tac.aea.mail.protocol import Envelope, JSONSerializer from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, Message, Address +from tac.aea.mail.messages import OEFMessage +from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer -from tac.agents.participant.base.actions import DialogueActions, ControllerActions, OEFActions -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.reactions import DialogueReactions, ControllerReactions, OEFReactions +from tac.agents.participant.v1.base.actions import DialogueActions, ControllerActions, OEFActions +from tac.agents.participant.v1.base.game_instance import GameInstance +from tac.agents.participant.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions +from tac.platform.game.base import GamePhase from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index 5ea4e457..7ea9cdca 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -30,10 +30,10 @@ from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.agents.participant.base.dialogues import Dialogue -from tac.agents.participant.base.game_instance import GameInstance -from tac.agents.participant.base.helpers import generate_transaction_id -from tac.agents.participant.base.stats_manager import EndState +from tac.agents.participant.v1.base.dialogues import Dialogue +from tac.agents.participant.v1.base.game_instance import GameInstance +from tac.agents.participant.v1.base.helpers import generate_transaction_id +from tac.agents.participant.v1.base.stats_manager import EndState from tac.platform.protocol import Transaction logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 73ffca33..46809f5b 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -37,13 +37,13 @@ from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.agents.participant.base.dialogues import Dialogue -from tac.agents.participant.base.game_instance import GameInstance, GamePhase -from tac.agents.participant.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME -from tac.agents.participant.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ +from tac.agents.participant.v1.base.dialogues import Dialogue +from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase +from tac.agents.participant.v1.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME +from tac.agents.participant.v1.base.interfaces import ControllerReactionInterface, OEFReactionInterface, \ DialogueReactionInterface -from tac.agents.participant.base.negotiation_behaviours import FIPABehaviour -from tac.agents.participant.base.stats_manager import EndState +from tac.agents.participant.v1.base.negotiation_behaviours import FIPABehaviour +from tac.agents.participant.v1.base.stats_manager import EndState from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ GetStateUpdate diff --git a/templates/v1/advanced.py b/templates/v1/advanced.py index b745fdbc..8cda3997 100644 --- a/templates/v1/advanced.py +++ b/templates/v1/advanced.py @@ -28,7 +28,7 @@ from oef.schema import Description from tac.aea.state.base import WorldState -from tac.agents.participant.base.strategy import RegisterAs, SearchFor, Strategy +from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor, Strategy from tac.agents.participant.examples.baseline import BaselineAgent from tac.gui.dashboards.agent import AgentDashboard diff --git a/templates/v1/basic.py b/templates/v1/basic.py index abcf7e07..a86c7516 100644 --- a/templates/v1/basic.py +++ b/templates/v1/basic.py @@ -24,9 +24,9 @@ import argparse import logging -from tac.agents.participant.base.strategy import SearchFor, RegisterAs -from tac.agents.participant.examples.baseline import BaselineAgent -from tac.agents.participant.examples.strategy import BaselineStrategy +from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor +from tac.agents.participant.v1.examples.baseline import BaselineAgent +from tac.agents.participant.v1.examples.strategy import BaselineStrategy from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) From 394240b8d925d0c10dd5e313da7e51ac935e5911 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 11:59:06 +0100 Subject: [PATCH 059/107] remove unused oef.proto file. --- tac/aea/protocols/oef/oef.proto | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tac/aea/protocols/oef/oef.proto diff --git a/tac/aea/protocols/oef/oef.proto b/tac/aea/protocols/oef/oef.proto deleted file mode 100644 index 2b04be9a..00000000 --- a/tac/aea/protocols/oef/oef.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; - -package fetch.aea.oef; - - -message OEFMessage{ - int32 message_id = 1; - int32 dialogue_id = 2; - -} \ No newline at end of file From ab7f27ebe24ddf69734d9ba42158b5af1bca160e Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 12:00:19 +0100 Subject: [PATCH 060/107] Resolve linting errors. --- tac/aea/agent.py | 7 +++++-- tac/aea/mail/base.py | 4 ++-- tac/aea/mail/messages.py | 2 ++ tac/agents/controller/base/actions.py | 7 +++---- tac/agents/participant/v1/agent.py | 1 - tac/agents/participant/v1/base/actions.py | 6 +++--- tac/agents/participant/v1/base/dialogues.py | 2 -- tac/agents/participant/v1/base/handlers.py | 6 +++--- tac/agents/participant/v1/base/negotiation_behaviours.py | 8 ++++---- tac/agents/participant/v1/base/reactions.py | 7 +++---- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tac/aea/agent.py b/tac/aea/agent.py index 9e578bf1..fbfa8d9c 100644 --- a/tac/aea/agent.py +++ b/tac/aea/agent.py @@ -33,6 +33,9 @@ logger = logging.getLogger(__name__) +Handler = object +Behaviour = object + class AgentState(Enum): """Enumeration for an agent state.""" @@ -81,8 +84,8 @@ def __init__(self, name: str, self._liveness = Liveness() self._timeout = timeout - self._handlers = {} # type: Dict[ProtocolId, Handler] - self._behaviours = {} # type: Dict[ProtocolId, Behaviour] + self._handlers = {} # type: Dict[ProtocolId, Handler] + self._behaviours = {} # type: Dict[ProtocolId, Behaviour] self.debug = debug diff --git a/tac/aea/mail/base.py b/tac/aea/mail/base.py index 11339548..2460d4c2 100644 --- a/tac/aea/mail/base.py +++ b/tac/aea/mail/base.py @@ -25,8 +25,8 @@ from queue import Queue from typing import Optional -from tac.aea.mail.messages import Address, ProtocolId, Message -from tac.aea.mail.protocol import Envelope, Encoder +from tac.aea.mail.messages import Address, ProtocolId +from tac.aea.mail.protocol import Envelope logger = logging.getLogger(__name__) diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index 91d41fd7..8062d18b 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -116,6 +116,7 @@ class Type(Enum): SEARCH_RESULT = "search_result" def __str__(self): + """Get string representation.""" return self.value def __init__(self, oef_type: Optional[Type] = None, @@ -237,6 +238,7 @@ class Performative(Enum): DECLINE = "decline" def __str__(self): + """Get string representation.""" return self.value def __init__(self, message_id: Optional[int] = None, diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index 61663a7a..ec9b969b 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -32,7 +32,6 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.messages import OEFMessage -from tac.aea.mail.protocol import JSONSerializer from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.controller.base.interfaces import OEFActionInterface @@ -70,6 +69,6 @@ def register_tac(self) -> None: """ desc = Description({"version": 1}, data_model=CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format(self.agent_name, desc.data_model.name)) - out = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") - out_bytes = OEFSerializer().encode(out) - self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=out_bytes) + msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, id=1, service_description=desc, service_id="") + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 8d575ecc..2f794d3d 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -27,7 +27,6 @@ from tac.aea.agent import Agent from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index b89528ad..29f5ed24 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -112,9 +112,9 @@ def search_for_tac(self) -> None: search_id = self.game_instance.search.get_next_id() self.game_instance.search.ids_for_tac.add(search_id) - message = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) - message_bytes = OEFSerializer().encode(message) - self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=message_bytes) + msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_SERVICES, id=search_id, query=query) + msg_bytes = OEFSerializer().encode(msg) + self.mailbox.outbox.put_message(to=DEFAULT_OEF, sender=self.crypto.public_key, protocol_id=OEFMessage.protocol_id, message=msg_bytes) def update_services(self) -> None: """ diff --git a/tac/agents/participant/v1/base/dialogues.py b/tac/agents/participant/v1/base/dialogues.py index 8ed50680..11d0776b 100644 --- a/tac/agents/participant/v1/base/dialogues.py +++ b/tac/agents/participant/v1/base/dialogues.py @@ -34,8 +34,6 @@ from tac.aea.dialogue.base import Dialogues as BaseDialogues from tac.aea.mail.messages import FIPAMessage, Message, Address -from tac.aea.mail.protocol import Envelope - Action = Any logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index a11b7987..2f69a09a 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -29,11 +29,11 @@ import logging from typing import Any -from tac.aea.mail.protocol import Envelope, JSONSerializer +from tac.aea.mail.protocol import Envelope from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, Message, Address +from tac.aea.mail.messages import OEFMessage, Message from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer @@ -73,7 +73,7 @@ def handle_dialogue_message(self, envelope: Envelope) -> None: :return: None """ - message = FIPASerializer().decode(envelope.message) # type: # Message + message = FIPASerializer().decode(envelope.message) # type: Message logger.debug("Handling Dialogue message. type={}".format(type(message.get("performative")))) if self.dialogues.is_belonging_to_registered_dialogue(message, self.crypto.public_key, envelope.sender): self.on_existing_dialogue(message, envelope.sender) diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index 5ea4e457..6aa26867 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -208,8 +208,8 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: :return: a Decline, or an Accept and a Transaction, or a Transaction (in a Message object) """ assert accept.get("performative") == FIPAMessage.Performative.ACCEPT \ - and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ - and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_proposals \ + and accept.get("target") in self.game_instance.transaction_manager.pending_proposals[dialogue.dialogue_label] logger.debug("[{}]: on_accept: msg_id={}, dialogue_id={}, origin={}, target={}" .format(self.agent_name, accept.get("id"), accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, accept.get("target"))) new_msg_id = accept.get("id") + 1 @@ -251,8 +251,8 @@ def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Env :return: a Transaction """ assert match_accept.get("performative") == FIPAMessage.Performative.MATCH_ACCEPT \ - and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ - and match_accept.get("target") in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label] + and dialogue.dialogue_label in self.game_instance.transaction_manager.pending_initial_acceptances \ + and match_accept.get("target") in self.game_instance.transaction_manager.pending_initial_acceptances[dialogue.dialogue_label] logger.debug("[{}]: on_match_accept: msg_id={}, dialogue_id={}, origin={}, target={}" .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, match_accept.get("target"))) results = [] diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 73ffca33..95954358 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -306,12 +306,11 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel dialogue = self.game_instance.dialogues.create_self_initiated(agent_pbk, self.crypto.public_key, not is_searching_for_sellers) cfp = FIPAMessage(message_id=STARTING_MESSAGE_ID, dialogue_id=dialogue.dialogue_label.dialogue_id, target=STARTING_MESSAGE_TARGET, performative=FIPAMessage.Performative.CFP, query=json.dumps(services).encode('utf-8')) + dialogue.outgoing_extend([cfp]) cfp_bytes = FIPASerializer().encode(cfp) - envelope = Envelope(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp_bytes) logger.debug("[{}]: send_cfp_as_{}: msg_id={}, dialogue_id={}, destination={}, target={}, services={}" - .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), envelope.to, cfp.get("target"), services)) - dialogue.outgoing_extend([cfp]) - self.mailbox.outbox.put(envelope) + .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), agent_pbk, cfp.get("target"), services)) + self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp_bytes) def _register_to_tac(self, controller_pbk: str) -> None: """ From c72265b6831a756e22c354cd5b0314996cb4ecd1 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 12:10:01 +0100 Subject: [PATCH 061/107] fix controller handler. --- tac/agents/controller/base/reactions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tac/agents/controller/base/reactions.py b/tac/agents/controller/base/reactions.py index 15cae60f..c86f7efe 100644 --- a/tac/agents/controller/base/reactions.py +++ b/tac/agents/controller/base/reactions.py @@ -30,6 +30,7 @@ from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.interfaces import OEFReactionInterface logger = logging.getLogger(__name__) @@ -54,24 +55,26 @@ def __init__(self, crypto: Crypto, liveness: Liveness, mailbox: MailBox, agent_n self.mailbox = mailbox self.agent_name = agent_name - def on_oef_error(self, oef_error: Envelope) -> None: + def on_oef_error(self, envelope: Envelope) -> None: """ Handle an OEF error message. - :param oef_error: the oef error + :param envelope: the oef error :return: None """ + oef_error = OEFSerializer().decode(envelope.message) logger.error("[{}]: Received OEF error: answer_id={}, operation={}" - .format(self.agent_name, oef_error.message.get("id"), oef_error.message.get("operation"))) + .format(self.agent_name, oef_error.get("id"), oef_error.get("operation"))) - def on_dialogue_error(self, dialogue_error: Envelope) -> None: + def on_dialogue_error(self, envelope: Envelope) -> None: """ Handle a dialogue error message. - :param dialogue_error: the dialogue error message + :param envelope: the dialogue error message :return: None """ + dialogue_error = OEFSerializer().decode(envelope.message) logger.error("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.message.get("id"), dialogue_error.message.get("dialogue_id"), dialogue_error.message.get("origin"))) + .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) From f7468313b2ce8a870e846154e19e1b786bfc91b6 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 12:10:16 +0100 Subject: [PATCH 062/107] Finish linting error cleanup. --- sandbox/playground.py | 1 + tac/aea/protocols/oef/serialization.py | 43 +++++++++++++++++++++- tac/agents/participant/v1/base/handlers.py | 2 +- tests/test_mail.py | 2 +- tests/test_messages/test_base.py | 1 - tests/test_messages/test_fipa.py | 9 ++--- 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/sandbox/playground.py b/sandbox/playground.py index bdeaf9a6..fb8d1c77 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -32,6 +32,7 @@ import docker +from tac.aea.dialogue.base import Dialogue from tac.aea.mail.messages import FIPAMessage from tac.aea.mail.protocol import Envelope from tac.aea.protocols.fipa.serialization import FIPASerializer diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py index 6475637b..16ea5f97 100644 --- a/tac/aea/protocols/oef/serialization.py +++ b/tac/aea/protocols/oef/serialization.py @@ -48,6 +48,11 @@ def __init__(self, c: ConstraintExpr): self.c = c def to_json(self) -> Dict: + """ + Convert to json. + + :return: the dictionary + """ result = {} if isinstance(self.c, And) or isinstance(self.c, Or): wraps = [ConstraintWrapper(subc).to_json() for subc in self.c.constraints] @@ -65,7 +70,13 @@ def to_json(self) -> Dict: return result @classmethod - def from_json(cls, d: Dict): + def from_json(cls, d: Dict) -> Constraint: + """ + Convert from json. + + :param d: the dictionary. + :return: the constraint + """ if "and" in d: return And([ConstraintWrapper.from_json(subc) for subc in d["and"]]) elif "or" in d: @@ -81,9 +92,19 @@ class QueryWrapper: """Make the query object pickable.""" def __init__(self, q: Query): + """ + Initialize. + + :param q: the query + """ self.q = q def to_json(self) -> Dict: + """ + Convert to json. + + :return: the dictionary + """ result = {} if self.q.model: result["data_model"] = base58.b58encode(self.q.model.to_pb().SerializeToString()).decode("utf-8") @@ -93,7 +114,13 @@ def to_json(self) -> Dict: return result @classmethod - def from_json(self, d: Dict): + def from_json(self, d: Dict) -> Query: + """ + Convert from json. + + :param d: the dictionary. + :return: the query + """ if d["data_model"]: data_model_pb = dap_interface_pb2.ValueMessage.DataModel() data_model_pb.ParseFromString(base58.b58decode(d["data_model"])) @@ -109,6 +136,12 @@ class OEFSerializer(Serializer): """Serialization for the FIPA protocol.""" def encode(self, msg: Message) -> bytes: + """ + Decode the message. + + :param msg: the message object + :return: the bytes + """ oef_type = OEFMessage.Type(msg.get("type")) new_body = copy.copy(msg.body) @@ -137,6 +170,12 @@ def encode(self, msg: Message) -> bytes: return oef_message_bytes def decode(self, obj: bytes) -> Message: + """ + Decode the message. + + :param obj: the bytes object + :return: the message + """ json_msg = json.loads(obj.decode("utf-8")) oef_type = OEFMessage.Type(json_msg["type"]) new_body = copy.copy(json_msg) diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index fec43b09..ec5350ba 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -32,7 +32,7 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage +from tac.aea.mail.messages import OEFMessage, Message from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer diff --git a/tests/test_mail.py b/tests/test_mail.py index 1cd4d1d8..d8beebae 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,7 +20,7 @@ """This module contains tests for the mail module.""" import time -from tac.aea.mail.messages import FIPAMessage, ByteMessage, DefaultMessage # OEFMessage +from tac.aea.mail.messages import FIPAMessage, DefaultMessage from tac.aea.mail.oef import OEFNetworkMailBox from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index 38e497fd..65267b64 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -32,7 +32,6 @@ def setup_class(cls): def test_default_protobuf_serialization(self): """Test that the default Protobuf serialization works.""" - message_bytes = ProtobufSerializer().encode(self.message) envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) envelope_bytes = envelope.encode() diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py index f7cfc6bc..17842747 100644 --- a/tests/test_messages/test_fipa.py +++ b/tests/test_messages/test_fipa.py @@ -18,7 +18,7 @@ # ------------------------------------------------------------------------------ """This module contains the tests for the FIPA protocol.""" -from oef.schema import Description, DataModel, AttributeSchema +from oef.schema import Description from tac.aea.mail.messages import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer @@ -27,7 +27,7 @@ def test_fipa_cfp_serialization(): - """Test that the serialization for the 'fipa' protocol works..""" + """Test that the serialization for the 'fipa' protocol works.""" msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query={"foo": "bar"}) msg_bytes = FIPASerializer().encode(msg) envelope = Envelope(to="receiver", sender="sender", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) @@ -43,10 +43,9 @@ def test_fipa_cfp_serialization(): def test_fipa_propose_serialization(): - """Test that the serialization for the 'fipa' protocol works..""" - + """Test that the serialization for the 'fipa' protocol works.""" proposal = [ - Description({"foo1": 1, "bar1": 2}), # DataModel("dm_bar", [AttributeSchema("foo1", int, True), AttributeSchema("bar1", int, True)])) + Description({"foo1": 1, "bar1": 2}), # DataModel("dm_bar", [AttributeSchema("foo1", int, True), AttributeSchema("bar1", int, True)])) Description({"foo2": 1, "bar2": 2}), ] msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=proposal) From 4ac607e4dff09de9938835f6f12da1db120f5d26 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 12:20:33 +0100 Subject: [PATCH 063/107] fix test localnode. --- tests/test_localnode.py | 58 ++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/tests/test_localnode.py b/tests/test_localnode.py index ede8e6f0..0e739b58 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -22,8 +22,11 @@ import time from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.messages import ByteMessage, FIPAMessage +from tac.aea.mail.messages import ByteMessage, FIPAMessage, DefaultMessage from tac.aea.mail.oef import OEFMailBox +from tac.aea.mail.protocol import Envelope, JSONSerializer +from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer def test_connection(): @@ -48,27 +51,52 @@ def test_communication(): mailbox1.connect() mailbox2.connect() - mailbox1.send(ByteMessage("mailbox2", "mailbox1", message_id=0, dialogue_id=0, content=b"hello")) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None)) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[])) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT)) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE)) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) time.sleep(1.0) - msg = mailbox2.inbox.get(block=True, timeout=1.0) + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = DefaultSerializer().decode(envelope.message) + assert envelope.protocol_id == "default" assert msg.get("content") == b"hello" - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.CFP - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.PROPOSE - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.ACCEPT - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.DECLINE mailbox1.disconnect() From e5e7b7cc8d801b07e76b2839d087f9039e5738fc Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 12:21:48 +0100 Subject: [PATCH 064/107] Start fixing localnode. --- tac/aea/mail/messages.py | 22 ++-------------------- tests/test_localnode.py | 4 ++-- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index 8062d18b..5f371fb1 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -184,31 +184,13 @@ def check_consistency(self) -> bool: return True -class ByteMessage(Message): - """The Byte message class.""" - - protocol_id = "bytes" - - def __init__(self, message_id: Optional[int] = None, - dialogue_id: Optional[int] = None, - content: bytes = b""): - """ - Initialize. - - :param message_id: the message id. - :param dialogue_id: the dialogue id. - :param content: the message content. - """ - super().__init__(id=message_id, dialogue_id=dialogue_id, content=content) - - class DefaultMessage(Message): - """The Simple message class.""" + """The Default message class.""" protocol_id = "default" class Type(Enum): - """Simple message types.""" + """Default message types.""" BYTES = "bytes" ERROR = "error" diff --git a/tests/test_localnode.py b/tests/test_localnode.py index ede8e6f0..03c37baa 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -22,7 +22,7 @@ import time from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.messages import ByteMessage, FIPAMessage +from tac.aea.mail.messages import DefaultMessage, FIPAMessage from tac.aea.mail.oef import OEFMailBox @@ -48,7 +48,7 @@ def test_communication(): mailbox1.connect() mailbox2.connect() - mailbox1.send(ByteMessage("mailbox2", "mailbox1", message_id=0, dialogue_id=0, content=b"hello")) + mailbox1.send(DefaultMessage("mailbox2", "mailbox1", type=DefaultMessage.Type.BYTES, content=b"hello")) mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None)) mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[])) mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT)) From f2f5d508bd18ac70171093b36c187c731692f9c9 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 12:20:33 +0100 Subject: [PATCH 065/107] fix test localnode. --- tests/test_localnode.py | 58 ++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/tests/test_localnode.py b/tests/test_localnode.py index ede8e6f0..852febbe 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -22,8 +22,11 @@ import time from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.messages import ByteMessage, FIPAMessage +from tac.aea.mail.messages import FIPAMessage, DefaultMessage from tac.aea.mail.oef import OEFMailBox +from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer def test_connection(): @@ -48,27 +51,52 @@ def test_communication(): mailbox1.connect() mailbox2.connect() - mailbox1.send(ByteMessage("mailbox2", "mailbox1", message_id=0, dialogue_id=0, content=b"hello")) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.CFP, query=None)) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[])) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.ACCEPT)) - mailbox1.send(FIPAMessage("mailbox2", "mailbox1", 0, 0, 0, FIPAMessage.Performative.DECLINE)) + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) time.sleep(1.0) - msg = mailbox2.inbox.get(block=True, timeout=1.0) + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = DefaultSerializer().decode(envelope.message) + assert envelope.protocol_id == "default" assert msg.get("content") == b"hello" - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.CFP - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.PROPOSE - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.ACCEPT - msg = mailbox2.inbox.get(block=True, timeout=1.0) - assert msg.protocol_id == "fipa" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" assert msg.get("performative") == FIPAMessage.Performative.DECLINE mailbox1.disconnect() From b81867d26fcad719fee6254098bbc94ec5caca98 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 14:12:59 +0100 Subject: [PATCH 066/107] remove dependencies on the SDK internal objects. --- tac/aea/mail/oef.py | 57 +++------ tac/agents/controller/agent.py | 4 +- tac/agents/participant/v1/agent.py | 4 +- tac/agents/participant/v2/agent.py | 4 +- templates/v1/expert.py | 4 +- tests/test_agent/test_agent_state.py | 4 +- tests/test_agent/test_misc.py | 4 +- tests/test_localnode.py | 166 +++++++++++++-------------- tests/test_mail.py | 6 +- 9 files changed, 113 insertions(+), 140 deletions(-) diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 49d8596e..01228657 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -24,14 +24,11 @@ import logging from queue import Empty, Queue from threading import Thread -from typing import List, Dict +from typing import List, Dict, Optional -from oef.agents import Agent -from oef.core import OEFProxy +from oef.agents import OEFAgent from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES -from oef.proxy import OEFNetworkProxy -from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy from tac.aea.mail.base import Connection, MailBox from tac.aea.mail.messages import OEFMessage, FIPAMessage from tac.aea.mail.protocol import Envelope @@ -91,17 +88,19 @@ def search_end(self, search_id: int, nb_search_results: int) -> None: self._search_result_counts[search_id] = nb_search_results -class OEFChannel(Agent): +class OEFChannel(OEFAgent): """The OEFChannel connects the OEF Agent with the connection.""" - def __init__(self, oef_proxy: OEFProxy, in_queue: Queue): + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, in_queue: Optional[Queue] = None): """ Initialize. - :param oef_proxy: the OEFProxy. + :param public_key: the public key of the agent. + :param oef_addr: the OEF IP address. + :param oef_port: the OEF port. :param in_queue: the in queue. """ - super().__init__(oef_proxy) + super().__init__(public_key, oef_addr, oef_port) self.in_queue = in_queue self.mail_stats = MailStats() @@ -352,7 +351,7 @@ def send_default_message(self, envelope: Envelope): class OEFConnection(Connection): """The OEFConnection connects the to the mailbox.""" - def __init__(self, oef_proxy: OEFProxy): + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): """ Initialize. @@ -360,7 +359,7 @@ def __init__(self, oef_proxy: OEFProxy): """ super().__init__() - self.bridge = OEFChannel(oef_proxy, self.in_queue) + self.bridge = OEFChannel(public_key, oef_addr, oef_port, self.in_queue) self._stopped = True self.in_thread = Thread(target=self.bridge.run) @@ -424,44 +423,18 @@ def send(self, envelope: Envelope): class OEFMailBox(MailBox): """The OEF mail box.""" - def __init__(self, oef_proxy: OEFProxy): + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): """ Initialize. - :param oef_proxy: the oef proxy. + :param public_key: the public key of the agent. + :param oef_addr: the OEF IP address. + :param oef_port: the OEF port. """ - connection = OEFConnection(oef_proxy) + connection = OEFConnection(public_key, oef_addr, oef_port) super().__init__(connection) @property def mail_stats(self) -> MailStats: """Get the mail stats object.""" return self._connection.bridge.mail_stats - - -class OEFNetworkMailBox(OEFMailBox): - """The OEF network mail box.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): - """ - Initialize. - - :param public_key: the public key of the agent. - :param oef_addr: the OEF address. - :param oef_port: the oef port. - """ - super().__init__(OEFNetworkProxy(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop())) - - -class OEFLocalMailBox(OEFMailBox): - """The OEF local mail box.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): - """ - Initialize. - - :param public_key: the public key of the agent. - :param oef_addr: the OEF address. - :param oef_port: the oef port. - """ - super().__init__(OEFLocalProxy(public_key, LocalNode(), loop=asyncio.new_event_loop())) diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 6c2e49fc..8742b77b 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -32,7 +32,7 @@ import dateutil from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox from tac.aea.mail.protocol import Envelope from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters @@ -73,7 +73,7 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) self.oef_handler = OEFHandler(self.crypto, self.liveness, self.mailbox, self.name) self.agent_message_dispatcher = AgentMessageDispatcher(self) diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 2f794d3d..3eff31ca 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -25,7 +25,7 @@ from typing import Optional from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox from tac.aea.mail.protocol import Envelope from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler @@ -67,7 +67,7 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) self._game_instance = GameInstance(name, strategy, self.mailbox.mail_stats, services_interval, pending_transaction_timeout, dashboard) # type: Optional[GameInstance] self.max_reactions = max_reactions diff --git a/tac/agents/participant/v2/agent.py b/tac/agents/participant/v2/agent.py index ff66ab62..174aca1d 100644 --- a/tac/agents/participant/v2/agent.py +++ b/tac/agents/participant/v2/agent.py @@ -25,7 +25,7 @@ from typing import Optional from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox from tac.aea.mail.protocol import Envelope from tac.gui.dashboards.agent import AgentDashboard @@ -54,7 +54,7 @@ def __init__(self, name: str, :param debug: if True, run the agent in debug mode. """ super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) def act(self) -> None: """ diff --git a/templates/v1/expert.py b/templates/v1/expert.py index b2f4055f..beae4cec 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -26,7 +26,7 @@ from typing import Optional from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox logger = logging.getLogger(__name__) @@ -49,7 +49,7 @@ class MyAgent(Agent): def __init__(self, name: str, oef_addr: str, oef_port: int, agent_timeout: float = 1.0, private_key_pem_path: Optional[str] = None): """Agent initialization.""" super().__init__(name, oef_addr, oef_port, private_key_pem_path, agent_timeout) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, oef_addr, oef_port) + self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) raise NotImplementedError("Your agent must implement the interface defined in Agent.") diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 551c5184..4d221240 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -24,7 +24,7 @@ from threading import Thread from tac.aea.agent import Agent, AgentState -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox class TAgent(Agent): @@ -33,7 +33,7 @@ class TAgent(Agent): def __init__(self): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mailbox = OEFMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: """Setup.""" diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 20b10fae..3bc8778e 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -23,7 +23,7 @@ from unittest.mock import MagicMock from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox class TAgent(Agent): @@ -32,7 +32,7 @@ class TAgent(Agent): def __init__(self, **kwargs): """Initialize the test agent.""" super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) - self.mailbox = OEFNetworkMailBox(self.crypto.public_key, "127.0.0.1", 10000) + self.mailbox = OEFMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: """Set up the agent.""" diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 48658793..731e96c7 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -18,86 +18,86 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the local OEF node implementation.""" -import asyncio -import time - -from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.messages import DefaultMessage, FIPAMessage -from tac.aea.mail.oef import OEFMailBox -from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.serialization import FIPASerializer - - -def test_connection(): - """Test that two mailbox can connect to the node.""" - with LocalNode() as node: - mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) - mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) - - mailbox1.connect() - mailbox2.connect() - - mailbox1.disconnect() - mailbox2.disconnect() - - -def test_communication(): - """Test that two mailbox can communicate through the node.""" - with LocalNode() as node: - mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) - mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) - - mailbox1.connect() - mailbox2.connect() - - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - time.sleep(1.0) - - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = DefaultSerializer().decode(envelope.message) - assert envelope.protocol_id == "default" - assert msg.get("content") == b"hello" - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.CFP - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.PROPOSE - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.ACCEPT - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.DECLINE - - mailbox1.disconnect() - mailbox2.disconnect() +# import asyncio +# import time +# +# from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy +# from tac.aea.mail.messages import DefaultMessage, FIPAMessage +# from tac.aea.mail.oef import OEFMailBox +# from tac.aea.mail.protocol import Envelope +# from tac.aea.protocols.default.serialization import DefaultSerializer +# from tac.aea.protocols.fipa.serialization import FIPASerializer +# +# +# def test_connection(): +# """Test that two mailbox can connect to the node.""" +# with LocalNode() as node: +# mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) +# mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) +# +# mailbox1.connect() +# mailbox2.connect() +# +# mailbox1.disconnect() +# mailbox2.disconnect() +# +# +# def test_communication(): +# """Test that two mailbox can communicate through the node.""" +# with LocalNode() as node: +# mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) +# mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) +# +# mailbox1.connect() +# mailbox2.connect() +# +# msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") +# msg_bytes = DefaultSerializer().encode(msg) +# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) +# mailbox1.send(envelope) +# +# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) +# msg_bytes = FIPASerializer().encode(msg) +# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) +# mailbox1.send(envelope) +# +# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) +# msg_bytes = FIPASerializer().encode(msg) +# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) +# mailbox1.send(envelope) +# +# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) +# msg_bytes = FIPASerializer().encode(msg) +# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) +# mailbox1.send(envelope) +# +# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) +# msg_bytes = FIPASerializer().encode(msg) +# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) +# mailbox1.send(envelope) +# +# time.sleep(1.0) +# +# envelope = mailbox2.inbox.get(block=True, timeout=1.0) +# msg = DefaultSerializer().decode(envelope.message) +# assert envelope.protocol_id == "default" +# assert msg.get("content") == b"hello" +# envelope = mailbox2.inbox.get(block=True, timeout=1.0) +# msg = FIPASerializer().decode(envelope.message) +# assert envelope.protocol_id == "fipa" +# assert msg.get("performative") == FIPAMessage.Performative.CFP +# envelope = mailbox2.inbox.get(block=True, timeout=1.0) +# msg = FIPASerializer().decode(envelope.message) +# assert envelope.protocol_id == "fipa" +# assert msg.get("performative") == FIPAMessage.Performative.PROPOSE +# envelope = mailbox2.inbox.get(block=True, timeout=1.0) +# msg = FIPASerializer().decode(envelope.message) +# assert envelope.protocol_id == "fipa" +# assert msg.get("performative") == FIPAMessage.Performative.ACCEPT +# envelope = mailbox2.inbox.get(block=True, timeout=1.0) +# msg = FIPASerializer().decode(envelope.message) +# assert envelope.protocol_id == "fipa" +# assert msg.get("performative") == FIPAMessage.Performative.DECLINE +# +# mailbox1.disconnect() +# mailbox2.disconnect() diff --git a/tests/test_mail.py b/tests/test_mail.py index d8beebae..eb56bbd1 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -21,7 +21,7 @@ import time from tac.aea.mail.messages import FIPAMessage, DefaultMessage -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.mail.oef import OEFMailBox from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer @@ -29,8 +29,8 @@ def test_example(network_node): """Test the mailbox.""" - mailbox1 = OEFNetworkMailBox("mailbox1", "127.0.0.1", 10000) - mailbox2 = OEFNetworkMailBox("mailbox2", "127.0.0.1", 10000) + mailbox1 = OEFMailBox("mailbox1", "127.0.0.1", 10000) + mailbox2 = OEFMailBox("mailbox2", "127.0.0.1", 10000) mailbox1.connect() mailbox2.connect() From 0cb2e4c05ca760ebdd71c934f45083db1d140528 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 15:06:32 +0100 Subject: [PATCH 067/107] fix OEF channel constructor by including asyncio loop param. --- tac/aea/mail/oef.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 01228657..6b9db21f 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -22,6 +22,7 @@ import asyncio import datetime import logging +from asyncio import AbstractEventLoop from queue import Empty, Queue from threading import Thread from typing import List, Dict, Optional @@ -91,7 +92,7 @@ def search_end(self, search_id: int, nb_search_results: int) -> None: class OEFChannel(OEFAgent): """The OEFChannel connects the OEF Agent with the connection.""" - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, in_queue: Optional[Queue] = None): + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, loop: Optional[AbstractEventLoop] = None, in_queue: Optional[Queue] = None): """ Initialize. @@ -100,7 +101,7 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, in_que :param oef_port: the OEF port. :param in_queue: the in queue. """ - super().__init__(public_key, oef_addr, oef_port) + super().__init__(public_key, oef_addr, oef_port, loop=loop) self.in_queue = in_queue self.mail_stats = MailStats() @@ -355,11 +356,13 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): """ Initialize. - :param oef_proxy: the OEFProxy + :param public_key: the public key of the agent. + :param oef_addr: the OEF IP address. + :param oef_port: the OEF port. """ super().__init__() - self.bridge = OEFChannel(public_key, oef_addr, oef_port, self.in_queue) + self.bridge = OEFChannel(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop(), in_queue=self.in_queue) self._stopped = True self.in_thread = Thread(target=self.bridge.run) @@ -398,7 +401,7 @@ def disconnect(self) -> None: """ self._stopped = True if self.bridge.is_active(): - self.bridge.loop.call_soon_threadsafe(self.bridge.stop) + self.bridge.stop() self.in_thread.join() self.out_thread.join() From c4db735785bf66e3d5c97266f586d78bc9815de0 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Wed, 21 Aug 2019 15:29:49 +0100 Subject: [PATCH 068/107] Refactor aea to extract protocols and channels. --- sandbox/playground.py | 4 +- tac/aea/agent.py | 3 +- tac/aea/channel/__init__.py | 22 ++ tac/aea/channel/local.py | 21 ++ tac/aea/{mail => channel}/oef.py | 6 +- tac/aea/dialogue/base.py | 2 +- tac/aea/{protocols => mail}/base.proto | 0 tac/aea/mail/base.py | 120 +++++++- tac/aea/{protocols => mail}/base_pb2.py | 0 tac/aea/mail/messages.py | 266 ------------------ tac/aea/mail/protocol.py | 241 ---------------- tac/aea/protocols/base/__init__.py | 21 ++ tac/aea/protocols/base/message.py | 87 ++++++ tac/aea/protocols/base/serialization.py | 105 +++++++ tac/aea/protocols/default/__init__.py | 2 +- tac/aea/protocols/default/message.py | 46 +++ tac/aea/protocols/default/serialization.py | 5 +- tac/aea/protocols/fipa/message.py | 89 ++++++ tac/aea/protocols/fipa/serialization.py | 5 +- tac/aea/protocols/oef/__init__.py | 21 ++ tac/aea/protocols/oef/message.py | 116 ++++++++ tac/aea/protocols/oef/serialization.py | 7 +- tac/aea/protocols/tac/__init__.py | 21 ++ tac/aea/protocols/tac/message.py | 133 +++++++++ tac/aea/protocols/tac/serialization.py | 65 +++++ tac/agents/controller/agent.py | 4 +- tac/agents/controller/base/actions.py | 2 +- tac/agents/controller/base/handlers.py | 6 +- tac/agents/controller/base/interfaces.py | 2 +- tac/agents/controller/base/reactions.py | 3 +- tac/agents/participant/v1/agent.py | 4 +- tac/agents/participant/v1/base/actions.py | 3 +- tac/agents/participant/v1/base/dialogues.py | 4 +- .../participant/v1/base/game_instance.py | 2 +- tac/agents/participant/v1/base/handlers.py | 6 +- tac/agents/participant/v1/base/helpers.py | 2 +- tac/agents/participant/v1/base/interfaces.py | 4 +- .../v1/base/negotiation_behaviours.py | 7 +- tac/agents/participant/v1/base/reactions.py | 7 +- .../participant/v1/base/stats_manager.py | 2 +- tac/agents/participant/v2/agent.py | 4 +- tests/test_agent/test_agent_state.py | 2 +- tests/test_agent/test_misc.py | 2 +- tests/test_controller.py | 4 +- tests/test_localnode.py | 7 +- tests/test_mail.py | 7 +- tests/test_messages/test_base.py | 5 +- tests/test_messages/test_fipa.py | 5 +- tests/test_messages/test_simple.py | 4 +- 49 files changed, 936 insertions(+), 570 deletions(-) create mode 100644 tac/aea/channel/__init__.py create mode 100644 tac/aea/channel/local.py rename tac/aea/{mail => channel}/oef.py (99%) rename tac/aea/{protocols => mail}/base.proto (100%) rename tac/aea/{protocols => mail}/base_pb2.py (100%) delete mode 100644 tac/aea/mail/messages.py delete mode 100644 tac/aea/mail/protocol.py create mode 100644 tac/aea/protocols/base/__init__.py create mode 100644 tac/aea/protocols/base/message.py create mode 100644 tac/aea/protocols/base/serialization.py create mode 100644 tac/aea/protocols/default/message.py create mode 100644 tac/aea/protocols/fipa/message.py create mode 100644 tac/aea/protocols/oef/__init__.py create mode 100644 tac/aea/protocols/oef/message.py create mode 100644 tac/aea/protocols/tac/__init__.py create mode 100644 tac/aea/protocols/tac/message.py create mode 100644 tac/aea/protocols/tac/serialization.py diff --git a/sandbox/playground.py b/sandbox/playground.py index fb8d1c77..05f5ff1a 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -33,8 +33,8 @@ import docker from tac.aea.dialogue.base import Dialogue -from tac.aea.mail.messages import FIPAMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.examples.baseline import BaselineAgent from tac.agents.participant.v1.examples.strategy import BaselineStrategy diff --git a/tac/aea/agent.py b/tac/aea/agent.py index fbfa8d9c..6f72eb32 100644 --- a/tac/aea/agent.py +++ b/tac/aea/agent.py @@ -27,9 +27,8 @@ from enum import Enum from typing import Dict, Optional -from tac.aea.mail.base import InBox, OutBox, MailBox -from tac.aea.mail.messages import ProtocolId from tac.aea.crypto.base import Crypto +from tac.aea.mail.base import InBox, OutBox, MailBox, ProtocolId logger = logging.getLogger(__name__) diff --git a/tac/aea/channel/__init__.py b/tac/aea/channel/__init__.py new file mode 100644 index 00000000..e5cae999 --- /dev/null +++ b/tac/aea/channel/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the channel modules.""" diff --git a/tac/aea/channel/local.py b/tac/aea/channel/local.py new file mode 100644 index 00000000..50e83be9 --- /dev/null +++ b/tac/aea/channel/local.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Extension to the Local Node.""" diff --git a/tac/aea/mail/oef.py b/tac/aea/channel/oef.py similarity index 99% rename from tac/aea/mail/oef.py rename to tac/aea/channel/oef.py index 49d8596e..09933fbd 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/channel/oef.py @@ -32,9 +32,9 @@ from oef.proxy import OEFNetworkProxy from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.base import Connection, MailBox -from tac.aea.mail.messages import OEFMessage, FIPAMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Connection, MailBox, Envelope +from tac.aea.protocols.fipa.message import FIPAMessage +from tac.aea.protocols.oef.message import OEFMessage from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer diff --git a/tac/aea/dialogue/base.py b/tac/aea/dialogue/base.py index a63361c6..22d2c7db 100644 --- a/tac/aea/dialogue/base.py +++ b/tac/aea/dialogue/base.py @@ -30,7 +30,7 @@ from typing import Dict, List, TYPE_CHECKING if TYPE_CHECKING: - from tac.aea.mail.messages import Message + from tac.aea.protocols.base.message import Message class DialogueLabel: diff --git a/tac/aea/protocols/base.proto b/tac/aea/mail/base.proto similarity index 100% rename from tac/aea/protocols/base.proto rename to tac/aea/mail/base.proto diff --git a/tac/aea/mail/base.py b/tac/aea/mail/base.py index 2460d4c2..dd43cb79 100644 --- a/tac/aea/mail/base.py +++ b/tac/aea/mail/base.py @@ -25,11 +25,127 @@ from queue import Queue from typing import Optional -from tac.aea.mail.messages import Address, ProtocolId -from tac.aea.mail.protocol import Envelope +from tac.aea.mail import base_pb2 logger = logging.getLogger(__name__) +Address = str +ProtocolId = str + + +class Envelope: + """The top level message class.""" + + def __init__(self, to: Optional[Address] = None, + sender: Optional[Address] = None, + protocol_id: Optional[ProtocolId] = None, + message: Optional[bytes] = b""): + """ + Initialize a Message object. + + :param to: the public key of the receiver. + :param sender: the public key of the sender. + :param protocol_id: the protocol id. + :param message: the protocol-specific message + """ + self._to = to + self._sender = sender + self._protocol_id = protocol_id + self._message = message + assert type(self._to) == str or self._to is None + try: + if self._to is not None and type(self._to) == str: + self._to.encode('utf-8') + except Exception: + assert False + + @property + def to(self) -> Address: + """Get public key of receiver.""" + return self._to + + @to.setter + def to(self, to: Address) -> None: + """Set public key of receiver.""" + self._to = to + + @property + def sender(self) -> Address: + """Get public key of sender.""" + return self._sender + + @sender.setter + def sender(self, sender: Address) -> None: + """Set public key of sender.""" + self._sender = sender + + @property + def protocol_id(self) -> Optional[ProtocolId]: + """Get protocol id.""" + return self._protocol_id + + @protocol_id.setter + def protocol_id(self, protocol_id: ProtocolId) -> None: + """Set the protocol id.""" + self._protocol_id = protocol_id + + @property + def message(self) -> bytes: + """Get the Message.""" + return self._message + + @message.setter + def message(self, message: bytes) -> None: + """Set the message.""" + self._message = message + + def __eq__(self, other): + """Compare with another object.""" + return isinstance(other, Envelope) \ + and self.to == other.to \ + and self.sender == other.sender \ + and self.protocol_id == other.protocol_id \ + and self._message == other._message + + def encode(self) -> bytes: + """ + Encode the envelope. + + :return: the encoded envelope. + """ + envelope = self + envelope_pb = base_pb2.Envelope() + if envelope.to is not None: + envelope_pb.to = envelope.to + if envelope.sender is not None: + envelope_pb.sender = envelope.sender + if envelope.protocol_id is not None: + envelope_pb.protocol_id = envelope.protocol_id + if envelope.message is not None: + envelope_pb.message = envelope.message + + envelope_bytes = envelope_pb.SerializeToString() + return envelope_bytes + + @classmethod + def decode(cls, envelope_bytes: bytes) -> 'Envelope': + """ + Decode the envelope. + + :param envelope_bytes: the bytes to be decoded. + :return: the decoded envelope. + """ + envelope_pb = base_pb2.Envelope() + envelope_pb.ParseFromString(envelope_bytes) + + to = envelope_pb.to if envelope_pb.to else None + sender = envelope_pb.sender if envelope_pb.sender else None + protocol_id = envelope_pb.protocol_id if envelope_pb.protocol_id else None + message = envelope_pb.message if envelope_pb.message else None + + envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) + return envelope + class InBox(object): """A queue from where you can only consume messages.""" diff --git a/tac/aea/protocols/base_pb2.py b/tac/aea/mail/base_pb2.py similarity index 100% rename from tac/aea/protocols/base_pb2.py rename to tac/aea/mail/base_pb2.py diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py deleted file mode 100644 index 5f371fb1..00000000 --- a/tac/aea/mail/messages.py +++ /dev/null @@ -1,266 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the messages definitions.""" -from copy import copy -from enum import Enum -from typing import Optional, Any, Union, Dict - -from oef.messages import OEFErrorOperation -from oef.query import Query -from oef.schema import Description - -# from tac.aea.dialogue.base import DialogueLabel - -Address = str -ProtocolId = str - - -class Message: - """This class implements a message.""" - - def __init__(self, body: Optional[Dict] = None, - **kwargs): - """ - Initialize a Message object. - - :param body: the dictionary of values to hold. - """ - self._body = copy(body) if body else {} # type: Dict[str, Any] - self._body.update(kwargs) - - @property - def body(self) -> Dict: - """ - Get the body of the message (in dictionary form). - - :return: the body - """ - return self._body - - @body.setter - def body(self, body: Dict) -> None: - """ - Set the body of hte message. - - :param body: the body. - :return: None - """ - self._body = body - - def set(self, key: str, value: Any) -> None: - """ - Set key and value pair. - - :param key: the key. - :param value: the value. - :return: None - """ - self._body[key] = value - - def get(self, key: str) -> Optional[Any]: - """Get value for key.""" - return self._body.get(key, None) - - def unset(self, key: str) -> None: - """Unset valye for key.""" - self._body.pop(key, None) - - def is_set(self, key: str) -> bool: - """Check value is set for key.""" - return key in self._body - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - return True - - def __eq__(self, other): - """Compare with another object.""" - return isinstance(other, Message) \ - and self.body == other.body - - -class OEFMessage(Message): - """The OEF message class.""" - - protocol_id = "oef" - - class Type(Enum): - """OEF Message types.""" - - REGISTER_SERVICE = "register_service" - REGISTER_AGENT = "register_agent" - UNREGISTER_SERVICE = "unregister_service" - UNREGISTER_AGENT = "unregister_agent" - SEARCH_SERVICES = "search_services" - SEARCH_AGENTS = "search_agents" - OEF_ERROR = "oef_error" - DIALOGUE_ERROR = "dialogue_error" - SEARCH_RESULT = "search_result" - - def __str__(self): - """Get string representation.""" - return self.value - - def __init__(self, oef_type: Optional[Type] = None, - **kwargs): - """ - Initialize. - - :param oef_type: the type of OEF message. - """ - super().__init__(type=str(oef_type), **kwargs) - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - try: - assert self.is_set("type") - oef_type = OEFMessage.Type(self.get("type")) - if oef_type == OEFMessage.Type.REGISTER_SERVICE: - service_description = self.get("service_description") - assert self.is_set("id") - assert self.is_set("service_id") - service_id = self.get("service_id") - assert isinstance(service_description, Description) - assert isinstance(service_id, str) - elif oef_type == OEFMessage.Type.REGISTER_AGENT: - assert self.is_set("id") - assert self.is_set("agent_description") - agent_description = self.get("agent_description") - assert isinstance(agent_description, Description) - elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - assert self.is_set("id") - assert self.is_set("service_id") - service_id = self.get("service_id") - assert isinstance(service_id, str) - elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - assert self.is_set("id") - elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - assert self.is_set("id") - assert self.is_set("query") - query = self.get("query") - assert isinstance(query, Query) - elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - assert self.is_set("id") - assert self.is_set("query") - query = self.get("query") - assert isinstance(query, Query) - elif oef_type == OEFMessage.Type.SEARCH_RESULT: - assert self.is_set("id") - assert self.is_set("agents") - agents = self.get("agents") - assert type(agents) == list and all(type(a) == str for a in agents) - elif oef_type == OEFMessage.Type.OEF_ERROR: - assert self.is_set("id") - assert self.is_set("operation") - operation = self.get("operation") - assert operation in set(OEFErrorOperation) - elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: - assert self.is_set("id") - assert self.is_set("dialogue_id") - assert self.is_set("origin") - else: - raise ValueError("Type not recognized.") - except (AssertionError, ValueError): - return False - - return True - - -class DefaultMessage(Message): - """The Default message class.""" - - protocol_id = "default" - - class Type(Enum): - """Default message types.""" - - BYTES = "bytes" - ERROR = "error" - - def __init__(self, type: Optional[Type] = None, - **kwargs): - """ - Initialize. - - :param type: the type. - """ - super().__init__(type=type, **kwargs) - - -class FIPAMessage(Message): - """The FIPA message class.""" - - protocol_id = "fipa" - - class Performative(Enum): - """FIPA performatives.""" - - CFP = "cfp" - PROPOSE = "propose" - ACCEPT = "accept" - MATCH_ACCEPT = "match_accept" - DECLINE = "decline" - - def __str__(self): - """Get string representation.""" - return self.value - - def __init__(self, message_id: Optional[int] = None, - dialogue_id: Optional[int] = None, - target: Optional[int] = None, - performative: Optional[Union[str, Performative]] = None, - **kwargs): - """ - Initialize. - - :param message_id: the message id. - :param dialogue_id: the dialogue id. - :param target: the message target. - :param performative: the message performative. - """ - super().__init__(id=message_id, - dialogue_id=dialogue_id, - target=target, - performative=FIPAMessage.Performative(performative), - **kwargs) - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - try: - assert self.is_set("target") - performative = FIPAMessage.Performative(self.get("performative")) - if performative == FIPAMessage.Performative.CFP: - query = self.get("query") - assert isinstance(query, Query) or isinstance(query, bytes) or query is None - elif performative == FIPAMessage.Performative.PROPOSE: - proposal = self.get("proposal") - assert type(proposal) == list and all(isinstance(d, Description) or type(d) == bytes for d in proposal) - elif performative == FIPAMessage.Performative.ACCEPT \ - or performative == FIPAMessage.Performative.MATCH_ACCEPT \ - or performative == FIPAMessage.Performative.DECLINE: - pass - else: - raise ValueError("Performative not recognized.") - - except (AssertionError, ValueError, KeyError): - return False - - return True diff --git a/tac/aea/mail/protocol.py b/tac/aea/mail/protocol.py deleted file mode 100644 index bcb7c1db..00000000 --- a/tac/aea/mail/protocol.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Protocol module v2.""" -import json -from abc import abstractmethod, ABC -from typing import List, Optional - -from google.protobuf.struct_pb2 import Struct - -from tac.aea.mail.messages import Message, Address, ProtocolId -from tac.aea.protocols import base_pb2 - - -class Encoder(ABC): - """Encoder interface.""" - - @abstractmethod - def encode(self, msg: Message) -> bytes: - """ - Encode a message. - - :param msg: the message to be encoded. - :return: the encoded message. - """ - - -class Decoder(ABC): - """Decoder interface.""" - - @abstractmethod - def decode(self, obj: bytes) -> Message: - """ - Decode a message. - - :param obj: the sequence of bytes to be decoded. - :return: the decoded message. - """ - - -class Serializer(Encoder, Decoder, ABC): - """The implementations of this class defines a serialization layer for a protocol.""" - - -class ProtobufSerializer(Serializer): - """ - Default Protobuf serializer. - - It assumes that the Message contains a JSON-serializable body. - """ - - def encode(self, msg: Message) -> bytes: - """Encode a message into bytes using Protobuf.""" - body_json = Struct() - body_json.update(msg.body) - body_bytes = body_json.SerializeToString() - return body_bytes - - def decode(self, obj: bytes) -> Message: - """Decode bytes into a message using Protobuf.""" - body_json = Struct() - body_json.ParseFromString(obj) - - body = dict(body_json) - msg = Message(body=body) - return msg - - -class JSONSerializer(Serializer): - """Default serialization in JSON for the Message object.""" - - def encode(self, msg: Message) -> bytes: - """ - Encode a message into bytes using JSON format. - - :param msg: the message to be encoded. - :return: the serialized message. - """ - bytes_msg = json.dumps(msg.body).encode("utf-8") - return bytes_msg - - def decode(self, obj: bytes) -> Message: - """ - Decode bytes into a message using JSON. - - :param obj: the serialized message. - :return: the decoded message. - """ - json_msg = json.loads(obj.decode("utf-8")) - return Message(json_msg) - - -class Envelope: - """The message class.""" - - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - protocol_id: Optional[ProtocolId] = None, - message: Optional[bytes] = b""): - """ - Initialize a Message object. - - :param to: the public key of the receiver. - :param sender: the public key of the sender. - :param protocol_id: the protocol id. - :param message: the protocol-specific message - """ - self._to = to - self._sender = sender - self._protocol_id = protocol_id - self._message = message - assert type(self._to) == str or self._to is None - try: - if self._to is not None and type(self._to) == str: - self._to.encode('utf-8') - except Exception: - assert False - - @property - def to(self) -> Address: - """Get public key of receiver.""" - return self._to - - @to.setter - def to(self, to: Address) -> None: - """Set public key of receiver.""" - self._to = to - - @property - def sender(self) -> Address: - """Get public key of sender.""" - return self._sender - - @sender.setter - def sender(self, sender: Address) -> None: - """Set public key of sender.""" - self._sender = sender - - @property - def protocol_id(self) -> Optional[ProtocolId]: - """Get protocol id.""" - return self._protocol_id - - @protocol_id.setter - def protocol_id(self, protocol_id: ProtocolId) -> None: - """Set the protocol id.""" - self._protocol_id = protocol_id - - @property - def message(self) -> bytes: - """Get the Message.""" - return self._message - - @message.setter - def message(self, message: bytes) -> None: - """Set the message.""" - self._message = message - - def __eq__(self, other): - """Compare with another object.""" - return isinstance(other, Envelope) \ - and self.to == other.to \ - and self.sender == other.sender \ - and self.protocol_id == other.protocol_id \ - and self._message == other._message - - def encode(self) -> bytes: - """ - Encode the envelope. - - :return: the encoded envelope. - """ - envelope = self - envelope_pb = base_pb2.Envelope() - if envelope.to is not None: - envelope_pb.to = envelope.to - if envelope.sender is not None: - envelope_pb.sender = envelope.sender - if envelope.protocol_id is not None: - envelope_pb.protocol_id = envelope.protocol_id - if envelope.message is not None: - envelope_pb.message = envelope.message - - envelope_bytes = envelope_pb.SerializeToString() - return envelope_bytes - - @classmethod - def decode(cls, envelope_bytes: bytes) -> 'Envelope': - """ - Decode the envelope. - - :param envelope_bytes: the bytes to be decoded. - :return: the decoded envelope. - """ - envelope_pb = base_pb2.Envelope() - envelope_pb.ParseFromString(envelope_bytes) - - to = envelope_pb.to if envelope_pb.to else None - sender = envelope_pb.sender if envelope_pb.sender else None - protocol_id = envelope_pb.protocol_id if envelope_pb.protocol_id else None - message = envelope_pb.message if envelope_pb.message else None - - envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) - return envelope - - -class Protocol: - """The class that implements a protocol.""" - - def __init__(self, serializer: Serializer): - """ - Define a protocol. - - :param serializer: the serialization layer. - """ - self.serializer = serializer - - @abstractmethod - def is_message_valid(self, envelope: Envelope): - """Determine whether a message is valid.""" - - @abstractmethod - def is_trace_valid(self, trace: List[Envelope]) -> bool: - """Determine whether a sequence of messages follows the protocol.""" diff --git a/tac/aea/protocols/base/__init__.py b/tac/aea/protocols/base/__init__.py new file mode 100644 index 00000000..4f1c59df --- /dev/null +++ b/tac/aea/protocols/base/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the support resources for the base protocol.""" diff --git a/tac/aea/protocols/base/message.py b/tac/aea/protocols/base/message.py new file mode 100644 index 00000000..fbe1e768 --- /dev/null +++ b/tac/aea/protocols/base/message.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the base message definition.""" +from copy import copy +from typing import Any, Dict, Optional + + +class Message: + """This class implements a message.""" + + def __init__(self, body: Optional[Dict] = None, + **kwargs): + """ + Initialize a Message object. + + :param body: the dictionary of values to hold. + """ + self._body = copy(body) if body else {} # type: Dict[str, Any] + self._body.update(kwargs) + + @property + def body(self) -> Dict: + """ + Get the body of the message (in dictionary form). + + :return: the body + """ + return self._body + + @body.setter + def body(self, body: Dict) -> None: + """ + Set the body of hte message. + + :param body: the body. + :return: None + """ + self._body = body + + def set(self, key: str, value: Any) -> None: + """ + Set key and value pair. + + :param key: the key. + :param value: the value. + :return: None + """ + self._body[key] = value + + def get(self, key: str) -> Optional[Any]: + """Get value for key.""" + return self._body.get(key, None) + + def unset(self, key: str) -> None: + """Unset valye for key.""" + self._body.pop(key, None) + + def is_set(self, key: str) -> bool: + """Check value is set for key.""" + return key in self._body + + def check_consistency(self) -> bool: + """Check that the data is consistent.""" + return True + + def __eq__(self, other): + """Compare with another object.""" + return isinstance(other, Message) \ + and self.body == other.body diff --git a/tac/aea/protocols/base/serialization.py b/tac/aea/protocols/base/serialization.py new file mode 100644 index 00000000..4440b4f5 --- /dev/null +++ b/tac/aea/protocols/base/serialization.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Serializer for base.""" +import json +from abc import abstractmethod, ABC + +from google.protobuf.struct_pb2 import Struct + +from tac.aea.protocols.base.message import Message + + +class Encoder(ABC): + """Encoder interface.""" + + @abstractmethod + def encode(self, msg: Message) -> bytes: + """ + Encode a message. + + :param msg: the message to be encoded. + :return: the encoded message. + """ + + +class Decoder(ABC): + """Decoder interface.""" + + @abstractmethod + def decode(self, obj: bytes) -> Message: + """ + Decode a message. + + :param obj: the sequence of bytes to be decoded. + :return: the decoded message. + """ + + +class Serializer(Encoder, Decoder, ABC): + """The implementations of this class defines a serialization layer for a protocol.""" + + +class ProtobufSerializer(Serializer): + """ + Default Protobuf serializer. + + It assumes that the Message contains a JSON-serializable body. + """ + + def encode(self, msg: Message) -> bytes: + """Encode a message into bytes using Protobuf.""" + body_json = Struct() + body_json.update(msg.body) + body_bytes = body_json.SerializeToString() + return body_bytes + + def decode(self, obj: bytes) -> Message: + """Decode bytes into a message using Protobuf.""" + body_json = Struct() + body_json.ParseFromString(obj) + + body = dict(body_json) + msg = Message(body=body) + return msg + + +class JSONSerializer(Serializer): + """Default serialization in JSON for the Message object.""" + + def encode(self, msg: Message) -> bytes: + """ + Encode a message into bytes using JSON format. + + :param msg: the message to be encoded. + :return: the serialized message. + """ + bytes_msg = json.dumps(msg.body).encode("utf-8") + return bytes_msg + + def decode(self, obj: bytes) -> Message: + """ + Decode bytes into a message using JSON. + + :param obj: the serialized message. + :return: the decoded message. + """ + json_msg = json.loads(obj.decode("utf-8")) + return Message(json_msg) diff --git a/tac/aea/protocols/default/__init__.py b/tac/aea/protocols/default/__init__.py index 107d92a4..52e51b51 100644 --- a/tac/aea/protocols/default/__init__.py +++ b/tac/aea/protocols/default/__init__.py @@ -18,4 +18,4 @@ # # ------------------------------------------------------------------------------ -"""This module contains the support resources for the 'simple' protocol.""" +"""This module contains the support resources for the default protocol.""" diff --git a/tac/aea/protocols/default/message.py b/tac/aea/protocols/default/message.py new file mode 100644 index 00000000..c54ca728 --- /dev/null +++ b/tac/aea/protocols/default/message.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the default message definition.""" +from enum import Enum +from typing import Optional + +from tac.aea.protocols.base.message import Message + + +class DefaultMessage(Message): + """The Default message class.""" + + protocol_id = "default" + + class Type(Enum): + """Default message types.""" + + BYTES = "bytes" + ERROR = "error" + + def __init__(self, type: Optional[Type] = None, + **kwargs): + """ + Initialize. + + :param type: the type. + """ + super().__init__(type=type, **kwargs) diff --git a/tac/aea/protocols/default/serialization.py b/tac/aea/protocols/default/serialization.py index bffb0f5a..1fed0531 100644 --- a/tac/aea/protocols/default/serialization.py +++ b/tac/aea/protocols/default/serialization.py @@ -23,8 +23,9 @@ import base58 -from tac.aea.mail.messages import Message, DefaultMessage -from tac.aea.mail.protocol import Serializer +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.base.serialization import Serializer +from tac.aea.protocols.default.message import DefaultMessage class DefaultSerializer(Serializer): diff --git a/tac/aea/protocols/fipa/message.py b/tac/aea/protocols/fipa/message.py new file mode 100644 index 00000000..3f1efafc --- /dev/null +++ b/tac/aea/protocols/fipa/message.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the FIPA message definition.""" +from enum import Enum +from typing import Optional, Union + +from oef.query import Query +from oef.schema import Description + +from tac.aea.protocols.base.message import Message + + +class FIPAMessage(Message): + """The FIPA message class.""" + + protocol_id = "fipa" + + class Performative(Enum): + """FIPA performatives.""" + + CFP = "cfp" + PROPOSE = "propose" + ACCEPT = "accept" + MATCH_ACCEPT = "match_accept" + DECLINE = "decline" + + def __str__(self): + """Get string representation.""" + return self.value + + def __init__(self, message_id: Optional[int] = None, + dialogue_id: Optional[int] = None, + target: Optional[int] = None, + performative: Optional[Union[str, Performative]] = None, + **kwargs): + """ + Initialize. + + :param message_id: the message id. + :param dialogue_id: the dialogue id. + :param target: the message target. + :param performative: the message performative. + """ + super().__init__(id=message_id, + dialogue_id=dialogue_id, + target=target, + performative=FIPAMessage.Performative(performative), + **kwargs) + + def check_consistency(self) -> bool: + """Check that the data is consistent.""" + try: + assert self.is_set("target") + performative = FIPAMessage.Performative(self.get("performative")) + if performative == FIPAMessage.Performative.CFP: + query = self.get("query") + assert isinstance(query, Query) or isinstance(query, bytes) or query is None + elif performative == FIPAMessage.Performative.PROPOSE: + proposal = self.get("proposal") + assert type(proposal) == list and all(isinstance(d, Description) or type(d) == bytes for d in proposal) + elif performative == FIPAMessage.Performative.ACCEPT \ + or performative == FIPAMessage.Performative.MATCH_ACCEPT \ + or performative == FIPAMessage.Performative.DECLINE: + pass + else: + raise ValueError("Performative not recognized.") + + except (AssertionError, ValueError, KeyError): + return False + + return True diff --git a/tac/aea/protocols/fipa/serialization.py b/tac/aea/protocols/fipa/serialization.py index 502725d5..4633e305 100644 --- a/tac/aea/protocols/fipa/serialization.py +++ b/tac/aea/protocols/fipa/serialization.py @@ -23,8 +23,9 @@ from oef import data_model_instance_pb2 from oef.schema import Description -from tac.aea.mail.messages import Message, FIPAMessage -from tac.aea.mail.protocol import Serializer +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.base.serialization import Serializer +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa import fipa_pb2 diff --git a/tac/aea/protocols/oef/__init__.py b/tac/aea/protocols/oef/__init__.py new file mode 100644 index 00000000..8b7bf970 --- /dev/null +++ b/tac/aea/protocols/oef/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the support resources for the OEF protocol.""" diff --git a/tac/aea/protocols/oef/message.py b/tac/aea/protocols/oef/message.py new file mode 100644 index 00000000..2cf57673 --- /dev/null +++ b/tac/aea/protocols/oef/message.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the default message definition.""" +from enum import Enum +from typing import Optional + +from oef.messages import OEFErrorOperation +from oef.query import Query +from oef.schema import Description + +from tac.aea.protocols.base.message import Message + + +class OEFMessage(Message): + """The OEF message class.""" + + protocol_id = "oef" + + class Type(Enum): + """OEF Message types.""" + + REGISTER_SERVICE = "register_service" + REGISTER_AGENT = "register_agent" + UNREGISTER_SERVICE = "unregister_service" + UNREGISTER_AGENT = "unregister_agent" + SEARCH_SERVICES = "search_services" + SEARCH_AGENTS = "search_agents" + OEF_ERROR = "oef_error" + DIALOGUE_ERROR = "dialogue_error" + SEARCH_RESULT = "search_result" + + def __str__(self): + """Get string representation.""" + return self.value + + def __init__(self, oef_type: Optional[Type] = None, + **kwargs): + """ + Initialize. + + :param oef_type: the type of OEF message. + """ + super().__init__(type=str(oef_type), **kwargs) + + def check_consistency(self) -> bool: + """Check that the data is consistent.""" + try: + assert self.is_set("type") + oef_type = OEFMessage.Type(self.get("type")) + if oef_type == OEFMessage.Type.REGISTER_SERVICE: + service_description = self.get("service_description") + assert self.is_set("id") + assert self.is_set("service_id") + service_id = self.get("service_id") + assert isinstance(service_description, Description) + assert isinstance(service_id, str) + elif oef_type == OEFMessage.Type.REGISTER_AGENT: + assert self.is_set("id") + assert self.is_set("agent_description") + agent_description = self.get("agent_description") + assert isinstance(agent_description, Description) + elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: + assert self.is_set("id") + assert self.is_set("service_id") + service_id = self.get("service_id") + assert isinstance(service_id, str) + elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: + assert self.is_set("id") + elif oef_type == OEFMessage.Type.SEARCH_SERVICES: + assert self.is_set("id") + assert self.is_set("query") + query = self.get("query") + assert isinstance(query, Query) + elif oef_type == OEFMessage.Type.SEARCH_AGENTS: + assert self.is_set("id") + assert self.is_set("query") + query = self.get("query") + assert isinstance(query, Query) + elif oef_type == OEFMessage.Type.SEARCH_RESULT: + assert self.is_set("id") + assert self.is_set("agents") + agents = self.get("agents") + assert type(agents) == list and all(type(a) == str for a in agents) + elif oef_type == OEFMessage.Type.OEF_ERROR: + assert self.is_set("id") + assert self.is_set("operation") + operation = self.get("operation") + assert operation in set(OEFErrorOperation) + elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: + assert self.is_set("id") + assert self.is_set("dialogue_id") + assert self.is_set("origin") + else: + raise ValueError("Type not recognized.") + except (AssertionError, ValueError): + return False + + return True diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py index 16ea5f97..6a794ffa 100644 --- a/tac/aea/protocols/oef/serialization.py +++ b/tac/aea/protocols/oef/serialization.py @@ -32,8 +32,9 @@ from oef.query import Query, ConstraintExpr, And, Or, Not, Constraint from oef.schema import Description, DataModel -from tac.aea.mail.messages import Message, OEFMessage -from tac.aea.mail.protocol import Serializer +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.base.serialization import Serializer +from tac.aea.protocols.oef.message import OEFMessage """default 'to' field for OEF envelopes.""" @@ -133,7 +134,7 @@ def from_json(self, d: Dict) -> Query: class OEFSerializer(Serializer): - """Serialization for the FIPA protocol.""" + """Serialization for the OEF protocol.""" def encode(self, msg: Message) -> bytes: """ diff --git a/tac/aea/protocols/tac/__init__.py b/tac/aea/protocols/tac/__init__.py new file mode 100644 index 00000000..430d160a --- /dev/null +++ b/tac/aea/protocols/tac/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the support resources for the TAC protocol.""" diff --git a/tac/aea/protocols/tac/message.py b/tac/aea/protocols/tac/message.py new file mode 100644 index 00000000..c977a96e --- /dev/null +++ b/tac/aea/protocols/tac/message.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the default message definition.""" +from enum import Enum +from typing import Dict, Optional + +from tac.aea.protocols.base.message import Message + + +class TACMessage(Message): + """The TAC message class.""" + + protocol_id = "tac" + + class Type(Enum): + """TAC Message types.""" + + REGISTER = "register" + UNREGISTER = "unregister" + TRANSACTION = "transaction" + GET_STATE_UPDATE = "get_state_update" + CANCELLED = "cancelled" + GAME_DATA = "game_data" + TRANSACTION_CONFIRMATION = "transaction_confirmation" + STATE_UPDATE = "state_update" + TAC_ERROR = "tac_error" + + def __str__(self): + """Get string representation.""" + return self.value + + class ErrorCode(Enum): + """This class defines the error codes.""" + + GENERIC_ERROR = 0 + REQUEST_NOT_VALID = 1 + AGENT_PBK_ALREADY_REGISTERED = 2 + AGENT_NAME_ALREADY_REGISTERED = 3 + AGENT_NOT_REGISTERED = 4 + TRANSACTION_NOT_VALID = 5 + TRANSACTION_NOT_MATCHING = 6 + AGENT_NAME_NOT_IN_WHITELIST = 7 + COMPETITION_NOT_RUNNING = 8 + DIALOGUE_INCONSISTENT = 9 + + _from_ec_to_msg = { + ErrorCode.GENERIC_ERROR: "Unexpected error.", + ErrorCode.REQUEST_NOT_VALID: "Request not recognized", + ErrorCode.AGENT_PBK_ALREADY_REGISTERED: "Agent pbk already registered.", + ErrorCode.AGENT_NAME_ALREADY_REGISTERED: "Agent name already registered.", + ErrorCode.AGENT_NOT_REGISTERED: "Agent not registered.", + ErrorCode.TRANSACTION_NOT_VALID: "Error in checking transaction", + ErrorCode.TRANSACTION_NOT_MATCHING: "The transaction request does not match with a previous transaction request with the same id.", + ErrorCode.AGENT_NAME_NOT_IN_WHITELIST: "Agent name not in whitelist.", + ErrorCode.COMPETITION_NOT_RUNNING: "The competition is not running yet.", + ErrorCode.DIALOGUE_INCONSISTENT: "The message is inconsistent with the dialogue." + } # type: Dict[ErrorCode, str] + + def __init__(self, tac_type: Optional[Type] = None, + **kwargs): + """ + Initialize. + + :param tac_type: the type of TAC message. + """ + super().__init__(type=str(tac_type), **kwargs) + + def check_consistency(self) -> bool: + """Check that the data is consistent.""" + try: + assert self.is_set("type") + tac_type = TACMessage.Type(self.get("type")) + if tac_type == TACMessage.Type.REGISTER: + assert self.is_set("agent_name") + elif tac_type == TACMessage.Type.UNREGISTER: + pass + elif tac_type == TACMessage.Type.TRANSACTION: + assert self.is_set("transaction_id") + assert self.is_set("is_sender_buyer") + assert self.is_set("counterparty") + assert self.is_set("amount") + amount = self.get("amount") + assert amount >= 0 + assert self.is_set("quantities_by_good_pbk") + quantities_by_good_pbk = self.get("quantities_by_good_pbk") + assert len(quantities_by_good_pbk.keys()) == len(set(quantities_by_good_pbk.keys())) + assert all(quantity >= 0 for quantity in quantities_by_good_pbk.values()) + elif tac_type == TACMessage.Type.GET_STATE_UPDATE: + pass + elif tac_type == TACMessage.Type.CANCELLED: + pass + elif tac_type == TACMessage.Type.GAME_DATA: + assert self.is_set("money") + assert self.is_set("endowment") + assert self.is_set("utility_params") + assert self.is_set("nb_agents") + assert self.is_set("nb_goods") + assert self.is_set("tx_fee") + assert self.is_set("agent_pbk_to_name") + assert self.is_set("good_pbk_to_name") + elif tac_type == TACMessage.Type.TRANSACTION_CONFIRMATION: + assert self.is_set("transaction_id") + elif tac_type == TACMessage.Type.STATE_UPDATE: + assert self.is_set("initial_state") + assert self.is_set("transactions") + elif tac_type == TACMessage.Type.TAC_ERROR: + assert self.is_set("error_code") + error_code = self.get("error_code") + assert error_code in set(self.ErrorCode) + else: + raise ValueError("Type not recognized.") + except (AssertionError, ValueError): + return False + + return True diff --git a/tac/aea/protocols/tac/serialization.py b/tac/aea/protocols/tac/serialization.py new file mode 100644 index 00000000..2379805b --- /dev/null +++ b/tac/aea/protocols/tac/serialization.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Serialization for the TAC protocol.""" +import copy +import json + +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.base.serialization import Serializer +from tac.aea.protocols.tac.message import TACMessage + + +class TACSerializer(Serializer): + """Serialization for the TAC protocol.""" + + def encode(self, msg: Message) -> bytes: + """ + Decode the message. + + :param msg: the message object + :return: the bytes + """ + tac_type = TACMessage.Type(msg.get("type")) + new_body = copy.copy(msg.body) + + if tac_type in {}: + pass # TODO + + tac_message_bytes = json.dumps(new_body).encode("utf-8") + return tac_message_bytes + + def decode(self, obj: bytes) -> Message: + """ + Decode the message. + + :param obj: the bytes object + :return: the message + """ + json_msg = json.loads(obj.decode("utf-8")) + tac_type = TACMessage.Type(json_msg["type"]) + new_body = copy.copy(json_msg) + new_body["type"] = tac_type + + if tac_type in {}: + pass # TODO + + tac_message = Message(body=new_body) + return tac_message diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 6c2e49fc..854a156b 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -32,8 +32,8 @@ import dateutil from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.channel.oef import OEFNetworkMailBox +from tac.aea.mail.base import Envelope from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters from tac.agents.participant.v1.base.helpers import is_oef_message diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index ec9b969b..56e3284f 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -31,7 +31,7 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage +from tac.aea.protocols.oef.message import OEFMessage from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.controller.base.interfaces import OEFActionInterface diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 17d6ca96..1e7d3a71 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -42,10 +42,10 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, DefaultMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import MailBox, Envelope +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.oef.message import OEFMessage from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.actions import OEFActions from tac.agents.controller.base.helpers import generate_good_pbk_to_name diff --git a/tac/agents/controller/base/interfaces.py b/tac/agents/controller/base/interfaces.py index 2dcca9d8..917d720e 100644 --- a/tac/agents/controller/base/interfaces.py +++ b/tac/agents/controller/base/interfaces.py @@ -28,7 +28,7 @@ from abc import abstractmethod -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope class OEFReactionInterface: diff --git a/tac/agents/controller/base/reactions.py b/tac/agents/controller/base/reactions.py index c86f7efe..e578c4ec 100644 --- a/tac/agents/controller/base/reactions.py +++ b/tac/agents/controller/base/reactions.py @@ -28,8 +28,7 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import MailBox, Envelope from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.interfaces import OEFReactionInterface diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 2f794d3d..aa149589 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -25,8 +25,8 @@ from typing import Optional from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.channel.oef import OEFNetworkMailBox +from tac.aea.mail.base import Envelope from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index fa9c3eb6..758eae3f 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -33,8 +33,9 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, DefaultMessage +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.oef.message import OEFMessage from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, \ diff --git a/tac/agents/participant/v1/base/dialogues.py b/tac/agents/participant/v1/base/dialogues.py index 11d0776b..37b1786a 100644 --- a/tac/agents/participant/v1/base/dialogues.py +++ b/tac/agents/participant/v1/base/dialogues.py @@ -32,7 +32,9 @@ from tac.aea.dialogue.base import DialogueLabel from tac.aea.dialogue.base import Dialogue as BaseDialogue from tac.aea.dialogue.base import Dialogues as BaseDialogues -from tac.aea.mail.messages import FIPAMessage, Message, Address +from tac.aea.mail.base import Address +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.fipa.message import FIPAMessage Action = Any logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py index 7cc2ad7c..0212a5fe 100644 --- a/tac/agents/participant/v1/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -27,7 +27,7 @@ from oef.query import Query from oef.schema import Description -from tac.aea.mail.oef import MailStats +from tac.aea.channel.oef import MailStats from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue from tac.agents.participant.v1.base.helpers import build_dict, build_query, get_goods_quantities_description from tac.agents.participant.v1.base.states import AgentState, WorldState diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index ec5350ba..9e84f287 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -31,11 +31,11 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import OEFMessage, Message -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import MailBox, Envelope +from tac.aea.protocols.base.message import Message from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer +from tac.aea.protocols.oef.message import OEFMessage from tac.aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.v1.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.v1.base.game_instance import GameInstance diff --git a/tac/agents/participant/v1/base/helpers.py b/tac/agents/participant/v1/base/helpers.py index 2cd26bfd..a95112e2 100644 --- a/tac/agents/participant/v1/base/helpers.py +++ b/tac/agents/participant/v1/base/helpers.py @@ -26,7 +26,7 @@ from oef.schema import AttributeSchema, DataModel, Description from tac.aea.dialogue.base import DialogueLabel -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/interfaces.py b/tac/agents/participant/v1/base/interfaces.py index 654a4ae3..6d805fe8 100644 --- a/tac/agents/participant/v1/base/interfaces.py +++ b/tac/agents/participant/v1/base/interfaces.py @@ -22,8 +22,8 @@ from abc import abstractmethod -from tac.aea.mail.messages import Message -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope +from tac.aea.protocols.base.message import Message from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index d3cd94ce..90513753 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -26,9 +26,12 @@ from typing import List from tac.aea.crypto.base import Crypto -from tac.aea.mail.messages import FIPAMessage, Message, DefaultMessage -from tac.aea.mail.protocol import Envelope + +from tac.aea.mail.base import Envelope +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index dcc15efe..49e5386a 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -32,10 +32,11 @@ from tac.aea.agent import Liveness from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.mail.messages import FIPAMessage, Address, Message, DefaultMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import MailBox, Envelope, Address +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase diff --git a/tac/agents/participant/v1/base/stats_manager.py b/tac/agents/participant/v1/base/stats_manager.py index eaf2eeef..b7ee3b91 100644 --- a/tac/agents/participant/v1/base/stats_manager.py +++ b/tac/agents/participant/v1/base/stats_manager.py @@ -27,7 +27,7 @@ import numpy as np -from tac.aea.mail.oef import MailStats +from tac.aea.channel.oef import MailStats class EndState(Enum): diff --git a/tac/agents/participant/v2/agent.py b/tac/agents/participant/v2/agent.py index ff66ab62..e8b620f4 100644 --- a/tac/agents/participant/v2/agent.py +++ b/tac/agents/participant/v2/agent.py @@ -25,8 +25,8 @@ from typing import Optional from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.channel.oef import OEFNetworkMailBox +from tac.aea.mail.base import Envelope from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 551c5184..f7394d10 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -24,7 +24,7 @@ from threading import Thread from tac.aea.agent import Agent, AgentState -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.channel.oef import OEFNetworkMailBox class TAgent(Agent): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 20b10fae..ce9b6ae4 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -23,7 +23,7 @@ from unittest.mock import MagicMock from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFNetworkMailBox +from tac.aea.channel.oef import OEFNetworkMailBox class TAgent(Agent): diff --git a/tests/test_controller.py b/tests/test_controller.py index ff743aa7..c84f5fe1 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -26,8 +26,8 @@ import time from threading import Thread -from tac.aea.mail.messages import DefaultMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 48658793..99b66662 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -21,11 +21,12 @@ import asyncio import time +from tac.aea.mail.base import Envelope +from tac.aea.channel.oef import OEFMailBox from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -from tac.aea.mail.messages import DefaultMessage, FIPAMessage -from tac.aea.mail.oef import OEFMailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer diff --git a/tests/test_mail.py b/tests/test_mail.py index d8beebae..cd6c1ba2 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,9 +20,10 @@ """This module contains tests for the mail module.""" import time -from tac.aea.mail.messages import FIPAMessage, DefaultMessage -from tac.aea.mail.oef import OEFNetworkMailBox -from tac.aea.mail.protocol import Envelope +from tac.aea.channel.oef import OEFNetworkMailBox +from tac.aea.mail.base import Envelope +from tac.aea.protocols.default.message import DefaultMessage +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index 65267b64..d58a24e3 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -18,8 +18,9 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" -from tac.aea.mail.messages import Message -from tac.aea.mail.protocol import ProtobufSerializer, JSONSerializer, Envelope +from tac.aea.mail.base import Envelope +from tac.aea.protocols.base.message import Message +from tac.aea.protocols.base.serialization import ProtobufSerializer, JSONSerializer class TestDefaultSerializations: diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py index 17842747..13526b3e 100644 --- a/tests/test_messages/test_fipa.py +++ b/tests/test_messages/test_fipa.py @@ -20,11 +20,10 @@ """This module contains the tests for the FIPA protocol.""" from oef.schema import Description -from tac.aea.mail.messages import FIPAMessage +from tac.aea.mail.base import Envelope +from tac.aea.protocols.fipa.message import FIPAMessage from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.aea.mail.protocol import Envelope - def test_fipa_cfp_serialization(): """Test that the serialization for the 'fipa' protocol works.""" diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index bbcbc681..7985e429 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -19,8 +19,8 @@ """This module contains the tests of the messages module.""" -from tac.aea.mail.messages import DefaultMessage -from tac.aea.mail.protocol import Envelope +from tac.aea.mail.base import Envelope +from tac.aea.protocols.default.message import DefaultMessage from tac.aea.protocols.default.serialization import DefaultSerializer From eda837b635a623fef5af98a41371dbf912cd3e6a Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 16:16:53 +0100 Subject: [PATCH 069/107] wip --- tac/aea/helpers/local_node.py | 274 +++++++++++++------------ tac/aea/mail/messages.py | 14 +- tac/aea/mail/oef.py | 4 +- tac/aea/protocols/oef/serialization.py | 1 - tests/test_localnode.py | 167 +++++++-------- 5 files changed, 239 insertions(+), 221 deletions(-) diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index 5dc9cc40..e8fe875c 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -24,19 +24,20 @@ import logging import queue import threading -from asyncio import AbstractEventLoop, Queue from collections import defaultdict +from queue import Queue from threading import Thread from typing import Dict, List, Tuple, Optional -from oef import agent_pb2, uri -from oef.core import OEFProxy -from oef.messages import BaseMessage, AgentMessage, OEFErrorOperation, OEFErrorMessage, SearchResult, \ - DialogueErrorMessage, PROPOSE_TYPES, Message, CFP, Propose, Accept, Decline, CFP_TYPES -from oef.proxy import OEFConnectionError from oef.query import Query from oef.schema import Description +from tac.aea.mail.base import Connection +from tac.aea.mail.messages import OEFMessage +from tac.aea.mail.oef import STUB_DIALOGUE_ID +from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF, OEFErrorOperation + logger = logging.getLogger(__name__) @@ -49,14 +50,12 @@ def __init__(self): self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._lock = threading.Lock() - self.loop = asyncio.new_event_loop() self._task = None # type: Optional[asyncio.Task] self._stopped = True # type: bool self._thread = None # type: Optional[Thread] - self._read_queue = Queue(loop=self.loop) # type: Queue - self._queues = {} # type: Dict[str, asyncio.Queue] - self._loops = {} # type: Dict[str, AbstractEventLoop] + self._read_queue = Queue() + self._queues = {} # type: Dict[str, Queue] def __enter__(self): """Start the OEF Node.""" @@ -67,7 +66,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): """Stop the OEF Node.""" self.stop() - def connect(self, public_key: str, loop: AbstractEventLoop) -> Optional[Tuple[Queue, Queue]]: + def connect(self, public_key: str) -> Optional[Tuple[Queue, Queue]]: """ Connect a public key to the node. @@ -77,9 +76,8 @@ def connect(self, public_key: str, loop: AbstractEventLoop) -> Optional[Tuple[Qu if public_key in self._queues: return None - q = Queue(loop=loop) + q = Queue() self._queues[public_key] = q - self._loops[public_key] = loop return self._read_queue, q async def _process_messages(self) -> None: @@ -90,14 +88,57 @@ async def _process_messages(self) -> None: """ while not self._stopped: try: - data = await self._read_queue.get() # type: Tuple[str, BaseMessage] + envelope = await self._read_queue.get() # type: Envelope except asyncio.CancelledError: logger.debug("Local Node: loop cancelled.") break - public_key, msg = data - assert isinstance(msg, AgentMessage) - logger.debug("Processing message from {}: {}".format(public_key, msg)) - self._send_agent_message(public_key, msg) + + sender = envelope.sender + assert isinstance(envelope, Envelope) + logger.debug("Processing message from {}: {}".format(sender, envelope)) + + self._decode_envelope(envelope) + + def _decode_envelope(self, envelope): + """Decode the envelope.""" + if envelope.protocol_id == "oef": + self.handle_oef_message(envelope) + else: + self.handle_agent_message(envelope) + + def handle_oef_message(self, envelope): + oef_message = OEFSerializer().decode(envelope.message) + sender = envelope.sender + request_id = oef_message.get("id") + oef_type = OEFMessage.Type(oef_message.get("type")) + if oef_type == OEFMessage.Type.REGISTER_SERVICE: + self.register_service(sender, oef_message.get("service_description")) + elif oef_type == OEFMessage.Type.REGISTER_AGENT: + self.register_agent(sender, oef_message.get("agent_description")) + elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: + self.unregister_service(sender, request_id, oef_message.get("service_description")) + elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: + self.unregister_agent(sender, request_id) + elif oef_type == OEFMessage.Type.SEARCH_AGENTS: + self.search_agents(sender, request_id, oef_message.get("query")) + elif oef_type == OEFMessage.Type.SEARCH_SERVICES: + self.search_services(sender, request_id, oef_message.get("query")) + else: + # request not recognized + pass + + def handle_agent_message(self, envelope: Envelope): + """Forward an envelope to the right agent.""" + destination = envelope.to + + if destination not in self._queues: + msg = OEFMessage(type=OEFMessage.Type.DIALOGUE_ERROR, id=STUB_DIALOGUE_ID, dialogue_id=STUB_DIALOGUE_ID, origin=destination) + msg_bytes = OEFSerializer().encode(msg) + error_envelope = Envelope(to=destination, sender=envelope.sender, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(error_envelope) + return + else: + self._send(envelope) def run(self) -> None: """ @@ -164,8 +205,10 @@ def unregister_agent(self, public_key: str, msg_id: int) -> None: """ with self._lock: if public_key not in self.agents: - msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_DESCRIPTION) - self._send(public_key, msg.to_pb()) + msg = OEFMessage(type=OEFMessage.Type.OEF_ERROR, id=msg_id, operation=OEFErrorOperation.UNREGISTER_DESCRIPTION) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(envelope) else: self.agents.pop(public_key) @@ -180,8 +223,10 @@ def unregister_service(self, public_key: str, msg_id: int, service_description: """ with self._lock: if public_key not in self.services: - msg = OEFErrorMessage(msg_id, OEFErrorOperation.UNREGISTER_SERVICE) - self._send(public_key, msg.to_pb()) + msg = OEFMessage(type=OEFMessage.Type.OEF_ERROR, id=msg_id, operation=OEFErrorOperation.UNREGISTER_SERVICE) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(envelope) else: self.services[public_key].remove(service_description) if len(self.services[public_key]) == 0: @@ -203,8 +248,10 @@ def search_agents(self, public_key: str, search_id: int, query: Query) -> None: if query.check(description): result.append(agent_public_key) - msg = SearchResult(search_id, sorted(set(result))) - self._send(public_key, msg.to_pb()) + msg = OEFMessage(type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=sorted(set(result))) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(envelope) def search_services(self, public_key: str, search_id: int, query: Query) -> None: """ @@ -223,44 +270,25 @@ def search_services(self, public_key: str, search_id: int, query: Query) -> None if query.check(description): result.append(agent_public_key) - msg = SearchResult(search_id, sorted(set(result))) - self._send(public_key, msg.to_pb()) - - def _send_agent_message(self, origin: str, msg: AgentMessage) -> None: - """ - Send an :class:`~oef.messages.AgentMessage`. - - :param origin: the public key of the sender agent. - :param msg: the message. - :return: None - """ - e = msg.to_pb() - destination = e.send_message.destination - - if destination not in self._queues: - msg = DialogueErrorMessage(msg.msg_id, e.send_message.dialogue_id, destination) - self._send(origin, msg.to_pb()) - return - - new_msg = agent_pb2.Server.AgentMessage() - new_msg.answer_id = msg.msg_id - new_msg.content.origin = origin - new_msg.content.dialogue_id = e.send_message.dialogue_id + msg = OEFMessage(type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=sorted(set(result))) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(envelope) - payload = e.send_message.WhichOneof("payload") - if payload == "content": - new_msg.content.content = e.send_message.content - elif payload == "fipa": - new_msg.content.fipa.CopyFrom(e.send_message.fipa) + def _send(self, envelope: Envelope): + destination = envelope.to + loop = self._loops[destination] + loop.call_soon_threadsafe(self._queues[destination].put_nowait, envelope) - self._send(destination, new_msg) - - def _send(self, public_key: str, msg): - loop = self._loops[public_key] - loop.call_soon_threadsafe(self._queues[public_key].put_nowait, msg.SerializeToString()) + def disconnect(self, public_key: str): + with self._lock: + self._loops.pop(public_key, None) + self._queues.pop(public_key, None) + self.services.pop(public_key, None) + self.agents.pop(public_key, None) -class OEFLocalProxy(OEFProxy): +class OEFLocalConnection(Connection): """ Proxy to the functionality of the OEF. @@ -276,95 +304,73 @@ def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.Abstrac :param local_node: the Local OEF Node object. This reference must be the same across the agents of interest. :param loop: the event loop. """ - super().__init__(public_key, loop) + super().__init__() + self.public_key = public_key self.local_node = local_node + self._loop = loop if loop is not None else asyncio.new_event_loop() + self._connection = None # type: Optional[Tuple[queue.Queue, queue.Queue]] self._read_queue = None # type: Optional[Queue] self._write_queue = None # type: Optional[Queue] - def register_agent(self, msg_id: int, agent_description: Description) -> None: - """Register an agent.""" - self.local_node.register_agent(self.public_key, agent_description) + self._stopped = True + self.in_thread = None + self.out_thread = None - def register_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: - """Register a service.""" - self.local_node.register_service(self.public_key, service_description) + def _fetch(self) -> None: + """ + Fetch the messages from the outqueue and send them. - def search_agents(self, search_id: int, query: Query) -> None: - """Search agents.""" - self.local_node.search_agents(self.public_key, search_id, query) + :return: None + """ + while not self._stopped: + try: + msg = self.out_queue.get(block=True, timeout=1.0) + self.send(msg) + except queue.Empty: + pass - def search_services(self, search_id: int, query: Query) -> None: - """Search services.""" - self.local_node.search_services(self.public_key, search_id, query) + async def _receive_loop(self): + """Receive messages.""" + while not self._stopped: + data = await self._read_queue.get() + self.in_queue.put_nowait(data) - def search_services_wide(self, msg_id: int, query: Query) -> None: - """Search wide.""" - raise NotImplementedError + @property + def is_established(self) -> bool: + """Return True if the connection has been established, False otherwise.""" + return self._connection is not None - def unregister_agent(self, msg_id: int) -> None: - """Unregister an agent.""" - self.local_node.unregister_agent(self.public_key, msg_id) - - def unregister_service(self, msg_id: int, service_description: Description, service_id: str = "") -> None: - """Unregister a service.""" - self.local_node.unregister_service(self.public_key, msg_id, service_description) - - def send_message(self, msg_id: int, dialogue_id: int, destination: str, msg: bytes, context=uri.Context()): - """Send a simple message.""" - msg = Message(msg_id, dialogue_id, destination, msg, context) - self._send(msg) - - def send_cfp(self, msg_id: int, dialogue_id: int, destination: str, target: int, query: CFP_TYPES, context=uri.Context()) -> None: - """Send a CFP.""" - msg = CFP(msg_id, dialogue_id, destination, target, query, context) - self._send(msg) - - def send_propose(self, msg_id: int, dialogue_id: int, destination: str, target: int, proposals: PROPOSE_TYPES, context=uri.Context()) -> None: - """Send a propose.""" - msg = Propose(msg_id, dialogue_id, destination, target, proposals, context) - self._send(msg) - - def send_accept(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: - """Send an accept.""" - msg = Accept(msg_id, dialogue_id, destination, target, context) - self._send(msg) - - def send_decline(self, msg_id: int, dialogue_id: int, destination: str, target: int, context=uri.Context()) -> None: - """Send a decline.""" - msg = Decline(msg_id, dialogue_id, destination, target, context) - self._send(msg) - - async def connect(self) -> bool: - """Connect the proxy.""" - if self._connection is not None: - return True - - self._connection = self.local_node.connect(self.public_key, self._loop) - if self._connection is None: - return False - self._write_queue, self._read_queue = self._connection - return True - - async def _receive(self) -> bytes: - """Receive a message.""" - if not self.is_connected(): - raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") - data = await self._read_queue.get() - return data - - def _send(self, msg: BaseMessage) -> None: + def connect(self): + """Connect to the local OEF Node.""" + if self._stopped: + self._stopped = False + self._connection = self.local_node.connect(self.public_key, self._loop) + self._write_queue, self._read_queue = self._connection + self.in_thread = Thread(target=self._receive_loop) + self.out_thread = Thread(target=self._fetch) + self.in_thread.start() + self.out_thread.start() + + def disconnect(self): + """Disconnect from the local OEF Node.""" + if not self._stopped: + self._stopped = True + self.in_thread.join() + self.out_thread.join() + self.in_thread = None + self.out_thread = None + self.local_node.disconnect(self.public_key) + self.stop() + + def send(self, envelope: Envelope): """Send a message.""" - if not self.is_connected(): - raise OEFConnectionError("Connection not established yet. Please use 'connect()'.") - self.local_node.loop.call_soon_threadsafe(self._write_queue.put_nowait, (self.public_key, msg)) + if not self.is_established: + raise ConnectionError("Connection not established yet. Please use 'connect()'.") + self.local_node.loop.call_soon_threadsafe(self._write_queue.put_nowait,envelope) - async def stop(self): + def stop(self): """Tear down the connection.""" self._connection = None self._read_queue = None self._write_queue = None - - def is_connected(self) -> bool: - """Return True if the proxy is connected, False otherwise.""" - return self._connection is not None diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index 5f371fb1..a08f6a6d 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -23,7 +23,6 @@ from enum import Enum from typing import Optional, Any, Union, Dict -from oef.messages import OEFErrorOperation from oef.query import Query from oef.schema import Description @@ -97,6 +96,19 @@ def __eq__(self, other): and self.body == other.body +class OEFErrorOperation(Enum): + """Operation code for the OEF. It is returned in the OEF Error messages.""" + REGISTER_SERVICE = 0 + UNREGISTER_SERVICE = 1 + REGISTER_DESCRIPTION = 2 + UNREGISTER_DESCRIPTION = 3 + SEARCH_SERVICES = 4 + SEARCH_SERVICES_WIDE = 5 + SEARCH_AGENTS = 6 + SEND_MESSAGE = 7 + GENERIC_ERROR = 10000 + + class OEFMessage(Message): """The OEF message class.""" diff --git a/tac/aea/mail/oef.py b/tac/aea/mail/oef.py index 6b9db21f..4d159c5a 100644 --- a/tac/aea/mail/oef.py +++ b/tac/aea/mail/oef.py @@ -28,10 +28,10 @@ from typing import List, Dict, Optional from oef.agents import OEFAgent -from oef.messages import OEFErrorOperation, CFP_TYPES, PROPOSE_TYPES +from oef.messages import CFP_TYPES, PROPOSE_TYPES from tac.aea.mail.base import Connection, MailBox -from tac.aea.mail.messages import OEFMessage, FIPAMessage +from tac.aea.mail.messages import OEFMessage, FIPAMessage, OEFErrorOperation from tac.aea.mail.protocol import Envelope from tac.aea.protocols.fipa.serialization import FIPASerializer from tac.aea.protocols.oef.serialization import OEFSerializer diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py index 16ea5f97..314d22c2 100644 --- a/tac/aea/protocols/oef/serialization.py +++ b/tac/aea/protocols/oef/serialization.py @@ -35,7 +35,6 @@ from tac.aea.mail.messages import Message, OEFMessage from tac.aea.mail.protocol import Serializer - """default 'to' field for OEF envelopes.""" DEFAULT_OEF = "oef" diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 731e96c7..08791db1 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -18,86 +18,87 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the local OEF node implementation.""" -# import asyncio -# import time -# -# from tac.aea.helpers.local_node import LocalNode, OEFLocalProxy -# from tac.aea.mail.messages import DefaultMessage, FIPAMessage -# from tac.aea.mail.oef import OEFMailBox -# from tac.aea.mail.protocol import Envelope -# from tac.aea.protocols.default.serialization import DefaultSerializer -# from tac.aea.protocols.fipa.serialization import FIPASerializer -# -# -# def test_connection(): -# """Test that two mailbox can connect to the node.""" -# with LocalNode() as node: -# mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) -# mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) -# -# mailbox1.connect() -# mailbox2.connect() -# -# mailbox1.disconnect() -# mailbox2.disconnect() -# -# -# def test_communication(): -# """Test that two mailbox can communicate through the node.""" -# with LocalNode() as node: -# mailbox1 = OEFMailBox(OEFLocalProxy("mailbox1", node, loop=asyncio.new_event_loop())) -# mailbox2 = OEFMailBox(OEFLocalProxy("mailbox2", node, loop=asyncio.new_event_loop())) -# -# mailbox1.connect() -# mailbox2.connect() -# -# msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") -# msg_bytes = DefaultSerializer().encode(msg) -# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) -# mailbox1.send(envelope) -# -# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) -# msg_bytes = FIPASerializer().encode(msg) -# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) -# mailbox1.send(envelope) -# -# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) -# msg_bytes = FIPASerializer().encode(msg) -# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) -# mailbox1.send(envelope) -# -# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) -# msg_bytes = FIPASerializer().encode(msg) -# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) -# mailbox1.send(envelope) -# -# msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) -# msg_bytes = FIPASerializer().encode(msg) -# envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) -# mailbox1.send(envelope) -# -# time.sleep(1.0) -# -# envelope = mailbox2.inbox.get(block=True, timeout=1.0) -# msg = DefaultSerializer().decode(envelope.message) -# assert envelope.protocol_id == "default" -# assert msg.get("content") == b"hello" -# envelope = mailbox2.inbox.get(block=True, timeout=1.0) -# msg = FIPASerializer().decode(envelope.message) -# assert envelope.protocol_id == "fipa" -# assert msg.get("performative") == FIPAMessage.Performative.CFP -# envelope = mailbox2.inbox.get(block=True, timeout=1.0) -# msg = FIPASerializer().decode(envelope.message) -# assert envelope.protocol_id == "fipa" -# assert msg.get("performative") == FIPAMessage.Performative.PROPOSE -# envelope = mailbox2.inbox.get(block=True, timeout=1.0) -# msg = FIPASerializer().decode(envelope.message) -# assert envelope.protocol_id == "fipa" -# assert msg.get("performative") == FIPAMessage.Performative.ACCEPT -# envelope = mailbox2.inbox.get(block=True, timeout=1.0) -# msg = FIPASerializer().decode(envelope.message) -# assert envelope.protocol_id == "fipa" -# assert msg.get("performative") == FIPAMessage.Performative.DECLINE -# -# mailbox1.disconnect() -# mailbox2.disconnect() +import asyncio +import time + +from tac.aea.helpers.local_node import LocalNode, OEFLocalConnection +from tac.aea.mail.base import MailBox +from tac.aea.mail.messages import DefaultMessage, FIPAMessage +from tac.aea.mail.oef import OEFMailBox +from tac.aea.mail.protocol import Envelope +from tac.aea.protocols.default.serialization import DefaultSerializer +from tac.aea.protocols.fipa.serialization import FIPASerializer + + +def test_connection(): + """Test that two mailbox can connect to the node.""" + with LocalNode() as node: + mailbox1 = MailBox(OEFLocalConnection("mailbox1", node, loop=asyncio.new_event_loop())) + mailbox2 = MailBox(OEFLocalConnection("mailbox2", node, loop=asyncio.new_event_loop())) + + mailbox1.connect() + mailbox2.connect() + + mailbox1.disconnect() + mailbox2.disconnect() + + +def test_communication(): + """Test that two mailbox can communicate through the node.""" + with LocalNode() as node: + mailbox1 = MailBox(OEFLocalConnection("mailbox1", node, loop=asyncio.new_event_loop())) + mailbox2 = MailBox(OEFLocalConnection("mailbox2", node, loop=asyncio.new_event_loop())) + + mailbox1.connect() + mailbox2.connect() + + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + time.sleep(5.0) + + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = DefaultSerializer().decode(envelope.message) + assert envelope.protocol_id == "default" + assert msg.get("content") == b"hello" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.CFP + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.PROPOSE + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.ACCEPT + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.DECLINE + + mailbox1.disconnect() + mailbox2.disconnect() From f70d236eeaed2584409355d3227a85431b3599cf Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 16:23:01 +0100 Subject: [PATCH 070/107] remove running loop to the local node. --- tac/aea/helpers/local_node.py | 81 ++++++++--------------------------- 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index e8fe875c..2cdff9cc 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -50,23 +50,19 @@ def __init__(self): self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._lock = threading.Lock() - self._task = None # type: Optional[asyncio.Task] self._stopped = True # type: bool self._thread = None # type: Optional[Thread] - self._read_queue = Queue() self._queues = {} # type: Dict[str, Queue] def __enter__(self): """Start the OEF Node.""" - self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): """Stop the OEF Node.""" - self.stop() - def connect(self, public_key: str) -> Optional[Tuple[Queue, Queue]]: + def connect(self, public_key: str) -> Optional[Queue]: """ Connect a public key to the node. @@ -78,26 +74,17 @@ def connect(self, public_key: str) -> Optional[Tuple[Queue, Queue]]: q = Queue() self._queues[public_key] = q - return self._read_queue, q + return q - async def _process_messages(self) -> None: + def send_envelope(self, envelope: Envelope) -> None: """ Process the incoming messages. :return: None """ - while not self._stopped: - try: - envelope = await self._read_queue.get() # type: Envelope - except asyncio.CancelledError: - logger.debug("Local Node: loop cancelled.") - break - - sender = envelope.sender - assert isinstance(envelope, Envelope) - logger.debug("Processing message from {}: {}".format(sender, envelope)) - - self._decode_envelope(envelope) + sender = envelope.sender + logger.debug("Processing message from {}: {}".format(sender, envelope)) + self._decode_envelope(envelope) def _decode_envelope(self, envelope): """Decode the envelope.""" @@ -140,35 +127,6 @@ def handle_agent_message(self, envelope: Envelope): else: self._send(envelope) - def run(self) -> None: - """ - Run the node, i.e. start processing the messages. - - :return: None - """ - self._stopped = False - self._task = asyncio.ensure_future(self._process_messages(), loop=self.loop) - self.loop.run_until_complete(self._task) - - def start(self): - """Start the node in its own thread.""" - self._thread = Thread(target=self.run) - self._thread.start() - - def stop(self) -> None: - """ - Stop the execution of the node. - - :return: None - """ - self._stopped = True - - if self._task and not self._task.cancelled(): - self.loop.call_soon_threadsafe(self._task.cancel) - - if self._thread: - self._thread.join() - def register_agent(self, public_key: str, agent_description: Description) -> None: """ Register an agent in the agent directory of the node. @@ -277,12 +235,10 @@ def search_services(self, public_key: str, search_id: int, query: Query) -> None def _send(self, envelope: Envelope): destination = envelope.to - loop = self._loops[destination] - loop.call_soon_threadsafe(self._queues[destination].put_nowait, envelope) + self._queues[destination].put_nowait(envelope) def disconnect(self, public_key: str): with self._lock: - self._loops.pop(public_key, None) self._queues.pop(public_key, None) self.services.pop(public_key, None) self.agents.pop(public_key, None) @@ -309,9 +265,7 @@ def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.Abstrac self.local_node = local_node self._loop = loop if loop is not None else asyncio.new_event_loop() - self._connection = None # type: Optional[Tuple[queue.Queue, queue.Queue]] - self._read_queue = None # type: Optional[Queue] - self._write_queue = None # type: Optional[Queue] + self._connection = None # type: Optional[Queue] self._stopped = True self.in_thread = None @@ -325,16 +279,19 @@ def _fetch(self) -> None: """ while not self._stopped: try: - msg = self.out_queue.get(block=True, timeout=1.0) + msg = self.out_queue.get(block=True, timeout=2.0) self.send(msg) except queue.Empty: pass - async def _receive_loop(self): + def _receive_loop(self): """Receive messages.""" while not self._stopped: - data = await self._read_queue.get() - self.in_queue.put_nowait(data) + try: + data = self._connection.get(timeout=2.0) + self.in_queue.put_nowait(data) + except queue.Empty: + pass @property def is_established(self) -> bool: @@ -345,8 +302,7 @@ def connect(self): """Connect to the local OEF Node.""" if self._stopped: self._stopped = False - self._connection = self.local_node.connect(self.public_key, self._loop) - self._write_queue, self._read_queue = self._connection + self._connection = self.local_node.connect(self.public_key) self.in_thread = Thread(target=self._receive_loop) self.out_thread = Thread(target=self._fetch) self.in_thread.start() @@ -367,10 +323,9 @@ def send(self, envelope: Envelope): """Send a message.""" if not self.is_established: raise ConnectionError("Connection not established yet. Please use 'connect()'.") - self.local_node.loop.call_soon_threadsafe(self._write_queue.put_nowait,envelope) + self.local_node.send_envelope(envelope) def stop(self): """Tear down the connection.""" self._connection = None - self._read_queue = None - self._write_queue = None + From 8d6d3c91ed2bc0771ea686ab347723ea4691fe06 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 21 Aug 2019 16:25:05 +0100 Subject: [PATCH 071/107] fix style checks. --- tac/aea/helpers/local_node.py | 6 ++++-- tac/aea/mail/messages.py | 1 + tests/test_localnode.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py index 2cdff9cc..18fa9efc 100644 --- a/tac/aea/helpers/local_node.py +++ b/tac/aea/helpers/local_node.py @@ -27,7 +27,7 @@ from collections import defaultdict from queue import Queue from threading import Thread -from typing import Dict, List, Tuple, Optional +from typing import Dict, List, Optional from oef.query import Query from oef.schema import Description @@ -94,6 +94,7 @@ def _decode_envelope(self, envelope): self.handle_agent_message(envelope) def handle_oef_message(self, envelope): + """Handle an OEF request.""" oef_message = OEFSerializer().decode(envelope.message) sender = envelope.sender request_id = oef_message.get("id") @@ -234,10 +235,12 @@ def search_services(self, public_key: str, search_id: int, query: Query) -> None self._send(envelope) def _send(self, envelope: Envelope): + """Send a message.""" destination = envelope.to self._queues[destination].put_nowait(envelope) def disconnect(self, public_key: str): + """Disconnect an agent.""" with self._lock: self._queues.pop(public_key, None) self.services.pop(public_key, None) @@ -328,4 +331,3 @@ def send(self, envelope: Envelope): def stop(self): """Tear down the connection.""" self._connection = None - diff --git a/tac/aea/mail/messages.py b/tac/aea/mail/messages.py index a08f6a6d..9a96c209 100644 --- a/tac/aea/mail/messages.py +++ b/tac/aea/mail/messages.py @@ -98,6 +98,7 @@ def __eq__(self, other): class OEFErrorOperation(Enum): """Operation code for the OEF. It is returned in the OEF Error messages.""" + REGISTER_SERVICE = 0 UNREGISTER_SERVICE = 1 REGISTER_DESCRIPTION = 2 diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 08791db1..9fe50300 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -24,7 +24,6 @@ from tac.aea.helpers.local_node import LocalNode, OEFLocalConnection from tac.aea.mail.base import MailBox from tac.aea.mail.messages import DefaultMessage, FIPAMessage -from tac.aea.mail.oef import OEFMailBox from tac.aea.mail.protocol import Envelope from tac.aea.protocols.default.serialization import DefaultSerializer from tac.aea.protocols.fipa.serialization import FIPASerializer From 5f9ecdba3d3e6ac7dd3a4fc620b1e3513d02d90e Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 22 Aug 2019 15:52:35 +0100 Subject: [PATCH 072/107] This removes the aea subdir, adds it to pypi and reimports it --- Pipfile | 15 +- Pipfile.lock | 48 +- docs/reference/api/modules.rst | 7 - docs/reference/api/tac.aea.crypto.rst | 22 - docs/reference/api/tac.aea.dialogue.rst | 22 - docs/reference/api/tac.aea.mail.rst | 38 -- docs/reference/api/tac.aea.rst | 32 -- docs/reference/api/tac.aea.state.rst | 22 - docs/reference/api/tac.agents.controller.rst | 22 - .../api/tac.agents.participant.base.rst | 118 ----- .../api/tac.agents.participant.examples.rst | 30 -- docs/reference/api/tac.agents.participant.rst | 30 -- docs/reference/api/tac.agents.rst | 18 - docs/reference/api/tac.gui.dashboards.rst | 54 --- .../api/tac.gui.launcher.api.resources.rst | 30 -- docs/reference/api/tac.gui.launcher.api.rst | 17 - docs/reference/api/tac.gui.launcher.forms.rst | 30 -- docs/reference/api/tac.gui.launcher.rst | 46 -- docs/reference/api/tac.gui.rst | 30 -- docs/reference/api/tac.platform.game.rst | 38 -- docs/reference/api/tac.platform.rst | 45 -- docs/reference/api/tac.rst | 32 -- sandbox/playground.py | 8 +- scripts/generate_private_key.py | 2 +- setup.py | 4 +- tac/aea/__init__.py | 21 - tac/aea/agent.py | 232 --------- tac/aea/channel/__init__.py | 22 - tac/aea/channel/local.py | 21 - tac/aea/channel/oef.py | 443 ------------------ tac/aea/crypto/__init__.py | 22 - tac/aea/crypto/base.py | 260 ---------- tac/aea/dialogue/__init__.py | 22 - tac/aea/dialogue/base.py | 224 --------- tac/aea/helpers/__init__.py | 20 - tac/aea/helpers/local_node.py | 353 -------------- tac/aea/mail/__init__.py | 22 - tac/aea/mail/base.proto | 10 - tac/aea/mail/base.py | 290 ------------ tac/aea/mail/base_pb2.py | 90 ---- tac/aea/protocols/__init__.py | 22 - tac/aea/protocols/base/__init__.py | 21 - tac/aea/protocols/base/message.py | 87 ---- tac/aea/protocols/base/serialization.py | 105 ----- tac/aea/protocols/default/__init__.py | 21 - tac/aea/protocols/default/message.py | 46 -- tac/aea/protocols/default/serialization.py | 69 --- tac/aea/protocols/fipa/__init__.py | 21 - tac/aea/protocols/fipa/fipa.proto | 35 -- tac/aea/protocols/fipa/fipa_pb2.py | 377 --------------- tac/aea/protocols/fipa/message.py | 89 ---- tac/aea/protocols/fipa/serialization.py | 120 ----- tac/aea/protocols/oef/__init__.py | 21 - tac/aea/protocols/oef/message.py | 116 ----- tac/aea/protocols/oef/serialization.py | 204 -------- tac/aea/protocols/tac/__init__.py | 21 - tac/aea/protocols/tac/message.py | 133 ------ tac/aea/protocols/tac/serialization.py | 65 --- tac/aea/state/__init__.py | 22 - tac/aea/state/base.py | 35 -- tac/agents/controller/agent.py | 6 +- tac/agents/controller/base/actions.py | 10 +- tac/agents/controller/base/handlers.py | 14 +- tac/agents/controller/base/interfaces.py | 2 +- tac/agents/controller/base/reactions.py | 8 +- tac/agents/controller/base/states.py | 2 +- tac/agents/participant/v1/agent.py | 6 +- tac/agents/participant/v1/base/actions.py | 14 +- tac/agents/participant/v1/base/dialogues.py | 12 +- .../participant/v1/base/game_instance.py | 2 +- tac/agents/participant/v1/base/handlers.py | 16 +- tac/agents/participant/v1/base/helpers.py | 4 +- tac/agents/participant/v1/base/interfaces.py | 4 +- .../v1/base/negotiation_behaviours.py | 16 +- tac/agents/participant/v1/base/reactions.py | 16 +- tac/agents/participant/v1/base/states.py | 4 +- .../participant/v1/base/stats_manager.py | 2 +- tac/agents/participant/v2/agent.py | 6 +- tac/gui/dashboards/controller.py | 2 +- tac/gui/dashboards/leaderboard.py | 2 +- tac/platform/game/stats.py | 2 +- tac/platform/protocol.py | 2 +- templates/v1/advanced.py | 2 +- templates/v1/expert.py | 4 +- tests/test_agent/test_agent_state.py | 4 +- tests/test_agent/test_misc.py | 4 +- tests/test_controller.py | 8 +- tests/test_crypto.py | 2 +- tests/test_game.py | 2 +- tests/test_localnode.py | 13 +- tests/test_mail.py | 12 +- tests/test_messages/test_base.py | 6 +- tests/test_messages/test_fipa.py | 6 +- tests/test_messages/test_simple.py | 6 +- tests/test_protocol.py | 2 +- 95 files changed, 158 insertions(+), 4507 deletions(-) delete mode 100644 docs/reference/api/modules.rst delete mode 100644 docs/reference/api/tac.aea.crypto.rst delete mode 100644 docs/reference/api/tac.aea.dialogue.rst delete mode 100644 docs/reference/api/tac.aea.mail.rst delete mode 100644 docs/reference/api/tac.aea.rst delete mode 100644 docs/reference/api/tac.aea.state.rst delete mode 100644 docs/reference/api/tac.agents.controller.rst delete mode 100644 docs/reference/api/tac.agents.participant.base.rst delete mode 100644 docs/reference/api/tac.agents.participant.examples.rst delete mode 100644 docs/reference/api/tac.agents.participant.rst delete mode 100644 docs/reference/api/tac.agents.rst delete mode 100644 docs/reference/api/tac.gui.dashboards.rst delete mode 100644 docs/reference/api/tac.gui.launcher.api.resources.rst delete mode 100644 docs/reference/api/tac.gui.launcher.api.rst delete mode 100644 docs/reference/api/tac.gui.launcher.forms.rst delete mode 100644 docs/reference/api/tac.gui.launcher.rst delete mode 100644 docs/reference/api/tac.gui.rst delete mode 100644 docs/reference/api/tac.platform.game.rst delete mode 100644 docs/reference/api/tac.platform.rst delete mode 100644 docs/reference/api/tac.rst delete mode 100644 tac/aea/__init__.py delete mode 100644 tac/aea/agent.py delete mode 100644 tac/aea/channel/__init__.py delete mode 100644 tac/aea/channel/local.py delete mode 100644 tac/aea/channel/oef.py delete mode 100644 tac/aea/crypto/__init__.py delete mode 100644 tac/aea/crypto/base.py delete mode 100644 tac/aea/dialogue/__init__.py delete mode 100644 tac/aea/dialogue/base.py delete mode 100644 tac/aea/helpers/__init__.py delete mode 100644 tac/aea/helpers/local_node.py delete mode 100644 tac/aea/mail/__init__.py delete mode 100644 tac/aea/mail/base.proto delete mode 100644 tac/aea/mail/base.py delete mode 100644 tac/aea/mail/base_pb2.py delete mode 100644 tac/aea/protocols/__init__.py delete mode 100644 tac/aea/protocols/base/__init__.py delete mode 100644 tac/aea/protocols/base/message.py delete mode 100644 tac/aea/protocols/base/serialization.py delete mode 100644 tac/aea/protocols/default/__init__.py delete mode 100644 tac/aea/protocols/default/message.py delete mode 100644 tac/aea/protocols/default/serialization.py delete mode 100644 tac/aea/protocols/fipa/__init__.py delete mode 100644 tac/aea/protocols/fipa/fipa.proto delete mode 100644 tac/aea/protocols/fipa/fipa_pb2.py delete mode 100644 tac/aea/protocols/fipa/message.py delete mode 100644 tac/aea/protocols/fipa/serialization.py delete mode 100644 tac/aea/protocols/oef/__init__.py delete mode 100644 tac/aea/protocols/oef/message.py delete mode 100644 tac/aea/protocols/oef/serialization.py delete mode 100644 tac/aea/protocols/tac/__init__.py delete mode 100644 tac/aea/protocols/tac/message.py delete mode 100644 tac/aea/protocols/tac/serialization.py delete mode 100644 tac/aea/state/__init__.py delete mode 100644 tac/aea/state/base.py diff --git a/Pipfile b/Pipfile index 023b64d4..580f8a91 100644 --- a/Pipfile +++ b/Pipfile @@ -21,20 +21,21 @@ flake8-docstrings = "*" pygments = "*" [packages] +aea = {version = "==0.1.0.post3",index = "test-pypi"} +fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} +base58 = "*" +cryptography = "*" +flask-restful = "*" +nbsphinx = "*" numpy = "*" matplotlib = "*" -python-dateutil = "*" -visdom = "*" -cryptography = "*" -base58 = "*" -fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} oef = {version = "==0.6.0",index = "test-pypi"} +python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" sphinx = "*" -nbsphinx = "*" -flask-restful = "*" wtforms = "*" +visdom = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index e37a3d3c..a61ca35e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f67c5d7b25cef332c01bb413338d4cb74a55c207930de615e989c9ea97dc2052" + "sha256": "13219dde216f73c945f18c20b136b748a0f131dd3aa4505d11ba29a60e797574" }, "pipfile-spec": 6, "requires": { @@ -21,6 +21,14 @@ ] }, "default": { + "aea": { + "hashes": [ + "sha256:299da7e504547a4e143a3f792d276d79f550826d8d869f815a1421453972c58e", + "sha256:c00a7aeb9d005a669cc05dea1ec257660b5ef39bc9e29281e8ebf5bc2889c86a" + ], + "index": "test-pypi", + "version": "==0.1.0.post3" + }, "alabaster": { "hashes": [ "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", @@ -212,10 +220,10 @@ }, "graphviz": { "hashes": [ - "sha256:6d0f69c107cfdc9bd1df3763fad99569bbcba29d0c52ffcbc6f266621d8bf709", - "sha256:914b8b124942d82e3e1dcef499c9fe77c10acd3d18a1cfeeb2b9de05f6d24805" + "sha256:8adc6460f9eddca440b826dd89790090d76fb8e693e2206fd4c2a4ed80841999", + "sha256:c60e232a66e4847f9f644fbaa94730ca4f78385a1314a2cc1e7f4cb2d7461298" ], - "version": "==0.11.1" + "version": "==0.12" }, "idna": { "hashes": [ @@ -593,11 +601,11 @@ }, "sphinx": { "hashes": [ - "sha256:22538e1bbe62b407cf5a8aabe1bb15848aa66bb79559f42f5202bbce6b757a69", - "sha256:f9a79e746b87921cabc3baa375199c6076d1270cee53915dbd24fdbeaaacc427" + "sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845", + "sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069" ], "index": "pypi", - "version": "==2.1.2" + "version": "==2.2.0" }, "sphinxcontrib-apidoc": { "hashes": [ @@ -734,6 +742,14 @@ } }, "develop": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -1038,10 +1054,10 @@ }, "notebook": { "hashes": [ - "sha256:0be97e939cec73cde37fc4d2a606a6f497a9addf3afcf61a09a21b0c35e699c5", - "sha256:5c16dbf4fa824db19de43637ebfb24bcbd3b4f646e5d6a0414ed3a376d6bc951" + "sha256:660976fe4fe45c7aa55e04bf4bccb9f9566749ff637e9020af3422f9921f9a5d", + "sha256:b0a290f5cc7792d50a21bec62b3c221dd820bf00efa916ce9aeec4b5354bde20" ], - "version": "==6.0.0" + "version": "==6.0.1" }, "packaging": { "hashes": [ @@ -1165,11 +1181,11 @@ }, "pytest": { "hashes": [ - "sha256:3805d095f1ea279b9870c3eeae5dddf8a81b10952c8835cd628cf1875b0ef031", - "sha256:abc562321c2d190dd63c2faadf70b86b7af21a553b61f0df5f5e1270717dc5a3" + "sha256:95b1f6db806e5b1b5b443efeb58984c24945508f93a866c1719e1a507a957d7c", + "sha256:c3d5020755f70c82eceda3feaf556af9a341334414a8eca521a18f463bcead88" ], "index": "pypi", - "version": "==5.1.0" + "version": "==5.1.1" }, "pytest-cov": { "hashes": [ @@ -1219,10 +1235,10 @@ }, "qtconsole": { "hashes": [ - "sha256:0877c4c66cb4f102dc796dc63a08ddfa55a981c36a83690b45dee819d15c1a38", - "sha256:84b43391ad0a54d91a628dbcd95562651052ea20457a75af85a66fea35397950" + "sha256:40f53ab58ef77aa4b53aca1ee314eed873d05952dec22d2ab84d20660943b7de", + "sha256:756bdcb6de6900dc50b14430accff2e47b846c3e7820e04075d4067b4c0ab52f" ], - "version": "==4.5.3" + "version": "==4.5.4" }, "requests": { "hashes": [ diff --git a/docs/reference/api/modules.rst b/docs/reference/api/modules.rst deleted file mode 100644 index 605667f2..00000000 --- a/docs/reference/api/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -tac -=== - -.. toctree:: - :maxdepth: 4 - - tac diff --git a/docs/reference/api/tac.aea.crypto.rst b/docs/reference/api/tac.aea.crypto.rst deleted file mode 100644 index d2409952..00000000 --- a/docs/reference/api/tac.aea.crypto.rst +++ /dev/null @@ -1,22 +0,0 @@ -tac.aea.crypto package -====================== - -Submodules ----------- - -tac.aea.crypto.base module --------------------------- - -.. automodule:: tac.aea.crypto.base - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.aea.crypto - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.aea.dialogue.rst b/docs/reference/api/tac.aea.dialogue.rst deleted file mode 100644 index 9ed60d3a..00000000 --- a/docs/reference/api/tac.aea.dialogue.rst +++ /dev/null @@ -1,22 +0,0 @@ -tac.aea.dialogue package -======================== - -Submodules ----------- - -tac.aea.dialogue.base module ----------------------------- - -.. automodule:: tac.aea.dialogue.base - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.aea.dialogue - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.aea.mail.rst b/docs/reference/api/tac.aea.mail.rst deleted file mode 100644 index f24ec578..00000000 --- a/docs/reference/api/tac.aea.mail.rst +++ /dev/null @@ -1,38 +0,0 @@ -tac.aea.mail package -==================== - -Submodules ----------- - -tac.aea.mail.base module ------------------------- - -.. automodule:: tac.aea.mail.base - :members: - :undoc-members: - :show-inheritance: - -tac.aea.mail.messages module ----------------------------- - -.. automodule:: tac.aea.mail.messages - :members: - :undoc-members: - :show-inheritance: - -tac.aea.mail.oef module ------------------------ - -.. automodule:: tac.aea.mail.oef - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.aea.mail - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.aea.rst b/docs/reference/api/tac.aea.rst deleted file mode 100644 index 1350e936..00000000 --- a/docs/reference/api/tac.aea.rst +++ /dev/null @@ -1,32 +0,0 @@ -tac.aea package -=============== - -Subpackages ------------ - -.. toctree:: - - tac.aea.crypto - tac.aea.dialogue - tac.aea.mail - tac.aea.state - -Submodules ----------- - -tac.aea.agent module --------------------- - -.. automodule:: tac.aea.agent - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.aea - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.aea.state.rst b/docs/reference/api/tac.aea.state.rst deleted file mode 100644 index ccb8ca8f..00000000 --- a/docs/reference/api/tac.aea.state.rst +++ /dev/null @@ -1,22 +0,0 @@ -tac.aea.state package -===================== - -Submodules ----------- - -tac.aea.state.base module -------------------------- - -.. automodule:: tac.aea.state.base - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.aea.state - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.controller.rst b/docs/reference/api/tac.agents.controller.rst deleted file mode 100644 index a36cb463..00000000 --- a/docs/reference/api/tac.agents.controller.rst +++ /dev/null @@ -1,22 +0,0 @@ -tac.agents.controller package -============================= - -Submodules ----------- - -tac.agents.controller.agent module ----------------------------------- - -.. automodule:: tac.agents.controller.agent - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.controller - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.base.rst b/docs/reference/api/tac.agents.participant.base.rst deleted file mode 100644 index 90f7b64b..00000000 --- a/docs/reference/api/tac.agents.participant.base.rst +++ /dev/null @@ -1,118 +0,0 @@ -tac.agents.participant.base package -=================================== - -Submodules ----------- - -tac.agents.participant.base.actions module ------------------------------------------- - -.. automodule:: tac.agents.participant.base.actions - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.dialogues module --------------------------------------------- - -.. automodule:: tac.agents.participant.base.dialogues - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.game\_instance module -------------------------------------------------- - -.. automodule:: tac.agents.participant.base.game_instance - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.handlers module -------------------------------------------- - -.. automodule:: tac.agents.participant.base.handlers - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.helpers module ------------------------------------------- - -.. automodule:: tac.agents.participant.base.helpers - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.interfaces module ---------------------------------------------- - -.. automodule:: tac.agents.participant.base.interfaces - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.negotiation\_behaviours module ----------------------------------------------------------- - -.. automodule:: tac.agents.participant.base.negotiation_behaviours - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.price\_model module ------------------------------------------------ - -.. automodule:: tac.agents.participant.base.price_model - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.reactions module --------------------------------------------- - -.. automodule:: tac.agents.participant.base.reactions - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.states module ------------------------------------------ - -.. automodule:: tac.agents.participant.base.states - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.stats\_manager module -------------------------------------------------- - -.. automodule:: tac.agents.participant.base.stats_manager - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.strategy module -------------------------------------------- - -.. automodule:: tac.agents.participant.base.strategy - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.base.transaction\_manager module -------------------------------------------------------- - -.. automodule:: tac.agents.participant.base.transaction_manager - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.participant.base - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.examples.rst b/docs/reference/api/tac.agents.participant.examples.rst deleted file mode 100644 index 1e439f9f..00000000 --- a/docs/reference/api/tac.agents.participant.examples.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.agents.participant.examples package -======================================= - -Submodules ----------- - -tac.agents.participant.examples.baseline module ------------------------------------------------ - -.. automodule:: tac.agents.participant.examples.baseline - :members: - :undoc-members: - :show-inheritance: - -tac.agents.participant.examples.strategy module ------------------------------------------------ - -.. automodule:: tac.agents.participant.examples.strategy - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.participant.examples - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.participant.rst b/docs/reference/api/tac.agents.participant.rst deleted file mode 100644 index db447ad9..00000000 --- a/docs/reference/api/tac.agents.participant.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.agents.participant package -============================== - -Subpackages ------------ - -.. toctree:: - - tac.agents.participant.base - tac.agents.participant.examples - -Submodules ----------- - -tac.agents.participant.agent module ------------------------------------ - -.. automodule:: tac.agents.participant.agent - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.agents.participant - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.agents.rst b/docs/reference/api/tac.agents.rst deleted file mode 100644 index 5823815d..00000000 --- a/docs/reference/api/tac.agents.rst +++ /dev/null @@ -1,18 +0,0 @@ -tac.agents package -================== - -Subpackages ------------ - -.. toctree:: - - tac.agents.controller - tac.agents.participant - -Module contents ---------------- - -.. automodule:: tac.agents - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.dashboards.rst b/docs/reference/api/tac.gui.dashboards.rst deleted file mode 100644 index 104380af..00000000 --- a/docs/reference/api/tac.gui.dashboards.rst +++ /dev/null @@ -1,54 +0,0 @@ -tac.gui.dashboards package -========================== - -Submodules ----------- - -tac.gui.dashboards.agent module -------------------------------- - -.. automodule:: tac.gui.dashboards.agent - :members: - :undoc-members: - :show-inheritance: - -tac.gui.dashboards.base module ------------------------------- - -.. automodule:: tac.gui.dashboards.base - :members: - :undoc-members: - :show-inheritance: - -tac.gui.dashboards.controller module ------------------------------------- - -.. automodule:: tac.gui.dashboards.controller - :members: - :undoc-members: - :show-inheritance: - -tac.gui.dashboards.helpers module ---------------------------------- - -.. automodule:: tac.gui.dashboards.helpers - :members: - :undoc-members: - :show-inheritance: - -tac.gui.dashboards.leaderboard module -------------------------------------- - -.. automodule:: tac.gui.dashboards.leaderboard - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.gui.dashboards - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.launcher.api.resources.rst b/docs/reference/api/tac.gui.launcher.api.resources.rst deleted file mode 100644 index 2e6c3bfa..00000000 --- a/docs/reference/api/tac.gui.launcher.api.resources.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.gui.launcher.api.resources package -====================================== - -Submodules ----------- - -tac.gui.launcher.api.resources.agents module --------------------------------------------- - -.. automodule:: tac.gui.launcher.api.resources.agents - :members: - :undoc-members: - :show-inheritance: - -tac.gui.launcher.api.resources.sandboxes module ------------------------------------------------ - -.. automodule:: tac.gui.launcher.api.resources.sandboxes - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.gui.launcher.api.resources - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.launcher.api.rst b/docs/reference/api/tac.gui.launcher.api.rst deleted file mode 100644 index a9d5df5d..00000000 --- a/docs/reference/api/tac.gui.launcher.api.rst +++ /dev/null @@ -1,17 +0,0 @@ -tac.gui.launcher.api package -============================ - -Subpackages ------------ - -.. toctree:: - - tac.gui.launcher.api.resources - -Module contents ---------------- - -.. automodule:: tac.gui.launcher.api - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.launcher.forms.rst b/docs/reference/api/tac.gui.launcher.forms.rst deleted file mode 100644 index 8ff3207e..00000000 --- a/docs/reference/api/tac.gui.launcher.forms.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.gui.launcher.forms package -============================== - -Submodules ----------- - -tac.gui.launcher.forms.agent module ------------------------------------ - -.. automodule:: tac.gui.launcher.forms.agent - :members: - :undoc-members: - :show-inheritance: - -tac.gui.launcher.forms.sandbox module -------------------------------------- - -.. automodule:: tac.gui.launcher.forms.sandbox - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.gui.launcher.forms - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.launcher.rst b/docs/reference/api/tac.gui.launcher.rst deleted file mode 100644 index 5bcb891e..00000000 --- a/docs/reference/api/tac.gui.launcher.rst +++ /dev/null @@ -1,46 +0,0 @@ -tac.gui.launcher package -======================== - -Subpackages ------------ - -.. toctree:: - - tac.gui.launcher.api - tac.gui.launcher.forms - -Submodules ----------- - -tac.gui.launcher.app module ---------------------------- - -.. automodule:: tac.gui.launcher.app - :members: - :undoc-members: - :show-inheritance: - -tac.gui.launcher.config module ------------------------------- - -.. automodule:: tac.gui.launcher.config - :members: - :undoc-members: - :show-inheritance: - -tac.gui.launcher.home module ----------------------------- - -.. automodule:: tac.gui.launcher.home - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.gui.launcher - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.gui.rst b/docs/reference/api/tac.gui.rst deleted file mode 100644 index e9c31ed9..00000000 --- a/docs/reference/api/tac.gui.rst +++ /dev/null @@ -1,30 +0,0 @@ -tac.gui package -=============== - -Subpackages ------------ - -.. toctree:: - - tac.gui.dashboards - tac.gui.launcher - -Submodules ----------- - -tac.gui.monitor module ----------------------- - -.. automodule:: tac.gui.monitor - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.gui - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.platform.game.rst b/docs/reference/api/tac.platform.game.rst deleted file mode 100644 index 8794ef93..00000000 --- a/docs/reference/api/tac.platform.game.rst +++ /dev/null @@ -1,38 +0,0 @@ -tac.platform.game package -========================= - -Submodules ----------- - -tac.platform.game.base module ------------------------------ - -.. automodule:: tac.platform.game.base - :members: - :undoc-members: - :show-inheritance: - -tac.platform.game.helpers module --------------------------------- - -.. automodule:: tac.platform.game.helpers - :members: - :undoc-members: - :show-inheritance: - -tac.platform.game.stats module ------------------------------- - -.. automodule:: tac.platform.game.stats - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.platform.game - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.platform.rst b/docs/reference/api/tac.platform.rst deleted file mode 100644 index 24642943..00000000 --- a/docs/reference/api/tac.platform.rst +++ /dev/null @@ -1,45 +0,0 @@ -tac.platform package -==================== - -Subpackages ------------ - -.. toctree:: - - tac.platform.game - -Submodules ----------- - -tac.platform.oef\_health\_check module --------------------------------------- - -.. automodule:: tac.platform.oef_health_check - :members: - :undoc-members: - :show-inheritance: - -tac.platform.protocol module ----------------------------- - -.. automodule:: tac.platform.protocol - :members: - :undoc-members: - :show-inheritance: - -tac.platform.simulation module ------------------------------- - -.. automodule:: tac.platform.simulation - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac.platform - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/reference/api/tac.rst b/docs/reference/api/tac.rst deleted file mode 100644 index affa375f..00000000 --- a/docs/reference/api/tac.rst +++ /dev/null @@ -1,32 +0,0 @@ -tac package -=========== - -Subpackages ------------ - -.. toctree:: - - tac.aea - tac.agents - tac.gui - tac.platform - -Submodules ----------- - -tac.tac\_pb2 module -------------------- - -.. automodule:: tac.tac_pb2 - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: tac - :members: - :undoc-members: - :show-inheritance: diff --git a/sandbox/playground.py b/sandbox/playground.py index 05f5ff1a..f4e77046 100644 --- a/sandbox/playground.py +++ b/sandbox/playground.py @@ -32,10 +32,10 @@ import docker -from tac.aea.dialogue.base import Dialogue -from tac.aea.mail.base import Envelope -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.dialogue.base import Dialogue +from aea.mail.base import Envelope +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.examples.baseline import BaselineAgent from tac.agents.participant.v1.examples.strategy import BaselineStrategy from tac.platform.protocol import GameData diff --git a/scripts/generate_private_key.py b/scripts/generate_private_key.py index dfb2a768..e7e9e96a 100644 --- a/scripts/generate_private_key.py +++ b/scripts/generate_private_key.py @@ -29,7 +29,7 @@ import argparse -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto parser = argparse.ArgumentParser("generate_private_key", description=__doc__) parser.add_argument("out_file", type=str, help="Where to save the private key.") diff --git a/setup.py b/setup.py index 7dc847f7..c7d3aeb7 100644 --- a/setup.py +++ b/setup.py @@ -103,7 +103,7 @@ def _fix_import_statements_in_protobuf_module(self, filename): author=about['__author__'], url=about['__url__'], long_description=readme, - packages=find_packages(include=["tac"]), + packages=find_packages(include=["tac*"]), cmdclass={ 'protoc': protoc, }, @@ -111,7 +111,7 @@ def _fix_import_statements_in_protobuf_module(self, filename): 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'Natural Language :: English', - 'License :: OSI Approved :: MIT Software License', + 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], diff --git a/tac/aea/__init__.py b/tac/aea/__init__.py deleted file mode 100644 index 4763e0c6..00000000 --- a/tac/aea/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Contains the AEA package.""" diff --git a/tac/aea/agent.py b/tac/aea/agent.py deleted file mode 100644 index 6f72eb32..00000000 --- a/tac/aea/agent.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the implementation of a template agent.""" - -import logging -import time - -from abc import abstractmethod -from enum import Enum -from typing import Dict, Optional - -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import InBox, OutBox, MailBox, ProtocolId - -logger = logging.getLogger(__name__) - -Handler = object -Behaviour = object - - -class AgentState(Enum): - """Enumeration for an agent state.""" - - INITIATED = "initiated" - CONNECTED = "connected" - RUNNING = "running" - - -class Liveness: - """Determines the liveness of the agent.""" - - def __init__(self): - """Instantiate the liveness.""" - self._is_stopped = True - - @property - def is_stopped(self) -> bool: - """Check whether the liveness is stopped.""" - return self._is_stopped - - -class Agent: - """This class implements a template agent.""" - - def __init__(self, name: str, - oef_addr: str, - oef_port: int = 10000, - private_key_pem_path: Optional[str] = None, - timeout: Optional[float] = 1.0, - debug: bool = False) -> None: - """ - Instantiate the agent. - - :param name: the name of the agent - :param oef_addr: TCP/IP address of the OEF Agent - :param oef_port: TCP/IP port of the OEF Agent - :param private_key_pem_path: the path to the private key of the agent. - :param timeout: the time in (fractions of) seconds to time out an agent between act and react - :param debug: if True, run the agent in debug mode. - - :return: None - """ - self._name = name - self._crypto = Crypto(private_key_pem_path=private_key_pem_path) - self._liveness = Liveness() - self._timeout = timeout - - self._handlers = {} # type: Dict[ProtocolId, Handler] - self._behaviours = {} # type: Dict[ProtocolId, Behaviour] - - self.debug = debug - - self.mailbox = None # type: Optional[MailBox] - - @property - def inbox(self) -> Optional[InBox]: - """Get the inbox.""" - return self.mailbox.inbox if self.mailbox else None - - @property - def outbox(self) -> Optional[OutBox]: - """Get the outbox.""" - return self.mailbox.outbox if self.mailbox else None - - @property - def name(self) -> str: - """Get the agent name.""" - return self._name - - @property - def crypto(self) -> Crypto: - """Get the crypto.""" - return self._crypto - - @property - def liveness(self) -> Liveness: - """Get the liveness.""" - return self._liveness - - @property - def handlers(self) -> Dict[str, object]: - """Get the registered handlers.""" - return self._behaviours - - @property - def behaviours(self) -> Dict[str, object]: - """Get the registered behaviours.""" - return self._behaviours - - @property - def agent_state(self) -> AgentState: - """ - Get the state of the agent. - - In particular, it can be one of the following states: - - AgentState.INITIATED: when the Agent object has been created. - - AgentState.CONNECTED: when the agent is connected. - - AgentState.RUNNING: when the agent is running. - - :return the agent state. - :raises ValueError: if the state does not satisfy any of the foreseen conditions. - """ - if self.mailbox is None or not self.mailbox.is_connected: - return AgentState.INITIATED - elif self.mailbox.is_connected and self.liveness.is_stopped: - return AgentState.CONNECTED - elif self.mailbox.is_connected and not self.liveness.is_stopped: - return AgentState.RUNNING - else: - raise ValueError("Agent state not recognized.") - - def start(self) -> None: - """ - Start the agent. - - :return: None - """ - if not self.debug and not self.mailbox.is_connected: - self.mailbox.connect() - - self.liveness._is_stopped = False - self._run_main_loop() - - def _run_main_loop(self) -> None: - """ - Run the main loop of the agent. - - :return: None - """ - logger.debug("[{}]: Calling setup method...".format(self.name)) - self.setup() - - logger.debug("[{}]: Start processing messages...".format(self.name)) - while not self.liveness.is_stopped: - self.act() - time.sleep(self._timeout) - self.react() - self.update() - - logger.debug("[{}]: Calling teardown method...".format(self.name)) - self.teardown() - - self.stop() - logger.debug("[{}]: Exiting main loop...".format(self.name)) - - def stop(self) -> None: - """ - Stop the agent. - - :return: None - """ - logger.debug("[{}]: Stopping message processing...".format(self.name)) - self.liveness._is_stopped = True - if self.mailbox.is_connected: - self.mailbox.disconnect() - - @abstractmethod - def setup(self) -> None: - """ - Set up the agent. - - :return: None - """ - - @abstractmethod - def act(self) -> None: - """ - Perform actions. - - :return: None - """ - - @abstractmethod - def react(self) -> None: - """ - React to incoming events. - - :return: None - """ - - @abstractmethod - def update(self) -> None: - """Update the current state of the agent. - - :return None - """ - - @abstractmethod - def teardown(self) -> None: - """ - Tear down the agent. - - :return: None - """ diff --git a/tac/aea/channel/__init__.py b/tac/aea/channel/__init__.py deleted file mode 100644 index e5cae999..00000000 --- a/tac/aea/channel/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the channel modules.""" diff --git a/tac/aea/channel/local.py b/tac/aea/channel/local.py deleted file mode 100644 index 50e83be9..00000000 --- a/tac/aea/channel/local.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Extension to the Local Node.""" diff --git a/tac/aea/channel/oef.py b/tac/aea/channel/oef.py deleted file mode 100644 index f1b5bc7f..00000000 --- a/tac/aea/channel/oef.py +++ /dev/null @@ -1,443 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Extension to the OEF Python SDK.""" -import asyncio -import datetime -import logging -from asyncio import AbstractEventLoop -from queue import Empty, Queue -from threading import Thread -from typing import List, Dict, Optional - -from oef.agents import OEFAgent -from oef.messages import CFP_TYPES, PROPOSE_TYPES, OEFErrorOperation - -from tac.aea.mail.base import Connection, MailBox, Envelope -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.oef.message import OEFMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.aea.protocols.oef.serialization import OEFSerializer - -logger = logging.getLogger(__name__) - - -STUB_MESSSAGE_ID = 0 -STUB_DIALOGUE_ID = 0 - - -class MailStats(object): - """The MailStats class tracks statistics on messages processed by MailBox.""" - - def __init__(self) -> None: - """ - Instantiate mail stats. - - :return: None - """ - self._search_count = 0 - self._search_start_time = {} # type: Dict[int, datetime.datetime] - self._search_timedelta = {} # type: Dict[int, float] - self._search_result_counts = {} # type: Dict[int, int] - - @property - def search_count(self) -> int: - """Get the search count.""" - return self._search_count - - def search_start(self, search_id: int) -> None: - """ - Add a search id and start time. - - :param search_id: the search id - - :return: None - """ - assert search_id not in self._search_start_time - self._search_count += 1 - self._search_start_time[search_id] = datetime.datetime.now() - - def search_end(self, search_id: int, nb_search_results: int) -> None: - """ - Add end time for a search id. - - :param search_id: the search id - :param nb_search_results: the number of agents returned in the search result - - :return: None - """ - assert search_id in self._search_start_time - assert search_id not in self._search_timedelta - self._search_timedelta[search_id] = (datetime.datetime.now() - self._search_start_time[search_id]).total_seconds() * 1000 - self._search_result_counts[search_id] = nb_search_results - - -class OEFChannel(OEFAgent): - """The OEFChannel connects the OEF Agent with the connection.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000, loop: Optional[AbstractEventLoop] = None, in_queue: Optional[Queue] = None): - """ - Initialize. - - :param public_key: the public key of the agent. - :param oef_addr: the OEF IP address. - :param oef_port: the OEF port. - :param in_queue: the in queue. - """ - super().__init__(public_key, oef_addr, oef_port, loop=loop) - self.in_queue = in_queue - self.mail_stats = MailStats() - - @property - def loop(self) -> asyncio.AbstractEventLoop: - """Get the event loop.""" - return self._loop - - def is_connected(self) -> bool: - """Get connected status.""" - return self._oef_proxy.is_connected() - - def is_active(self) -> bool: - """Get active status.""" - return self._oef_proxy._active_loop - - def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: - """ - On message event handler. - - :param msg_id: the message id. - :param dialogue_id: the dialogue id. - :param origin: the public key of the sender. - :param content: the bytes content. - :return: None - """ - # We are not using the 'origin' parameter because 'content' contains a serialized instance of 'Envelope', - # hence it already contains the address of the sender. - envelope = Envelope.decode(content) - self.in_queue.put(envelope) - - def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, 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 public key of the sender. - :param target: the message target. - :param query: the query. - :return: None - """ - msg = FIPAMessage(message_id=msg_id, - dialogue_id=dialogue_id, - target=target, - performative=FIPAMessage.Performative.CFP, - query=query) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: - """ - On propose event handler. - - :param msg_id: the message id. - :param dialogue_id: the dialogue id. - :param origin: the public key of the sender. - :param target: the message target. - :param proposals: the proposals. - :return: None - """ - msg = FIPAMessage(message_id=msg_id, - dialogue_id=dialogue_id, - target=target, - performative=FIPAMessage.Performative.PROPOSE, - proposal=proposals) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - On accept event handler. - - :param msg_id: the message id. - :param dialogue_id: the dialogue id. - :param origin: the public key of the sender. - :param target: the message target. - :return: None - """ - performative = FIPAMessage.Performative.MATCH_ACCEPT if msg_id == 4 and target == 3 else FIPAMessage.Performative.ACCEPT - msg = FIPAMessage(message_id=msg_id, - dialogue_id=dialogue_id, - target=target, - performative=performative) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - On decline event handler. - - :param msg_id: the message id. - :param dialogue_id: the dialogue id. - :param origin: the public key of the sender. - :param target: the message target. - :return: None - """ - msg = FIPAMessage(message_id=msg_id, - dialogue_id=dialogue_id, - target=target, - performative=FIPAMessage.Performative.DECLINE) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=origin, protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_search_result(self, search_id: int, agents: List[str]) -> None: - """ - On accept event handler. - - :param search_id: the search id. - :param agents: the list of agents. - :return: None - """ - self.mail_stats.search_end(search_id, len(agents)) - msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=agents) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: - """ - On oef error event handler. - - :param answer_id: the answer id. - :param operation: the error operation. - :return: None - """ - msg = OEFMessage(oef_type=OEFMessage.Type.OEF_ERROR, - id=answer_id, - operation=operation) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: - """ - On dialogue error event handler. - - :param answer_id: the answer id. - :param dialogue_id: the dialogue id. - :param origin: the message sender. - :return: None - """ - msg = OEFMessage(oef_type=OEFMessage.Type.DIALOGUE_ERROR, - id=answer_id, - dialogue_id=dialogue_id, - origin=origin) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=self.public_key, sender=None, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self.in_queue.put(envelope) - - def send(self, envelope: Envelope) -> None: - """ - Send message handler. - - :param envelope: the message. - :return: None - """ - if envelope.protocol_id == "oef": - self.send_oef_message(envelope) - elif envelope.protocol_id == "fipa": - self.send_fipa_message(envelope) - elif envelope.protocol_id == "bytes": - self.send_bytes_message(envelope) - elif envelope.protocol_id == "default": - self.send_default_message(envelope) - else: - raise ValueError("Cannot send message.") - - def send_oef_message(self, envelope: Envelope) -> None: - """ - Send oef message handler. - - :param envelope: the message. - :return: None - """ - oef_message = OEFSerializer().decode(envelope.message) - oef_type = OEFMessage.Type(oef_message.get("type")) - if oef_type == OEFMessage.Type.REGISTER_SERVICE: - id = oef_message.get("id") - service_description = oef_message.get("service_description") - service_id = oef_message.get("service_id") - self.register_service(id, service_description, service_id) - elif oef_type == OEFMessage.Type.REGISTER_AGENT: - id = oef_message.get("id") - agent_description = oef_message.get("agent_description") - self.register_agent(id, agent_description) - elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - id = oef_message.get("id") - service_description = oef_message.get("service_description") - service_id = oef_message.get("service_id") - self.unregister_service(id, service_description, service_id) - elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - id = oef_message.get("id") - self.unregister_agent(id) - elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - id = oef_message.get("id") - query = oef_message.get("query") - self.search_agents(id, query) - elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - id = oef_message.get("id") - query = oef_message.get("query") - self.mail_stats.search_start(id) - self.search_services(id, query) - else: - raise ValueError("OEF request not recognized.") - - def send_fipa_message(self, envelope: Envelope) -> None: - """ - Send fipa message handler. - - :param envelope: the message. - :return: None - """ - fipa_message = FIPASerializer().decode(envelope.message) - id = fipa_message.get("id") - dialogue_id = fipa_message.get("dialogue_id") - destination = envelope.to - target = fipa_message.get("target") - performative = FIPAMessage.Performative(fipa_message.get("performative")) - if performative == FIPAMessage.Performative.CFP: - query = fipa_message.get("query") - self.send_cfp(id, dialogue_id, destination, target, query) - elif performative == FIPAMessage.Performative.PROPOSE: - proposal = fipa_message.get("proposal") - self.send_propose(id, dialogue_id, destination, target, proposal) - elif performative == FIPAMessage.Performative.ACCEPT: - self.send_accept(id, dialogue_id, destination, target) - elif performative == FIPAMessage.Performative.MATCH_ACCEPT: - self.send_accept(id, dialogue_id, destination, target) - elif performative == FIPAMessage.Performative.DECLINE: - self.send_decline(id, dialogue_id, destination, target) - else: - raise ValueError("OEF FIPA message not recognized.") - - def send_bytes_message(self, envelope: Envelope): - """Send a 'bytes' message.""" - self.send_message(STUB_MESSSAGE_ID, STUB_DIALOGUE_ID, envelope.to, envelope.encode()) - - def send_default_message(self, envelope: Envelope): - """Send a 'default' message.""" - self.send_message(STUB_MESSSAGE_ID, STUB_DIALOGUE_ID, envelope.to, envelope.encode()) - - -class OEFConnection(Connection): - """The OEFConnection connects the to the mailbox.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): - """ - Initialize. - - :param public_key: the public key of the agent. - :param oef_addr: the OEF IP address. - :param oef_port: the OEF port. - """ - super().__init__() - - self.bridge = OEFChannel(public_key, oef_addr, oef_port, loop=asyncio.new_event_loop(), in_queue=self.in_queue) - - self._stopped = True - self.in_thread = Thread(target=self.bridge.run) - self.out_thread = Thread(target=self._fetch) - - def _fetch(self) -> None: - """ - Fetch the messages from the outqueue and send them. - - :return: None - """ - while not self._stopped: - try: - msg = self.out_queue.get(block=True, timeout=1.0) - self.send(msg) - except Empty: - pass - - def connect(self) -> None: - """ - Connect to the bridge. - - :return: None - """ - if self._stopped: - self._stopped = False - self.bridge.connect() - self.in_thread.start() - self.out_thread.start() - - def disconnect(self) -> None: - """ - Disconnect from the bridge. - - :return: None - """ - self._stopped = True - if self.bridge.is_active(): - self.bridge.stop() - - self.in_thread.join() - self.out_thread.join() - self.in_thread = Thread(target=self.bridge.run) - self.out_thread = Thread(target=self._fetch) - self.bridge.disconnect() - - @property - def is_established(self) -> bool: - """Get the connection status.""" - return self.bridge.is_connected() - - def send(self, envelope: Envelope): - """ - Send messages. - - :return: None - """ - self.bridge.send(envelope) - - -class OEFMailBox(MailBox): - """The OEF mail box.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): - """ - Initialize. - - :param public_key: the public key of the agent. - :param oef_addr: the OEF IP address. - :param oef_port: the OEF port. - """ - connection = OEFConnection(public_key, oef_addr, oef_port) - super().__init__(connection) - - @property - def mail_stats(self) -> MailStats: - """Get the mail stats object.""" - return self._connection.bridge.mail_stats diff --git a/tac/aea/crypto/__init__.py b/tac/aea/crypto/__init__.py deleted file mode 100644 index fdfae79e..00000000 --- a/tac/aea/crypto/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the crypto modules.""" diff --git a/tac/aea/crypto/base.py b/tac/aea/crypto/base.py deleted file mode 100644 index ddb9b71c..00000000 --- a/tac/aea/crypto/base.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Module wrapping the public and private key cryptography.""" - -from typing import Optional - -import base58 -import logging - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, utils -from cryptography.hazmat.primitives.serialization import load_pem_private_key - -logger = logging.getLogger(__name__) - - -def _load_pem_private_key_from_path(path): - return load_pem_private_key(open(path, "rb").read(), None, default_backend()) - - -class CryptoError(Exception): - """Exception to be thrown when cryptographic signatures don't match!.""" - - -CHOSEN_ALGORITHM_ID = b'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE' -CHOSEN_PBK_LENGTH = 160 - - -class Crypto(object): - """Class wrapping the public and private key cryptography.""" - - def __init__(self, private_key_pem_path: Optional[str] = None): - """Instantiate a crypto object.""" - self._chosen_ec = ec.SECP384R1() - self._chosen_hash = hashes.SHA256() - self._private_key = self._generate_pk() if private_key_pem_path is None else self._load_pem_private_key_from_path(private_key_pem_path) - self._public_key_obj = self._compute_pbk() - self._public_key_pem = self._pbk_obj_to_pem(self._public_key_obj) - self._public_key_b64 = self._pbk_pem_to_b64(self._public_key_pem) - self._public_key_b58 = self._pbk_b64_to_b58(self._public_key_b64) - self._fingerprint_hex = self._pbk_b64_to_hex(self._public_key_b64) - assert self._pbk_obj_to_b58(self._public_key_obj) == self._pbk_obj_to_b58(self._pbk_b58_to_obj(self._public_key_b58)) - - @property - def public_key(self) -> str: - """ - Return a 219 character public key in base58 format. - - :return: a public key string in base58 format - """ - return self._public_key_b58 - - @property - def public_key_pem(self) -> bytes: - """ - Return a PEM encoded public key in base64 format. It consists of an algorithm identifier and the public key as a bit string. - - :return: a public key bytes string - """ - return self._public_key_pem - - @property - def fingerprint(self) -> str: - """ - Return a 64 character fingerprint of the public key in hexadecimal format (32 bytes). - - :return: the fingerprint - """ - return self._fingerprint_hex - - def _generate_pk(self) -> object: - """ - Generate a private key object. - - :return: private key object - """ - private_key = ec.generate_private_key(self._chosen_ec, default_backend()) - return private_key - - def _load_pem_private_key_from_path(self, path) -> object: - """ - Load a private key in PEM format from a file. - - :param path: the path to the PEM file. - - :return: the private key. - """ - private_key = load_pem_private_key(open(path, "rb").read(), None, default_backend()) - try: - assert private_key.curve.name == self._chosen_ec.name - except AssertionError: - raise ValueError("Expected elliptic curve: {} actual: {}".format(private_key.curve.name, self._chosen_ec.name)) - return private_key - - def _compute_pbk(self) -> object: - """ - Derive the public key from the private key. - - :return: public key object - """ - public_key = self._private_key.public_key() - return public_key - - def _pbk_obj_to_pem(self, pbk: object) -> bytes: - """ - Serialize the public key from object to bytes. - - :param pbk: the public key as an object - - :return: the public key as a bytes string in pem format (base64) - """ - result = pbk.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) - return result - - def _pbk_pem_to_b64(self, pbk: bytes) -> bytes: - """ - Convert the public key from pem bytes string format to standard bytes string format. - - :param pbk: the public key as a bytes string (PEM base64) - - :return: the public key as a bytes string (base64) - """ - result = b''.join(pbk.splitlines()[1:-1]) - return result - - def _pbk_b64_to_b58(self, pbk: bytes) -> str: - """ - Convert the public key from base64 to base58 string. - - :param pbk: the public key as a bytes string (base64) - - :return: the public key as a string (base58) - """ - result = base58.b58encode(pbk).decode('utf-8') - return result - - def _pbk_obj_to_b58(self, pbk: object) -> str: - """ - Convert the public key from object to string. - - :param pbk: the public key as an object - - :return: the public key as a string (base58) - """ - pbk = self._pbk_obj_to_pem(pbk) - pbk = self._pbk_pem_to_b64(pbk) - pbk = self._pbk_b64_to_b58(pbk) - return pbk - - def _pbk_b58_to_b64(self, pbk: str) -> bytes: - """ - Convert the public key from base58 string to base64 bytes string. - - :param pbk: the public key in base58 - - :return: the public key in base64 - """ - pbk_b64 = base58.b58decode(str.encode(pbk)) - return pbk_b64 - - def _pbk_b64_to_pem(self, pbk: bytes) -> bytes: - """ - Convert the public key from standard bytes string format to pem bytes string format. - - :param pbk: the public key as a bytes string (base64) - - :return: the public key as a bytes string (PEM base64) - """ - assert len(pbk) == CHOSEN_PBK_LENGTH, "Public key is not of expected length." - assert pbk[0:32] == CHOSEN_ALGORITHM_ID, "Public key has not expected algorithm id." - pbk = pbk[0:64] + b'\n' + pbk[64:128] + b'\n' + pbk[128:] + b'\n' - pbk_pem = b'-----BEGIN PUBLIC KEY-----\n' + pbk + b'-----END PUBLIC KEY-----\n' - return pbk_pem - - def _pbk_b58_to_obj(self, pbk: str) -> object: - """ - Convert the public key from string (base58) to object. - - :param pbk: the public key as a string (base58) - - :return: the public key object - """ - pbk_b64 = self._pbk_b58_to_b64(pbk) - pbk_pem = self._pbk_b64_to_pem(pbk_b64) - pbk_obj = serialization.load_pem_public_key(pbk_pem, backend=default_backend()) - return pbk_obj - - def sign_data(self, data: bytes) -> bytes: - """ - Sign data with your own private key. - - :param data: the data to sign - :return: the signature - """ - digest = self._hash_data(data) - signature = self._private_key.sign(digest, ec.ECDSA(utils.Prehashed(self._chosen_hash))) - return signature - - def is_confirmed_integrity(self, data: bytes, signature: bytes, signer_pbk: str) -> bool: - """ - Confirrm the integrity of the data with respect to its signature. - - :param data: the data to be confirmed - :param signature: the signature associated with the data - :param signer_pbk: the public key of the signer - - :return: bool indicating whether the integrity is confirmed or not - """ - signer_pbk_obj = self._pbk_b58_to_obj(signer_pbk) - digest = self._hash_data(data) - try: - signer_pbk_obj.verify(signature, digest, ec.ECDSA(utils.Prehashed(self._chosen_hash))) - return True - except CryptoError as e: - logger.exception(str(e)) - return False - - def _hash_data(self, data: bytes) -> bytes: - """ - Hash data. - - :param data: the data to be hashed - :return: digest of the data - """ - hasher = hashes.Hash(self._chosen_hash, default_backend()) - hasher.update(data) - digest = hasher.finalize() - return digest - - def _pbk_b64_to_hex(self, pbk: bytes) -> str: - """ - Hash the public key to obtain a fingerprint. - - :return: the fingerprint in hex format - """ - pbk_hash = self._hash_data(pbk) - pbk_hex = pbk_hash.hex() - return pbk_hex - - def _pvk_obj_to_pem(self, pvk: object) -> bytes: - return pvk.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL, serialization.NoEncryption()) diff --git a/tac/aea/dialogue/__init__.py b/tac/aea/dialogue/__init__.py deleted file mode 100644 index d4d42dda..00000000 --- a/tac/aea/dialogue/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the dialogue modules.""" diff --git a/tac/aea/dialogue/base.py b/tac/aea/dialogue/base.py deleted file mode 100644 index 22d2c7db..00000000 --- a/tac/aea/dialogue/base.py +++ /dev/null @@ -1,224 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -""" -This module contains the classes required for dialogue management. - -- DialogueLabel: The dialogue label class acts as an identifier for dialogues. -- Dialogue: The dialogue class maintains state of a dialogue and manages it. -- Dialogues: The dialogues class keeps track of all dialogues. -""" - -from abc import abstractmethod -from typing import Dict, List, TYPE_CHECKING - -if TYPE_CHECKING: - from tac.aea.protocols.base.message import Message - - -class DialogueLabel: - """The dialogue label class acts as an identifier for dialogues.""" - - def __init__(self, dialogue_id: int, dialogue_opponent_pbk: str, dialogue_starter_pbk: str) -> None: - """ - Initialize a dialogue label. - - :param dialogue_id: the id of the dialogue. - :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. - :param dialogue_starter_pbk: the pbk of the agent which started the dialogue. - - :return: None - """ - self._dialogue_id = dialogue_id - self._dialogue_opponent_pbk = dialogue_opponent_pbk - self._dialogue_starter_pbk = dialogue_starter_pbk - - @property - def dialogue_id(self) -> int: - """Get the dialogue id.""" - return self._dialogue_id - - @property - def dialogue_opponent_pbk(self) -> str: - """Get the public key of the dialogue opponent.""" - return self._dialogue_opponent_pbk - - @property - def dialogue_starter_pbk(self) -> str: - """Get the public key of the dialogue starter.""" - return self._dialogue_starter_pbk - - def __eq__(self, other) -> bool: - """Check for equality between two DialogueLabel objects.""" - if type(other) == DialogueLabel: - return self._dialogue_id == other.dialogue_id and self._dialogue_starter_pbk == other.dialogue_starter_pbk and self._dialogue_opponent_pbk == other.dialogue_opponent_pbk - else: - return False - - def __hash__(self) -> int: - """Turn object into hash.""" - return hash((self.dialogue_id, self.dialogue_opponent_pbk, self.dialogue_starter_pbk)) - - -class Dialogue: - """The dialogue class maintains state of a dialogue and manages it.""" - - def __init__(self, dialogue_label: DialogueLabel) -> None: - """ - Initialize a dialogue label. - - :param dialogue_label: the identifier of the dialogue - - :return: None - """ - self._dialogue_label = dialogue_label - self._is_self_initiated = dialogue_label.dialogue_opponent_pbk is not dialogue_label.dialogue_starter_pbk - self._outgoing_messages = [] # type: List['Message'] - self._outgoing_messages_controller = [] # type: List['Message'] - self._incoming_messages = [] # type: List['Message'] - - @property - def dialogue_label(self) -> DialogueLabel: - """Get the dialogue lable.""" - return self._dialogue_label - - @property - def is_self_initiated(self) -> bool: - """Check whether the agent initiated the dialogue.""" - return self._is_self_initiated - - def outgoing_extend(self, messages: List['Message']) -> None: - """ - Extend the list of messages which keeps track of outgoing messages. - - :param messages: a list of messages to be added - :return: None - """ - for message in messages: - self._outgoing_messages.extend([message]) - - def incoming_extend(self, messages: List['Message']) -> None: - """ - Extend the list of messages which keeps track of incoming messages. - - :param messages: a list of messages to be added - :return: None - """ - self._incoming_messages.extend(messages) - - -class Dialogues: - """The dialogues class keeps track of all dialogues.""" - - def __init__(self) -> None: - """ - Initialize dialogues. - - :return: None - """ - self._dialogues = {} # type: Dict[DialogueLabel, Dialogue] - self._dialogue_id = 0 - - @property - def dialogues(self) -> Dict[DialogueLabel, Dialogue]: - """Get dictionary of dialogues in which the agent is engaged in.""" - return self._dialogues - - @abstractmethod - def is_permitted_for_new_dialogue(self, msg: 'Message', known_pbks: List[str]) -> bool: - """ - Check whether an agent message is permitted for a new dialogue. - - :param msg: the agent message - :param known_pbks: the list of known public keys - - :return: a boolean indicating whether the message is permitted for a new dialogue - """ - - @abstractmethod - def is_belonging_to_registered_dialogue(self, msg: 'Message', agent_pbk: str) -> bool: - """ - Check whether an agent message is part of a registered dialogue. - - :param msg: the agent message - :param agent_pbk: the public key of the agent - - :return: boolean indicating whether the message belongs to a registered dialogue - """ - - @abstractmethod - def get_dialogue(self, msg: 'Message', agent_pbk: str) -> Dialogue: - """ - Retrieve dialogue. - - :param msg: the agent message - :param agent_pbk: the public key of the agent - - :return: the dialogue - """ - - def _next_dialogue_id(self) -> int: - """ - Increment the id and returns it. - - :return: the next id - """ - self._dialogue_id += 1 - return self._dialogue_id - - def create_self_initiated(self, dialogue_opponent_pbk: str, dialogue_starter_pbk: str) -> Dialogue: - """ - Create a self initiated dialogue. - - :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. - :param dialogue_starter_pbk: the pbk of the agent which started the dialogue - - :return: the created dialogue. - """ - dialogue_label = DialogueLabel(self._next_dialogue_id(), dialogue_opponent_pbk, dialogue_starter_pbk) - result = self._create(dialogue_label) - return result - - def create_opponent_initiated(self, dialogue_opponent_pbk: str, dialogue_id: int) -> Dialogue: - """ - Save an opponent initiated dialogue. - - :param dialogue_opponent_pbk: the pbk of the agent with which the dialogue is kept. - :param dialogue_id: the id of the dialogue - - :return: the created dialogue - """ - dialogue_starter_pbk = dialogue_opponent_pbk - dialogue_label = DialogueLabel(dialogue_id, dialogue_opponent_pbk, dialogue_starter_pbk) - result = self._create(dialogue_label) - return result - - def _create(self, dialogue_label: DialogueLabel) -> Dialogue: - """ - Create a dialogue. - - :param dialogue_label: the dialogue label - - :return: the created dialogue - """ - assert dialogue_label not in self.dialogues - dialogue = Dialogue(dialogue_label) - self.dialogues.update({dialogue_label: dialogue}) - return dialogue diff --git a/tac/aea/helpers/__init__.py b/tac/aea/helpers/__init__.py deleted file mode 100644 index ac3e5d55..00000000 --- a/tac/aea/helpers/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains helper methods and classes for the 'aea' package.""" diff --git a/tac/aea/helpers/local_node.py b/tac/aea/helpers/local_node.py deleted file mode 100644 index 8b6a747e..00000000 --- a/tac/aea/helpers/local_node.py +++ /dev/null @@ -1,353 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Naive implementation of the OEF Node features.""" - -import asyncio -import logging -import queue -import threading -from collections import defaultdict -from queue import Queue -from threading import Thread -from typing import Dict, List, Optional - -from oef.query import Query -from oef.schema import Description - -from tac.aea.mail.base import Connection -from tac.aea.mail.messages import OEFMessage -from tac.aea.mail.oef import STUB_DIALOGUE_ID -from tac.aea.mail.protocol import Envelope -from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF, OEFErrorOperation - -logger = logging.getLogger(__name__) - - -class LocalNode: - """A light-weight local implementation of a OEF Node.""" - - def __init__(self): - """Initialize a local (i.e. non-networked) implementation of an OEF Node.""" - self.agents = dict() # type: Dict[str, Description] - self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] - self._lock = threading.Lock() - - self._stopped = True # type: bool - self._thread = None # type: Optional[Thread] - - self._queues = {} # type: Dict[str, Queue] - - def __enter__(self): - """Start the OEF Node.""" - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Stop the OEF Node.""" - - def connect(self, public_key: str) -> Optional[Queue]: - """ - Connect a public key to the node. - - :param public_key: the public key of the agent. - :return: an asynchronous queue, that constitutes the communication channel. - """ - if public_key in self._queues: - return None - - q = Queue() - self._queues[public_key] = q - return q - - def send_envelope(self, envelope: Envelope) -> None: - """ - Process the incoming messages. - - :return: None - """ - sender = envelope.sender - logger.debug("Processing message from {}: {}".format(sender, envelope)) - self._decode_envelope(envelope) - - def _decode_envelope(self, envelope: Envelope) -> None: - """ - Decode the envelope. - - :param envelope: the envelope - :return: None - """ - if envelope.protocol_id == "oef": - self.handle_oef_message(envelope) - else: - self.handle_agent_message(envelope) - - def handle_oef_message(self, envelope: Envelope) -> None: - """ - Handle oef messages. - - :param envelope: the envelope - :return: None - """ - oef_message = OEFSerializer().decode(envelope.message) - sender = envelope.sender - request_id = oef_message.get("id") - oef_type = OEFMessage.Type(oef_message.get("type")) - if oef_type == OEFMessage.Type.REGISTER_SERVICE: - self.register_service(sender, oef_message.get("service_description")) - elif oef_type == OEFMessage.Type.REGISTER_AGENT: - self.register_agent(sender, oef_message.get("agent_description")) - elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - self.unregister_service(sender, request_id, oef_message.get("service_description")) - elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - self.unregister_agent(sender, request_id) - elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - self.search_agents(sender, request_id, oef_message.get("query")) - elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - self.search_services(sender, request_id, oef_message.get("query")) - else: - # request not recognized - pass - - def handle_agent_message(self, envelope: Envelope) -> None: - """ - Forward an envelope to the right agent. - - :param envelope: the envelope - :return: None - """ - destination = envelope.to - - if destination not in self._queues: - msg = OEFMessage(type=OEFMessage.Type.DIALOGUE_ERROR, id=STUB_DIALOGUE_ID, dialogue_id=STUB_DIALOGUE_ID, origin=destination) - msg_bytes = OEFSerializer().encode(msg) - error_envelope = Envelope(to=destination, sender=envelope.sender, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self._send(error_envelope) - return - else: - self._send(envelope) - - def register_agent(self, public_key: str, agent_description: Description) -> None: - """ - Register an agent in the agent directory of the node. - - :param public_key: the public key of the agent to be registered. - :param agent_description: the description of the agent to be registered. - :return: None - """ - with self._lock: - self.agents[public_key] = agent_description - - def register_service(self, public_key: str, service_description: Description): - """ - Register a service agent in the service directory of the node. - - :param public_key: the public key of the service agent to be registered. - :param service_description: the description of the service agent to be registered. - :return: None - """ - with self._lock: - self.services[public_key].append(service_description) - - def register_service_wide(self, public_key: str, service_description: Description): - """Register service wide.""" - raise NotImplementedError - - def unregister_agent(self, public_key: str, msg_id: int) -> None: - """ - Unregister an agent. - - :param public_key: the public key of the agent to be unregistered. - :param msg_id: the message id of the request. - :return: None - """ - with self._lock: - if public_key not in self.agents: - msg = OEFMessage(type=OEFMessage.Type.OEF_ERROR, id=msg_id, operation=OEFErrorOperation.UNREGISTER_DESCRIPTION) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self._send(envelope) - else: - self.agents.pop(public_key) - - def unregister_service(self, public_key: str, msg_id: int, service_description: Description) -> None: - """ - Unregister a service agent. - - :param public_key: the public key of the service agent to be unregistered. - :param msg_id: the message id of the request. - :param service_description: the description of the service agent to be unregistered. - :return: None - """ - with self._lock: - if public_key not in self.services: - msg = OEFMessage(type=OEFMessage.Type.OEF_ERROR, id=msg_id, operation=OEFErrorOperation.UNREGISTER_SERVICE) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self._send(envelope) - else: - self.services[public_key].remove(service_description) - if len(self.services[public_key]) == 0: - self.services.pop(public_key) - - def search_agents(self, public_key: str, search_id: int, query: Query) -> None: - """ - Search the agents in the local Agent Directory, and send back the result. - - The provided query will be checked with every instance of the Agent Directory. - - :param public_key: the source of the search request. - :param search_id: the search identifier associated with the search request. - :param query: the query that constitutes the search. - :return: None - """ - result = [] - for agent_public_key, description in self.agents.items(): - if query.check(description): - result.append(agent_public_key) - - msg = OEFMessage(type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=sorted(set(result))) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self._send(envelope) - - def search_services(self, public_key: str, search_id: int, query: Query) -> None: - """ - Search the agents in the local Service Directory, and send back the result. - - The provided query will be checked with every instance of the Agent Directory. - - :param public_key: the source of the search request. - :param search_id: the search identifier associated with the search request. - :param query: the query that constitutes the search. - :return: None - """ - result = [] - for agent_public_key, descriptions in self.services.items(): - for description in descriptions: - if query.check(description): - result.append(agent_public_key) - - msg = OEFMessage(type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=sorted(set(result))) - msg_bytes = OEFSerializer().encode(msg) - envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) - self._send(envelope) - - def _send(self, envelope: Envelope): - """Send a message.""" - destination = envelope.to - self._queues[destination].put_nowait(envelope) - - def disconnect(self, public_key: str) -> None: - """ - Disconnect. - - :param public_key: the public key - :return: None - """ - with self._lock: - self._queues.pop(public_key, None) - self.services.pop(public_key, None) - self.agents.pop(public_key, None) - - -class OEFLocalConnection(Connection): - """ - Proxy to the functionality of the OEF. - - It allows the interaction between agents, but not the search functionality. - It is useful for local testing. - """ - - def __init__(self, public_key: str, local_node: LocalNode, loop: asyncio.AbstractEventLoop = None): - """ - Initialize a OEF proxy for a local OEF Node (that is, :class:`~oef.proxy.OEFLocalProxy.LocalNode`. - - :param public_key: the public key used in the protocols. - :param local_node: the Local OEF Node object. This reference must be the same across the agents of interest. - :param loop: the event loop. - """ - super().__init__() - self.public_key = public_key - self.local_node = local_node - self._loop = loop if loop is not None else asyncio.new_event_loop() - - self._connection = None # type: Optional[Queue] - - self._stopped = True - self.in_thread = None - self.out_thread = None - - def _fetch(self) -> None: - """ - Fetch the messages from the outqueue and send them. - - :return: None - """ - while not self._stopped: - try: - msg = self.out_queue.get(block=True, timeout=2.0) - self.send(msg) - except queue.Empty: - pass - - def _receive_loop(self): - """Receive messages.""" - while not self._stopped: - try: - data = self._connection.get(timeout=2.0) - self.in_queue.put_nowait(data) - except queue.Empty: - pass - - @property - def is_established(self) -> bool: - """Return True if the connection has been established, False otherwise.""" - return self._connection is not None - - def connect(self): - """Connect to the local OEF Node.""" - if self._stopped: - self._stopped = False - self._connection = self.local_node.connect(self.public_key) - self.in_thread = Thread(target=self._receive_loop) - self.out_thread = Thread(target=self._fetch) - self.in_thread.start() - self.out_thread.start() - - def disconnect(self): - """Disconnect from the local OEF Node.""" - if not self._stopped: - self._stopped = True - self.in_thread.join() - self.out_thread.join() - self.in_thread = None - self.out_thread = None - self.local_node.disconnect(self.public_key) - self.stop() - - def send(self, envelope: Envelope): - """Send a message.""" - if not self.is_established: - raise ConnectionError("Connection not established yet. Please use 'connect()'.") - self.local_node.send_envelope(envelope) - - def stop(self): - """Tear down the connection.""" - self._connection = None diff --git a/tac/aea/mail/__init__.py b/tac/aea/mail/__init__.py deleted file mode 100644 index 4d952845..00000000 --- a/tac/aea/mail/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the mail modules.""" diff --git a/tac/aea/mail/base.proto b/tac/aea/mail/base.proto deleted file mode 100644 index eaf5eebd..00000000 --- a/tac/aea/mail/base.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto3"; - -package fetch.aea; - -message Envelope{ - string to = 1; - string sender = 2; - string protocol_id = 3; - bytes message = 4; -} diff --git a/tac/aea/mail/base.py b/tac/aea/mail/base.py deleted file mode 100644 index dd43cb79..00000000 --- a/tac/aea/mail/base.py +++ /dev/null @@ -1,290 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Mail module abstract base classes.""" - -import logging -from abc import abstractmethod -from queue import Queue -from typing import Optional - -from tac.aea.mail import base_pb2 - -logger = logging.getLogger(__name__) - -Address = str -ProtocolId = str - - -class Envelope: - """The top level message class.""" - - def __init__(self, to: Optional[Address] = None, - sender: Optional[Address] = None, - protocol_id: Optional[ProtocolId] = None, - message: Optional[bytes] = b""): - """ - Initialize a Message object. - - :param to: the public key of the receiver. - :param sender: the public key of the sender. - :param protocol_id: the protocol id. - :param message: the protocol-specific message - """ - self._to = to - self._sender = sender - self._protocol_id = protocol_id - self._message = message - assert type(self._to) == str or self._to is None - try: - if self._to is not None and type(self._to) == str: - self._to.encode('utf-8') - except Exception: - assert False - - @property - def to(self) -> Address: - """Get public key of receiver.""" - return self._to - - @to.setter - def to(self, to: Address) -> None: - """Set public key of receiver.""" - self._to = to - - @property - def sender(self) -> Address: - """Get public key of sender.""" - return self._sender - - @sender.setter - def sender(self, sender: Address) -> None: - """Set public key of sender.""" - self._sender = sender - - @property - def protocol_id(self) -> Optional[ProtocolId]: - """Get protocol id.""" - return self._protocol_id - - @protocol_id.setter - def protocol_id(self, protocol_id: ProtocolId) -> None: - """Set the protocol id.""" - self._protocol_id = protocol_id - - @property - def message(self) -> bytes: - """Get the Message.""" - return self._message - - @message.setter - def message(self, message: bytes) -> None: - """Set the message.""" - self._message = message - - def __eq__(self, other): - """Compare with another object.""" - return isinstance(other, Envelope) \ - and self.to == other.to \ - and self.sender == other.sender \ - and self.protocol_id == other.protocol_id \ - and self._message == other._message - - def encode(self) -> bytes: - """ - Encode the envelope. - - :return: the encoded envelope. - """ - envelope = self - envelope_pb = base_pb2.Envelope() - if envelope.to is not None: - envelope_pb.to = envelope.to - if envelope.sender is not None: - envelope_pb.sender = envelope.sender - if envelope.protocol_id is not None: - envelope_pb.protocol_id = envelope.protocol_id - if envelope.message is not None: - envelope_pb.message = envelope.message - - envelope_bytes = envelope_pb.SerializeToString() - return envelope_bytes - - @classmethod - def decode(cls, envelope_bytes: bytes) -> 'Envelope': - """ - Decode the envelope. - - :param envelope_bytes: the bytes to be decoded. - :return: the decoded envelope. - """ - envelope_pb = base_pb2.Envelope() - envelope_pb.ParseFromString(envelope_bytes) - - to = envelope_pb.to if envelope_pb.to else None - sender = envelope_pb.sender if envelope_pb.sender else None - protocol_id = envelope_pb.protocol_id if envelope_pb.protocol_id else None - message = envelope_pb.message if envelope_pb.message else None - - envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) - return envelope - - -class InBox(object): - """A queue from where you can only consume messages.""" - - def __init__(self, queue: Queue): - """ - Initialize the inbox. - - :param queue: the queue. - """ - super().__init__() - self._queue = queue - - def empty(self) -> bool: - """ - Check for a message on the in queue. - - :return: boolean indicating whether there is a message or not - """ - return self._queue.empty() - - def get(self, block: bool = True, timeout: Optional[float] = None) -> Optional[Envelope]: - """ - Check for a message on the in queue. - - :param block: if true makes it blocking. - :param timeout: times out the block after timeout seconds. - - :return: the message object. - """ - logger.debug("Checks for message from the in queue...") - msg = self._queue.get(block=block, timeout=timeout) - logger.debug("Incoming message type: type={}".format(type(msg))) - return msg - - def get_nowait(self) -> Optional[Envelope]: - """ - Check for a message on the in queue and wait for no time. - - :return: the message object - """ - item = self._queue.get_nowait() - return item - - -class OutBox(object): - """A queue from where you can only enqueue messages.""" - - def __init__(self, queue: Queue) -> None: - """ - Initialize the outbox. - - :param queue: the queue. - """ - super().__init__() - self._queue = queue - - def empty(self) -> bool: - """ - Check for a message on the out queue. - - :return: boolean indicating whether there is a message or not - """ - return self._queue.empty() - - def put(self, item: Envelope) -> None: - """ - Put an item into the queue. - - :param item: the message. - :return: None - """ - logger.debug("Put a message in the queue...") - self._queue.put(item) - - def put_message(self, to: Optional[Address] = None, sender: Optional[Address] = None, - protocol_id: Optional[ProtocolId] = None, message: bytes = b"") -> None: - """ - Put a message in the outbox. - - :param to: the recipient of the message. - :param sender: the sender of the message. - :param protocol_id: the protocol id. - :param message: the content of the message. - :return: None - """ - envelope = Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message) - self._queue.put(envelope) - - -class Connection: - """Abstract definition of a connection.""" - - def __init__(self): - """Initialize the connection.""" - self.in_queue = Queue() - self.out_queue = Queue() - - @abstractmethod - def connect(self): - """Set up the connection.""" - - @abstractmethod - def disconnect(self): - """Tear down the connection.""" - - @property - @abstractmethod - def is_established(self) -> bool: - """Check if the connection is established.""" - - @abstractmethod - def send(self, envelope: Envelope): - """Send a message.""" - - -class MailBox(object): - """Abstract definition of a mailbox.""" - - def __init__(self, connection: Connection): - """Initialize the mailbox.""" - self._connection = connection - - self.inbox = InBox(self._connection.in_queue) - self.outbox = OutBox(self._connection.out_queue) - - @property - def is_connected(self) -> bool: - """Check whether the mailbox is processing messages.""" - return self._connection.is_established - - def connect(self) -> None: - """Connect.""" - self._connection.connect() - - def disconnect(self) -> None: - """Disconnect.""" - self._connection.disconnect() - - def send(self, out: Envelope) -> None: - """Send.""" - self.outbox.put(out) diff --git a/tac/aea/mail/base_pb2.py b/tac/aea/mail/base_pb2.py deleted file mode 100644 index b379bfbc..00000000 --- a/tac/aea/mail/base_pb2.py +++ /dev/null @@ -1,90 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: base.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='base.proto', - package='fetch.aea', - syntax='proto3', - serialized_pb=_b('\n\nbase.proto\x12\tfetch.aea\"L\n\x08\x45nvelope\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\x0c\x62\x06proto3') -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_ENVELOPE = _descriptor.Descriptor( - name='Envelope', - full_name='fetch.aea.Envelope', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='to', full_name='fetch.aea.Envelope.to', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='sender', full_name='fetch.aea.Envelope.sender', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='protocol_id', full_name='fetch.aea.Envelope.protocol_id', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='message', full_name='fetch.aea.Envelope.message', index=3, - number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=25, - serialized_end=101, -) - -DESCRIPTOR.message_types_by_name['Envelope'] = _ENVELOPE - -Envelope = _reflection.GeneratedProtocolMessageType('Envelope', (_message.Message,), dict( - DESCRIPTOR = _ENVELOPE, - __module__ = 'base_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.Envelope) - )) -_sym_db.RegisterMessage(Envelope) - - -# @@protoc_insertion_point(module_scope) diff --git a/tac/aea/protocols/__init__.py b/tac/aea/protocols/__init__.py deleted file mode 100644 index 5e911a4e..00000000 --- a/tac/aea/protocols/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the protocol modules.""" diff --git a/tac/aea/protocols/base/__init__.py b/tac/aea/protocols/base/__init__.py deleted file mode 100644 index 4f1c59df..00000000 --- a/tac/aea/protocols/base/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the support resources for the base protocol.""" diff --git a/tac/aea/protocols/base/message.py b/tac/aea/protocols/base/message.py deleted file mode 100644 index fbe1e768..00000000 --- a/tac/aea/protocols/base/message.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the base message definition.""" -from copy import copy -from typing import Any, Dict, Optional - - -class Message: - """This class implements a message.""" - - def __init__(self, body: Optional[Dict] = None, - **kwargs): - """ - Initialize a Message object. - - :param body: the dictionary of values to hold. - """ - self._body = copy(body) if body else {} # type: Dict[str, Any] - self._body.update(kwargs) - - @property - def body(self) -> Dict: - """ - Get the body of the message (in dictionary form). - - :return: the body - """ - return self._body - - @body.setter - def body(self, body: Dict) -> None: - """ - Set the body of hte message. - - :param body: the body. - :return: None - """ - self._body = body - - def set(self, key: str, value: Any) -> None: - """ - Set key and value pair. - - :param key: the key. - :param value: the value. - :return: None - """ - self._body[key] = value - - def get(self, key: str) -> Optional[Any]: - """Get value for key.""" - return self._body.get(key, None) - - def unset(self, key: str) -> None: - """Unset valye for key.""" - self._body.pop(key, None) - - def is_set(self, key: str) -> bool: - """Check value is set for key.""" - return key in self._body - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - return True - - def __eq__(self, other): - """Compare with another object.""" - return isinstance(other, Message) \ - and self.body == other.body diff --git a/tac/aea/protocols/base/serialization.py b/tac/aea/protocols/base/serialization.py deleted file mode 100644 index 4440b4f5..00000000 --- a/tac/aea/protocols/base/serialization.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Serializer for base.""" -import json -from abc import abstractmethod, ABC - -from google.protobuf.struct_pb2 import Struct - -from tac.aea.protocols.base.message import Message - - -class Encoder(ABC): - """Encoder interface.""" - - @abstractmethod - def encode(self, msg: Message) -> bytes: - """ - Encode a message. - - :param msg: the message to be encoded. - :return: the encoded message. - """ - - -class Decoder(ABC): - """Decoder interface.""" - - @abstractmethod - def decode(self, obj: bytes) -> Message: - """ - Decode a message. - - :param obj: the sequence of bytes to be decoded. - :return: the decoded message. - """ - - -class Serializer(Encoder, Decoder, ABC): - """The implementations of this class defines a serialization layer for a protocol.""" - - -class ProtobufSerializer(Serializer): - """ - Default Protobuf serializer. - - It assumes that the Message contains a JSON-serializable body. - """ - - def encode(self, msg: Message) -> bytes: - """Encode a message into bytes using Protobuf.""" - body_json = Struct() - body_json.update(msg.body) - body_bytes = body_json.SerializeToString() - return body_bytes - - def decode(self, obj: bytes) -> Message: - """Decode bytes into a message using Protobuf.""" - body_json = Struct() - body_json.ParseFromString(obj) - - body = dict(body_json) - msg = Message(body=body) - return msg - - -class JSONSerializer(Serializer): - """Default serialization in JSON for the Message object.""" - - def encode(self, msg: Message) -> bytes: - """ - Encode a message into bytes using JSON format. - - :param msg: the message to be encoded. - :return: the serialized message. - """ - bytes_msg = json.dumps(msg.body).encode("utf-8") - return bytes_msg - - def decode(self, obj: bytes) -> Message: - """ - Decode bytes into a message using JSON. - - :param obj: the serialized message. - :return: the decoded message. - """ - json_msg = json.loads(obj.decode("utf-8")) - return Message(json_msg) diff --git a/tac/aea/protocols/default/__init__.py b/tac/aea/protocols/default/__init__.py deleted file mode 100644 index 52e51b51..00000000 --- a/tac/aea/protocols/default/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the support resources for the default protocol.""" diff --git a/tac/aea/protocols/default/message.py b/tac/aea/protocols/default/message.py deleted file mode 100644 index c54ca728..00000000 --- a/tac/aea/protocols/default/message.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the default message definition.""" -from enum import Enum -from typing import Optional - -from tac.aea.protocols.base.message import Message - - -class DefaultMessage(Message): - """The Default message class.""" - - protocol_id = "default" - - class Type(Enum): - """Default message types.""" - - BYTES = "bytes" - ERROR = "error" - - def __init__(self, type: Optional[Type] = None, - **kwargs): - """ - Initialize. - - :param type: the type. - """ - super().__init__(type=type, **kwargs) diff --git a/tac/aea/protocols/default/serialization.py b/tac/aea/protocols/default/serialization.py deleted file mode 100644 index 1fed0531..00000000 --- a/tac/aea/protocols/default/serialization.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Serialization module for the default protocol.""" -import json - -import base58 - -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.base.serialization import Serializer -from tac.aea.protocols.default.message import DefaultMessage - - -class DefaultSerializer(Serializer): - """Serialization for the 'default' protocol.""" - - def encode(self, msg: Message) -> bytes: - """Encode a 'default' message into bytes.""" - body = {} - - msg_type = msg.get("type") - assert msg_type in set(DefaultMessage.Type) - body["type"] = str(msg_type.value) - - if msg_type == DefaultMessage.Type.BYTES: - body["content"] = base58.b58encode(msg.get("content")).decode("utf-8") - elif msg_type == DefaultMessage.Type.ERROR: - body["error_code"] = msg.get("error_code") - body["error_msg"] = msg.get("error_msg") - else: - raise ValueError("Type not recognized.") - - bytes_msg = json.dumps(body).encode("utf-8") - return bytes_msg - - def decode(self, obj: bytes) -> Message: - """Decode bytes into a 'default' message.""" - json_body = json.loads(obj.decode("utf-8")) - body = {} - - msg_type = DefaultMessage.Type(json_body["type"]) - body["type"] = msg_type - if msg_type == DefaultMessage.Type.BYTES: - content = base58.b58decode(json_body["content"].encode("utf-8")) - body["content"] = content - elif msg_type == DefaultMessage.Type.ERROR: - body["error_code"] = json_body["error_code"] - body["error_msg"] = json_body["error_msg"] - else: - raise ValueError("Type not recognized.") - - return Message(body=body) diff --git a/tac/aea/protocols/fipa/__init__.py b/tac/aea/protocols/fipa/__init__.py deleted file mode 100644 index 88af132f..00000000 --- a/tac/aea/protocols/fipa/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the support resources for the FIPA protocol.""" diff --git a/tac/aea/protocols/fipa/fipa.proto b/tac/aea/protocols/fipa/fipa.proto deleted file mode 100644 index 3f06c8f1..00000000 --- a/tac/aea/protocols/fipa/fipa.proto +++ /dev/null @@ -1,35 +0,0 @@ -syntax = "proto3"; - -package fetch.aea.fipa; - -import "google/protobuf/struct.proto"; - -message FIPAMessage{ - - message CFP{ - message Nothing { - } - oneof query{ - google.protobuf.Struct json = 1; - bytes bytes = 2; - Nothing nothing = 3; - } - } - message Propose{ - repeated bytes proposal = 4; - } - message Accept{} - message MatchAccept{} - message Decline{} - - int32 message_id = 1; - int32 dialogue_id = 2; - int32 target = 3; - oneof performative{ - CFP cfp = 4; - Propose propose = 5; - Accept accept = 6; - MatchAccept match_accept = 7; - Decline decline = 8; - } -} diff --git a/tac/aea/protocols/fipa/fipa_pb2.py b/tac/aea/protocols/fipa/fipa_pb2.py deleted file mode 100644 index 2251b1fa..00000000 --- a/tac/aea/protocols/fipa/fipa_pb2.py +++ /dev/null @@ -1,377 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: fipa.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='fipa.proto', - package='fetch.aea.fipa', - syntax='proto3', - serialized_pb=_b('\n\nfipa.proto\x12\x0e\x66\x65tch.aea.fipa\x1a\x1cgoogle/protobuf/struct.proto\"\xc0\x04\n\x0b\x46IPAMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12\x13\n\x0b\x64ialogue_id\x18\x02 \x01(\x05\x12\x0e\n\x06target\x18\x03 \x01(\x05\x12.\n\x03\x63\x66p\x18\x04 \x01(\x0b\x32\x1f.fetch.aea.fipa.FIPAMessage.CFPH\x00\x12\x36\n\x07propose\x18\x05 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.ProposeH\x00\x12\x34\n\x06\x61\x63\x63\x65pt\x18\x06 \x01(\x0b\x32\".fetch.aea.fipa.FIPAMessage.AcceptH\x00\x12?\n\x0cmatch_accept\x18\x07 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.MatchAcceptH\x00\x12\x36\n\x07\x64\x65\x63line\x18\x08 \x01(\x0b\x32#.fetch.aea.fipa.FIPAMessage.DeclineH\x00\x1a\x8f\x01\n\x03\x43\x46P\x12\'\n\x04json\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12\x0f\n\x05\x62ytes\x18\x02 \x01(\x0cH\x00\x12:\n\x07nothing\x18\x03 \x01(\x0b\x32\'.fetch.aea.fipa.FIPAMessage.CFP.NothingH\x00\x1a\t\n\x07NothingB\x07\n\x05query\x1a\x1b\n\x07Propose\x12\x10\n\x08proposal\x18\x04 \x03(\x0c\x1a\x08\n\x06\x41\x63\x63\x65pt\x1a\r\n\x0bMatchAccept\x1a\t\n\x07\x44\x65\x63lineB\x0e\n\x0cperformativeb\x06proto3') - , - dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_FIPAMESSAGE_CFP_NOTHING = _descriptor.Descriptor( - name='Nothing', - full_name='fetch.aea.fipa.FIPAMessage.CFP.Nothing', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=538, - serialized_end=547, -) - -_FIPAMESSAGE_CFP = _descriptor.Descriptor( - name='CFP', - full_name='fetch.aea.fipa.FIPAMessage.CFP', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='json', full_name='fetch.aea.fipa.FIPAMessage.CFP.json', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='bytes', full_name='fetch.aea.fipa.FIPAMessage.CFP.bytes', index=1, - number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='nothing', full_name='fetch.aea.fipa.FIPAMessage.CFP.nothing', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[_FIPAMESSAGE_CFP_NOTHING, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='query', full_name='fetch.aea.fipa.FIPAMessage.CFP.query', - index=0, containing_type=None, fields=[]), - ], - serialized_start=413, - serialized_end=556, -) - -_FIPAMESSAGE_PROPOSE = _descriptor.Descriptor( - name='Propose', - full_name='fetch.aea.fipa.FIPAMessage.Propose', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='proposal', full_name='fetch.aea.fipa.FIPAMessage.Propose.proposal', index=0, - number=4, type=12, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=558, - serialized_end=585, -) - -_FIPAMESSAGE_ACCEPT = _descriptor.Descriptor( - name='Accept', - full_name='fetch.aea.fipa.FIPAMessage.Accept', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=587, - serialized_end=595, -) - -_FIPAMESSAGE_MATCHACCEPT = _descriptor.Descriptor( - name='MatchAccept', - full_name='fetch.aea.fipa.FIPAMessage.MatchAccept', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=597, - serialized_end=610, -) - -_FIPAMESSAGE_DECLINE = _descriptor.Descriptor( - name='Decline', - full_name='fetch.aea.fipa.FIPAMessage.Decline', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=612, - serialized_end=621, -) - -_FIPAMESSAGE = _descriptor.Descriptor( - name='FIPAMessage', - full_name='fetch.aea.fipa.FIPAMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='message_id', full_name='fetch.aea.fipa.FIPAMessage.message_id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='dialogue_id', full_name='fetch.aea.fipa.FIPAMessage.dialogue_id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='target', full_name='fetch.aea.fipa.FIPAMessage.target', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='cfp', full_name='fetch.aea.fipa.FIPAMessage.cfp', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='propose', full_name='fetch.aea.fipa.FIPAMessage.propose', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='accept', full_name='fetch.aea.fipa.FIPAMessage.accept', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='match_accept', full_name='fetch.aea.fipa.FIPAMessage.match_accept', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='decline', full_name='fetch.aea.fipa.FIPAMessage.decline', index=7, - number=8, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[_FIPAMESSAGE_CFP, _FIPAMESSAGE_PROPOSE, _FIPAMESSAGE_ACCEPT, _FIPAMESSAGE_MATCHACCEPT, _FIPAMESSAGE_DECLINE, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='performative', full_name='fetch.aea.fipa.FIPAMessage.performative', - index=0, containing_type=None, fields=[]), - ], - serialized_start=61, - serialized_end=637, -) - -_FIPAMESSAGE_CFP_NOTHING.containing_type = _FIPAMESSAGE_CFP -_FIPAMESSAGE_CFP.fields_by_name['json'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT -_FIPAMESSAGE_CFP.fields_by_name['nothing'].message_type = _FIPAMESSAGE_CFP_NOTHING -_FIPAMESSAGE_CFP.containing_type = _FIPAMESSAGE -_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( - _FIPAMESSAGE_CFP.fields_by_name['json']) -_FIPAMESSAGE_CFP.fields_by_name['json'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] -_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( - _FIPAMESSAGE_CFP.fields_by_name['bytes']) -_FIPAMESSAGE_CFP.fields_by_name['bytes'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] -_FIPAMESSAGE_CFP.oneofs_by_name['query'].fields.append( - _FIPAMESSAGE_CFP.fields_by_name['nothing']) -_FIPAMESSAGE_CFP.fields_by_name['nothing'].containing_oneof = _FIPAMESSAGE_CFP.oneofs_by_name['query'] -_FIPAMESSAGE_PROPOSE.containing_type = _FIPAMESSAGE -_FIPAMESSAGE_ACCEPT.containing_type = _FIPAMESSAGE -_FIPAMESSAGE_MATCHACCEPT.containing_type = _FIPAMESSAGE -_FIPAMESSAGE_DECLINE.containing_type = _FIPAMESSAGE -_FIPAMESSAGE.fields_by_name['cfp'].message_type = _FIPAMESSAGE_CFP -_FIPAMESSAGE.fields_by_name['propose'].message_type = _FIPAMESSAGE_PROPOSE -_FIPAMESSAGE.fields_by_name['accept'].message_type = _FIPAMESSAGE_ACCEPT -_FIPAMESSAGE.fields_by_name['match_accept'].message_type = _FIPAMESSAGE_MATCHACCEPT -_FIPAMESSAGE.fields_by_name['decline'].message_type = _FIPAMESSAGE_DECLINE -_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( - _FIPAMESSAGE.fields_by_name['cfp']) -_FIPAMESSAGE.fields_by_name['cfp'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] -_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( - _FIPAMESSAGE.fields_by_name['propose']) -_FIPAMESSAGE.fields_by_name['propose'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] -_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( - _FIPAMESSAGE.fields_by_name['accept']) -_FIPAMESSAGE.fields_by_name['accept'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] -_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( - _FIPAMESSAGE.fields_by_name['match_accept']) -_FIPAMESSAGE.fields_by_name['match_accept'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] -_FIPAMESSAGE.oneofs_by_name['performative'].fields.append( - _FIPAMESSAGE.fields_by_name['decline']) -_FIPAMESSAGE.fields_by_name['decline'].containing_oneof = _FIPAMESSAGE.oneofs_by_name['performative'] -DESCRIPTOR.message_types_by_name['FIPAMessage'] = _FIPAMESSAGE - -FIPAMessage = _reflection.GeneratedProtocolMessageType('FIPAMessage', (_message.Message,), dict( - - CFP = _reflection.GeneratedProtocolMessageType('CFP', (_message.Message,), dict( - - Nothing = _reflection.GeneratedProtocolMessageType('Nothing', (_message.Message,), dict( - DESCRIPTOR = _FIPAMESSAGE_CFP_NOTHING, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.CFP.Nothing) - )) - , - DESCRIPTOR = _FIPAMESSAGE_CFP, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.CFP) - )) - , - - Propose = _reflection.GeneratedProtocolMessageType('Propose', (_message.Message,), dict( - DESCRIPTOR = _FIPAMESSAGE_PROPOSE, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Propose) - )) - , - - Accept = _reflection.GeneratedProtocolMessageType('Accept', (_message.Message,), dict( - DESCRIPTOR = _FIPAMESSAGE_ACCEPT, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Accept) - )) - , - - MatchAccept = _reflection.GeneratedProtocolMessageType('MatchAccept', (_message.Message,), dict( - DESCRIPTOR = _FIPAMESSAGE_MATCHACCEPT, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.MatchAccept) - )) - , - - Decline = _reflection.GeneratedProtocolMessageType('Decline', (_message.Message,), dict( - DESCRIPTOR = _FIPAMESSAGE_DECLINE, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage.Decline) - )) - , - DESCRIPTOR = _FIPAMESSAGE, - __module__ = 'fipa_pb2' - # @@protoc_insertion_point(class_scope:fetch.aea.fipa.FIPAMessage) - )) -_sym_db.RegisterMessage(FIPAMessage) -_sym_db.RegisterMessage(FIPAMessage.CFP) -_sym_db.RegisterMessage(FIPAMessage.CFP.Nothing) -_sym_db.RegisterMessage(FIPAMessage.Propose) -_sym_db.RegisterMessage(FIPAMessage.Accept) -_sym_db.RegisterMessage(FIPAMessage.MatchAccept) -_sym_db.RegisterMessage(FIPAMessage.Decline) - - -# @@protoc_insertion_point(module_scope) diff --git a/tac/aea/protocols/fipa/message.py b/tac/aea/protocols/fipa/message.py deleted file mode 100644 index 3f1efafc..00000000 --- a/tac/aea/protocols/fipa/message.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the FIPA message definition.""" -from enum import Enum -from typing import Optional, Union - -from oef.query import Query -from oef.schema import Description - -from tac.aea.protocols.base.message import Message - - -class FIPAMessage(Message): - """The FIPA message class.""" - - protocol_id = "fipa" - - class Performative(Enum): - """FIPA performatives.""" - - CFP = "cfp" - PROPOSE = "propose" - ACCEPT = "accept" - MATCH_ACCEPT = "match_accept" - DECLINE = "decline" - - def __str__(self): - """Get string representation.""" - return self.value - - def __init__(self, message_id: Optional[int] = None, - dialogue_id: Optional[int] = None, - target: Optional[int] = None, - performative: Optional[Union[str, Performative]] = None, - **kwargs): - """ - Initialize. - - :param message_id: the message id. - :param dialogue_id: the dialogue id. - :param target: the message target. - :param performative: the message performative. - """ - super().__init__(id=message_id, - dialogue_id=dialogue_id, - target=target, - performative=FIPAMessage.Performative(performative), - **kwargs) - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - try: - assert self.is_set("target") - performative = FIPAMessage.Performative(self.get("performative")) - if performative == FIPAMessage.Performative.CFP: - query = self.get("query") - assert isinstance(query, Query) or isinstance(query, bytes) or query is None - elif performative == FIPAMessage.Performative.PROPOSE: - proposal = self.get("proposal") - assert type(proposal) == list and all(isinstance(d, Description) or type(d) == bytes for d in proposal) - elif performative == FIPAMessage.Performative.ACCEPT \ - or performative == FIPAMessage.Performative.MATCH_ACCEPT \ - or performative == FIPAMessage.Performative.DECLINE: - pass - else: - raise ValueError("Performative not recognized.") - - except (AssertionError, ValueError, KeyError): - return False - - return True diff --git a/tac/aea/protocols/fipa/serialization.py b/tac/aea/protocols/fipa/serialization.py deleted file mode 100644 index 4633e305..00000000 --- a/tac/aea/protocols/fipa/serialization.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Serialization for the FIPA protocol.""" -from google.protobuf.struct_pb2 import Struct -from oef import data_model_instance_pb2 -from oef.schema import Description - -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.base.serialization import Serializer -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa import fipa_pb2 - - -class FIPASerializer(Serializer): - """Serialization for the FIPA protocol.""" - - def encode(self, msg: Message) -> bytes: - """Encode a FIPA message into bytes.""" - fipa_msg = fipa_pb2.FIPAMessage() - fipa_msg.message_id = msg.get("id") - fipa_msg.dialogue_id = msg.get("dialogue_id") - fipa_msg.target = msg.get("target") - - performative_id = msg.get("performative").value - if performative_id == "cfp": - performative = fipa_pb2.FIPAMessage.CFP() - query = msg.get("query") - if query is None: - nothing = fipa_pb2.FIPAMessage.CFP.Nothing() - performative.nothing.CopyFrom(nothing) - elif type(query) == dict: - performative.json.update(query) - elif type(query) == bytes: - performative.bytes = query - else: - raise ValueError("Query type not supported: {}".format(type(query))) - fipa_msg.cfp.CopyFrom(performative) - elif performative_id == "propose": - performative = fipa_pb2.FIPAMessage.Propose() - proposal = msg.get("proposal") - p_array_bytes = [p.to_pb().SerializeToString() for p in proposal] - performative.proposal.extend(p_array_bytes) - fipa_msg.propose.CopyFrom(performative) - - elif performative_id == "accept": - performative = fipa_pb2.FIPAMessage.Accept() - fipa_msg.accept.CopyFrom(performative) - elif performative_id == "match_accept": - performative = fipa_pb2.FIPAMessage.MatchAccept() - fipa_msg.match_accept.CopyFrom(performative) - elif performative_id == "decline": - performative = fipa_pb2.FIPAMessage.Decline() - fipa_msg.decline.CopyFrom(performative) - else: - raise ValueError("Performative not valid: {}".format(performative_id)) - - fipa_bytes = fipa_msg.SerializeToString() - return fipa_bytes - - 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_id = fipa_pb.dialogue_id - 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 == "json": - query_pb = Struct() - query_pb.update(fipa_pb.cfp.json) - query = dict(query_pb) - elif query_type == "bytes": - query = fipa_pb.cfp.bytes - else: - raise ValueError("Query type not recognized.") - - performative_content["query"] = query - elif performative_id == FIPAMessage.Performative.PROPOSE: - descriptions = [] - for p_bytes in fipa_pb.propose.proposal: - p_pb = data_model_instance_pb2.Instance() - p_pb.ParseFromString(p_bytes) - descriptions.append(Description.from_pb(p_pb)) - performative_content["proposal"] = descriptions - elif performative_id == FIPAMessage.Performative.ACCEPT: - pass - elif performative_id == FIPAMessage.Performative.MATCH_ACCEPT: - pass - elif performative_id == FIPAMessage.Performative.DECLINE: - pass - else: - raise ValueError("Performative not valid: {}.".format(performative)) - - return FIPAMessage(message_id=message_id, dialogue_id=dialogue_id, target=target, - performative=performative, **performative_content) diff --git a/tac/aea/protocols/oef/__init__.py b/tac/aea/protocols/oef/__init__.py deleted file mode 100644 index 8b7bf970..00000000 --- a/tac/aea/protocols/oef/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the support resources for the OEF protocol.""" diff --git a/tac/aea/protocols/oef/message.py b/tac/aea/protocols/oef/message.py deleted file mode 100644 index 2cf57673..00000000 --- a/tac/aea/protocols/oef/message.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the default message definition.""" -from enum import Enum -from typing import Optional - -from oef.messages import OEFErrorOperation -from oef.query import Query -from oef.schema import Description - -from tac.aea.protocols.base.message import Message - - -class OEFMessage(Message): - """The OEF message class.""" - - protocol_id = "oef" - - class Type(Enum): - """OEF Message types.""" - - REGISTER_SERVICE = "register_service" - REGISTER_AGENT = "register_agent" - UNREGISTER_SERVICE = "unregister_service" - UNREGISTER_AGENT = "unregister_agent" - SEARCH_SERVICES = "search_services" - SEARCH_AGENTS = "search_agents" - OEF_ERROR = "oef_error" - DIALOGUE_ERROR = "dialogue_error" - SEARCH_RESULT = "search_result" - - def __str__(self): - """Get string representation.""" - return self.value - - def __init__(self, oef_type: Optional[Type] = None, - **kwargs): - """ - Initialize. - - :param oef_type: the type of OEF message. - """ - super().__init__(type=str(oef_type), **kwargs) - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - try: - assert self.is_set("type") - oef_type = OEFMessage.Type(self.get("type")) - if oef_type == OEFMessage.Type.REGISTER_SERVICE: - service_description = self.get("service_description") - assert self.is_set("id") - assert self.is_set("service_id") - service_id = self.get("service_id") - assert isinstance(service_description, Description) - assert isinstance(service_id, str) - elif oef_type == OEFMessage.Type.REGISTER_AGENT: - assert self.is_set("id") - assert self.is_set("agent_description") - agent_description = self.get("agent_description") - assert isinstance(agent_description, Description) - elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: - assert self.is_set("id") - assert self.is_set("service_id") - service_id = self.get("service_id") - assert isinstance(service_id, str) - elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: - assert self.is_set("id") - elif oef_type == OEFMessage.Type.SEARCH_SERVICES: - assert self.is_set("id") - assert self.is_set("query") - query = self.get("query") - assert isinstance(query, Query) - elif oef_type == OEFMessage.Type.SEARCH_AGENTS: - assert self.is_set("id") - assert self.is_set("query") - query = self.get("query") - assert isinstance(query, Query) - elif oef_type == OEFMessage.Type.SEARCH_RESULT: - assert self.is_set("id") - assert self.is_set("agents") - agents = self.get("agents") - assert type(agents) == list and all(type(a) == str for a in agents) - elif oef_type == OEFMessage.Type.OEF_ERROR: - assert self.is_set("id") - assert self.is_set("operation") - operation = self.get("operation") - assert operation in set(OEFErrorOperation) - elif oef_type == OEFMessage.Type.DIALOGUE_ERROR: - assert self.is_set("id") - assert self.is_set("dialogue_id") - assert self.is_set("origin") - else: - raise ValueError("Type not recognized.") - except (AssertionError, ValueError): - return False - - return True diff --git a/tac/aea/protocols/oef/serialization.py b/tac/aea/protocols/oef/serialization.py deleted file mode 100644 index 133eca63..00000000 --- a/tac/aea/protocols/oef/serialization.py +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Serialization for the FIPA protocol.""" -import copy -import json -import pickle -from typing import Dict - -import base58 -from google.protobuf import json_format -from google.protobuf.json_format import MessageToJson -from oef import query_pb2, dap_interface_pb2 -from oef.messages import OEFErrorOperation -from oef.query import Query, ConstraintExpr, And, Or, Not, Constraint -from oef.schema import Description, DataModel - -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.base.serialization import Serializer -from tac.aea.protocols.oef.message import OEFMessage - -"""default 'to' field for OEF envelopes.""" -DEFAULT_OEF = "oef" - - -class ConstraintWrapper: - """Make the constraint object pickable.""" - - def __init__(self, c: ConstraintExpr): - """Wrap the constraint object.""" - self.c = c - - def to_json(self) -> Dict: - """ - Convert to json. - - :return: the dictionary - """ - result = {} - if isinstance(self.c, And) or isinstance(self.c, Or): - wraps = [ConstraintWrapper(subc).to_json() for subc in self.c.constraints] - key = "and" if isinstance(self.c, And) else "or" if isinstance(self.c, Or) else "" - result[key] = wraps - elif isinstance(self.c, Not): - wrap = ConstraintWrapper(self.c.constraint).to_json() - result["not"] = wrap - elif isinstance(self.c, Constraint): - result["attribute_name"] = self.c.attribute_name - result["constraint_type"] = base58.b58encode(pickle.dumps(self.c.constraint)).decode("utf-8") - else: - raise ValueError("ConstraintExpr not recognized.") - - return result - - @classmethod - def from_json(cls, d: Dict) -> Constraint: - """ - Convert from json. - - :param d: the dictionary. - :return: the constraint - """ - if "and" in d: - return And([ConstraintWrapper.from_json(subc) for subc in d["and"]]) - elif "or" in d: - return Or([ConstraintWrapper.from_json(subc) for subc in d["or"]]) - elif "not" in d: - return Not(ConstraintWrapper.from_json(d["not"])) - else: - constraint_type = pickle.loads(base58.b58decode(d["constraint_type"])) - return Constraint(d["attribute_name"], constraint_type) - - -class QueryWrapper: - """Make the query object pickable.""" - - def __init__(self, q: Query): - """ - Initialize. - - :param q: the query - """ - self.q = q - - def to_json(self) -> Dict: - """ - Convert to json. - - :return: the dictionary - """ - result = {} - if self.q.model: - result["data_model"] = base58.b58encode(self.q.model.to_pb().SerializeToString()).decode("utf-8") - else: - result["data_model"] = None - result["constraints"] = [ConstraintWrapper(c).to_json() for c in self.q.constraints] - return result - - @classmethod - def from_json(self, d: Dict) -> Query: - """ - Convert from json. - - :param d: the dictionary. - :return: the query - """ - if d["data_model"]: - data_model_pb = dap_interface_pb2.ValueMessage.DataModel() - data_model_pb.ParseFromString(base58.b58decode(d["data_model"])) - data_model = DataModel.from_pb(data_model_pb) - else: - data_model = None - - constraints = [ConstraintWrapper.from_json(c) for c in d["constraints"]] - return Query(constraints, data_model) - - -class OEFSerializer(Serializer): - """Serialization for the OEF protocol.""" - - def encode(self, msg: Message) -> bytes: - """ - Decode the message. - - :param msg: the message object - :return: the bytes - """ - oef_type = OEFMessage.Type(msg.get("type")) - new_body = copy.copy(msg.body) - - if oef_type in {OEFMessage.Type.REGISTER_SERVICE, OEFMessage.Type.UNREGISTER_SERVICE}: - service_description = msg.body["service_description"] # type: Description - service_description_pb = service_description.to_pb() - service_description_json = MessageToJson(service_description_pb) - new_body["service_description"] = service_description_json - elif oef_type in {OEFMessage.Type.REGISTER_AGENT}: - agent_description = msg.body["agent_description"] # type: Description - agent_description_pb = agent_description.to_pb() - agent_description_json = MessageToJson(agent_description_pb) - new_body["agent_description"] = agent_description_json - elif oef_type in {OEFMessage.Type.SEARCH_SERVICES, OEFMessage.Type.SEARCH_AGENTS}: - query = msg.body["query"] # type: Query - new_body["query"] = QueryWrapper(query).to_json() - elif oef_type in {OEFMessage.Type.SEARCH_RESULT}: - # we need this cast because the "agents" field might contains - # the Protobuf type "RepeatedScalarContainer", which is not JSON serializable. - new_body["agents"] = list(msg.body["agents"]) - elif oef_type in {OEFMessage.Type.OEF_ERROR}: - operation = msg.body["operation"] - new_body["operation"] = str(operation) - - oef_message_bytes = json.dumps(new_body).encode("utf-8") - return oef_message_bytes - - def decode(self, obj: bytes) -> Message: - """ - Decode the message. - - :param obj: the bytes object - :return: the message - """ - json_msg = json.loads(obj.decode("utf-8")) - oef_type = OEFMessage.Type(json_msg["type"]) - new_body = copy.copy(json_msg) - new_body["type"] = oef_type - - if oef_type in {OEFMessage.Type.REGISTER_SERVICE, OEFMessage.Type.UNREGISTER_SERVICE}: - service_description_json = json_msg["service_description"] - service_description_pb = json_format.Parse(service_description_json, query_pb2.Query.Instance()) - service_description = Description.from_pb(service_description_pb) - new_body["service_description"] = service_description - elif oef_type in {OEFMessage.Type.REGISTER_AGENT}: - agent_description_json = json_msg["agent_description"] - agent_description_pb = json_format.Parse(agent_description_json, query_pb2.Query.Instance()) - agent_description = Description.from_pb(agent_description_pb) - new_body["agent_description"] = agent_description - elif oef_type in {OEFMessage.Type.SEARCH_SERVICES, OEFMessage.Type.SEARCH_AGENTS}: - query = QueryWrapper.from_json(json_msg["query"]) - new_body["query"] = query - elif oef_type in {OEFMessage.Type.SEARCH_RESULT}: - new_body["agents"] = list(json_msg["agents"]) - elif oef_type in {OEFMessage.Type.OEF_ERROR}: - operation = json_msg["operation"] - new_body["operation"] = OEFErrorOperation(operation) - - oef_message = Message(body=new_body) - return oef_message diff --git a/tac/aea/protocols/tac/__init__.py b/tac/aea/protocols/tac/__init__.py deleted file mode 100644 index 430d160a..00000000 --- a/tac/aea/protocols/tac/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the support resources for the TAC protocol.""" diff --git a/tac/aea/protocols/tac/message.py b/tac/aea/protocols/tac/message.py deleted file mode 100644 index c977a96e..00000000 --- a/tac/aea/protocols/tac/message.py +++ /dev/null @@ -1,133 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the default message definition.""" -from enum import Enum -from typing import Dict, Optional - -from tac.aea.protocols.base.message import Message - - -class TACMessage(Message): - """The TAC message class.""" - - protocol_id = "tac" - - class Type(Enum): - """TAC Message types.""" - - REGISTER = "register" - UNREGISTER = "unregister" - TRANSACTION = "transaction" - GET_STATE_UPDATE = "get_state_update" - CANCELLED = "cancelled" - GAME_DATA = "game_data" - TRANSACTION_CONFIRMATION = "transaction_confirmation" - STATE_UPDATE = "state_update" - TAC_ERROR = "tac_error" - - def __str__(self): - """Get string representation.""" - return self.value - - class ErrorCode(Enum): - """This class defines the error codes.""" - - GENERIC_ERROR = 0 - REQUEST_NOT_VALID = 1 - AGENT_PBK_ALREADY_REGISTERED = 2 - AGENT_NAME_ALREADY_REGISTERED = 3 - AGENT_NOT_REGISTERED = 4 - TRANSACTION_NOT_VALID = 5 - TRANSACTION_NOT_MATCHING = 6 - AGENT_NAME_NOT_IN_WHITELIST = 7 - COMPETITION_NOT_RUNNING = 8 - DIALOGUE_INCONSISTENT = 9 - - _from_ec_to_msg = { - ErrorCode.GENERIC_ERROR: "Unexpected error.", - ErrorCode.REQUEST_NOT_VALID: "Request not recognized", - ErrorCode.AGENT_PBK_ALREADY_REGISTERED: "Agent pbk already registered.", - ErrorCode.AGENT_NAME_ALREADY_REGISTERED: "Agent name already registered.", - ErrorCode.AGENT_NOT_REGISTERED: "Agent not registered.", - ErrorCode.TRANSACTION_NOT_VALID: "Error in checking transaction", - ErrorCode.TRANSACTION_NOT_MATCHING: "The transaction request does not match with a previous transaction request with the same id.", - ErrorCode.AGENT_NAME_NOT_IN_WHITELIST: "Agent name not in whitelist.", - ErrorCode.COMPETITION_NOT_RUNNING: "The competition is not running yet.", - ErrorCode.DIALOGUE_INCONSISTENT: "The message is inconsistent with the dialogue." - } # type: Dict[ErrorCode, str] - - def __init__(self, tac_type: Optional[Type] = None, - **kwargs): - """ - Initialize. - - :param tac_type: the type of TAC message. - """ - super().__init__(type=str(tac_type), **kwargs) - - def check_consistency(self) -> bool: - """Check that the data is consistent.""" - try: - assert self.is_set("type") - tac_type = TACMessage.Type(self.get("type")) - if tac_type == TACMessage.Type.REGISTER: - assert self.is_set("agent_name") - elif tac_type == TACMessage.Type.UNREGISTER: - pass - elif tac_type == TACMessage.Type.TRANSACTION: - assert self.is_set("transaction_id") - assert self.is_set("is_sender_buyer") - assert self.is_set("counterparty") - assert self.is_set("amount") - amount = self.get("amount") - assert amount >= 0 - assert self.is_set("quantities_by_good_pbk") - quantities_by_good_pbk = self.get("quantities_by_good_pbk") - assert len(quantities_by_good_pbk.keys()) == len(set(quantities_by_good_pbk.keys())) - assert all(quantity >= 0 for quantity in quantities_by_good_pbk.values()) - elif tac_type == TACMessage.Type.GET_STATE_UPDATE: - pass - elif tac_type == TACMessage.Type.CANCELLED: - pass - elif tac_type == TACMessage.Type.GAME_DATA: - assert self.is_set("money") - assert self.is_set("endowment") - assert self.is_set("utility_params") - assert self.is_set("nb_agents") - assert self.is_set("nb_goods") - assert self.is_set("tx_fee") - assert self.is_set("agent_pbk_to_name") - assert self.is_set("good_pbk_to_name") - elif tac_type == TACMessage.Type.TRANSACTION_CONFIRMATION: - assert self.is_set("transaction_id") - elif tac_type == TACMessage.Type.STATE_UPDATE: - assert self.is_set("initial_state") - assert self.is_set("transactions") - elif tac_type == TACMessage.Type.TAC_ERROR: - assert self.is_set("error_code") - error_code = self.get("error_code") - assert error_code in set(self.ErrorCode) - else: - raise ValueError("Type not recognized.") - except (AssertionError, ValueError): - return False - - return True diff --git a/tac/aea/protocols/tac/serialization.py b/tac/aea/protocols/tac/serialization.py deleted file mode 100644 index 2379805b..00000000 --- a/tac/aea/protocols/tac/serialization.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""Serialization for the TAC protocol.""" -import copy -import json - -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.base.serialization import Serializer -from tac.aea.protocols.tac.message import TACMessage - - -class TACSerializer(Serializer): - """Serialization for the TAC protocol.""" - - def encode(self, msg: Message) -> bytes: - """ - Decode the message. - - :param msg: the message object - :return: the bytes - """ - tac_type = TACMessage.Type(msg.get("type")) - new_body = copy.copy(msg.body) - - if tac_type in {}: - pass # TODO - - tac_message_bytes = json.dumps(new_body).encode("utf-8") - return tac_message_bytes - - def decode(self, obj: bytes) -> Message: - """ - Decode the message. - - :param obj: the bytes object - :return: the message - """ - json_msg = json.loads(obj.decode("utf-8")) - tac_type = TACMessage.Type(json_msg["type"]) - new_body = copy.copy(json_msg) - new_body["type"] = tac_type - - if tac_type in {}: - pass # TODO - - tac_message = Message(body=new_body) - return tac_message diff --git a/tac/aea/state/__init__.py b/tac/aea/state/__init__.py deleted file mode 100644 index 225dd473..00000000 --- a/tac/aea/state/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the state modules.""" diff --git a/tac/aea/state/base.py b/tac/aea/state/base.py deleted file mode 100644 index d0177513..00000000 --- a/tac/aea/state/base.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the classes which define the states of an agent.""" - - -class AgentState: - """Represent the state of an agent during the game.""" - - def __init__(self): - """Initialize.""" - - -class WorldState: - """Represent the state of an agent during the game.""" - - def __init__(self): - """Initialize.""" diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 6dc440e7..ca7cecb7 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -31,9 +31,9 @@ import dateutil -from tac.aea.agent import Agent -from tac.aea.channel.oef import OEFMailBox -from tac.aea.mail.base import Envelope +from aea.agent import Agent +from aea.channel.oef import OEFMailBox +from aea.mail.base import Envelope from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters from tac.agents.participant.v1.base.helpers import is_oef_message diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index 56e3284f..b53eb678 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -28,11 +28,11 @@ from oef.schema import Description, DataModel, AttributeSchema -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.protocols.oef.message import OEFMessage -from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox +from aea.protocols.oef.message import OEFMessage +from aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.controller.base.interfaces import OEFActionInterface logger = logging.getLogger(__name__) diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 1e7d3a71..7d48f744 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -40,13 +40,13 @@ from collections import defaultdict from typing import Any, Dict, Optional, List, Set, Type, TYPE_CHECKING -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox, Envelope -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.oef.message import OEFMessage -from tac.aea.protocols.oef.serialization import OEFSerializer +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox, Envelope +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.oef.message import OEFMessage +from aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.actions import OEFActions from tac.agents.controller.base.helpers import generate_good_pbk_to_name from tac.agents.controller.base.reactions import OEFReactions diff --git a/tac/agents/controller/base/interfaces.py b/tac/agents/controller/base/interfaces.py index 917d720e..ace1584b 100644 --- a/tac/agents/controller/base/interfaces.py +++ b/tac/agents/controller/base/interfaces.py @@ -28,7 +28,7 @@ from abc import abstractmethod -from tac.aea.mail.base import Envelope +from aea.mail.base import Envelope class OEFReactionInterface: diff --git a/tac/agents/controller/base/reactions.py b/tac/agents/controller/base/reactions.py index e578c4ec..a9b92295 100644 --- a/tac/agents/controller/base/reactions.py +++ b/tac/agents/controller/base/reactions.py @@ -26,10 +26,10 @@ import logging -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox, Envelope -from tac.aea.protocols.oef.serialization import OEFSerializer +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox, Envelope +from aea.protocols.oef.serialization import OEFSerializer from tac.agents.controller.base.interfaces import OEFReactionInterface logger = logging.getLogger(__name__) diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index c7065a57..de144487 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -29,7 +29,7 @@ import logging from typing import List, Dict, Any -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState from tac.platform.game.helpers import generate_money_endowments, generate_good_endowments, generate_utility_params, \ diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 3c819057..c01e45f3 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -24,9 +24,9 @@ import time from typing import Optional -from tac.aea.agent import Agent -from tac.aea.channel.oef import OEFMailBox -from tac.aea.mail.base import Envelope +from aea.agent import Agent +from aea.channel.oef import OEFMailBox +from aea.mail.base import Envelope from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index 758eae3f..091d2630 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -30,13 +30,13 @@ from oef.query import Query, Constraint, GtEq -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.oef.message import OEFMessage -from tac.aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.oef.message import OEFMessage +from aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, \ DialogueActionInterface diff --git a/tac/agents/participant/v1/base/dialogues.py b/tac/agents/participant/v1/base/dialogues.py index 37b1786a..01aa6bed 100644 --- a/tac/agents/participant/v1/base/dialogues.py +++ b/tac/agents/participant/v1/base/dialogues.py @@ -29,12 +29,12 @@ import logging from typing import Any, Dict, List, Optional -from tac.aea.dialogue.base import DialogueLabel -from tac.aea.dialogue.base import Dialogue as BaseDialogue -from tac.aea.dialogue.base import Dialogues as BaseDialogues -from tac.aea.mail.base import Address -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.fipa.message import FIPAMessage +from aea.dialogue.base import DialogueLabel +from aea.dialogue.base import Dialogue as BaseDialogue +from aea.dialogue.base import Dialogues as BaseDialogues +from aea.mail.base import Address +from aea.protocols.base.message import Message +from aea.protocols.fipa.message import FIPAMessage Action = Any logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py index 0212a5fe..14be54ac 100644 --- a/tac/agents/participant/v1/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -27,7 +27,7 @@ from oef.query import Query from oef.schema import Description -from tac.aea.channel.oef import MailStats +from aea.channel.oef import MailStats from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue from tac.agents.participant.v1.base.helpers import build_dict, build_query, get_goods_quantities_description from tac.agents.participant.v1.base.states import AgentState, WorldState diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index 9e84f287..d33b3e2d 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -29,14 +29,14 @@ import logging from typing import Any -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox, Envelope -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.serialization import FIPASerializer -from tac.aea.protocols.oef.message import OEFMessage -from tac.aea.protocols.oef.serialization import OEFSerializer +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox, Envelope +from aea.protocols.base.message import Message +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.fipa.serialization import FIPASerializer +from aea.protocols.oef.message import OEFMessage +from aea.protocols.oef.serialization import OEFSerializer from tac.agents.participant.v1.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions diff --git a/tac/agents/participant/v1/base/helpers.py b/tac/agents/participant/v1/base/helpers.py index a95112e2..c9a1bd0f 100644 --- a/tac/agents/participant/v1/base/helpers.py +++ b/tac/agents/participant/v1/base/helpers.py @@ -25,8 +25,8 @@ from oef.query import Query, Constraint, GtEq, Or from oef.schema import AttributeSchema, DataModel, Description -from tac.aea.dialogue.base import DialogueLabel -from tac.aea.mail.base import Envelope +from aea.dialogue.base import DialogueLabel +from aea.mail.base import Envelope logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/interfaces.py b/tac/agents/participant/v1/base/interfaces.py index 6d805fe8..b80aac1f 100644 --- a/tac/agents/participant/v1/base/interfaces.py +++ b/tac/agents/participant/v1/base/interfaces.py @@ -22,8 +22,8 @@ from abc import abstractmethod -from tac.aea.mail.base import Envelope -from tac.aea.protocols.base.message import Message +from aea.mail.base import Envelope +from aea.protocols.base.message import Message from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index 90513753..fac7e913 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -25,14 +25,14 @@ import pprint from typing import List -from tac.aea.crypto.base import Crypto - -from tac.aea.mail.base import Envelope -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.crypto.base import Crypto + +from aea.mail.base import Envelope +from aea.protocols.base.message import Message +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.helpers import generate_transaction_id diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 49e5386a..fd70a13c 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -30,14 +30,14 @@ import logging from typing import List -from tac.aea.agent import Liveness -from tac.aea.crypto.base import Crypto -from tac.aea.mail.base import MailBox, Envelope, Address -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.agent import Liveness +from aea.crypto.base import Crypto +from aea.mail.base import MailBox, Envelope, Address +from aea.protocols.base.message import Message +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.fipa.serialization import FIPASerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase from tac.agents.participant.v1.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME diff --git a/tac/agents/participant/v1/base/states.py b/tac/agents/participant/v1/base/states.py index f6585665..2a0e0821 100644 --- a/tac/agents/participant/v1/base/states.py +++ b/tac/agents/participant/v1/base/states.py @@ -30,8 +30,8 @@ import pprint from typing import Dict, List -from tac.aea.state.base import AgentState as BaseAgentState -from tac.aea.state.base import WorldState as BaseWorldState +from aea.state.base import AgentState as BaseAgentState +from aea.state.base import WorldState as BaseWorldState from tac.agents.participant.v1.base.price_model import GoodPriceModel from tac.platform.game.helpers import logarithmic_utility from tac.platform.protocol import Transaction diff --git a/tac/agents/participant/v1/base/stats_manager.py b/tac/agents/participant/v1/base/stats_manager.py index b7ee3b91..84209a49 100644 --- a/tac/agents/participant/v1/base/stats_manager.py +++ b/tac/agents/participant/v1/base/stats_manager.py @@ -27,7 +27,7 @@ import numpy as np -from tac.aea.channel.oef import MailStats +from aea.channel.oef import MailStats class EndState(Enum): diff --git a/tac/agents/participant/v2/agent.py b/tac/agents/participant/v2/agent.py index 7b51a85b..bda10a8c 100644 --- a/tac/agents/participant/v2/agent.py +++ b/tac/agents/participant/v2/agent.py @@ -24,9 +24,9 @@ import time from typing import Optional -from tac.aea.agent import Agent -from tac.aea.channel.oef import OEFMailBox -from tac.aea.mail.base import Envelope +from aea.agent import Agent +from aea.channel.oef import OEFMailBox +from aea.mail.base import Envelope from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tac/gui/dashboards/controller.py b/tac/gui/dashboards/controller.py index f772ff24..b813396d 100644 --- a/tac/gui/dashboards/controller.py +++ b/tac/gui/dashboards/controller.py @@ -29,7 +29,7 @@ import numpy as np from tac.gui.dashboards.base import start_visdom_server, Dashboard -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.controller.base.states import Game from tac.platform.game.stats import GameStats diff --git a/tac/gui/dashboards/leaderboard.py b/tac/gui/dashboards/leaderboard.py index becdf386..3358b560 100644 --- a/tac/gui/dashboards/leaderboard.py +++ b/tac/gui/dashboards/leaderboard.py @@ -26,7 +26,7 @@ from collections import defaultdict from typing import Optional, Dict, List -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.controller.base.states import Game from tac.gui.dashboards.base import start_visdom_server, Dashboard from tac.platform.game.stats import GameStats diff --git a/tac/platform/game/stats.py b/tac/platform/game/stats.py index 90c40f6b..66c94ab6 100644 --- a/tac/platform/game/stats.py +++ b/tac/platform/game/stats.py @@ -27,7 +27,7 @@ import os import pylab as plt -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.controller.base.states import Game from tac.agents.participant.v1.base.states import AgentState diff --git a/tac/platform/protocol.py b/tac/platform/protocol.py index b4662465..1c8c674e 100644 --- a/tac/platform/protocol.py +++ b/tac/platform/protocol.py @@ -48,7 +48,7 @@ from google.protobuf.message import DecodeError -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto import tac.tac_pb2 as tac_pb2 from oef.schema import Description diff --git a/templates/v1/advanced.py b/templates/v1/advanced.py index 8cda3997..03b45242 100644 --- a/templates/v1/advanced.py +++ b/templates/v1/advanced.py @@ -27,7 +27,7 @@ from oef.schema import Description -from tac.aea.state.base import WorldState +from aea.state.base import WorldState from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor, Strategy from tac.agents.participant.examples.baseline import BaselineAgent from tac.gui.dashboards.agent import AgentDashboard diff --git a/templates/v1/expert.py b/templates/v1/expert.py index beae4cec..4c0bc721 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -25,8 +25,8 @@ import logging from typing import Optional -from tac.aea.agent import Agent -from tac.aea.mail.oef import OEFMailBox +from aea.agent import Agent +from aea.mail.oef import OEFMailBox logger = logging.getLogger(__name__) diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index 272fd660..bd8fe461 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -23,8 +23,8 @@ import time from threading import Thread -from tac.aea.agent import Agent, AgentState -from tac.aea.channel.oef import OEFMailBox +from aea.agent import Agent, AgentState +from aea.channel.oef import OEFMailBox class TAgent(Agent): diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index 43a02e58..e3310388 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -22,8 +22,8 @@ from threading import Timer from unittest.mock import MagicMock -from tac.aea.agent import Agent -from tac.aea.channel.oef import OEFMailBox +from aea.agent import Agent +from aea.channel.oef import OEFMailBox class TAgent(Agent): diff --git a/tests/test_controller.py b/tests/test_controller.py index c84f5fe1..61bdb359 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -26,13 +26,13 @@ import time from threading import Thread -from tac.aea.mail.base import Envelope -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer +from aea.mail.base import Envelope +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.controller.agent import ControllerAgent from tac.agents.controller.base.tac_parameters import TACParameters from tac.platform.protocol import Register diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 9c6edc86..cd1c348b 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -23,7 +23,7 @@ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, \ load_pem_private_key -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from .conftest import ROOT_DIR diff --git a/tests/test_game.py b/tests/test_game.py index cfa45ee2..c4cccecc 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -21,7 +21,7 @@ import pytest -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.agents.controller.base.states import GameInitialization, Game from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 4ef597d2..9a274b1e 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -21,13 +21,12 @@ import asyncio import time -from tac.aea.mail.base import Envelope, MailBox -from tac.aea.channel.oef import OEFLocalConnection -from tac.aea.helpers.local_node import LocalNode -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.mail.base import Envelope, MailBox +from aea.helpers.local_node import LocalNode, OEFLocalConnection +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.fipa.serialization import FIPASerializer def test_connection(): diff --git a/tests/test_mail.py b/tests/test_mail.py index 25be6a98..f8bd797f 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -20,12 +20,12 @@ """This module contains tests for the mail module.""" import time -from tac.aea.channel.oef import OEFMailBox -from tac.aea.mail.base import Envelope -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.default.serialization import DefaultSerializer -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.channel.oef import OEFMailBox +from aea.mail.base import Envelope +from aea.protocols.default.message import DefaultMessage +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.fipa.serialization import FIPASerializer def test_example(network_node): diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py index d58a24e3..fd8eced7 100644 --- a/tests/test_messages/test_base.py +++ b/tests/test_messages/test_base.py @@ -18,9 +18,9 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the messages module.""" -from tac.aea.mail.base import Envelope -from tac.aea.protocols.base.message import Message -from tac.aea.protocols.base.serialization import ProtobufSerializer, JSONSerializer +from aea.mail.base import Envelope +from aea.protocols.base.message import Message +from aea.protocols.base.serialization import ProtobufSerializer, JSONSerializer class TestDefaultSerializations: diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py index 13526b3e..fc2fc13f 100644 --- a/tests/test_messages/test_fipa.py +++ b/tests/test_messages/test_fipa.py @@ -20,9 +20,9 @@ """This module contains the tests for the FIPA protocol.""" from oef.schema import Description -from tac.aea.mail.base import Envelope -from tac.aea.protocols.fipa.message import FIPAMessage -from tac.aea.protocols.fipa.serialization import FIPASerializer +from aea.mail.base import Envelope +from aea.protocols.fipa.message import FIPAMessage +from aea.protocols.fipa.serialization import FIPASerializer def test_fipa_cfp_serialization(): diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py index 7985e429..56fa3ea1 100644 --- a/tests/test_messages/test_simple.py +++ b/tests/test_messages/test_simple.py @@ -19,9 +19,9 @@ """This module contains the tests of the messages module.""" -from tac.aea.mail.base import Envelope -from tac.aea.protocols.default.message import DefaultMessage -from tac.aea.protocols.default.serialization import DefaultSerializer +from aea.mail.base import Envelope +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer def test_simple_bytes_serialization(): diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 80f28edf..7b173185 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -21,7 +21,7 @@ import pytest -from tac.aea.crypto.base import Crypto +from aea.crypto.base import Crypto from tac.platform.protocol import Register, Unregister, Transaction, TransactionConfirmation, Error, \ GameData, Request, Response, ErrorCode, Cancelled, GetStateUpdate, StateUpdate From 45e94ddd25421024d39c736207184a89e1105fe7 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 22 Aug 2019 17:41:40 +0100 Subject: [PATCH 073/107] wip --- tac/agents/controller/agent.py | 3 +- tac/agents/controller/base/actions.py | 4 +- tac/agents/controller/base/handlers.py | 2 +- tac/agents/participant/v1/base/actions.py | 3 +- .../participant/v1/base/game_instance.py | 3 +- tac/agents/participant/v1/base/helpers.py | 9 ++- tac/agents/participant/v1/base/strategy.py | 2 +- .../participant/v1/examples/strategy.py | 2 +- tac/platform/protocol.py | 3 +- templates/v1/advanced.py | 5 +- tests/test_messages/__init__.py | 20 ------ tests/test_messages/test_base.py | 60 ----------------- tests/test_messages/test_fipa.py | 65 ------------------- tests/test_messages/test_simple.py | 56 ---------------- 14 files changed, 16 insertions(+), 221 deletions(-) delete mode 100644 tests/test_messages/__init__.py delete mode 100644 tests/test_messages/test_base.py delete mode 100644 tests/test_messages/test_fipa.py delete mode 100644 tests/test_messages/test_simple.py diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index ca7cecb7..bc91315b 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -30,10 +30,9 @@ from typing import Union, Optional import dateutil - from aea.agent import Agent from aea.channel.oef import OEFMailBox -from aea.mail.base import Envelope + from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters from tac.agents.participant.v1.base.helpers import is_oef_message diff --git a/tac/agents/controller/base/actions.py b/tac/agents/controller/base/actions.py index b53eb678..e4a57c98 100644 --- a/tac/agents/controller/base/actions.py +++ b/tac/agents/controller/base/actions.py @@ -26,7 +26,7 @@ import logging -from oef.schema import Description, DataModel, AttributeSchema +from aea.protocols.oef.models import Description, DataModel, Attribute from aea.agent import Liveness from aea.crypto.base import Crypto @@ -38,7 +38,7 @@ logger = logging.getLogger(__name__) CONTROLLER_DATAMODEL = DataModel("tac", [ - AttributeSchema("version", int, True, "Version number of the TAC Controller Agent."), + Attribute("version", int, True, "Version number of the TAC Controller Agent."), ]) diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 7d48f744..c24ce0ee 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -59,7 +59,7 @@ Transaction, TransactionConfirmation, ErrorCode, Cancelled, GetStateUpdate, StateUpdate if TYPE_CHECKING: - from tac.platform.controller.controller_agent import ControllerAgent + from tac.agents.controller.agent import ControllerAgent logger = logging.getLogger(__name__) diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index 091d2630..b9e6444e 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -28,7 +28,8 @@ import logging -from oef.query import Query, Constraint, GtEq +from aea.protocols.oef.models import Query, Constraint +from oef.query import GtEq from aea.agent import Liveness from aea.crypto.base import Crypto diff --git a/tac/agents/participant/v1/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py index 14be54ac..8ff79666 100644 --- a/tac/agents/participant/v1/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -24,8 +24,7 @@ import random from typing import List, Optional, Set, Tuple, Dict, Union -from oef.query import Query -from oef.schema import Description +from aea.protocols.oef.models import Description, Query from aea.channel.oef import MailStats from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue diff --git a/tac/agents/participant/v1/base/helpers.py b/tac/agents/participant/v1/base/helpers.py index c9a1bd0f..b73d869f 100644 --- a/tac/agents/participant/v1/base/helpers.py +++ b/tac/agents/participant/v1/base/helpers.py @@ -22,11 +22,10 @@ import logging from typing import Dict, List, Set, Union -from oef.query import Query, Constraint, GtEq, Or -from oef.schema import AttributeSchema, DataModel, Description - from aea.dialogue.base import DialogueLabel from aea.mail.base import Envelope +from aea.protocols.oef.models import DataModel, Attribute, Description, Query, Constraint, Or +from oef.query import GtEq logger = logging.getLogger(__name__) @@ -104,9 +103,9 @@ def build_datamodel(good_pbks: List[str], is_supply: bool) -> DataModel: :return: the data model. """ - goods_quantities_attributes = [AttributeSchema(good_pbk, int, False) + goods_quantities_attributes = [Attribute(good_pbk, int, False) for good_pbk in good_pbks] - price_attribute = AttributeSchema("price", float, False) + price_attribute = Attribute("price", float, False) description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME data_model = DataModel(description, goods_quantities_attributes + [price_attribute]) return data_model diff --git a/tac/agents/participant/v1/base/strategy.py b/tac/agents/participant/v1/base/strategy.py index f3d855cf..f7f50b10 100644 --- a/tac/agents/participant/v1/base/strategy.py +++ b/tac/agents/participant/v1/base/strategy.py @@ -24,7 +24,7 @@ from enum import Enum from typing import List, Set, Optional -from oef.schema import Description +from aea.protocols.oef.models import Description from tac.agents.participant.v1.base.states import WorldState diff --git a/tac/agents/participant/v1/examples/strategy.py b/tac/agents/participant/v1/examples/strategy.py index 2bb3a85d..f7c2505d 100644 --- a/tac/agents/participant/v1/examples/strategy.py +++ b/tac/agents/participant/v1/examples/strategy.py @@ -22,7 +22,7 @@ from typing import List, Optional, Set -from oef.schema import Description +from aea.protocols.oef.models import Description from tac.agents.participant.v1.base.helpers import get_goods_quantities_description from tac.agents.participant.v1.base.states import WorldState diff --git a/tac/platform/protocol.py b/tac/platform/protocol.py index 1c8c674e..cd7bdfda 100644 --- a/tac/platform/protocol.py +++ b/tac/platform/protocol.py @@ -46,13 +46,12 @@ from typing import List, Dict, Any from typing import Optional +from aea.protocols.oef.models import Description from google.protobuf.message import DecodeError from aea.crypto.base import Crypto import tac.tac_pb2 as tac_pb2 -from oef.schema import Description - logger = logging.getLogger(__name__) diff --git a/templates/v1/advanced.py b/templates/v1/advanced.py index 03b45242..9902977e 100644 --- a/templates/v1/advanced.py +++ b/templates/v1/advanced.py @@ -25,11 +25,10 @@ import logging from typing import List, Optional, Set -from oef.schema import Description - +from aea.protocols.oef.models import Description from aea.state.base import WorldState from tac.agents.participant.v1.base.strategy import RegisterAs, SearchFor, Strategy -from tac.agents.participant.examples.baseline import BaselineAgent +from tac.agents.participant.v1.examples.baseline import BaselineAgent from tac.gui.dashboards.agent import AgentDashboard logger = logging.getLogger(__name__) diff --git a/tests/test_messages/__init__.py b/tests/test_messages/__init__.py deleted file mode 100644 index 138d502b..00000000 --- a/tests/test_messages/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""The tests module contains the tests for the messaging layer.""" diff --git a/tests/test_messages/test_base.py b/tests/test_messages/test_base.py deleted file mode 100644 index fd8eced7..00000000 --- a/tests/test_messages/test_base.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the tests of the messages module.""" -from aea.mail.base import Envelope -from aea.protocols.base.message import Message -from aea.protocols.base.serialization import ProtobufSerializer, JSONSerializer - - -class TestDefaultSerializations: - """Test that the default serializations work.""" - - @classmethod - def setup_class(cls): - """Set up the use case.""" - cls.message = Message(content="hello") - - def test_default_protobuf_serialization(self): - """Test that the default Protobuf serialization works.""" - message_bytes = ProtobufSerializer().encode(self.message) - envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) - envelope_bytes = envelope.encode() - - expected_envelope = Envelope.decode(envelope_bytes) - actual_envelope = envelope - assert expected_envelope == actual_envelope - - expected_msg = ProtobufSerializer().decode(expected_envelope.message) - actual_msg = self.message - assert expected_msg == actual_msg - - def test_default_json_serialization(self): - """Test that the default JSON serialization works.""" - message_bytes = JSONSerializer().encode(self.message) - envelope = Envelope(to="receiver", sender="sender", protocol_id="my_own_protocol", message=message_bytes) - envelope_bytes = envelope.encode() - - expected_envelope = Envelope.decode(envelope_bytes) - actual_envelope = envelope - assert expected_envelope == actual_envelope - - expected_msg = JSONSerializer().decode(expected_envelope.message) - actual_msg = self.message - assert expected_msg == actual_msg diff --git a/tests/test_messages/test_fipa.py b/tests/test_messages/test_fipa.py deleted file mode 100644 index fc2fc13f..00000000 --- a/tests/test_messages/test_fipa.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the tests for the FIPA protocol.""" -from oef.schema import Description - -from aea.mail.base import Envelope -from aea.protocols.fipa.message import FIPAMessage -from aea.protocols.fipa.serialization import FIPASerializer - - -def test_fipa_cfp_serialization(): - """Test that the serialization for the 'fipa' protocol works.""" - msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query={"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 - - actual_msg = FIPASerializer().decode(actual_envelope.message) - expected_msg = msg - assert expected_msg == actual_msg - - -def test_fipa_propose_serialization(): - """Test that the serialization for the 'fipa' protocol works.""" - proposal = [ - Description({"foo1": 1, "bar1": 2}), # DataModel("dm_bar", [AttributeSchema("foo1", int, True), AttributeSchema("bar1", int, True)])) - Description({"foo2": 1, "bar2": 2}), - ] - msg = FIPAMessage(message_id=0, dialogue_id=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 diff --git a/tests/test_messages/test_simple.py b/tests/test_messages/test_simple.py deleted file mode 100644 index 56fa3ea1..00000000 --- a/tests/test_messages/test_simple.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the tests of the messages module.""" - -from aea.mail.base import Envelope -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer - - -def test_simple_bytes_serialization(): - """Test that the serialization for the 'simple' protocol works for the BYTES message.""" - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) - envelope_bytes = envelope.encode() - - actual_envelope = Envelope.decode(envelope_bytes) - expected_envelope = envelope - assert expected_envelope == actual_envelope - - actual_msg = DefaultSerializer().decode(actual_envelope.message) - expected_msg = msg - assert expected_msg == actual_msg - - -def test_simple_error_serialization(): - """Test that the serialization for the 'simple' protocol works for the ERROR message.""" - msg = DefaultMessage(type=DefaultMessage.Type.ERROR, error_code=-1, error_msg="An error") - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to="receiver", sender="sender", protocol_id="simple", message=msg_bytes) - envelope_bytes = envelope.encode() - - actual_envelope = Envelope.decode(envelope_bytes) - expected_envelope = envelope - assert expected_envelope == actual_envelope - - actual_msg = DefaultSerializer().decode(actual_envelope.message) - expected_msg = msg - assert expected_msg == actual_msg From 713f31b5a744f1b47f0e71d72573adafec49df0b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 23 Aug 2019 14:30:19 +0100 Subject: [PATCH 074/107] update Pipfile. install aea package from git. --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 580f8a91..3914eed8 100644 --- a/Pipfile +++ b/Pipfile @@ -21,7 +21,6 @@ flake8-docstrings = "*" pygments = "*" [packages] -aea = {version = "==0.1.0.post3",index = "test-pypi"} fetchai-ledger-api = {git = "https://github.com/fetchai/ledger-api-py.git"} base58 = "*" cryptography = "*" @@ -36,6 +35,7 @@ sphinxcontrib-apidoc = "*" sphinx = "*" wtforms = "*" visdom = "*" +aea = {editable = true,git = "https://github.com/fetchai/agents-aea.git",ref = "develop"} [requires] python_version = "3.7" From 074978b1e5f7bcc0cc0c4c9e44c2a1796e577a31 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 23 Aug 2019 15:22:59 +0100 Subject: [PATCH 075/107] fix test localnode. --- Pipfile.lock | 25 +++----- tests/test_localnode.py | 131 ++++++++++++++++++++-------------------- 2 files changed, 72 insertions(+), 84 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index a61ca35e..1f8081a4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "13219dde216f73c945f18c20b136b748a0f131dd3aa4505d11ba29a60e797574" + "sha256": "bd24006788c89b0a5678e335640502b980c3d377d6d6e65c26505b57cc568093" }, "pipfile-spec": 6, "requires": { @@ -22,12 +22,9 @@ }, "default": { "aea": { - "hashes": [ - "sha256:299da7e504547a4e143a3f792d276d79f550826d8d869f815a1421453972c58e", - "sha256:c00a7aeb9d005a669cc05dea1ec257660b5ef39bc9e29281e8ebf5bc2889c86a" - ], - "index": "test-pypi", - "version": "==0.1.0.post3" + "editable": true, + "git": "https://github.com/fetchai/agents-aea.git", + "ref": "c0e643c7caa6cdd6def2283a95905414bf6cc6f6" }, "alabaster": { "hashes": [ @@ -742,14 +739,6 @@ } }, "develop": { - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -1332,10 +1321,10 @@ }, "virtualenv": { "hashes": [ - "sha256:5e4d92f9a36359a745ddb113cabb662e6100e71072a1e566eb6ddfcc95fdb7ed", - "sha256:b6711690882013bc79e0eac55889d901596f0967165d80adfa338c5729db1c71" + "sha256:94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", + "sha256:f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d" ], - "version": "==16.7.3" + "version": "==16.7.4" }, "virtualenv-clone": { "hashes": [ diff --git a/tests/test_localnode.py b/tests/test_localnode.py index 9a274b1e..cdac7a32 100644 --- a/tests/test_localnode.py +++ b/tests/test_localnode.py @@ -18,11 +18,10 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the local OEF node implementation.""" -import asyncio import time +from aea.channel.local import LocalNode, OEFLocalConnection from aea.mail.base import Envelope, MailBox -from aea.helpers.local_node import LocalNode, OEFLocalConnection from aea.protocols.default.message import DefaultMessage from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.fipa.message import FIPAMessage @@ -31,73 +30,73 @@ def test_connection(): """Test that two mailbox can connect to the node.""" - with LocalNode() as node: - mailbox1 = MailBox(OEFLocalConnection("mailbox1", node, loop=asyncio.new_event_loop())) - mailbox2 = MailBox(OEFLocalConnection("mailbox2", node, loop=asyncio.new_event_loop())) + node = LocalNode() + mailbox1 = MailBox(OEFLocalConnection("mailbox1", node)) + mailbox2 = MailBox(OEFLocalConnection("mailbox2", node)) - mailbox1.connect() - mailbox2.connect() + mailbox1.connect() + mailbox2.connect() - mailbox1.disconnect() - mailbox2.disconnect() + mailbox1.disconnect() + mailbox2.disconnect() def test_communication(): """Test that two mailbox can communicate through the node.""" - with LocalNode() as node: - mailbox1 = MailBox(OEFLocalConnection("mailbox1", node, loop=asyncio.new_event_loop())) - mailbox2 = MailBox(OEFLocalConnection("mailbox2", node, loop=asyncio.new_event_loop())) - - mailbox1.connect() - mailbox2.connect() - - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - time.sleep(5.0) - - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = DefaultSerializer().decode(envelope.message) - assert envelope.protocol_id == "default" - assert msg.get("content") == b"hello" - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.CFP - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.PROPOSE - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.ACCEPT - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.DECLINE - - mailbox1.disconnect() - mailbox2.disconnect() + node = LocalNode() + mailbox1 = MailBox(OEFLocalConnection("mailbox1", node)) + mailbox2 = MailBox(OEFLocalConnection("mailbox2", node)) + + mailbox1.connect() + mailbox2.connect() + + msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + msg_bytes = DefaultSerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) + msg_bytes = FIPASerializer().encode(msg) + envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) + mailbox1.send(envelope) + + time.sleep(5.0) + + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = DefaultSerializer().decode(envelope.message) + assert envelope.protocol_id == "default" + assert msg.get("content") == b"hello" + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.CFP + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.PROPOSE + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.ACCEPT + envelope = mailbox2.inbox.get(block=True, timeout=1.0) + msg = FIPASerializer().decode(envelope.message) + assert envelope.protocol_id == "fipa" + assert msg.get("performative") == FIPAMessage.Performative.DECLINE + + mailbox1.disconnect() + mailbox2.disconnect() From 11c2ee846ff485bbb20e35d6cccf6bbdb12aaf01 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 23 Aug 2019 15:27:53 +0100 Subject: [PATCH 076/107] fix mailbox tests. --- tests/test_mail.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_mail.py b/tests/test_mail.py index f8bd797f..4616f10b 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -21,6 +21,7 @@ import time from aea.channel.oef import OEFMailBox +from aea.crypto.base import Crypto from aea.mail.base import Envelope from aea.protocols.default.message import DefaultMessage from aea.protocols.fipa.message import FIPAMessage @@ -30,31 +31,35 @@ def test_example(network_node): """Test the mailbox.""" - mailbox1 = OEFMailBox("mailbox1", "127.0.0.1", 10000) - mailbox2 = OEFMailBox("mailbox2", "127.0.0.1", 10000) + + crypto1 = Crypto() + crypto2 = Crypto() + + mailbox1 = OEFMailBox(crypto1.public_key, "127.0.0.1", 10000) + mailbox2 = OEFMailBox(crypto2.public_key, "127.0.0.1", 10000) mailbox1.connect() mailbox2.connect() msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") msg_bytes = DefaultSerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) + mailbox1.outbox.put(Envelope(to=crypto2.public_key, sender=crypto1.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.CFP, query=None) msg_bytes = FIPASerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + mailbox1.outbox.put(Envelope(to=crypto2.public_key, sender=crypto1.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.PROPOSE, proposal=[]) msg_bytes = FIPASerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + mailbox1.outbox.put(Envelope(to=crypto2.public_key, sender=crypto1.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.ACCEPT) msg_bytes = FIPASerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + mailbox1.outbox.put(Envelope(to=crypto2.public_key, sender=crypto1.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) msg = FIPAMessage(message_id=0, dialogue_id=0, target=0, performative=FIPAMessage.Performative.DECLINE) msg_bytes = FIPASerializer().encode(msg) - mailbox1.outbox.put(Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) + mailbox1.outbox.put(Envelope(to=crypto2.public_key, sender=crypto1.public_key, protocol_id=FIPAMessage.protocol_id, message=msg_bytes)) time.sleep(5.0) From 4f04b07b1f269be57e0ce8944828a209f8cd2782 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 23 Aug 2019 15:31:42 +0100 Subject: [PATCH 077/107] fix stylechecks. --- tac/agents/controller/agent.py | 1 + tests/test_mail.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index bc91315b..46f72caf 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -32,6 +32,7 @@ import dateutil from aea.agent import Agent from aea.channel.oef import OEFMailBox +from aea.mail.base import Envelope from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters diff --git a/tests/test_mail.py b/tests/test_mail.py index 4616f10b..d54721d8 100644 --- a/tests/test_mail.py +++ b/tests/test_mail.py @@ -31,7 +31,6 @@ def test_example(network_node): """Test the mailbox.""" - crypto1 = Crypto() crypto2 = Crypto() From 46b65568a10dd4e00521531cdf9b6184f5ab3b16 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sat, 24 Aug 2019 01:04:15 +0100 Subject: [PATCH 078/107] WIP: transition to new tac protocol --- tac/agents/controller/base/handlers.py | 296 +++---- tac/agents/controller/base/states.py | 8 +- tac/agents/participant/v1/base/actions.py | 12 +- .../participant/v1/base/game_instance.py | 11 +- tac/agents/participant/v1/base/handlers.py | 30 +- tac/agents/participant/v1/base/interfaces.py | 38 +- .../v1/base/negotiation_behaviours.py | 32 +- tac/agents/participant/v1/base/reactions.py | 65 +- tac/agents/participant/v1/base/states.py | 2 +- .../v1/base/transaction_manager.py | 2 +- tac/gui/dashboards/agent.py | 2 +- tac/platform/game/base.py | 168 +++- tac/platform/protocol.py | 774 ------------------ tests/test_controller.py | 12 +- tests/test_game.py | 15 +- tests/test_protocol.py | 138 ---- 16 files changed, 440 insertions(+), 1165 deletions(-) delete mode 100644 tac/platform/protocol.py delete mode 100644 tests/test_protocol.py diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index c24ce0ee..aa0802a5 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -25,7 +25,7 @@ The methods are split in three classes: - AgentMessageDispatcher: class to wrap the decoding procedure and dispatching the handling of the message to the right function. - GameHandler: handles an instance of the game. -- RequestHandler: abstract class for a request handler. +- TACMessageHandler: abstract class for a TACMessage handler. - RegisterHandler: class for a register handler. - UnregisterHandler: class for an unregister handler - TransactionHandler: class for a transaction handler. @@ -38,25 +38,23 @@ import os from abc import ABC, abstractmethod from collections import defaultdict -from typing import Any, Dict, Optional, List, Set, Type, TYPE_CHECKING +from typing import Any, Dict, Optional, List, Set, TYPE_CHECKING from aea.agent import Liveness from aea.crypto.base import Crypto -from aea.mail.base import MailBox, Envelope -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer +from aea.mail.base import MailBox, Envelope, Address from aea.protocols.oef.message import OEFMessage from aea.protocols.oef.serialization import OEFSerializer +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from tac.agents.controller.base.actions import OEFActions from tac.agents.controller.base.helpers import generate_good_pbk_to_name from tac.agents.controller.base.reactions import OEFReactions from tac.agents.controller.base.states import Game from tac.agents.controller.base.tac_parameters import TACParameters from tac.gui.monitor import Monitor, NullMonitor -from tac.platform.game.base import GamePhase +from tac.platform.game.base import GamePhase, Transaction, GameData from tac.platform.game.stats import GameStats -from tac.platform.protocol import Response, Request, Register, Unregister, Error, GameData, \ - Transaction, TransactionConfirmation, ErrorCode, Cancelled, GetStateUpdate, StateUpdate if TYPE_CHECKING: from tac.agents.controller.agent import ControllerAgent @@ -64,97 +62,108 @@ logger = logging.getLogger(__name__) -class RequestHandler(ABC): - """Abstract class for a request handler.""" +class TACMessageHandler(ABC): + """Abstract class for a TACMessage handler.""" def __init__(self, controller_agent: 'ControllerAgent') -> None: """ - Instantiate a request handler. + Instantiate a TACMessage handler. :param controller_agent: the controller agent instance :return: None """ self.controller_agent = controller_agent - def __call__(self, request: Request) -> Optional[Response]: + def __call__(self, message: TACMessage, sender_pbk: str) -> None: """Call the handler.""" - return self.handle(request) + return self.handle(message, sender_pbk) @abstractmethod - def handle(self, request: Request) -> Optional[Response]: + def handle(self, message: TACMessage, sender_pbk: str) -> None: """ - Handle a request from an OEF agent. + Handle a TACMessage from an OEF agent. - :param request: the request message. - :return: a response, or None. + :param message: the 'get agent state' TACMessage. + :param sender_pbk: the public key of the sender + :return: None """ -class RegisterHandler(RequestHandler): +class RegisterHandler(TACMessageHandler): """Class for a register handler.""" - def handle(self, request: Register) -> Optional[Response]: + def __init__(self, controller_agent: 'ControllerAgent') -> None: + """Instantiate a RegisterHandler.""" + super().__init__(controller_agent) + + def handle(self, message: TACMessage, sender_pbk: str) -> None: """ Handle a register message. If the public key is already registered, answer with an error message. - If this is the n_th registration request, where n is equal to nb_agents, then start the competition. - :param request: the register request. - :return: an Error response if an error occurred, else None. + :param message: the 'get agent state' TACMessage. + :param sender_pbk: the public key of the sender + :return: None """ whitelist = self.controller_agent.game_handler.tac_parameters.whitelist - if whitelist is not None and request.agent_name not in whitelist: - error_msg = "[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, request.agent_name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) + agent_name = message.get("agent_name") + if whitelist is not None and agent_name not in whitelist: + logger.error("[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, agent_name)) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) + + if sender_pbk in self.controller_agent.game_handler.registered_agents: + logger.error("[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_PBK_ALREADY_REGISTERED) - if request.public_key in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key]) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_PBK_ALREADY_REGISTERED) + if agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): + logger.error("[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, agent_name)) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED) - if request.agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): - error_msg = "[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, request.agent_name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NAME_ALREADY_REGISTERED) + if tac_msg is not None: + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) try: - self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({request.public_key: request.agent_name}) + self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({sender_pbk: agent_name}) self.controller_agent.game_handler.monitor.update() except Exception as e: logger.error(str(e)) - self.controller_agent.game_handler.agent_pbk_to_name[request.public_key] = request.agent_name - logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) - self.controller_agent.game_handler.registered_agents.add(request.public_key) - return None + self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk] = agent_name + logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) + self.controller_agent.game_handler.registered_agents.add(sender_pbk) -class UnregisterHandler(RequestHandler): +class UnregisterHandler(TACMessageHandler): """Class for an unregister handler.""" - def handle(self, request: Unregister) -> Optional[Response]: + def __init__(self, controller_agent: 'ControllerAgent') -> None: + """Instantiate an UnregisterHandler.""" + super().__init__(controller_agent) + + def handle(self, message: TACMessage, sender_pbk: str) -> None: """ Handle a unregister message. If the public key is not registered, answer with an error message. - :param request: the register request. - :return: an Error response if an error occurred, else None. + :param message: the 'get agent state' TACMessage. + :param sender_pbk: the public key of the sender + :return: None """ - if request.public_key not in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) + if sender_pbk not in self.controller_agent.game_handler.registered_agents: + logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, sender_pbk)) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) else: - logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[request.public_key])) - self.controller_agent.game_handler.registered_agents.remove(request.public_key) - self.controller_agent.game_handler.agent_pbk_to_name.pop(request.public_key) - return None + logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) + self.controller_agent.game_handler.registered_agents.remove(sender_pbk) + self.controller_agent.game_handler.agent_pbk_to_name.pop(sender_pbk) -class TransactionHandler(RequestHandler): +class TransactionHandler(TACMessageHandler): """Class for a transaction handler.""" def __init__(self, controller_agent: 'ControllerAgent') -> None: @@ -162,38 +171,40 @@ def __init__(self, controller_agent: 'ControllerAgent') -> None: super().__init__(controller_agent) self._pending_transaction_requests = {} # type: Dict[str, Transaction] - def handle(self, tx: Transaction) -> Optional[Response]: + def handle(self, message: TACMessage, sender_pbk: Address) -> None: """ - Handle a transaction request message. + Handle a transaction TACMessage message. If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. - :param tx: the transaction request. - :return: an Error response if an error occurred, else None (no response to send back). + :param message: the 'get agent state' TACMessage. + :param sender_pbk: the public key of the sender + :return: None """ - logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, tx)) + transaction = Transaction.from_message(message, sender_pbk) + logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, transaction)) # if transaction arrives first time then put it into the pending pool - if tx.transaction_id not in self._pending_transaction_requests: - if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): - logger.debug("[{}]: Put transaction request in the pool: {}".format(self.controller_agent.name, tx.transaction_id)) - self._pending_transaction_requests[tx.transaction_id] = tx + if message.get("transaction_id") not in self._pending_transaction_requests: + if self.controller_agent.game_handler.current_game.is_transaction_valid(message): + logger.debug("[{}]: Put transaction TACMessage in the pool: {}".format(self.controller_agent.name, message.get("transaction_id"))) + self._pending_transaction_requests[message.get("transaction_id")] = message else: - return self._handle_invalid_transaction(tx) + self._handle_invalid_transaction(message, sender_pbk) # if transaction arrives second time then process it else: - pending_tx = self._pending_transaction_requests.pop(tx.transaction_id) - if tx.matches(pending_tx): - if self.controller_agent.game_handler.current_game.is_transaction_valid(tx): + pending_tx = self._pending_transaction_requests.pop(message.get("transaction_id")) + if transaction.matches(pending_tx): + if self.controller_agent.game_handler.current_game.is_transaction_valid(message): self.controller_agent.game_handler.confirmed_transaction_per_participant[pending_tx.sender].append(pending_tx) - self.controller_agent.game_handler.confirmed_transaction_per_participant[tx.sender].append(tx) - self._handle_valid_transaction(tx) + self.controller_agent.game_handler.confirmed_transaction_per_participant[transaction.sender].append(transaction) + self._handle_valid_transaction(message, sender_pbk) else: - return self._handle_invalid_transaction(tx) + self._handle_invalid_transaction(message, sender_pbk) else: - return self._handle_non_matching_transaction(tx) + self._handle_non_matching_transaction(message, sender_pbk) - def _handle_valid_transaction(self, tx: Transaction) -> None: + def _handle_valid_transaction(self, message: TACMessage, sender_pbk: str) -> None: """ Handle a valid transaction. @@ -204,64 +215,64 @@ def _handle_valid_transaction(self, tx: Transaction) -> None: :param tx: the transaction. :return: None """ - logger.debug("[{}]: Handling valid transaction: {}".format(self.controller_agent.name, tx.transaction_id)) + logger.debug("[{}]: Handling valid transaction: {}".format(self.controller_agent.name, message.get("transaction_id"))) # update the game state. - self.controller_agent.game_handler.current_game.settle_transaction(tx) + self.controller_agent.game_handler.current_game.settle_transaction(message) # update the dashboard monitor self.controller_agent.game_handler.monitor.update() # send the transaction confirmation. - tx_confirmation = TransactionConfirmation(tx.public_key, self.controller_agent.crypto, tx.transaction_id) - - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tx_confirmation.serialize()) - msg_bytes = DefaultSerializer().encode(msg) - self.controller_agent.outbox.put_message(to=tx.public_key, sender=self.controller_agent.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) - self.controller_agent.outbox.put_message(to=tx.counterparty, sender=self.controller_agent.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id=message.get("transaction_id")) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.outbox.put_message(to=message.get("counterparty"), sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) # log messages - logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, tx.transaction_id)) + logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, message.get("transaction_id"))) holdings_summary = self.controller_agent.game_handler.current_game.get_holdings_summary() logger.debug("[{}]: Current state:\n{}".format(self.controller_agent.name, holdings_summary)) - return None - - def _handle_invalid_transaction(self, tx: Transaction) -> Response: + def _handle_invalid_transaction(self, message: TACMessage, sender_pbk: str) -> None: """Handle an invalid transaction.""" - return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_VALID, - details={"transaction_id": tx.transaction_id}) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.TRANSACTION_NOT_VALID, details={"transaction_id": message.get("transaction_id")}) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) - def _handle_non_matching_transaction(self, tx: Transaction) -> Response: + def _handle_non_matching_transaction(self, message: TACMessage, sender_pbk: str) -> None: """Handle non-matching transaction.""" - return Error(tx.public_key, self.controller_agent.crypto, ErrorCode.TRANSACTION_NOT_MATCHING) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.TRANSACTION_NOT_MATCHING) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) -class GetStateUpdateHandler(RequestHandler): +class GetStateUpdateHandler(TACMessageHandler): """Class for a state update handler.""" - def handle(self, request: GetStateUpdate) -> Optional[Response]: + def handle(self, message: TACMessage, sender_pbk: str) -> None: """ - Handle a 'get agent state' request. + Handle a 'get agent state' TACMessage. If the public key is not registered, answer with an error message. - :param request: the 'get agent state' request. - :return: an Error response if an error occurred, else None. + :param message: the 'get agent state' TACMessage. + :param sender_pbk: the public key of the sender + :return: None """ - logger.debug("[{}]: Handling the 'get agent state' request: {}".format(self.controller_agent.name, request)) + logger.debug("[{}]: Handling the 'get agent state' TACMessage: {}".format(self.controller_agent.name, message)) if not self.controller_agent.game_handler.is_game_running(): - error_msg = "[{}]: GetStateUpdate request is not valid while the competition is not running.".format(self.controller_agent.name) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.COMPETITION_NOT_RUNNING) - if request.public_key not in self.controller_agent.game_handler.registered_agents: - error_msg = "[{}]: Agent not registered: '{}'".format(self.controller_agent.name, request.public_key) - logger.error(error_msg) - return Error(request.public_key, self.controller_agent.crypto, ErrorCode.AGENT_NOT_REGISTERED) + logger.error("[{}]: GetStateUpdate TACMessage is not valid while the competition is not running.".format(self.controller_agent.name)) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.COMPETITION_NOT_RUNNING) + if sender_pbk not in self.controller_agent.game_handler.registered_agents: + logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, message.get("agent_name"))) + tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) else: - transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[request.public_key] # type: List[Transaction] - initial_game_data = self.controller_agent.game_handler.game_data_per_participant[request.public_key] # type: GameData - return StateUpdate(request.public_key, self.controller_agent.crypto, initial_game_data, transactions) + transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[sender_pbk] # type: List[Transaction] + initial_game_data = self.controller_agent.game_handler.game_data_per_participant[sender_pbk] # type: Dict + tac_msg = TACMessage(tac_type=TACMessage.Type.STATE_UPDATE, initial_state=initial_game_data, transactions=transactions) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) class AgentMessageDispatcher(object): @@ -276,48 +287,39 @@ def __init__(self, controller_agent: 'ControllerAgent'): self.controller_agent = controller_agent self.handlers = { - Register: RegisterHandler(controller_agent), - Unregister: UnregisterHandler(controller_agent), - Transaction: TransactionHandler(controller_agent), - GetStateUpdate: GetStateUpdateHandler(controller_agent), - } # type: Dict[Type[Request], RequestHandler] + TACMessage.Type.REGISTER: RegisterHandler(controller_agent), + TACMessage.Type.UNREGISTER: UnregisterHandler(controller_agent), + TACMessage.Type.TRANSACTION: TransactionHandler(controller_agent), + TACMessage.Type.GET_STATE_UPDATE: GetStateUpdateHandler(controller_agent), + } # type: Dict[TACMessage.Type, TACMessageHandler] - def handle_agent_message(self, envelope: Envelope) -> Response: + def handle_agent_message(self, envelope: Envelope) -> None: """ - Dispatch the request to the right handler. + Dispatch the TACMessage to the right handler. - If no handler is found for the provided type of request, return an "invalid request" error. + If no handler is found for the provided type of TACMessage, return an "invalid TACMessage" error. If something bad happen, return a "generic" error. - :param envelope: the request to handle - :return: the response. - """ - assert envelope.protocol_id == "default" - msg = DefaultSerializer().decode(envelope.message) - sender = envelope.sender - logger.debug("[{}] on_message: origin={}" .format(self.controller_agent.name, sender)) - request = self.decode(msg.get("content"), sender) - handle_request = self.handlers.get(type(request), None) # type: RequestHandler - if handle_request is None: - logger.debug("[{}]: Unknown message from {}".format(self.controller_agent.name, sender)) - return Error(envelope.sender, self.controller_agent.crypto, ErrorCode.REQUEST_NOT_VALID) + :param envelope: the envelope to handle + :return: None + """ + assert envelope.protocol_id == "tac" + tac_msg = TACSerializer().decode(envelope.message) + logger.debug("[{}] on_message: origin={}" .format(self.controller_agent.name, envelope.sender)) + handle_tac_message = self.handlers.get(TACMessage.Type(tac_msg.get("type")), None) # type: TACMessageHandler + if handle_tac_message is None: + logger.debug("[{}]: Unknown message from {}".format(self.controller_agent.name, envelope.sender)) + tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.REQUEST_NOT_VALID) + tac_bytes = TACSerializer().encode(tac_error) + self.controller_agent.mailbox.outbox.put_message(to=envelope.sender, sender=self.controller_agent.crypto.public_key, protocol_id=tac_error.protocol_id, message=tac_bytes) try: - return handle_request(request) + handle_tac_message(tac_msg, envelope.sender) except Exception as e: logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) logger.exception(e) - return Error(sender, self.controller_agent.crypto, ErrorCode.GENERIC_ERROR) - - def decode(self, msg: bytes, public_key: str) -> Request: - """ - From bytes to a Request message. - - :param msg: the serialized message. - :param public_key: the public key of the sender agent. - :return: the deserialized Request - """ - request = Request.from_pb(msg, public_key, self.controller_agent.crypto) - return request + tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.GENERIC_ERROR) + tac_bytes = TACSerializer().encode(tac_error) + self.controller_agent.mailbox.outbox.put_message(to=envelope.sender, sender=self.controller_agent.crypto.public_key, protocol_id=tac_error.protocol_id, message=tac_bytes) class GameHandler: @@ -428,7 +430,6 @@ def _send_game_data_to_agents(self) -> None: agent_state = self.current_game.get_agent_state_from_agent_pbk(public_key) game_data_response = GameData( public_key, - self.crypto, agent_state.balance, agent_state.current_holdings, agent_state.utility_params, @@ -440,19 +441,28 @@ def _send_game_data_to_agents(self) -> None: ) logger.debug("[{}]: sending GameData to '{}': {}" .format(self.agent_name, public_key, str(game_data_response))) - self.game_data_per_participant[public_key] = game_data_response - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=game_data_response.serialize()) - msg_bytes = DefaultSerializer().encode(msg) - self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + + msg = TACMessage(tac_type=TACMessage.Type.GAME_DATA, + money=agent_state.balance, + endowment=agent_state.current_holdings, + utility_params=agent_state.utility_params, + nb_agents=self.current_game.configuration.nb_agents, + nb_goods=self.current_game.configuration.nb_goods, + tx_fee=self.current_game.configuration.tx_fee, + agent_pbk_to_name=self.current_game.configuration.agent_pbk_to_name, + good_pbk_to_name=self.current_game.configuration.good_pbk_to_name + ) + tac_bytes = TACSerializer().encode(msg) + self.mailbox.outbox.put_message(to=public_key, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) def notify_competition_cancelled(self): """Notify agents that the TAC is cancelled.""" logger.debug("[{}]: Notifying agents that TAC is cancelled.".format(self.agent_name)) for agent_pbk in self.registered_agents: - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=Cancelled(agent_pbk, self.crypto).serialize()) - msg_bytes = DefaultSerializer().encode(msg) - self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED) + tac_bytes = TACSerializer().encode(tac_msg) + self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index de144487..c3c399da 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -31,10 +31,10 @@ from aea.crypto.base import Crypto from tac.agents.participant.v1.base.states import AgentState -from tac.platform.game.base import GameConfiguration, GoodState +from tac.platform.game.base import GameConfiguration, GoodState, Transaction from tac.platform.game.helpers import generate_money_endowments, generate_good_endowments, generate_utility_params, \ generate_equilibrium_prices_and_holdings, determine_scaling_factor -from tac.platform.protocol import Transaction + Endowment = List[int] # an element e_j is the endowment of good j. UtilityParams = List[float] # an element u_j is the utility value of good j. @@ -382,7 +382,7 @@ def settle_transaction(self, tx: Transaction) -> None: (20, [2, 1, 1]) >>> agent_state_2.balance, agent_state_2.current_holdings (20, [1, 1, 2]) - >>> tx = Transaction('some_tx_id', True, 'tac_agent_1_pbk', 15, {'tac_good_0': 1, 'tac_good_1': 0, 'tac_good_2': 0}, 'tac_agent_0_pbk', Crypto()) + >>> tx = Transaction('some_tx_id', True, 'tac_agent_1_pbk', 15, {'tac_good_0': 1, 'tac_good_1': 0, 'tac_good_2': 0}, 'tac_agent_0_pbk') >>> game.settle_transaction(tx) >>> agent_state_0.balance, agent_state_0.current_holdings (4.5, [2, 1, 1]) @@ -517,7 +517,7 @@ def from_dict(cls, d: Dict[str, Any], crypto: Crypto) -> 'Game': game = Game(configuration, initialization) for tx_dict in d["transactions"]: - tx = Transaction.from_dict(tx_dict, crypto) + tx = Transaction.from_dict(tx_dict) game.settle_transaction(tx) return game diff --git a/tac/agents/participant/v1/base/actions.py b/tac/agents/participant/v1/base/actions.py index b9e6444e..5a7c6256 100644 --- a/tac/agents/participant/v1/base/actions.py +++ b/tac/agents/participant/v1/base/actions.py @@ -34,14 +34,13 @@ from aea.agent import Liveness from aea.crypto.base import Crypto from aea.mail.base import MailBox -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.oef.message import OEFMessage from aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.interfaces import ControllerActionInterface, OEFActionInterface, \ DialogueActionInterface -from tac.platform.protocol import GetStateUpdate logger = logging.getLogger(__name__) @@ -73,11 +72,10 @@ def request_state_update(self) -> None: :return: None """ - tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) - msg_bytes = DefaultSerializer().encode(msg) + tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) + tac_bytes = TACSerializer().encode(tac_msg) self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, - protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + protocol_id=TACMessage.protocol_id, message=tac_bytes) class OEFActions(OEFActionInterface): diff --git a/tac/agents/participant/v1/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py index 8ff79666..4517d509 100644 --- a/tac/agents/participant/v1/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -25,6 +25,7 @@ from typing import List, Optional, Set, Tuple, Dict, Union from aea.protocols.oef.models import Description, Query +from aea.protocols.tac.message import TACMessage from aea.channel.oef import MailStats from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue @@ -35,7 +36,7 @@ from tac.agents.participant.v1.base.transaction_manager import TransactionManager from tac.gui.dashboards.agent import AgentDashboard from tac.platform.game.base import GamePhase, GameConfiguration -from tac.platform.protocol import GameData, StateUpdate, Transaction +from tac.platform.game.base import GameData, Transaction class Search: @@ -133,7 +134,7 @@ def init(self, game_data: GameData, agent_pbk: str) -> None: opponent_pbks.remove(agent_pbk) self._world_state = WorldState(opponent_pbks, self.game_configuration.good_pbks, self.initial_agent_state) - def on_state_update(self, state_update: StateUpdate, agent_pbk: str) -> None: + def on_state_update(self, message: TACMessage, agent_pbk: str) -> None: """ Update the game instance with a State Update from the controller. @@ -142,10 +143,10 @@ def on_state_update(self, state_update: StateUpdate, agent_pbk: str) -> None: :return: None """ - self.init(state_update.initial_state, agent_pbk) + self.init(message.get("initial_state"), agent_pbk) self._game_phase = GamePhase.GAME - for tx in state_update.transactions: - self.agent_state.update(tx, state_update.initial_state.tx_fee) + for tx in message.get("transactions"): + self.agent_state.update(tx, message.get("initial_state").get("tx_fee")) @property def strategy(self) -> Strategy: diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index d33b3e2d..6dbccf60 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -33,15 +33,15 @@ from aea.crypto.base import Crypto from aea.mail.base import MailBox, Envelope from aea.protocols.base.message import Message -from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.fipa.serialization import FIPASerializer from aea.protocols.oef.message import OEFMessage from aea.protocols.oef.serialization import OEFSerializer +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from tac.agents.participant.v1.base.actions import DialogueActions, ControllerActions, OEFActions from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.reactions import DialogueReactions, ControllerReactions, OEFReactions from tac.platform.game.base import GamePhase -from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, Response, GameData, Cancelled logger = logging.getLogger(__name__) @@ -111,29 +111,29 @@ def handle_controller_message(self, envelope: Envelope) -> None: :return: None """ assert envelope.protocol_id == "default" - msg = DefaultSerializer().decode(envelope.message) - response = Response.from_pb(msg.get("content"), envelope.sender, self.crypto) - logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, type(response))) + tac_msg = TACSerializer().decode(envelope.message) + tac_msg_type = TACMessage.Type(tac_msg.get("type")) + logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, tac_msg_type)) try: if envelope.sender != self.game_instance.controller_pbk: raise ValueError("The sender of the message is not the controller agent we registered with.") - if isinstance(response, Error): - self.on_tac_error(response) + if tac_msg_type == TACMessage.Type.TAC_ERROR: + self.on_tac_error(tac_msg, envelope.sender) elif self.game_instance.game_phase == GamePhase.PRE_GAME: raise ValueError("We do not expect a controller agent message in the pre game phase.") elif self.game_instance.game_phase == GamePhase.GAME_SETUP: - if isinstance(response, GameData): - self.on_start(response) - elif isinstance(response, Cancelled): + 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 self.game_instance.game_phase == GamePhase.GAME: - if isinstance(response, TransactionConfirmation): - self.on_transaction_confirmed(response) - elif isinstance(response, Cancelled): + 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 isinstance(response, StateUpdate): - self.on_state_update(response) + elif tac_msg_type == TACMessage.Type.STATE_UPDATE: + self.on_state_update(tac_msg, envelope.sender) elif self.game_instance.game_phase == GamePhase.POST_GAME: raise ValueError("We do not expect a controller agent message in the post game phase.") except ValueError as e: diff --git a/tac/agents/participant/v1/base/interfaces.py b/tac/agents/participant/v1/base/interfaces.py index b80aac1f..b2860f36 100644 --- a/tac/agents/participant/v1/base/interfaces.py +++ b/tac/agents/participant/v1/base/interfaces.py @@ -22,9 +22,8 @@ from abc import abstractmethod -from aea.mail.base import Envelope +from aea.mail.base import Envelope, Address from aea.protocols.base.message import Message -from tac.platform.protocol import Error, TransactionConfirmation, StateUpdate, GameData class ControllerReactionInterface: @@ -41,11 +40,12 @@ def on_dialogue_error(self, envelope: Envelope) -> None: """ @abstractmethod - def on_start(self, game_data: GameData) -> None: + def on_start(self, message: Message, sender: Address) -> None: """ On start of the competition, do the setup. - :param game_data: the data for the started game. + :param message: the message + :param sender: the address of the sender :return: None """ @@ -59,30 +59,35 @@ def on_cancelled(self) -> None: """ @abstractmethod - def on_transaction_confirmed(self, tx_confirmation: TransactionConfirmation) -> None: + def on_transaction_confirmed(self, message: Message, sender: Address) -> None: """ On Transaction confirmed handler. - :param tx_confirmation: the transaction confirmation + :param message: the message + :param sender: the address of the sender :return: None """ @abstractmethod - def on_state_update(self, agent_state: StateUpdate) -> None: + def on_state_update(self, message: Message, sender: Address) -> None: """ On receiving the agent state update. - :param agent_state: the current state of the agent in the competition. + :param message: the message + :param sender: the address of the sender :return: None """ @abstractmethod - def on_tac_error(self, error: Error) -> None: + def on_tac_error(self, message: Message, sender: Address) -> None: """ Handle error messages from the TAC controller. + :param message: the message + :param sender: the address of the sender + :return: None """ @@ -103,7 +108,7 @@ class OEFReactionInterface: """This interface contains the methods to react to events from the OEF.""" @abstractmethod - def on_search_result(self, search_result: Envelope) -> None: + def on_search_result(self, search_result: Message) -> None: """ Handle search results. @@ -113,7 +118,7 @@ def on_search_result(self, search_result: Envelope) -> None: """ @abstractmethod - def on_oef_error(self, oef_error: Envelope) -> None: + def on_oef_error(self, oef_error: Message) -> None: """ Handle an OEF error message. @@ -123,7 +128,7 @@ def on_oef_error(self, oef_error: Envelope) -> None: """ @abstractmethod - def on_dialogue_error(self, dialogue_error: Envelope) -> None: + def on_dialogue_error(self, dialogue_error: Message) -> None: """ Handle a dialogue error message. @@ -181,7 +186,7 @@ class DialogueReactionInterface: """This interface contains the methods to react to events from other agents.""" @abstractmethod - def on_new_dialogue(self, message: Message) -> None: + def on_new_dialogue(self, message: Message, sender: Address) -> None: """ React to a message for a new dialogue. @@ -190,26 +195,29 @@ def on_new_dialogue(self, message: Message) -> None: - how the agent behaves in this dialogue. :param message: the agent message + :param sender: the address of the sender :return: None """ @abstractmethod - def on_existing_dialogue(self, message: Message) -> None: + def on_existing_dialogue(self, message: Message, sender: Address) -> None: """ React to a message of an existing dialogue. :param message: the agent message + :param sender: the address of the sender :return: None """ @abstractmethod - def on_unidentified_dialogue(self, message: Message) -> None: + def on_unidentified_dialogue(self, message: Message, sender: Address) -> None: """ React to a message of an unidentified dialogue. :param message: the agent message + :param sender: the address of the sender :return: None """ diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index fac7e913..d5337780 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -29,15 +29,15 @@ from aea.mail.base import Envelope from aea.protocols.base.message import Message -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.fipa.message import FIPAMessage from aea.protocols.fipa.serialization import FIPASerializer +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.helpers import generate_transaction_id from tac.agents.participant.v1.base.stats_manager import EndState -from tac.platform.protocol import Transaction +from tac.platform.game.base import Transaction logger = logging.getLogger(__name__) @@ -226,10 +226,15 @@ def on_accept(self, accept: Message, dialogue: Dialogue) -> List[Envelope]: logger.debug("[{}]: Locking the current state (as {}).".format(self.agent_name, dialogue.role)) self.game_instance.transaction_manager.add_locked_tx(transaction, as_seller=dialogue.is_seller) - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=transaction.serialize()) - dialogue.outgoing_extend([msg]) - msg_bytes = DefaultSerializer().encode(msg) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) + tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION, + transaction_id=transaction.transaction_id, + is_sender_buyer=transaction.is_sender_buyer, + counterparty=transaction.counterparty, + amount=transaction.amount, + quantities_by_good_pbk=transaction.quantities_by_good_pbk) + dialogue.outgoing_extend([tac_msg]) + tac_bytes = TACSerializer().encode(tac_msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)) msg = FIPAMessage(message_id=new_msg_id, dialogue_id=accept.get("dialogue_id"), target=accept.get("id"), performative=FIPAMessage.Performative.MATCH_ACCEPT) dialogue.outgoing_extend([msg]) @@ -260,8 +265,13 @@ def on_match_accept(self, match_accept: Message, dialogue: Dialogue) -> List[Env .format(self.agent_name, match_accept.get("id"), match_accept.get("dialogue_id"), dialogue.dialogue_label.dialogue_opponent_pbk, match_accept.get("target"))) results = [] transaction = self.game_instance.transaction_manager.pop_pending_initial_acceptance(dialogue.dialogue_label, match_accept.get("target")) - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=transaction.serialize()) - msg_bytes = DefaultSerializer().encode(msg) - results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes)) - dialogue.outgoing_extend([msg]) + tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION, + transaction_id=transaction.transaction_id, + is_sender_buyer=transaction.is_sender_buyer, + counterparty=transaction.counterparty, + amount=transaction.amount, + quantities_by_good_pbk=transaction.quantities_by_good_pbk) + dialogue.outgoing_extend([tac_msg]) + tac_bytes = TACSerializer().encode(tac_msg) + results.append(Envelope(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes)) return results diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index fd70a13c..d7c4b99b 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -38,6 +38,8 @@ from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.fipa.message import FIPAMessage from aea.protocols.fipa.serialization import FIPASerializer +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from tac.agents.participant.v1.base.dialogues import Dialogue from tac.agents.participant.v1.base.game_instance import GameInstance, GamePhase from tac.agents.participant.v1.base.helpers import dialogue_label_from_transaction_id, TAC_DEMAND_DATAMODEL_NAME @@ -45,8 +47,7 @@ DialogueReactionInterface from tac.agents.participant.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.participant.v1.base.stats_manager import EndState -from tac.platform.protocol import Error, ErrorCode, GameData, TransactionConfirmation, StateUpdate, Register, \ - GetStateUpdate + logger = logging.getLogger(__name__) @@ -75,7 +76,7 @@ def __init__(self, crypto: Crypto, liveness: Liveness, game_instance: GameInstan self.mailbox = mailbox self.agent_name = agent_name - def on_dialogue_error(self, message: Message) -> None: + def on_dialogue_error(self, message: TACMessage, sender: Address) -> None: """ Handle dialogue error event emitted by the controller. @@ -83,11 +84,9 @@ def on_dialogue_error(self, message: Message) -> None: :return: None """ - dialogue_error = message - logger.warning("[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" - .format(self.agent_name, dialogue_error.get("id"), dialogue_error.get("dialogue_id"), dialogue_error.get("origin"))) + logger.warning("[{}]: Received Dialogue error from: details={}, sender={}".format(self.agent_name, message.get("details"), sender)) - def on_start(self, game_data: GameData) -> None: + def on_start(self, message: TACMessage, sender: Address) -> None: """ Handle the 'start' event emitted by the controller. @@ -96,7 +95,7 @@ def on_start(self, game_data: GameData) -> None: :return: None """ logger.debug("[{}]: Received start event from the controller. Starting to compete...".format(self.agent_name)) - self.game_instance.init(game_data, self.crypto.public_key) + self.game_instance.init(message.get("game_data"), self.crypto.public_key) self.game_instance._game_phase = GamePhase.GAME dashboard = self.game_instance.dashboard @@ -104,7 +103,7 @@ def on_start(self, game_data: GameData) -> None: dashboard.init() dashboard.update_from_agent_state(self.game_instance.agent_state, append=False) - def on_transaction_confirmed(self, tx_confirmation: TransactionConfirmation) -> None: + def on_transaction_confirmed(self, message: TACMessage, sender: Address) -> None: """ Handle 'on transaction confirmed' event emitted by the controller. @@ -112,15 +111,15 @@ def on_transaction_confirmed(self, tx_confirmation: TransactionConfirmation) -> :return: None """ - logger.debug("[{}]: Received transaction confirmation from the controller: transaction_id={}".format(self.agent_name, tx_confirmation.transaction_id)) - if tx_confirmation.transaction_id not in self.game_instance.transaction_manager.locked_txs: + logger.debug("[{}]: Received transaction confirmation from the controller: transaction_id={}".format(self.agent_name, message.get("transaction_id"))) + if message.get("transaction_id") not in self.game_instance.transaction_manager.locked_txs: logger.debug("[{}]: transaction not found - ask the controller an update of the state.".format(self.agent_name)) self._request_state_update() return - transaction = self.game_instance.transaction_manager.pop_locked_tx(tx_confirmation.transaction_id) + transaction = self.game_instance.transaction_manager.pop_locked_tx(message.get("transaction_id")) self.game_instance.agent_state.update(transaction, self.game_instance.game_configuration.tx_fee) - dialogue_label = dialogue_label_from_transaction_id(self.crypto.public_key, tx_confirmation.transaction_id) + dialogue_label = dialogue_label_from_transaction_id(self.crypto.public_key, message.get("transaction_id")) self.game_instance.stats_manager.add_dialogue_endstate(EndState.SUCCESSFUL, self.crypto.public_key == dialogue_label.dialogue_starter_pbk) dashboard = self.game_instance.dashboard @@ -130,7 +129,7 @@ def on_transaction_confirmed(self, tx_confirmation: TransactionConfirmation) -> agent_name = self.game_instance.game_configuration.agent_names[list(self.game_instance.game_configuration.agent_pbks).index(transaction.counterparty)] dashboard.add_transaction(transaction, agent_name=agent_name) - def on_state_update(self, state_update: StateUpdate) -> None: + def on_state_update(self, message: TACMessage, sender: Address) -> None: """ Handle 'on state update' event emitted by the controller. @@ -138,7 +137,7 @@ def on_state_update(self, state_update: StateUpdate) -> None: :return: None """ - self.game_instance.on_state_update(state_update, self.crypto.public_key) + self.game_instance.on_state_update(message.get("state_update"), self.crypto.public_key) dashboard = self.game_instance.dashboard if dashboard is not None: @@ -154,7 +153,7 @@ def on_cancelled(self) -> None: self.liveness._is_stopped = True self.game_instance._game_phase = GamePhase.POST_GAME - def on_tac_error(self, error: Error) -> None: + def on_tac_error(self, message: TACMessage, sender: Address) -> None: """ Handle 'on tac error' event emitted by the controller. @@ -162,21 +161,22 @@ def on_tac_error(self, error: Error) -> None: :return: None """ - logger.error("[{}]: Received error from the controller. error_msg={}".format(self.agent_name, error.error_msg)) - if error.error_code == ErrorCode.TRANSACTION_NOT_VALID: + error_code = TACMessage.ErrorCode(message.get("error_code")) + logger.error("[{}]: Received error from the controller. error_msg={}".format(self.agent_name, TACMessage._from_ec_to_msg.get(error_code))) + if error_code == TACMessage.ErrorCode.TRANSACTION_NOT_VALID: # if error in checking transaction, remove it from the pending transactions. start_idx_of_tx_id = len("Error in checking transaction: ") - transaction_id = error.error_msg[start_idx_of_tx_id:] + transaction_id = message.get("error_msg")[start_idx_of_tx_id:] if transaction_id in self.game_instance.transaction_manager.locked_txs: self.game_instance.transaction_manager.pop_locked_tx(transaction_id) else: logger.warning("[{}]: Received error on unknown transaction id: {}".format(self.agent_name, transaction_id)) pass - elif error.error_code == ErrorCode.TRANSACTION_NOT_MATCHING: + elif error_code == TACMessage.ErrorCode.TRANSACTION_NOT_MATCHING: pass - elif error.error_code == ErrorCode.AGENT_PBK_ALREADY_REGISTERED or error.error_code == ErrorCode.AGENT_NAME_ALREADY_REGISTERED or error.error_code == ErrorCode.AGENT_NOT_REGISTERED: + elif error_code == TACMessage.ErrorCode.AGENT_PBK_ALREADY_REGISTERED or error_code == TACMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED or error_code == TACMessage.ErrorCode.AGENT_NOT_REGISTERED: self.liveness._is_stopped = True - elif error.error_code == ErrorCode.REQUEST_NOT_VALID or error.error_code == ErrorCode.GENERIC_ERROR: + elif error_code == TACMessage.ErrorCode.REQUEST_NOT_VALID or error_code == TACMessage.ErrorCode.GENERIC_ERROR: logger.warning("[{}]: Check last request sent and investigate!".format(self.agent_name)) def _request_state_update(self) -> None: @@ -185,10 +185,9 @@ def _request_state_update(self) -> None: :return: None """ - tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) - msg_bytes = DefaultSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) + tac_bytes = TACSerializer.encode(tac_msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) class OEFReactions(OEFReactionInterface): @@ -323,10 +322,9 @@ def _register_to_tac(self, controller_pbk: str) -> None: """ self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP - tac_msg = Register(self.crypto.public_key, self.crypto, self.agent_name).serialize() - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) - msg_bytes = DefaultSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name=self.agent_name) + tac_bytes = TACSerializer().encode(tac_msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) def _rejoin_tac(self, controller_pbk: str) -> None: """ @@ -338,10 +336,9 @@ def _rejoin_tac(self, controller_pbk: str) -> None: """ self.game_instance.controller_pbk = controller_pbk self.game_instance._game_phase = GamePhase.GAME_SETUP - tac_msg = GetStateUpdate(self.crypto.public_key, self.crypto).serialize() - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) - msg_bytes = DefaultSerializer().encode(msg) - self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) + tac_bytes = TACSerializer().encode(tac_msg) + self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) class DialogueReactions(DialogueReactionInterface): diff --git a/tac/agents/participant/v1/base/states.py b/tac/agents/participant/v1/base/states.py index 2a0e0821..30571381 100644 --- a/tac/agents/participant/v1/base/states.py +++ b/tac/agents/participant/v1/base/states.py @@ -34,7 +34,7 @@ from aea.state.base import WorldState as BaseWorldState from tac.agents.participant.v1.base.price_model import GoodPriceModel from tac.platform.game.helpers import logarithmic_utility -from tac.platform.protocol import Transaction +from tac.platform.game.base import Transaction Endowment = List[int] # an element e_j is the endowment of good j. UtilityParams = List[float] # an element u_j is the utility value of good j. diff --git a/tac/agents/participant/v1/base/transaction_manager.py b/tac/agents/participant/v1/base/transaction_manager.py index 2c033f83..999cb7ec 100644 --- a/tac/agents/participant/v1/base/transaction_manager.py +++ b/tac/agents/participant/v1/base/transaction_manager.py @@ -26,7 +26,7 @@ from typing import Dict, Tuple, Deque from tac.agents.participant.v1.base.dialogues import DialogueLabel -from tac.platform.protocol import Transaction +from tac.platform.game.base import Transaction logger = logging.getLogger(__name__) diff --git a/tac/gui/dashboards/agent.py b/tac/gui/dashboards/agent.py index 751ff2a5..ef141d84 100644 --- a/tac/gui/dashboards/agent.py +++ b/tac/gui/dashboards/agent.py @@ -30,7 +30,7 @@ from tac.agents.participant.v1.base.stats_manager import StatsManager from tac.gui.dashboards.base import Dashboard from tac.gui.dashboards.helpers import generate_html_table_from_dict, escape_html -from tac.platform.protocol import Transaction +from tac.platform.game.base import Transaction CUR_PATH = inspect.getfile(inspect.currentframe()) CUR_DIR = os.path.dirname(CUR_PATH) diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index 8e3f15ed..fc21ed61 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -29,11 +29,15 @@ - GoodState: a class to hold the current state of a good. - WorldState represent the state of the world from the perspective of the agent. """ - +import copy from enum import Enum import logging from typing import List, Dict, Any +from aea.mail.base import Address +from aea.protocols.tac.message import TACMessage +from aea.protocols.oef.models import Description + Endowment = List[int] # an element e_j is the endowment of good j. UtilityParams = List[float] # an element u_j is the utility value of good j. @@ -191,3 +195,165 @@ def _check_consistency(self) -> None: :raises: AssertionError: if some constraint is not satisfied. """ assert self.price >= 0, "The price must be non-negative." + + +class Transaction: + """Convenience representation of a transaction.""" + + def __init__(self, transaction_id: str, is_sender_buyer: bool, counterparty: str, + amount: float, quantities_by_good_pbk: Dict[str, int], sender: str) -> None: + """ + Instantiate transaction request. + + :param transaction_id: the id of the transaction. + :param is_sender_buyer: whether the transaction is sent by a buyer. + :param counterparty: the counterparty of the transaction. + :param amount: the amount of money involved. + :param quantities_by_good_pbk: a map from good pbk to the quantity of that good involved in the transaction. + :param sender: the sender of the transaction. + + :return: None + """ + self.sender = sender + self.transaction_id = transaction_id + self.is_sender_buyer = is_sender_buyer + self.counterparty = counterparty + self.amount = amount + self.quantities_by_good_pbk = quantities_by_good_pbk + + self._check_consistency() + + @property + def sender(self): + """Get the sender public key.""" + return self.sender + + @property + def buyer_pbk(self) -> str: + """Get the publick key of the buyer.""" + result = self.sender if self.is_sender_buyer else self.counterparty + return result + + @property + def seller_pbk(self) -> str: + """Get the publick key of the seller.""" + result = self.counterparty if self.is_sender_buyer else self.sender + return result + + def _check_consistency(self) -> None: + """ + Check the consistency of the transaction parameters. + + :return: None + :raises AssertionError if some constraint is not satisfied. + """ + assert self.sender != self.counterparty + assert self.amount >= 0 + assert len(self.quantities_by_good_pbk.keys()) == len(set(self.quantities_by_good_pbk.keys())) + assert all(quantity >= 0 for quantity in self.quantities_by_good_pbk.values()) + + def to_dict(self) -> Dict[str, Any]: + """From object to dictionary.""" + return { + "transaction_id": self.transaction_id, + "is_sender_buyer": self.is_sender_buyer, + "counterparty": self.counterparty, + "amount": self.amount, + "quantities_by_good_pbk": self.quantities_by_good_pbk, + "sender": self.sender + } + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> 'Transaction': + """Return a class instance from a dictionary.""" + return cls( + transaction_id=d["transaction_id"], + is_sender_buyer=d["is_sender_buyer"], + counterparty=d["counterparty"], + amount=d["amount"], + quantities_by_good_pbk=d["quantities_by_good_pbk"], + sender=d["sender"] + ) + + @classmethod + def from_proposal(cls, proposal: Description, transaction_id: str, + is_sender_buyer: bool, counterparty: str, sender: str) -> 'Transaction': + """ + Create a transaction from a proposal. + + :param proposal: the proposal + :param transaction_id: the transaction id + :param is_sender_buyer: whether the sender is the buyer + :param counterparty: the counterparty public key + :param sender: the sender public key + :param crypto: the crypto object + :return: Transaction + """ + data = copy.deepcopy(proposal.values) + price = data.pop("price") + quantity_by_good_pbk = {key: value for key, value in data.items()} + return Transaction(transaction_id, is_sender_buyer, counterparty, price, quantity_by_good_pbk, sender) + + @classmethod + def from_message(cls, message: TACMessage, sender: Address) -> 'Transaction': + """ + Create a transaction from a proposal. + + :param message: the message + :return: Transaction + """ + return Transaction(message.get("transaction_id"), message.get("is_sender_buyer"), message.get("counterparty"), message.get("amount"), message.get("quantities_by_good_pbk"), sender) + + def matches(self, other: 'Transaction') -> bool: + """ + Check if the transaction matches with another (mirroring) transaction. + + Two transaction requests do match if: + - the transaction id is the same; + - one of them is from a buyer and the other one is from a seller + - the counterparty and the origin field are consistent. + - the amount and the quantities are equal. + + :param other: the other transaction to match. + :return: True if the two + """ + result = True + result = result and self.transaction_id == other.transaction_id + result = result and self.is_sender_buyer != other.is_sender_buyer + result = result and self.counterparty == other.sender + result = result and other.counterparty == self.sender + result = result and self.amount == other.amount + result = result and self.quantities_by_good_pbk == other.quantities_by_good_pbk + + return result + + +class GameData: + """Convenience representation of the game data.""" + + def __init__(self, sender: str, money: float, endowment: List[int], utility_params: List[float], + nb_agents: int, nb_goods: int, tx_fee: float, agent_pbk_to_name: Dict[str, str], good_pbk_to_name: Dict[str, str]) -> None: + """ + Initialize a game data object. + + :param sender: the sender + :param money: the money amount. + :param endowment: the endowment for every good. + :param utility_params: the utility params for every good. + :param nb_agents: the number of agents. + :param nb_goods: the number of goods. + :param tx_fee: the transaction fee. + :param agent_pbk_to_name: the mapping from the public keys to the names of the agents. + :param good_pbk_to_name: the mapping from the public keys to the names of the goods. + :return: None + """ + assert len(endowment) == len(utility_params) + self.sender = sender + self.money = money + self.endowment = endowment + self.utility_params = utility_params + self.nb_agents = nb_agents + self.nb_goods = nb_goods + self.tx_fee = tx_fee + self.agent_pbk_to_name = agent_pbk_to_name + self.good_pbk_to_name = good_pbk_to_name diff --git a/tac/platform/protocol.py b/tac/platform/protocol.py deleted file mode 100644 index cd7bdfda..00000000 --- a/tac/platform/protocol.py +++ /dev/null @@ -1,774 +0,0 @@ -# -*- coding: utf-8 -*- - -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -""" -Schemas for the protocol to communicate with the controller. - -Classes: - -- ErrorCode: this class defines the error codes. -- Message: abstract class representing a message between TAC agents and TAC controller. -- Request: message from client to controller. -- Register: message to register an agent to the competition.. -- Unregister: message to unregister an agent from the competition. -- Transaction: transaction message for an agent to submit to the controller. -- GetStateUpdate: message to request an agent state update from the controller. -- Response: message from controller to clients. -- Cancelled: this response means that the competition to which the agent was registered has been cancelled. -- Error: this response means that something bad happened while processing a request -- GameData: class that holds the game configuration and the initialization of a TAC agent. -- TransactionConfirmed: class that holds the transaction confirmation sent from the controller to the agent. -- StateUpdate: class that holds the state update sent from the controller to the agent. -""" - -import copy -import logging -import pprint -from abc import ABC, abstractmethod -from enum import Enum -from typing import List, Dict, Any -from typing import Optional - -from aea.protocols.oef.models import Description -from google.protobuf.message import DecodeError - -from aea.crypto.base import Crypto -import tac.tac_pb2 as tac_pb2 - -logger = logging.getLogger(__name__) - - -def _make_str_int_pair(key: str, value: int) -> tac_pb2.StrIntPair: - """ - Make a Protobuf StrIntPair. - - :param key: the first element of the pair. - :param value: the second element of the pair. - - :return: a StrIntPair protobuf object. - """ - pair = tac_pb2.StrIntPair() - pair.first = key - pair.second = value - return pair - - -def _make_str_str_pair(key: str, value: str) -> tac_pb2.StrStrPair: - """ - Make a Protobuf StrStrPair. - - :param key: the first element of the pair. - :param value: the second element of the pair. - - :return: a StrStrPair protobuf object. - """ - pair = tac_pb2.StrStrPair() - pair.first = key - pair.second = value - return pair - - -class TacError(Exception): - """General purpose exception to detect exception associated with the logic of the TAC application.""" - - -class ErrorCode(Enum): - """This class defines the error codes.""" - - GENERIC_ERROR = 0 - REQUEST_NOT_VALID = 1 - AGENT_PBK_ALREADY_REGISTERED = 2 - AGENT_NAME_ALREADY_REGISTERED = 3 - AGENT_NOT_REGISTERED = 4 - TRANSACTION_NOT_VALID = 5 - TRANSACTION_NOT_MATCHING = 6 - AGENT_NAME_NOT_IN_WHITELIST = 7 - COMPETITION_NOT_RUNNING = 8 - - -_from_ec_to_msg = { - ErrorCode.GENERIC_ERROR: "Unexpected error.", - ErrorCode.REQUEST_NOT_VALID: "Request not recognized", - ErrorCode.AGENT_PBK_ALREADY_REGISTERED: "Agent pbk already registered.", - ErrorCode.AGENT_NAME_ALREADY_REGISTERED: "Agent name already registered.", - ErrorCode.AGENT_NOT_REGISTERED: "Agent not registered.", - ErrorCode.TRANSACTION_NOT_VALID: "Error in checking transaction", - ErrorCode.TRANSACTION_NOT_MATCHING: "The transaction request does not match with a previous transaction request with the same id.", - ErrorCode.AGENT_NAME_NOT_IN_WHITELIST: "Agent name not in whitelist.", - ErrorCode.COMPETITION_NOT_RUNNING: "The competition is not running yet." -} # type: Dict[ErrorCode, str] - - -class Message(ABC): - """Abstract class representing a message between TAC agents and TAC controller.""" - - def __init__(self, public_key: str, crypto: Crypto) -> None: - """ - Instantiate the message. - - :param public_key: The public key of the TAC agent - :param crypto: the Crypto object - - :return: None - """ - self.public_key = public_key - self.crypto = crypto - - @classmethod - @abstractmethod - def from_pb(cls, obj, public_key: str): - """From Protobuf to :class:`~tac.protocol.Message` object.""" - - @abstractmethod - def to_pb(self): - """From :class:`~tac.protocol.Message` to Protobuf object.""" - - def serialize_message_part(self) -> bytes: - """Serialize the message.""" - return self.to_pb().SerializeToString() - - def sign_message(self, message: bytes) -> bytes: - """Sign a message.""" - return self.crypto.sign_data(message) - - def serialize(self) -> bytes: - """ - Serialize the message. - - :return: the signature bytes object - """ - result = tac_pb2.TACAgent.SignedMessage() - result.message = self.serialize_message_part() - result.signature = self.sign_message(result.message) - return result.SerializeToString() - - def _build_str(self, **kwargs) -> str: - """Buil a string.""" - return type(self).__name__ + "({})".format(pprint.pformat(kwargs)) - - def __str__(self): - """Return as string.""" - return self._build_str() - - -class Request(Message, ABC): - """Message from client to controller.""" - - @classmethod - def from_pb(cls, obj, public_key: str, crypto: Crypto) -> 'Request': - """ - Parse a string of bytes associated to a request message to the TAC controller. - - :param obj: the string of bytes to be parsed. - :param public_key: the public key of the request sender. - :param crypto: the Crypto object - :raises TacError: if the string of bytes cannot be parsed as a Response from the TAC Controller. - - :return: a :class:`~tac.protocol.Response` object. - """ - signed_msg = tac_pb2.TACAgent.SignedMessage() - signed_msg.ParseFromString(obj) - - if crypto.is_confirmed_integrity(signed_msg.message, signed_msg.signature, public_key): - msg = tac_pb2.TACAgent.Message() - msg.ParseFromString(signed_msg.message) - case = msg.WhichOneof("msg") - if case == "register": - return Register.from_pb(msg.register, public_key, crypto) - elif case == "unregister": - return Unregister(public_key, crypto) - elif case == "transaction": - return Transaction.from_pb(msg.transaction, public_key, crypto) - elif case == "get_state_update": - return GetStateUpdate(public_key, crypto) - else: - raise TacError("Unrecognized type of Request.") - else: - raise ValueError("Bad signature. Do not trust!") - - def to_pb(self): - """Convert to protobuf.""" - raise NotImplementedError - - def __eq__(self, other): - """Compare equality of two instances.""" - return type(self) == type(other) - - -class Register(Request): - """Message to register an agent to the competition.""" - - def __init__(self, public_key: str, crypto: Crypto, agent_name: str) -> None: - """ - Instantiate registration message. - - :param public_key: the public key of the agent - :param agent_name: the name of the agent - :param crypto: the Crypto object - - :return: None - """ - super().__init__(public_key, crypto) - self.agent_name = agent_name - - def to_pb(self) -> tac_pb2.TACAgent.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACAgent.Register() - msg.agent_name = self.agent_name - envelope = tac_pb2.TACAgent.Message() - envelope.register.CopyFrom(msg) - return envelope - - @classmethod - def from_pb(cls, obj: tac_pb2.TACAgent.Register, public_key: str, crypto: Crypto) -> 'Register': - """Read from protobuf.""" - return Register(public_key, crypto, obj.agent_name) - - -class Unregister(Request): - """Message to unregister an agent from the competition.""" - - def to_pb(self) -> tac_pb2.TACAgent.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACAgent.Unregister() - envelope = tac_pb2.TACAgent.Message() - envelope.unregister.CopyFrom(msg) - return envelope - - -class Transaction(Request): - """Transaction message for an agent to submit to the controller.""" - - def __init__(self, transaction_id: str, is_sender_buyer: bool, counterparty: str, - amount: float, quantities_by_good_pbk: Dict[str, int], sender: str, crypto: Crypto) -> None: - """ - Instantiate transaction request. - - :param transaction_id: the id of the transaction. - :param is_sender_buyer: whether the transaction is sent by a buyer. - :param counterparty: the counterparty of the transaction. - :param amount: the amount of money involved. - :param quantities_by_good_pbk: a map from good pbk to the quantity of that good involved in the transaction. - :param sender: the sender of the transaction request. - :param crypto: the crypto module. - - :return: None - """ - super().__init__(sender, crypto) - self.transaction_id = transaction_id - self.is_sender_buyer = is_sender_buyer - self.counterparty = counterparty - self.amount = amount - self.quantities_by_good_pbk = quantities_by_good_pbk - - self._check_consistency() - - @property - def sender(self): - """Get the sender public key.""" - return self.public_key - - @property - def buyer_pbk(self) -> str: - """Get the publick key of the buyer.""" - result = self.sender if self.is_sender_buyer else self.counterparty - return result - - @property - def seller_pbk(self) -> str: - """Get the publick key of the seller.""" - result = self.counterparty if self.is_sender_buyer else self.sender - return result - - def _check_consistency(self) -> None: - """ - Check the consistency of the transaction parameters. - - :return: None - :raises AssertionError if some constraint is not satisfied. - """ - assert self.sender != self.counterparty - assert self.amount >= 0 - assert len(self.quantities_by_good_pbk.keys()) == len(set(self.quantities_by_good_pbk.keys())) - assert all(quantity >= 0 for quantity in self.quantities_by_good_pbk.values()) - - def to_dict(self) -> Dict[str, Any]: - """From object to dictionary.""" - return { - "transaction_id": self.transaction_id, - "is_sender_buyer": self.is_sender_buyer, - "counterparty": self.counterparty, - "amount": self.amount, - "quantities_by_good_pbk": self.quantities_by_good_pbk, - "sender": self.sender - } - - def to_pb(self) -> tac_pb2.TACAgent.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACAgent.Transaction() - msg.transaction_id = self.transaction_id - msg.buyer = self.is_sender_buyer # maintain old notation for backwards compatibility - msg.counterparty = self.counterparty - msg.amount = self.amount - - good_pbk_quantity_pairs = [_make_str_int_pair(good_pbk, quantity) for good_pbk, quantity in self.quantities_by_good_pbk.items()] - msg.quantities.extend(good_pbk_quantity_pairs) - - envelope = tac_pb2.TACAgent.Message() - envelope.transaction.CopyFrom(msg) - return envelope - - @classmethod - def from_pb(cls, obj: tac_pb2.TACAgent.Transaction, public_key: str, crypto: Crypto) -> 'Transaction': - """ - Read from protobuf. - - :param obj: the protobuf object - :param public_key: the opponent's public key - :param crypto: the crypto module - - :return: the transaction - """ - quantities_per_good_pbk = {pair.first: pair.second for pair in obj.quantities} - - return Transaction(obj.transaction_id, - obj.buyer, - obj.counterparty, - obj.amount, - quantities_per_good_pbk, - public_key, - crypto) - - @classmethod - def from_dict(cls, d: Dict[str, Any], crypto: Crypto) -> 'Transaction': - """Return a class instance from a dictionary.""" - return cls( - transaction_id=d["transaction_id"], - is_sender_buyer=d["is_sender_buyer"], - counterparty=d["counterparty"], - amount=d["amount"], - quantities_by_good_pbk=d["quantities_by_good_pbk"], - sender=d["sender"], - crypto=crypto - ) - - @classmethod - def from_proposal(cls, proposal: Description, transaction_id: str, - is_sender_buyer: bool, counterparty: str, sender: str, crypto: Crypto) -> 'Transaction': - """ - Create a transaction from a proposal. - - :param proposal: the proposal - :param transaction_id: the transaction id - :param is_sender_buyer: whether the sender is the buyer - :param counterparty: the counterparty public key - :param sender: the sender public key - :param crypto: the crypto object - :return: Transaction - """ - data = copy.deepcopy(proposal.values) - price = data.pop("price") - quantity_by_good_pbk = {key: value for key, value in data.items()} - return Transaction(transaction_id, is_sender_buyer, counterparty, price, quantity_by_good_pbk, sender, crypto) - - def matches(self, other: 'Transaction') -> bool: - """ - Check if the transaction matches with another transaction request. - - Two transaction requests do match if: - - the transaction id is the same; - - one of them is from a buyer and the other one is from a seller - - the counterparty and the origin field are consistent. - - the amount and the quantities are equal. - - :param other: the other transaction to match. - :return: True if the two - """ - result = True - result = result and self.transaction_id == other.transaction_id - result = result and self.is_sender_buyer != other.is_sender_buyer - result = result and self.counterparty == other.sender - result = result and other.counterparty == self.sender - result = result and self.amount == other.amount - result = result and self.quantities_by_good_pbk == other.quantities_by_good_pbk - - return result - - def __str__(self): - """Return as string.""" - return self._build_str( - transaction_id=self.transaction_id, - is_sender_buyer=self.is_sender_buyer, - counterparty=self.counterparty, - amount=self.amount, - good_pbk_quantity_pairs=self.quantities_by_good_pbk - ) - - def __eq__(self, other): - """Compare equality of two instances.""" - if type(self) != type(other): - return False - return self.transaction_id == other.transaction_id and \ - self.is_sender_buyer == other.is_sender_buyer and \ - self.counterparty == other.counterparty and \ - self.amount == other.amount and \ - self.quantities_by_good_pbk == other.quantities_by_good_pbk - - -class GetStateUpdate(Request): - """Message to request an agent state update from the controller.""" - - def to_pb(self) -> tac_pb2.TACAgent.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACAgent.GetStateUpdate() - envelope = tac_pb2.TACAgent.Message() - envelope.get_state_update.CopyFrom(msg) - return envelope - - -class Response(Message): - """Message from controller to clients.""" - - @classmethod - def from_pb(cls, obj, public_key: str, crypto: Crypto) -> 'Response': - """ - Parse a string of bytes associated to a response message from the TAC controller. - - :param obj: the string of bytes to be parsed. - :param public_key: the public key of the recipient. - :param crypto: the crypto module - :raises TacError: if the string of bytes cannot be parsed as a Response from the TAC Controller. - - :return: a :class:`~tac.protocol.Response` object. - """ - try: - signed_msg = tac_pb2.TACAgent.SignedMessage() - signed_msg.ParseFromString(obj) - - if crypto.is_confirmed_integrity(signed_msg.message, signed_msg.signature, public_key): - msg = tac_pb2.TACController.Message() - msg.ParseFromString(signed_msg.message) - case = msg.WhichOneof("msg") - if case == "cancelled": - return Cancelled(public_key, crypto) - elif case == "game_data": - return GameData.from_pb(msg.game_data, public_key, crypto) - elif case == "tx_confirmation": - return TransactionConfirmation(public_key, crypto, msg.tx_confirmation.transaction_id) - elif case == "state_update": - return StateUpdate.from_pb(msg.state_update, public_key, crypto) - elif case == "error": - return Error.from_pb(msg.error, public_key, crypto) - else: - raise TacError("Unrecognized type of Response.") - else: - raise ValueError("Bad signature. Do not trust!") - except DecodeError as e: - logger.exception(str(e)) - raise TacError("Error in decoding the message.") - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - raise NotImplementedError - - def __eq__(self, other): - """Compare equality of two instances.""" - return type(self) == type(other) - - -class Cancelled(Response): - """This response means that the competition to which the agent was registered has been cancelled.""" - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACController.Cancelled() - envelope = tac_pb2.TACController.Message() - envelope.cancelled.CopyFrom(msg) - return envelope - - -class Error(Response): - """This response means that something bad happened while processing a request.""" - - def __init__(self, public_key: str, crypto: Crypto, - error_code: ErrorCode, - error_msg: Optional[str] = None, - details: Optional[Dict[str, Any]] = None): - """ - Instantiate error. - - :param public_key: the destination - :param crypto: the crypto module. - :param error_code: the error code - :param error_msg: the error message - :param details: the error details - - :return: None - """ - super().__init__(public_key, crypto) - self.error_code = error_code - self.error_msg = _from_ec_to_msg[error_code] if error_msg is None else error_msg - self.details = details if details is not None else {} - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACController.Error() - msg.error_code = self.error_code.value - msg.error_msg = self.error_msg - msg.details.update(self.details) - envelope = tac_pb2.TACController.Message() - envelope.error.CopyFrom(msg) - return envelope - - @classmethod - def from_pb(cls, obj, public_key: str, crypto: Crypto) -> 'Error': - """ - Read from protobuf. - - :param obj: the protobuf object - :param public_key: the opponent's public key - :param crypto: the crypto module - - :return: the error - """ - error_code = ErrorCode(obj.error_code) - error_msg = obj.error_msg - details = dict(obj.details.items()) - return Error(public_key, crypto, error_code, error_msg, details) - - def __str__(self): - """Convert to string.""" - return self._build_str(error_msg=self.error_msg) - - def __eq__(self, other): - """Compare equality of two instances.""" - return super().__eq__(other) and self.error_msg == other.error_msg - - -class GameData(Response): - """Class that holds the game configuration and the initialization of a TAC agent.""" - - def __init__(self, public_key: str, crypto: Crypto, money: float, endowment: List[int], utility_params: List[float], - nb_agents: int, nb_goods: int, tx_fee: float, agent_pbk_to_name: Dict[str, str], good_pbk_to_name: Dict[str, str]) -> None: - """ - Initialize a game data object. - - :param public_key: the destination - :param crypto: the crypto module. - :param money: the money amount. - :param endowment: the endowment for every good. - :param utility_params: the utility params for every good. - :param nb_agents: the number of agents. - :param nb_goods: the number of goods. - :param tx_fee: the transaction fee. - :param agent_pbk_to_name: the mapping from the public keys to the names of the agents. - :param good_pbk_to_name: the mapping from the public keys to the names of the goods. - - :return: None - """ - assert len(endowment) == len(utility_params) - super().__init__(public_key, crypto) - self.money = money - self.endowment = endowment - self.utility_params = utility_params - self.nb_agents = nb_agents - self.nb_goods = nb_goods - self.tx_fee = tx_fee - self.agent_pbk_to_name = agent_pbk_to_name - self.good_pbk_to_name = good_pbk_to_name - - @classmethod - def from_pb(cls, obj: tac_pb2.TACController.GameData, public_key: str, crypto: Crypto) -> 'GameData': - """Read from protobuf.""" - agent_pbk_to_name = {pair.first: pair.second for pair in obj.agent_pbk_to_name} - good_pbk_to_name = {pair.first: pair.second for pair in obj.good_pbk_to_name} - - return GameData(public_key, - crypto, - obj.money, - list(obj.endowment), - list(obj.utility_params), - obj.nb_agents, - obj.nb_goods, - obj.tx_fee, - agent_pbk_to_name, - good_pbk_to_name) - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACController.GameData() - msg.money = self.money - msg.endowment.extend(self.endowment) - msg.utility_params.extend(self.utility_params) - msg.nb_agents = self.nb_agents - msg.nb_goods = self.nb_goods - msg.tx_fee = self.tx_fee - agent_pbk_to_name_pairs = [_make_str_str_pair(agent_pbk, agent_name) for agent_pbk, agent_name in self.agent_pbk_to_name.items()] # type: List[tac_pb2.StrStrPair] - msg.agent_pbk_to_name.extend(agent_pbk_to_name_pairs) - good_pbk_to_name_pairs = [_make_str_str_pair(good_pbk, good_name) for good_pbk, good_name in self.good_pbk_to_name.items()] # type: List[tac_pb2.StrStrPair] - msg.good_pbk_to_name.extend(good_pbk_to_name_pairs) - envelope = tac_pb2.TACController.Message() - envelope.game_data.CopyFrom(msg) - return envelope - - def __str__(self): - """Convert to string.""" - return self._build_str( - money=self.money, - endowment=self.endowment, - utility_params=self.utility_params, - nb_agents=self.nb_agents, - nb_goods=self.nb_goods, - tx_fee=self.tx_fee, - agent_pbk_to_name=self.agent_pbk_to_name, - good_pbk_to_name=self.good_pbk_to_name - ) - - def __eq__(self, other): - """Compare equality of two instances.""" - return type(self) == type(other) and \ - self.money == other.money and \ - self.endowment == other.endowment and \ - self.utility_params == other.utility_params and \ - self.nb_agents == other.nb_agents and \ - self.nb_goods == other.nb_goods and \ - self.tx_fee == other.tx_fee and \ - self.agent_pbk_to_name == other.agent_pbk_to_name and \ - self.good_pbk_to_name == other.good_pbk_to_name - - -class TransactionConfirmation(Response): - """Class that holds the transaction confirmation sent from the controller to the agent.""" - - def __init__(self, public_key: str, crypto: Crypto, transaction_id: str): - """ - Instantiate the transaction confirmation. - - :param public_key: the public key of the opponent - :param crypto: the crypto module - :param transaction_id: the transaction id - - :return: None - """ - super().__init__(public_key, crypto) - self.transaction_id = transaction_id - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACController.TransactionConfirmation() - msg.transaction_id = self.transaction_id - envelope = tac_pb2.TACController.Message() - envelope.tx_confirmation.CopyFrom(msg) - return envelope - - def __str__(self): - """Convert to string.""" - return self._build_str(transaction_id=self.transaction_id) - - -class StateUpdate(Response): - """Class that holds the state update sent from the controller to the agent.""" - - def __init__(self, public_key: str, - crypto: Crypto, - initial_state: GameData, - transactions: List[Transaction]) -> None: - """ - Instantiate the state update. - - :param public_key: the public key of the opponent - :param crypto: the crypto module - :param initial_state: the initial state - :param transactions: a list of transactions - - :return: None - """ - super().__init__(public_key, crypto) - self.initial_state = initial_state - self.transactions = transactions - - def to_pb(self) -> tac_pb2.TACController.Message: - """Convert to protobuf.""" - msg = tac_pb2.TACController.StateUpdate() - - game_data = tac_pb2.TACController.GameData() - game_data.money = self.initial_state.money - game_data.endowment.extend(self.initial_state.endowment) - game_data.utility_params.extend(self.initial_state.utility_params) - game_data.nb_agents = self.initial_state.nb_agents - game_data.nb_goods = self.initial_state.nb_goods - game_data.tx_fee = self.initial_state.tx_fee - agent_pbk_to_name_pairs = [_make_str_str_pair(agent_pbk, agent_name) for agent_pbk, agent_name in self.initial_state.agent_pbk_to_name.items()] # type: List[tac_pb2.StrStrPair] - game_data.agent_pbk_to_name.extend(agent_pbk_to_name_pairs) - good_pbk_to_name_pairs = [_make_str_str_pair(good_pbk, good_name) for good_pbk, good_name in self.initial_state.good_pbk_to_name.items()] # type: List[tac_pb2.StrStrPair] - game_data.good_pbk_to_name.extend(good_pbk_to_name_pairs) - - msg.initial_state.CopyFrom(game_data) - - transactions = [] - for tx in self.transactions: - tx_pb = tac_pb2.TACAgent.Transaction() - tx_pb.transaction_id = tx.transaction_id - tx_pb.buyer = tx.is_sender_buyer - tx_pb.counterparty = tx.counterparty - tx_pb.amount = tx.amount - - good_pbk_quantity_pairs = [_make_str_int_pair(good_pbk, quantity) for good_pbk, quantity in - tx.quantities_by_good_pbk.items()] # type: List[tac_pb2.StrIntPair] - tx_pb.quantities.extend(good_pbk_quantity_pairs) - - transactions.append(tx_pb) - - msg.txs.extend(transactions) - - envelope = tac_pb2.TACController.Message() - envelope.state_update.CopyFrom(msg) - return envelope - - @classmethod - def from_pb(cls, obj, public_key: str, crypto: Crypto) -> 'StateUpdate': - """ - Read from protobuf. - - :param obj: the protobuf object - :param public_key: the opponent's public key - :param crypto: the crypto module - - :return: the state update - """ - initial_state = GameData.from_pb(obj.initial_state, public_key, crypto) - transactions = [Transaction.from_pb(tx_obj, public_key, crypto) for tx_obj in obj.txs] - - return StateUpdate(public_key, - crypto, - initial_state, - transactions) - - def __str__(self): - """Convert to string.""" - return self._build_str(public_key=self.public_key) - - def __eq__(self, other): - """Compare equality of two instances.""" - return type(self) == type(other) and \ - self.public_key == other.public_key and \ - self.crypto == other.crypto and \ - self.initial_state == other.initial_state and \ - self.transactions == other.transactions diff --git a/tests/test_controller.py b/tests/test_controller.py index 61bdb359..4f88b4c5 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -27,15 +27,14 @@ from threading import Thread from aea.mail.base import Envelope -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer +from aea.protocols.tac.message import TACMessage +from aea.protocols.tac.serialization import TACSerializer from .common import TOEFAgent # from oef.core import AsyncioCore # OEF-SDK 0.6.1 from aea.crypto.base import Crypto from tac.agents.controller.agent import ControllerAgent from tac.agents.controller.base.tac_parameters import TACParameters -from tac.platform.protocol import Register logger = logging.getLogger(__name__) @@ -89,10 +88,9 @@ def setup_class(cls): agent_job = Thread(target=cls.agent1.run) agent_job.start() - tac_msg = Register(crypto.public_key, crypto, 'agent_name').serialize() - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=tac_msg) - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=DefaultMessage.protocol_id, message=msg_bytes) + tac_msg = TACMessage(type=TACMessage.Type.REGISTER, agent_name='agent_name') + tac_bytes = TACSerializer().encode(tac_msg) + envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, envelope.encode()) time.sleep(10.0) diff --git a/tests/test_game.py b/tests/test_game.py index c4cccecc..b72f3cd3 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -24,8 +24,7 @@ from aea.crypto.base import Crypto from tac.agents.controller.base.states import GameInitialization, Game from tac.agents.participant.v1.base.states import AgentState -from tac.platform.game.base import GameConfiguration, GoodState -from tac.platform.protocol import Transaction +from tac.platform.game.base import GameConfiguration, GoodState, Transaction class TestGameConfiguration: @@ -372,7 +371,7 @@ def test_transaction_invalid_if_buyer_does_not_have_enough_money(self): seller_pbk = 'tac_agent_1_pbk' amount = 20 quantities_by_good = {0: 1, 1: 1, 2: 1} - invalid_transaction = Transaction(tx_id, is_sender_buyer, buyer_pbk, amount, quantities_by_good, seller_pbk, Crypto()) + invalid_transaction = Transaction(tx_id, is_sender_buyer, buyer_pbk, amount, quantities_by_good, seller_pbk) # transaction is invalide because buyer_balance < amount + fee assert not game.is_transaction_valid(invalid_transaction) @@ -427,7 +426,7 @@ def test_transaction_invalid_if_seller_does_not_have_enough_quantities(self): counterparty_pbk = 'tac_agent_1_pbk' amount = 20 quantities_by_good = {0: 3, 1: 0, 2: 0} - invalid_transaction = Transaction(tx_id, is_sender_buyer, counterparty_pbk, amount, quantities_by_good, sender_pbk, Crypto()) + invalid_transaction = Transaction(tx_id, is_sender_buyer, counterparty_pbk, amount, quantities_by_good, sender_pbk) assert not game.is_transaction_valid(invalid_transaction) @@ -547,8 +546,8 @@ def test_to_dict(self): tx_id = 'some_tx_id' sender_pbk = 'tac_agent_0_pbk' counterparty_pbk = 'tac_agent_1_pbk' - transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk, Crypto()) - transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk, Crypto()) + transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) + transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) game.settle_transaction(transaction_1) game.settle_transaction(transaction_2) @@ -611,8 +610,8 @@ def test_from_dict(self): tx_id = 'some_tx_id' sender_pbk = 'tac_agent_0_pbk' counterparty_pbk = 'tac_agent_1_pbk' - transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk, Crypto()) - transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk, Crypto()) + transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) + transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) expected_game.settle_transaction(transaction_1) expected_game.settle_transaction(transaction_2) diff --git a/tests/test_protocol.py b/tests/test_protocol.py deleted file mode 100644 index 7b173185..00000000 --- a/tests/test_protocol.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the tests of the protocol module.""" - -import pytest - -from aea.crypto.base import Crypto -from tac.platform.protocol import Register, Unregister, Transaction, TransactionConfirmation, Error, \ - GameData, Request, Response, ErrorCode, Cancelled, GetStateUpdate, StateUpdate - - -class TestRequest: - """Class to test the Request classes.""" - - class TestRegister: - """Class to test the Register class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = Register(crypto.public_key, crypto, "tac_agent_0") - actual_msg = Request.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestUnregister: - """Class to test the Unregister class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = Unregister(crypto.public_key, crypto) - actual_msg = Request.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestTransaction: - """Class to test the Transaction class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = Transaction("transaction_id", True, "seller", 10, {'tac_good_0_pbk': 1, 'tac_good_1_pbk': 1}, - crypto.public_key, crypto) - actual_msg = Request.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestGetStateUpdate: - """Class to test the GetStateUpdate class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = GetStateUpdate(crypto.public_key, crypto) - actual_msg = Request.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - -class TestResponse: - """Class to test the Response classes.""" - - class TestCancelled: - """Class to test the Cancelled class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = Cancelled(crypto.public_key, crypto) - actual_msg = Response.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestError: - """Class to test the Error class.""" - - @pytest.mark.parametrize("error_code", list(ErrorCode)) - def test_serialization_deserialization(self, error_code): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = Error(crypto.public_key, crypto, error_code, "this is an error message.") - actual_msg = Response.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestGameData: - """Class to test the GameData class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = GameData(crypto.public_key, crypto, 10, [1, 1, 2], [0.04, 0.80, 0.16], 3, 3, 1.0, {'tac_agent_0_pbk': 'tac_agent_0', 'tac_agent_1_pbk': 'tac_agent_1', 'tac_agent_2_pbk': 'tac_agent_2'}, {'tag_good_0_pbk': 'tag_good_0', 'tag_good_1_pbk': 'tag_good_1', 'tag_good_2_pbk': 'tag_good_2'}) - actual_msg = Response.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestTransactionConfirmation: - """Class to test the TransactionConfirmation class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - expected_msg = TransactionConfirmation(crypto.public_key, crypto, "transaction_id") - actual_msg = Response.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg - - class TestStateUpdate: - """Class to test the StateUpdate class.""" - - def test_serialization_deserialization(self): - """Test that serialization and deserialization gives the same result.""" - crypto = Crypto() - game_state = GameData(crypto.public_key, crypto, 10, [1, 1, 2], [0.04, 0.80, 0.16], 3, 3, 1.0, {'tac_agent_0_pbk': 'tac_agent_0', 'tac_agent_1_pbk': 'tac_agent_1', 'tac_agent_2_pbk': 'tac_agent_2'}, {'tag_good_0_pbk': 'tag_good_0', 'tag_good_1_pbk': 'tag_good_1', 'tag_good_2_pbk': 'tag_good_2'}) - transactions = [Transaction("transaction_id", True, "seller", 10.0, {"tac_good_0_pbk": 1}, crypto.public_key, crypto)] - - expected_msg = StateUpdate(crypto.public_key, crypto, game_state, transactions) - actual_msg = Response.from_pb(expected_msg.serialize(), crypto.public_key, crypto) - - assert expected_msg == actual_msg From 42ba9ec31da80de931d20a43644c25554c5785d9 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sat, 24 Aug 2019 10:08:47 +0100 Subject: [PATCH 079/107] Continue transition to new tac protocol, remove oef agents --- Pipfile.lock | 10 ++- sandbox/oef_healthcheck.py | 16 ++--- tac/agents/controller/base/handlers.py | 90 +++++++++++++------------- tac/platform/oef_health_check.py | 19 ++---- tests/common.py | 75 ++------------------- tests/test_controller.py | 18 +++--- 6 files changed, 79 insertions(+), 149 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 1f8081a4..60afc58b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -24,7 +24,7 @@ "aea": { "editable": true, "git": "https://github.com/fetchai/agents-aea.git", - "ref": "c0e643c7caa6cdd6def2283a95905414bf6cc6f6" + "ref": "ed2389ca853e5259c22cf9df6385eab769b05112" }, "alabaster": { "hashes": [ @@ -739,6 +739,14 @@ } }, "develop": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", diff --git a/sandbox/oef_healthcheck.py b/sandbox/oef_healthcheck.py index 11cd8f33..1c9b80e7 100644 --- a/sandbox/oef_healthcheck.py +++ b/sandbox/oef_healthcheck.py @@ -24,8 +24,8 @@ import argparse import logging -from oef.agents import OEFAgent -# from oef.core import AsyncioCore # OEF-SDK 0.6.1 +from aea.crypto.base import Crypto +from aea.channel.oef import OEFMailBox logger = logging.getLogger(__name__) @@ -39,15 +39,11 @@ args = parser.parse_args() host = args.addr port = args.port - pbk = 'check' print("Connecting to {}:{}".format(host, port)) - # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 - # core.run_threaded() # OEF-SDK 0.6.1 - # agent = OEFAgent(pbk, oef_addr=host, oef_port=port, core=core) # OEF-SDK 0.6.1 - import asyncio - agent = OEFAgent(pbk, oef_addr=host, oef_port=port, loop=asyncio.get_event_loop()) - agent.connect() - agent.disconnect() + crypto = Crypto() + mailbox = OEFMailBox(crypto.public_key, oef_addr=host, oef_port=port) + mailbox.connect() + mailbox.disconnect() print("OK!") exit(0) except Exception as e: diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index aa0802a5..92ea6809 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -42,7 +42,7 @@ from aea.agent import Liveness from aea.crypto.base import Crypto -from aea.mail.base import MailBox, Envelope, Address +from aea.mail.base import Address, Envelope, MailBox from aea.protocols.oef.message import OEFMessage from aea.protocols.oef.serialization import OEFSerializer from aea.protocols.tac.message import TACMessage @@ -53,7 +53,7 @@ from tac.agents.controller.base.states import Game from tac.agents.controller.base.tac_parameters import TACParameters from tac.gui.monitor import Monitor, NullMonitor -from tac.platform.game.base import GamePhase, Transaction, GameData +from tac.platform.game.base import GameData, GamePhase, Transaction from tac.platform.game.stats import GameStats if TYPE_CHECKING: @@ -74,17 +74,17 @@ def __init__(self, controller_agent: 'ControllerAgent') -> None: """ self.controller_agent = controller_agent - def __call__(self, message: TACMessage, sender_pbk: str) -> None: + def __call__(self, message: TACMessage, sender: Address) -> None: """Call the handler.""" - return self.handle(message, sender_pbk) + return self.handle(message, sender) @abstractmethod - def handle(self, message: TACMessage, sender_pbk: str) -> None: + def handle(self, message: TACMessage, sender: Address) -> None: """ Handle a TACMessage from an OEF agent. :param message: the 'get agent state' TACMessage. - :param sender_pbk: the public key of the sender + :param sender: the public key of the sender :return: None """ @@ -96,14 +96,14 @@ def __init__(self, controller_agent: 'ControllerAgent') -> None: """Instantiate a RegisterHandler.""" super().__init__(controller_agent) - def handle(self, message: TACMessage, sender_pbk: str) -> None: + def handle(self, message: TACMessage, sender: Address) -> None: """ Handle a register message. If the public key is already registered, answer with an error message. :param message: the 'get agent state' TACMessage. - :param sender_pbk: the public key of the sender + :param sender: the public key of the sender :return: None """ whitelist = self.controller_agent.game_handler.tac_parameters.whitelist @@ -111,28 +111,30 @@ def handle(self, message: TACMessage, sender_pbk: str) -> None: if whitelist is not None and agent_name not in whitelist: logger.error("[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) - if sender_pbk in self.controller_agent.game_handler.registered_agents: - logger.error("[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) + if sender in self.controller_agent.game_handler.registered_agents: + logger.error("[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender])) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_PBK_ALREADY_REGISTERED) + tac_bytes = TACSerializer().encode(tac_msg) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) if agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): logger.error("[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED) - - if tac_msg is not None: tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) try: - self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({sender_pbk: agent_name}) + self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({sender: agent_name}) self.controller_agent.game_handler.monitor.update() except Exception as e: logger.error(str(e)) - self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk] = agent_name - logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) - self.controller_agent.game_handler.registered_agents.add(sender_pbk) + self.controller_agent.game_handler.agent_pbk_to_name[sender] = agent_name + logger.debug("[{}]: Agent registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender])) + self.controller_agent.game_handler.registered_agents.add(sender) class UnregisterHandler(TACMessageHandler): @@ -142,25 +144,25 @@ def __init__(self, controller_agent: 'ControllerAgent') -> None: """Instantiate an UnregisterHandler.""" super().__init__(controller_agent) - def handle(self, message: TACMessage, sender_pbk: str) -> None: + def handle(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_pbk: the public key of the sender + :param sender: the public key of the sender :return: None """ - if sender_pbk not in self.controller_agent.game_handler.registered_agents: - logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, sender_pbk)) + if sender not in self.controller_agent.game_handler.registered_agents: + logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, sender)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) else: - logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender_pbk])) - self.controller_agent.game_handler.registered_agents.remove(sender_pbk) - self.controller_agent.game_handler.agent_pbk_to_name.pop(sender_pbk) + logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender])) + self.controller_agent.game_handler.registered_agents.remove(sender) + self.controller_agent.game_handler.agent_pbk_to_name.pop(sender) class TransactionHandler(TACMessageHandler): @@ -171,17 +173,17 @@ def __init__(self, controller_agent: 'ControllerAgent') -> None: super().__init__(controller_agent) self._pending_transaction_requests = {} # type: Dict[str, Transaction] - def handle(self, message: TACMessage, sender_pbk: Address) -> None: + def handle(self, message: TACMessage, sender: Address) -> None: """ Handle a transaction TACMessage message. If the transaction is invalid (e.g. because the state of the game are not consistent), reply with an error. :param message: the 'get agent state' TACMessage. - :param sender_pbk: the public key of the sender + :param sender: the public key of the sender :return: None """ - transaction = Transaction.from_message(message, sender_pbk) + transaction = Transaction.from_message(message, sender) logger.debug("[{}]: Handling transaction: {}".format(self.controller_agent.name, transaction)) # if transaction arrives first time then put it into the pending pool @@ -190,7 +192,7 @@ def handle(self, message: TACMessage, sender_pbk: Address) -> None: logger.debug("[{}]: Put transaction TACMessage in the pool: {}".format(self.controller_agent.name, message.get("transaction_id"))) self._pending_transaction_requests[message.get("transaction_id")] = message else: - self._handle_invalid_transaction(message, sender_pbk) + self._handle_invalid_transaction(message, sender) # if transaction arrives second time then process it else: pending_tx = self._pending_transaction_requests.pop(message.get("transaction_id")) @@ -198,13 +200,13 @@ def handle(self, message: TACMessage, sender_pbk: Address) -> None: if self.controller_agent.game_handler.current_game.is_transaction_valid(message): self.controller_agent.game_handler.confirmed_transaction_per_participant[pending_tx.sender].append(pending_tx) self.controller_agent.game_handler.confirmed_transaction_per_participant[transaction.sender].append(transaction) - self._handle_valid_transaction(message, sender_pbk) + self._handle_valid_transaction(message, sender) else: - self._handle_invalid_transaction(message, sender_pbk) + self._handle_invalid_transaction(message, sender) else: - self._handle_non_matching_transaction(message, sender_pbk) + self._handle_non_matching_transaction(message, sender) - def _handle_valid_transaction(self, message: TACMessage, sender_pbk: str) -> None: + def _handle_valid_transaction(self, message: TACMessage, sender: Address) -> None: """ Handle a valid transaction. @@ -226,7 +228,7 @@ def _handle_valid_transaction(self, message: TACMessage, sender_pbk: str) -> Non # send the transaction confirmation. tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id=message.get("transaction_id")) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) self.controller_agent.outbox.put_message(to=message.get("counterparty"), sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) # log messages @@ -234,45 +236,45 @@ def _handle_valid_transaction(self, message: TACMessage, sender_pbk: str) -> Non holdings_summary = self.controller_agent.game_handler.current_game.get_holdings_summary() logger.debug("[{}]: Current state:\n{}".format(self.controller_agent.name, holdings_summary)) - def _handle_invalid_transaction(self, message: TACMessage, sender_pbk: str) -> None: + 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")}) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) - def _handle_non_matching_transaction(self, message: TACMessage, sender_pbk: str) -> None: + def _handle_non_matching_transaction(self, message: TACMessage, sender: Address) -> None: """Handle non-matching transaction.""" tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.TRANSACTION_NOT_MATCHING) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) class GetStateUpdateHandler(TACMessageHandler): """Class for a state update handler.""" - def handle(self, message: TACMessage, sender_pbk: str) -> None: + def handle(self, message: TACMessage, sender: Address) -> None: """ Handle a 'get agent state' TACMessage. If the public key is not registered, answer with an error message. :param message: the 'get agent state' TACMessage. - :param sender_pbk: the public key of the sender + :param sender: the public key of the sender :return: None """ logger.debug("[{}]: Handling the 'get agent state' TACMessage: {}".format(self.controller_agent.name, message)) if not self.controller_agent.game_handler.is_game_running(): logger.error("[{}]: GetStateUpdate TACMessage is not valid while the competition is not running.".format(self.controller_agent.name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.COMPETITION_NOT_RUNNING) - if sender_pbk not in self.controller_agent.game_handler.registered_agents: + if sender not in self.controller_agent.game_handler.registered_agents: logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, message.get("agent_name"))) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) else: - transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[sender_pbk] # type: List[Transaction] - initial_game_data = self.controller_agent.game_handler.game_data_per_participant[sender_pbk] # type: Dict + transactions = self.controller_agent.game_handler.confirmed_transaction_per_participant[sender] # type: List[Transaction] + initial_game_data = self.controller_agent.game_handler.game_data_per_participant[sender] # type: Dict tac_msg = TACMessage(tac_type=TACMessage.Type.STATE_UPDATE, initial_state=initial_game_data, transactions=transactions) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender_pbk, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) class AgentMessageDispatcher(object): diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index 3c448709..872d2422 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -24,8 +24,8 @@ import argparse import logging -from oef.agents import OEFAgent -# from oef.core import AsyncioCore # OEF-SDK 0.6.1 +from aea.crypto.base import Crypto +from aea.channel.oef import OEFMailBox logger = logging.getLogger(__name__) @@ -45,6 +45,8 @@ def __init__(self, oef_addr: str, oef_port: int): :param oef_addr: IP address of the OEF node. :param oef_port: Port of the OEF node. """ + crypto = Crypto() + self.mailbox = OEFMailBox(crypto.public_key, oef_addr=oef_addr, oef_port=oef_port) self.oef_addr = oef_addr self.oef_port = oef_port @@ -56,24 +58,15 @@ def run(self) -> bool: """ result = False try: - pbk = 'check' print("Connecting to {}:{}".format(self.oef_addr, self.oef_port)) - # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 - # core.run_threaded() # OEF-SDK 0.6.1 - import asyncio - agent = OEFAgent(pbk, oef_addr=self.oef_addr, oef_port=self.oef_port, loop=asyncio.new_event_loop()) - # agent = OEFAgent(pbk, oef_addr=self.addr, oef_port=self.port, core=core) # OEF-SDK 0.6.1 - agent.connect() - agent.disconnect() - # core.stop() # OEF-SDK 0.6.1 + self.mailbox.connect() + self.mailbox.disconnect() print("OK!") result = True return result except Exception as e: print(str(e)) return result - # finally: - # core.stop(). # OEF-SDK 0.6.1 def main(oef_addr, oef_port): diff --git a/tests/common.py b/tests/common.py index 2e6a874a..814e7047 100644 --- a/tests/common.py +++ b/tests/common.py @@ -19,79 +19,12 @@ """This module contains an OEF agent for testing.""" -from typing import List +from aea.channel.oef import OEFMailBox -from oef.agents import OEFAgent -from oef.messages import PROPOSE_TYPES, BaseMessage, Message, CFP, CFP_TYPES, Propose, Accept, Decline -from oef.uri import Context - -class TOEFAgent(OEFAgent): +class TOEFAgent(OEFMailBox): """An OEF agent for testing.""" - def __init__(self, *args, **kwargs): + def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): """Initialize.""" - super().__init__(*args, **kwargs) - self.messages = [] # type: List[BaseMessage] - - def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: - """ - On message handler. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the origin public key - :param content: the message content - :return: None - """ - self.messages.append(Message(msg_id, dialogue_id, origin, content, Context())) - - def on_cfp(self, msg_id: int, dialogue_id: int, origin: str, target: int, query: CFP_TYPES) -> None: - """ - On cfp handler. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the origin public key - :param target: the message target - :param query: the query object - :return: None - """ - self.messages.append(CFP(msg_id, dialogue_id, origin, target, query, Context())) - - def on_propose(self, msg_id: int, dialogue_id: int, origin: str, target: int, proposals: PROPOSE_TYPES) -> None: - """ - On propose handler. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the origin public key - :param target: the message target - :param proposals: the proposals - :return: None - """ - self.messages.append(Propose(msg_id, dialogue_id, origin, target, proposals, Context())) - - def on_accept(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - On accept handler. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the origin public key - :param target: the message target - :return: None - """ - self.messages.append(Accept(msg_id, dialogue_id, origin, target, Context())) - - def on_decline(self, msg_id: int, dialogue_id: int, origin: str, target: int) -> None: - """ - On message handler. - - :param msg_id: the message id - :param dialogue_id: the dialogue id - :param origin: the origin public key - :param target: the message target - :return: None - """ - self.messages.append(Decline(msg_id, dialogue_id, origin, target, Context())) + super().__init__(public_key, oef_addr, oef_port) diff --git a/tests/test_controller.py b/tests/test_controller.py index 4f88b4c5..c604d018 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -26,11 +26,9 @@ import time from threading import Thread -from aea.mail.base import Envelope from aea.protocols.tac.message import TACMessage from aea.protocols.tac.serialization import TACSerializer from .common import TOEFAgent -# from oef.core import AsyncioCore # OEF-SDK 0.6.1 from aea.crypto.base import Crypto from tac.agents.controller.agent import ControllerAgent @@ -75,13 +73,9 @@ def setup_class(cls): tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) - import asyncio crypto = Crypto() - cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, loop=asyncio.new_event_loop()) + cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000) cls.agent1.connect() - # core = AsyncioCore(logger=logger) # OEF-SDK 0.6.1 - # core.run_threaded() # OEF-SDK 0.6.1 - # agent1 = TestOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000, core=core) # OEF-SDK 0.6.1 job = Thread(target=cls.controller_agent.start) job.start() @@ -90,8 +84,7 @@ def setup_class(cls): tac_msg = TACMessage(type=TACMessage.Type.REGISTER, agent_name='agent_name') tac_bytes = TACSerializer().encode(tac_msg) - envelope = Envelope(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) - cls.agent1.send_message(0, 0, cls.controller_agent.crypto.public_key, envelope.encode()) + cls.agent1.outbox.put_message(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) time.sleep(10.0) @@ -105,4 +98,9 @@ def test_only_one_agent_registered(self): def test_agent_receives_cancelled_message(self): """Test the agent receives a cancelled message.""" - assert len(self.agent1.messages) == 1 + counter = 0 + while not self.agent1.inbox.empty(): + counter += 1 + msg = self.inbox.get_nowait() + assert msg is not None + assert counter == 1 From 42bfe2d7b41e05c7a8f9863c0c8b52115f756ae8 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sat, 24 Aug 2019 10:55:42 +0100 Subject: [PATCH 080/107] Add further fixes. --- tac/agents/controller/agent.py | 15 ++++++----- tac/agents/controller/base/handlers.py | 10 +++---- tac/agents/participant/v1/agent.py | 7 +++-- tac/agents/participant/v1/base/handlers.py | 2 +- tac/agents/participant/v1/base/helpers.py | 26 ------------------- .../v1/base/negotiation_behaviours.py | 6 ++--- tac/agents/participant/v1/base/reactions.py | 5 +++- tac/platform/game/base.py | 3 +-- 8 files changed, 24 insertions(+), 50 deletions(-) diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 46f72caf..4d4a47ac 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -36,7 +36,6 @@ from tac.agents.controller.base.handlers import OEFHandler, GameHandler, AgentMessageDispatcher from tac.agents.controller.base.tac_parameters import TACParameters -from tac.agents.participant.v1.base.helpers import is_oef_message from tac.platform.game.base import GamePhase from tac.gui.monitor import Monitor, NullMonitor, VisdomMonitor @@ -141,13 +140,15 @@ def react(self) -> None: counter = 0 while (not self.inbox.empty() and counter < self.max_reactions): counter += 1 - msg = self.inbox.get_nowait() # type: Optional[Envelope] - if msg is not None: - if is_oef_message(msg): - self.oef_handler.handle_oef_message(msg) - else: - self.agent_message_dispatcher.handle_agent_message(msg) + envelope = self.inbox.get_nowait() # type: Optional[Envelope] + if envelope is not None: + if envelope.protocol_id == 'oef': + self.oef_handler.handle_oef_message(envelope) + elif envelope.protocol_id == 'tac': + self.agent_message_dispatcher.handle_agent_message(envelope) self.last_activity = datetime.datetime.now() + else: + raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) def update(self) -> None: """ diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 92ea6809..6717bb71 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -190,23 +190,23 @@ def handle(self, message: TACMessage, sender: Address) -> None: if message.get("transaction_id") not in self._pending_transaction_requests: if self.controller_agent.game_handler.current_game.is_transaction_valid(message): logger.debug("[{}]: Put transaction TACMessage in the pool: {}".format(self.controller_agent.name, message.get("transaction_id"))) - self._pending_transaction_requests[message.get("transaction_id")] = message + self._pending_transaction_requests[message.get("transaction_id")] = transaction else: self._handle_invalid_transaction(message, sender) # if transaction arrives second time then process it else: pending_tx = self._pending_transaction_requests.pop(message.get("transaction_id")) if transaction.matches(pending_tx): - if self.controller_agent.game_handler.current_game.is_transaction_valid(message): + if self.controller_agent.game_handler.current_game.is_transaction_valid(transaction): self.controller_agent.game_handler.confirmed_transaction_per_participant[pending_tx.sender].append(pending_tx) self.controller_agent.game_handler.confirmed_transaction_per_participant[transaction.sender].append(transaction) - self._handle_valid_transaction(message, sender) + self._handle_valid_transaction(message, sender, transaction) else: self._handle_invalid_transaction(message, sender) else: self._handle_non_matching_transaction(message, sender) - def _handle_valid_transaction(self, message: TACMessage, sender: Address) -> None: + def _handle_valid_transaction(self, message: TACMessage, sender: Address, transaction: Transaction) -> None: """ Handle a valid transaction. @@ -220,7 +220,7 @@ def _handle_valid_transaction(self, message: TACMessage, sender: Address) -> Non logger.debug("[{}]: Handling valid transaction: {}".format(self.controller_agent.name, message.get("transaction_id"))) # update the game state. - self.controller_agent.game_handler.current_game.settle_transaction(message) + self.controller_agent.game_handler.current_game.settle_transaction(transaction) # update the dashboard monitor self.controller_agent.game_handler.monitor.update() diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index c01e45f3..6d902940 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -29,7 +29,6 @@ from aea.mail.base import Envelope from tac.agents.participant.v1.base.game_instance import GameInstance from tac.agents.participant.v1.base.handlers import ControllerHandler, DialogueHandler, OEFHandler -from tac.agents.participant.v1.base.helpers import is_oef_message, is_controller_message, is_fipa_message from tac.agents.participant.v1.base.strategy import Strategy from tac.gui.dashboards.agent import AgentDashboard from tac.platform.game.base import GamePhase @@ -108,11 +107,11 @@ def react(self) -> None: if envelope is not None: logger.debug("processing message of protocol={}".format(envelope.protocol_id)) - if is_oef_message(envelope): + if envelope.protocol_id == "oef": self.oef_handler.handle_oef_message(envelope) - elif is_controller_message(envelope): + elif envelope.protocol_id == "tac": self.controller_handler.handle_controller_message(envelope) - elif is_fipa_message(envelope): + elif envelope.protocol_id == "fipa": self.dialogue_handler.handle_dialogue_message(envelope) else: logger.warning("Message type not recognized: sender={}".format(envelope.sender)) diff --git a/tac/agents/participant/v1/base/handlers.py b/tac/agents/participant/v1/base/handlers.py index 6dbccf60..5e1c4dc2 100644 --- a/tac/agents/participant/v1/base/handlers.py +++ b/tac/agents/participant/v1/base/handlers.py @@ -110,7 +110,7 @@ def handle_controller_message(self, envelope: Envelope) -> None: :return: None """ - assert envelope.protocol_id == "default" + assert envelope.protocol_id == "tac" tac_msg = TACSerializer().decode(envelope.message) tac_msg_type = TACMessage.Type(tac_msg.get("type")) logger.debug("[{}]: Handling controller response. type={}".format(self.agent_name, tac_msg_type)) diff --git a/tac/agents/participant/v1/base/helpers.py b/tac/agents/participant/v1/base/helpers.py index b73d869f..c47375fc 100644 --- a/tac/agents/participant/v1/base/helpers.py +++ b/tac/agents/participant/v1/base/helpers.py @@ -23,7 +23,6 @@ from typing import Dict, List, Set, Union from aea.dialogue.base import DialogueLabel -from aea.mail.base import Envelope from aea.protocols.oef.models import DataModel, Attribute, Description, Query, Constraint, Or from oef.query import GtEq @@ -35,31 +34,6 @@ QUANTITY_SHIFT = 1 # Any non-negative integer is fine. -def is_oef_message(envelope: Envelope) -> bool: - """ - Check whether a message is from the oef. - - :param envelope: the message - :return: boolean indicating whether or not the message is from the oef - """ - return envelope.protocol_id == "oef" - - -def is_controller_message(envelope: Envelope) -> bool: - """ - Check whether a message is from the controller. - - :param envelope: the message - :return: boolean indicating whether or not the message is from the controller - """ - return envelope.protocol_id == "default" - - -def is_fipa_message(envelope: Envelope) -> bool: - """Chcek whether a message is a FIPA message.""" - return envelope.protocol_id == "fipa" - - def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: """ Make a transaction id. diff --git a/tac/agents/participant/v1/base/negotiation_behaviours.py b/tac/agents/participant/v1/base/negotiation_behaviours.py index d5337780..102f0af2 100644 --- a/tac/agents/participant/v1/base/negotiation_behaviours.py +++ b/tac/agents/participant/v1/base/negotiation_behaviours.py @@ -118,8 +118,7 @@ def on_cfp(self, cfp: Message, dialogue: Dialogue) -> Envelope: transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, - sender=self.crypto.public_key, - crypto=self.crypto) + sender=self.crypto.public_key) self.game_instance.transaction_manager.add_pending_proposal(dialogue.dialogue_label, new_msg_id, transaction) logger.debug("[{}]: sending to {} a Propose{}".format(self.agent_name, dialogue.dialogue_label.dialogue_opponent_pbk, pprint.pformat({ @@ -153,8 +152,7 @@ def on_propose(self, propose: Message, dialogue: Dialogue) -> Envelope: transaction_id=transaction_id, is_sender_buyer=not dialogue.is_seller, counterparty=dialogue.dialogue_label.dialogue_opponent_pbk, - sender=self.crypto.public_key, - crypto=self.crypto) + sender=self.crypto.public_key) new_msg_id = propose.get("id") + 1 is_profitable_transaction, propose_log_msg = self.game_instance.is_profitable_transaction(transaction, dialogue) logger.debug(propose_log_msg) diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index d7c4b99b..f7ac286e 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -47,6 +47,7 @@ DialogueReactionInterface from tac.agents.participant.v1.base.negotiation_behaviours import FIPABehaviour from tac.agents.participant.v1.base.stats_manager import EndState +from tac.platform.game.base import GameData logger = logging.getLogger(__name__) @@ -95,7 +96,9 @@ def on_start(self, message: TACMessage, sender: Address) -> None: :return: None """ logger.debug("[{}]: Received start event from the controller. Starting to compete...".format(self.agent_name)) - self.game_instance.init(message.get("game_data"), self.crypto.public_key) + game_data = GameData(sender, message.get("money"), message.get("endowment"), message.get("utility_params"), + message.get("nb_agents"), message.get("nb_goods"), message.get("tx_fee"), message.get("agent_pbk_to_name"), message.get("good_pbk_to_name")) + self.game_instance.init(game_data, self.crypto.public_key) self.game_instance._game_phase = GamePhase.GAME dashboard = self.game_instance.dashboard diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index fc21ed61..89111714 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -214,12 +214,12 @@ def __init__(self, transaction_id: str, is_sender_buyer: bool, counterparty: str :return: None """ - self.sender = sender self.transaction_id = transaction_id self.is_sender_buyer = is_sender_buyer self.counterparty = counterparty self.amount = amount self.quantities_by_good_pbk = quantities_by_good_pbk + self.sender = sender self._check_consistency() @@ -286,7 +286,6 @@ def from_proposal(cls, proposal: Description, transaction_id: str, :param is_sender_buyer: whether the sender is the buyer :param counterparty: the counterparty public key :param sender: the sender public key - :param crypto: the crypto object :return: Transaction """ data = copy.deepcopy(proposal.values) From 8b6b664f1038bc35966d267ee9fd61707b005885 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sat, 24 Aug 2019 13:25:13 +0100 Subject: [PATCH 081/107] Fix typing issues --- proto/tac.proto | 115 -- tac/agents/controller/base/states.py | 3 +- tac/agents/participant/v1/base/dialogues.py | 8 +- .../participant/v1/base/game_instance.py | 7 +- tac/agents/participant/v1/base/helpers.py | 6 +- tac/agents/participant/v1/base/reactions.py | 4 +- tac/agents/participant/v1/base/states.py | 5 +- .../v1/base/transaction_manager.py | 6 +- tac/platform/game/base.py | 11 +- tac/tac_pb2.py | 1022 ----------------- 10 files changed, 28 insertions(+), 1159 deletions(-) delete mode 100644 proto/tac.proto delete mode 100644 tac/tac_pb2.py diff --git a/proto/tac.proto b/proto/tac.proto deleted file mode 100644 index 4d70ff57..00000000 --- a/proto/tac.proto +++ /dev/null @@ -1,115 +0,0 @@ -syntax = "proto3"; - -package fetch.oef.pb; - -import "google/protobuf/struct.proto"; - -message StrIntPair { - string first = 1; - int32 second = 2; -} - -message StrStrPair { - string first = 1; - string second = 2; -} - -message TACController { - - message Registered { - } - message Unregistered { - } - message Cancelled { - } - - message GameData { - double money = 1; - repeated int32 endowment = 2; - repeated double utility_params = 3; - int32 nb_agents = 4; - int32 nb_goods = 5; - double tx_fee = 6; - repeated StrStrPair agent_pbk_to_name = 7; - repeated StrStrPair good_pbk_to_name = 8; - } - - message TransactionConfirmation { - string transaction_id = 1; - } - - message StateUpdate { - GameData initial_state = 1; - repeated TACAgent.Transaction txs = 2; - } - - message Error { - enum ErrorCode { - GENERIC_ERROR = 0; - REQUEST_NOT_VALID = 1; - AGENT_PBK_ALREADY_REGISTERED = 2; - AGENT_NAME_ALREADY_REGISTERED = 3; - AGENT_NOT_REGISTERED = 4; - TRANSACTION_NOT_VALID = 5; - TRANSACTION_NOT_MATCHING = 6; - AGENT_NAME_NOT_IN_WHITELIST = 7; - COMPETITION_NOT_RUNNING = 8; - } - - ErrorCode error_code = 1; - string error_msg = 2; - google.protobuf.Struct details = 3; - } - - - message Message { - oneof msg { - Registered registered = 1; - Unregistered unregistered = 2; - Cancelled cancelled = 3; - GameData game_data = 4; - TransactionConfirmation tx_confirmation = 5; - StateUpdate state_update = 6; - Error error = 7; - } - } - - message SignedMessage { - bytes signature = 1; - bytes message = 100; - } -} - -message TACAgent { - - message Register { - string agent_name = 1; - } - message Unregister { - } - - message Transaction { - string transaction_id = 1; - bool buyer = 2; // is the sender of this message a buyer? - string counterparty = 3; - double amount = 4; - repeated StrIntPair quantities = 5; - } - - message GetStateUpdate { - } - - message Message { - oneof msg { - Register register = 1; - Unregister unregister = 2; - Transaction transaction = 3; - GetStateUpdate get_state_update = 4; - } - } - - message SignedMessage { - bytes signature = 1; - bytes message = 100; - } -} diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index c3c399da..690d51ff 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -30,6 +30,7 @@ from typing import List, Dict, Any from aea.crypto.base import Crypto +from aea.mail.base import Address from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState, Transaction from tac.platform.game.helpers import generate_money_endowments, generate_good_endowments, generate_utility_params, \ @@ -303,7 +304,7 @@ def get_scores(self) -> Dict[str, float]: """Get the current scores for every agent.""" return {agent_pbk: agent_state.get_score() for agent_pbk, agent_state in self.agent_states.items()} - def get_agent_state_from_agent_pbk(self, agent_pbk: str) -> 'AgentState': + def get_agent_state_from_agent_pbk(self, agent_pbk: Address) -> 'AgentState': """ Get agent state from agent pbk. diff --git a/tac/agents/participant/v1/base/dialogues.py b/tac/agents/participant/v1/base/dialogues.py index 01aa6bed..dc69f1ff 100644 --- a/tac/agents/participant/v1/base/dialogues.py +++ b/tac/agents/participant/v1/base/dialogues.py @@ -203,7 +203,7 @@ def is_permitted_for_new_dialogue(self, message: Message, known_pbks: List[str], and (sender in known_pbks) return result - def is_belonging_to_registered_dialogue(self, message: Message, agent_pbk: str, sender: Address) -> bool: + def is_belonging_to_registered_dialogue(self, message: Message, agent_pbk: Address, sender: Address) -> bool: """ Check whether an agent message is part of a registered dialogue. @@ -242,7 +242,7 @@ def is_belonging_to_registered_dialogue(self, message: Message, agent_pbk: str, result = self_initiated_dialogue.is_expecting_accept_decline() return result - def get_dialogue(self, message: Message, sender: Address, agent_pbk: str) -> Dialogue: + def get_dialogue(self, message: Message, sender: Address, agent_pbk: Address) -> Dialogue: """ Retrieve dialogue. @@ -282,7 +282,7 @@ def get_dialogue(self, message: Message, sender: Address, agent_pbk: str) -> Dia raise ValueError('Should have found dialogue.') return dialogue - def create_self_initiated(self, dialogue_opponent_pbk: str, dialogue_starter_pbk: str, is_seller: bool) -> Dialogue: + def create_self_initiated(self, dialogue_opponent_pbk: Address, dialogue_starter_pbk: Address, is_seller: bool) -> Dialogue: """ Create a self initiated dialogue. @@ -296,7 +296,7 @@ def create_self_initiated(self, dialogue_opponent_pbk: str, dialogue_starter_pbk result = self._create(dialogue_label, is_seller) return result - def create_opponent_initiated(self, dialogue_opponent_pbk: str, dialogue_id: int, is_seller: bool) -> Dialogue: + def create_opponent_initiated(self, dialogue_opponent_pbk: Address, dialogue_id: int, is_seller: bool) -> Dialogue: """ Save an opponent initiated dialogue. diff --git a/tac/agents/participant/v1/base/game_instance.py b/tac/agents/participant/v1/base/game_instance.py index 4517d509..2f66b3ea 100644 --- a/tac/agents/participant/v1/base/game_instance.py +++ b/tac/agents/participant/v1/base/game_instance.py @@ -24,10 +24,11 @@ import random from typing import List, Optional, Set, Tuple, Dict, Union +from aea.channel.oef import MailStats +from aea.mail.base import Address from aea.protocols.oef.models import Description, Query from aea.protocols.tac.message import TACMessage -from aea.channel.oef import MailStats from tac.agents.participant.v1.base.dialogues import Dialogues, Dialogue from tac.agents.participant.v1.base.helpers import build_dict, build_query, get_goods_quantities_description from tac.agents.participant.v1.base.states import AgentState, WorldState @@ -116,7 +117,7 @@ def __init__(self, agent_name: str, self.dashboard.start() self.stats_manager.start() - def init(self, game_data: GameData, agent_pbk: str) -> None: + def init(self, game_data: GameData, agent_pbk: Address) -> None: """ Populate data structures with the game data. @@ -134,7 +135,7 @@ def init(self, game_data: GameData, agent_pbk: str) -> None: opponent_pbks.remove(agent_pbk) self._world_state = WorldState(opponent_pbks, self.game_configuration.good_pbks, self.initial_agent_state) - def on_state_update(self, message: TACMessage, agent_pbk: str) -> None: + def on_state_update(self, message: TACMessage, agent_pbk: Address) -> None: """ Update the game instance with a State Update from the controller. diff --git a/tac/agents/participant/v1/base/helpers.py b/tac/agents/participant/v1/base/helpers.py index c47375fc..642ad142 100644 --- a/tac/agents/participant/v1/base/helpers.py +++ b/tac/agents/participant/v1/base/helpers.py @@ -23,7 +23,9 @@ from typing import Dict, List, Set, Union from aea.dialogue.base import DialogueLabel +from aea.mail.base import Address from aea.protocols.oef.models import DataModel, Attribute, Description, Query, Constraint, Or +from tac.platform.game.base import TransactionId from oef.query import GtEq logger = logging.getLogger(__name__) @@ -34,7 +36,7 @@ QUANTITY_SHIFT = 1 # Any non-negative integer is fine. -def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: +def generate_transaction_id(agent_pbk: Address, opponent_pbk: Address, dialogue_label: DialogueLabel, agent_is_seller: bool) -> str: """ Make a transaction id. @@ -51,7 +53,7 @@ def generate_transaction_id(agent_pbk: str, opponent_pbk: str, dialogue_label: D return transaction_id -def dialogue_label_from_transaction_id(agent_pbk: str, transaction_id: str) -> DialogueLabel: +def dialogue_label_from_transaction_id(agent_pbk: Address, transaction_id: TransactionId) -> DialogueLabel: """ Recover dialogue label from transaction id. diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index f7ac286e..89abf62a 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -315,7 +315,7 @@ def _on_services_search_result(self, agent_pbks: List[str], is_searching_for_sel .format(self.agent_name, dialogue.role, cfp.get("id"), cfp.get("dialogue_id"), agent_pbk, cfp.get("target"), services)) self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=FIPAMessage.protocol_id, message=cfp_bytes) - def _register_to_tac(self, controller_pbk: str) -> None: + def _register_to_tac(self, controller_pbk: Address) -> None: """ Register to active TAC Controller. @@ -329,7 +329,7 @@ def _register_to_tac(self, controller_pbk: str) -> None: tac_bytes = TACSerializer().encode(tac_msg) self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) - def _rejoin_tac(self, controller_pbk: str) -> None: + def _rejoin_tac(self, controller_pbk: Address) -> None: """ Rejoin the TAC run by a Controller. diff --git a/tac/agents/participant/v1/base/states.py b/tac/agents/participant/v1/base/states.py index 30571381..7bd9a911 100644 --- a/tac/agents/participant/v1/base/states.py +++ b/tac/agents/participant/v1/base/states.py @@ -30,6 +30,7 @@ import pprint from typing import Dict, List +from aea.mail.base import Address from aea.state.base import AgentState as BaseAgentState from aea.state.base import WorldState as BaseWorldState from tac.agents.participant.v1.base.price_model import GoodPriceModel @@ -269,7 +270,7 @@ def _expected_utility_params(self, utility_params: UtilityParams) -> UtilityPara expected_utility_params = utility_params return expected_utility_params - def expected_price(self, good_pbk: str, marginal_utility: float, is_seller: bool, share_of_tx_fee: float) -> float: + def expected_price(self, good_pbk: Address, marginal_utility: float, is_seller: bool, share_of_tx_fee: float) -> float: """ Compute expectation of the price for the good given a constraint. @@ -284,7 +285,7 @@ def expected_price(self, good_pbk: str, marginal_utility: float, is_seller: bool expected_price = good_price_model.get_price_expectation(constraint, is_seller) return expected_price - def _update_price(self, good_pbk: str, price: float, is_accepted: bool) -> None: + def _update_price(self, good_pbk: Address, price: float, is_accepted: bool) -> None: """ Update the price for the good based on an outcome. diff --git a/tac/agents/participant/v1/base/transaction_manager.py b/tac/agents/participant/v1/base/transaction_manager.py index 999cb7ec..a2ff46f3 100644 --- a/tac/agents/participant/v1/base/transaction_manager.py +++ b/tac/agents/participant/v1/base/transaction_manager.py @@ -26,7 +26,7 @@ from typing import Dict, Tuple, Deque from tac.agents.participant.v1.base.dialogues import DialogueLabel -from tac.platform.game.base import Transaction +from tac.platform.game.base import Transaction, TransactionId logger = logging.getLogger(__name__) @@ -92,7 +92,7 @@ def cleanup_pending_transactions(self) -> None: break next_date, next_item = queue[0] - def _register_transaction_with_time(self, transaction_id: str) -> None: + def _register_transaction_with_time(self, transaction_id: TransactionId) -> None: """ Register a transaction with a creation datetime. @@ -178,7 +178,7 @@ def add_locked_tx(self, transaction: Transaction, as_seller: bool) -> None: else: self.locked_txs_as_buyer[transaction_id] = transaction - def pop_locked_tx(self, transaction_id: str) -> Transaction: + def pop_locked_tx(self, transaction_id: TransactionId) -> Transaction: """ Remove a lock (in the form of a transaction). diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index 89111714..429fd2d2 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -40,6 +40,7 @@ Endowment = List[int] # an element e_j is the endowment of good j. UtilityParams = List[float] # an element u_j is the utility value of good j. +TransactionId = str logger = logging.getLogger(__name__) @@ -200,8 +201,8 @@ def _check_consistency(self) -> None: class Transaction: """Convenience representation of a transaction.""" - def __init__(self, transaction_id: str, is_sender_buyer: bool, counterparty: str, - amount: float, quantities_by_good_pbk: Dict[str, int], sender: str) -> None: + def __init__(self, transaction_id: TransactionId, is_sender_buyer: bool, counterparty: Address, + amount: float, quantities_by_good_pbk: Dict[str, int], sender: Address) -> None: """ Instantiate transaction request. @@ -276,8 +277,8 @@ def from_dict(cls, d: Dict[str, Any]) -> 'Transaction': ) @classmethod - def from_proposal(cls, proposal: Description, transaction_id: str, - is_sender_buyer: bool, counterparty: str, sender: str) -> 'Transaction': + def from_proposal(cls, proposal: Description, transaction_id: TransactionId, + is_sender_buyer: bool, counterparty: Address, sender: Address) -> 'Transaction': """ Create a transaction from a proposal. @@ -330,7 +331,7 @@ def matches(self, other: 'Transaction') -> bool: class GameData: """Convenience representation of the game data.""" - def __init__(self, sender: str, money: float, endowment: List[int], utility_params: List[float], + def __init__(self, sender: Address, money: float, endowment: List[int], utility_params: List[float], nb_agents: int, nb_goods: int, tx_fee: float, agent_pbk_to_name: Dict[str, str], good_pbk_to_name: Dict[str, str]) -> None: """ Initialize a game data object. diff --git a/tac/tac_pb2.py b/tac/tac_pb2.py deleted file mode 100644 index 3de6d28b..00000000 --- a/tac/tac_pb2.py +++ /dev/null @@ -1,1022 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: tac.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='tac.proto', - package='fetch.oef.pb', - syntax='proto3', - serialized_pb=_b('\n\ttac.proto\x12\x0c\x66\x65tch.oef.pb\x1a\x1cgoogle/protobuf/struct.proto\"+\n\nStrIntPair\x12\r\n\x05\x66irst\x18\x01 \x01(\t\x12\x0e\n\x06second\x18\x02 \x01(\x05\"+\n\nStrStrPair\x12\r\n\x05\x66irst\x18\x01 \x01(\t\x12\x0e\n\x06second\x18\x02 \x01(\t\"\xe9\n\n\rTACController\x1a\x0c\n\nRegistered\x1a\x0e\n\x0cUnregistered\x1a\x0b\n\tCancelled\x1a\xe2\x01\n\x08GameData\x12\r\n\x05money\x18\x01 \x01(\x01\x12\x11\n\tendowment\x18\x02 \x03(\x05\x12\x16\n\x0eutility_params\x18\x03 \x03(\x01\x12\x11\n\tnb_agents\x18\x04 \x01(\x05\x12\x10\n\x08nb_goods\x18\x05 \x01(\x05\x12\x0e\n\x06tx_fee\x18\x06 \x01(\x01\x12\x33\n\x11\x61gent_pbk_to_name\x18\x07 \x03(\x0b\x32\x18.fetch.oef.pb.StrStrPair\x12\x32\n\x10good_pbk_to_name\x18\x08 \x03(\x0b\x32\x18.fetch.oef.pb.StrStrPair\x1a\x31\n\x17TransactionConfirmation\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x1a{\n\x0bStateUpdate\x12;\n\rinitial_state\x18\x01 \x01(\x0b\x32$.fetch.oef.pb.TACController.GameData\x12/\n\x03txs\x18\x02 \x03(\x0b\x32\".fetch.oef.pb.TACAgent.Transaction\x1a\x93\x03\n\x05\x45rror\x12?\n\nerror_code\x18\x01 \x01(\x0e\x32+.fetch.oef.pb.TACController.Error.ErrorCode\x12\x11\n\terror_msg\x18\x02 \x01(\t\x12(\n\x07\x64\x65tails\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x8b\x02\n\tErrorCode\x12\x11\n\rGENERIC_ERROR\x10\x00\x12\x15\n\x11REQUEST_NOT_VALID\x10\x01\x12 \n\x1c\x41GENT_PBK_ALREADY_REGISTERED\x10\x02\x12!\n\x1d\x41GENT_NAME_ALREADY_REGISTERED\x10\x03\x12\x18\n\x14\x41GENT_NOT_REGISTERED\x10\x04\x12\x19\n\x15TRANSACTION_NOT_VALID\x10\x05\x12\x1c\n\x18TRANSACTION_NOT_MATCHING\x10\x06\x12\x1f\n\x1b\x41GENT_NAME_NOT_IN_WHITELIST\x10\x07\x12\x1b\n\x17\x43OMPETITION_NOT_RUNNING\x10\x08\x1a\xcc\x03\n\x07Message\x12<\n\nregistered\x18\x01 \x01(\x0b\x32&.fetch.oef.pb.TACController.RegisteredH\x00\x12@\n\x0cunregistered\x18\x02 \x01(\x0b\x32(.fetch.oef.pb.TACController.UnregisteredH\x00\x12:\n\tcancelled\x18\x03 \x01(\x0b\x32%.fetch.oef.pb.TACController.CancelledH\x00\x12\x39\n\tgame_data\x18\x04 \x01(\x0b\x32$.fetch.oef.pb.TACController.GameDataH\x00\x12N\n\x0ftx_confirmation\x18\x05 \x01(\x0b\x32\x33.fetch.oef.pb.TACController.TransactionConfirmationH\x00\x12?\n\x0cstate_update\x18\x06 \x01(\x0b\x32\'.fetch.oef.pb.TACController.StateUpdateH\x00\x12\x32\n\x05\x65rror\x18\x07 \x01(\x0b\x32!.fetch.oef.pb.TACController.ErrorH\x00\x42\x05\n\x03msg\x1a\x33\n\rSignedMessage\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x64 \x01(\x0c\"\x89\x04\n\x08TACAgent\x1a\x1e\n\x08Register\x12\x12\n\nagent_name\x18\x01 \x01(\t\x1a\x0c\n\nUnregister\x1a\x88\x01\n\x0bTransaction\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\r\n\x05\x62uyer\x18\x02 \x01(\x08\x12\x14\n\x0c\x63ounterparty\x18\x03 \x01(\t\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x01\x12,\n\nquantities\x18\x05 \x03(\x0b\x32\x18.fetch.oef.pb.StrIntPair\x1a\x10\n\x0eGetStateUpdate\x1a\xfc\x01\n\x07Message\x12\x33\n\x08register\x18\x01 \x01(\x0b\x32\x1f.fetch.oef.pb.TACAgent.RegisterH\x00\x12\x37\n\nunregister\x18\x02 \x01(\x0b\x32!.fetch.oef.pb.TACAgent.UnregisterH\x00\x12\x39\n\x0btransaction\x18\x03 \x01(\x0b\x32\".fetch.oef.pb.TACAgent.TransactionH\x00\x12\x41\n\x10get_state_update\x18\x04 \x01(\x0b\x32%.fetch.oef.pb.TACAgent.GetStateUpdateH\x00\x42\x05\n\x03msg\x1a\x33\n\rSignedMessage\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x64 \x01(\x0c\x62\x06proto3') - , - dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,]) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - -_TACCONTROLLER_ERROR_ERRORCODE = _descriptor.EnumDescriptor( - name='ErrorCode', - full_name='fetch.oef.pb.TACController.Error.ErrorCode', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='GENERIC_ERROR', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='REQUEST_NOT_VALID', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='AGENT_PBK_ALREADY_REGISTERED', index=2, number=2, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='AGENT_NAME_ALREADY_REGISTERED', index=3, number=3, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='AGENT_NOT_REGISTERED', index=4, number=4, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='TRANSACTION_NOT_VALID', index=5, number=5, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='TRANSACTION_NOT_MATCHING', index=6, number=6, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='AGENT_NAME_NOT_IN_WHITELIST', index=7, number=7, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='COMPETITION_NOT_RUNNING', index=8, number=8, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=750, - serialized_end=1017, -) -_sym_db.RegisterEnumDescriptor(_TACCONTROLLER_ERROR_ERRORCODE) - - -_STRINTPAIR = _descriptor.Descriptor( - name='StrIntPair', - full_name='fetch.oef.pb.StrIntPair', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='first', full_name='fetch.oef.pb.StrIntPair.first', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='second', full_name='fetch.oef.pb.StrIntPair.second', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=57, - serialized_end=100, -) - - -_STRSTRPAIR = _descriptor.Descriptor( - name='StrStrPair', - full_name='fetch.oef.pb.StrStrPair', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='first', full_name='fetch.oef.pb.StrStrPair.first', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='second', full_name='fetch.oef.pb.StrStrPair.second', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=102, - serialized_end=145, -) - - -_TACCONTROLLER_REGISTERED = _descriptor.Descriptor( - name='Registered', - full_name='fetch.oef.pb.TACController.Registered', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=165, - serialized_end=177, -) - -_TACCONTROLLER_UNREGISTERED = _descriptor.Descriptor( - name='Unregistered', - full_name='fetch.oef.pb.TACController.Unregistered', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=179, - serialized_end=193, -) - -_TACCONTROLLER_CANCELLED = _descriptor.Descriptor( - name='Cancelled', - full_name='fetch.oef.pb.TACController.Cancelled', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=195, - serialized_end=206, -) - -_TACCONTROLLER_GAMEDATA = _descriptor.Descriptor( - name='GameData', - full_name='fetch.oef.pb.TACController.GameData', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='money', full_name='fetch.oef.pb.TACController.GameData.money', index=0, - number=1, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='endowment', full_name='fetch.oef.pb.TACController.GameData.endowment', index=1, - number=2, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='utility_params', full_name='fetch.oef.pb.TACController.GameData.utility_params', index=2, - number=3, type=1, cpp_type=5, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='nb_agents', full_name='fetch.oef.pb.TACController.GameData.nb_agents', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='nb_goods', full_name='fetch.oef.pb.TACController.GameData.nb_goods', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='tx_fee', full_name='fetch.oef.pb.TACController.GameData.tx_fee', index=5, - number=6, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='agent_pbk_to_name', full_name='fetch.oef.pb.TACController.GameData.agent_pbk_to_name', index=6, - number=7, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='good_pbk_to_name', full_name='fetch.oef.pb.TACController.GameData.good_pbk_to_name', index=7, - number=8, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=209, - serialized_end=435, -) - -_TACCONTROLLER_TRANSACTIONCONFIRMATION = _descriptor.Descriptor( - name='TransactionConfirmation', - full_name='fetch.oef.pb.TACController.TransactionConfirmation', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='transaction_id', full_name='fetch.oef.pb.TACController.TransactionConfirmation.transaction_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=437, - serialized_end=486, -) - -_TACCONTROLLER_STATEUPDATE = _descriptor.Descriptor( - name='StateUpdate', - full_name='fetch.oef.pb.TACController.StateUpdate', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='initial_state', full_name='fetch.oef.pb.TACController.StateUpdate.initial_state', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='txs', full_name='fetch.oef.pb.TACController.StateUpdate.txs', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=488, - serialized_end=611, -) - -_TACCONTROLLER_ERROR = _descriptor.Descriptor( - name='Error', - full_name='fetch.oef.pb.TACController.Error', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='error_code', full_name='fetch.oef.pb.TACController.Error.error_code', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='error_msg', full_name='fetch.oef.pb.TACController.Error.error_msg', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='details', full_name='fetch.oef.pb.TACController.Error.details', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - _TACCONTROLLER_ERROR_ERRORCODE, - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=614, - serialized_end=1017, -) - -_TACCONTROLLER_MESSAGE = _descriptor.Descriptor( - name='Message', - full_name='fetch.oef.pb.TACController.Message', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='registered', full_name='fetch.oef.pb.TACController.Message.registered', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='unregistered', full_name='fetch.oef.pb.TACController.Message.unregistered', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='cancelled', full_name='fetch.oef.pb.TACController.Message.cancelled', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='game_data', full_name='fetch.oef.pb.TACController.Message.game_data', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='tx_confirmation', full_name='fetch.oef.pb.TACController.Message.tx_confirmation', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='state_update', full_name='fetch.oef.pb.TACController.Message.state_update', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='error', full_name='fetch.oef.pb.TACController.Message.error', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='msg', full_name='fetch.oef.pb.TACController.Message.msg', - index=0, containing_type=None, fields=[]), - ], - serialized_start=1020, - serialized_end=1480, -) - -_TACCONTROLLER_SIGNEDMESSAGE = _descriptor.Descriptor( - name='SignedMessage', - full_name='fetch.oef.pb.TACController.SignedMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='signature', full_name='fetch.oef.pb.TACController.SignedMessage.signature', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='message', full_name='fetch.oef.pb.TACController.SignedMessage.message', index=1, - number=100, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1482, - serialized_end=1533, -) - -_TACCONTROLLER = _descriptor.Descriptor( - name='TACController', - full_name='fetch.oef.pb.TACController', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[_TACCONTROLLER_REGISTERED, _TACCONTROLLER_UNREGISTERED, _TACCONTROLLER_CANCELLED, _TACCONTROLLER_GAMEDATA, _TACCONTROLLER_TRANSACTIONCONFIRMATION, _TACCONTROLLER_STATEUPDATE, _TACCONTROLLER_ERROR, _TACCONTROLLER_MESSAGE, _TACCONTROLLER_SIGNEDMESSAGE, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=148, - serialized_end=1533, -) - - -_TACAGENT_REGISTER = _descriptor.Descriptor( - name='Register', - full_name='fetch.oef.pb.TACAgent.Register', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='agent_name', full_name='fetch.oef.pb.TACAgent.Register.agent_name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1548, - serialized_end=1578, -) - -_TACAGENT_UNREGISTER = _descriptor.Descriptor( - name='Unregister', - full_name='fetch.oef.pb.TACAgent.Unregister', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1580, - serialized_end=1592, -) - -_TACAGENT_TRANSACTION = _descriptor.Descriptor( - name='Transaction', - full_name='fetch.oef.pb.TACAgent.Transaction', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='transaction_id', full_name='fetch.oef.pb.TACAgent.Transaction.transaction_id', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='buyer', full_name='fetch.oef.pb.TACAgent.Transaction.buyer', index=1, - number=2, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='counterparty', full_name='fetch.oef.pb.TACAgent.Transaction.counterparty', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='amount', full_name='fetch.oef.pb.TACAgent.Transaction.amount', index=3, - number=4, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='quantities', full_name='fetch.oef.pb.TACAgent.Transaction.quantities', index=4, - number=5, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1595, - serialized_end=1731, -) - -_TACAGENT_GETSTATEUPDATE = _descriptor.Descriptor( - name='GetStateUpdate', - full_name='fetch.oef.pb.TACAgent.GetStateUpdate', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1733, - serialized_end=1749, -) - -_TACAGENT_MESSAGE = _descriptor.Descriptor( - name='Message', - full_name='fetch.oef.pb.TACAgent.Message', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='register', full_name='fetch.oef.pb.TACAgent.Message.register', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='unregister', full_name='fetch.oef.pb.TACAgent.Message.unregister', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='transaction', full_name='fetch.oef.pb.TACAgent.Message.transaction', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='get_state_update', full_name='fetch.oef.pb.TACAgent.Message.get_state_update', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name='msg', full_name='fetch.oef.pb.TACAgent.Message.msg', - index=0, containing_type=None, fields=[]), - ], - serialized_start=1752, - serialized_end=2004, -) - -_TACAGENT_SIGNEDMESSAGE = _descriptor.Descriptor( - name='SignedMessage', - full_name='fetch.oef.pb.TACAgent.SignedMessage', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='signature', full_name='fetch.oef.pb.TACAgent.SignedMessage.signature', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='message', full_name='fetch.oef.pb.TACAgent.SignedMessage.message', index=1, - number=100, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1482, - serialized_end=1533, -) - -_TACAGENT = _descriptor.Descriptor( - name='TACAgent', - full_name='fetch.oef.pb.TACAgent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[_TACAGENT_REGISTER, _TACAGENT_UNREGISTER, _TACAGENT_TRANSACTION, _TACAGENT_GETSTATEUPDATE, _TACAGENT_MESSAGE, _TACAGENT_SIGNEDMESSAGE, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1536, - serialized_end=2057, -) - -_TACCONTROLLER_REGISTERED.containing_type = _TACCONTROLLER -_TACCONTROLLER_UNREGISTERED.containing_type = _TACCONTROLLER -_TACCONTROLLER_CANCELLED.containing_type = _TACCONTROLLER -_TACCONTROLLER_GAMEDATA.fields_by_name['agent_pbk_to_name'].message_type = _STRSTRPAIR -_TACCONTROLLER_GAMEDATA.fields_by_name['good_pbk_to_name'].message_type = _STRSTRPAIR -_TACCONTROLLER_GAMEDATA.containing_type = _TACCONTROLLER -_TACCONTROLLER_TRANSACTIONCONFIRMATION.containing_type = _TACCONTROLLER -_TACCONTROLLER_STATEUPDATE.fields_by_name['initial_state'].message_type = _TACCONTROLLER_GAMEDATA -_TACCONTROLLER_STATEUPDATE.fields_by_name['txs'].message_type = _TACAGENT_TRANSACTION -_TACCONTROLLER_STATEUPDATE.containing_type = _TACCONTROLLER -_TACCONTROLLER_ERROR.fields_by_name['error_code'].enum_type = _TACCONTROLLER_ERROR_ERRORCODE -_TACCONTROLLER_ERROR.fields_by_name['details'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT -_TACCONTROLLER_ERROR.containing_type = _TACCONTROLLER -_TACCONTROLLER_ERROR_ERRORCODE.containing_type = _TACCONTROLLER_ERROR -_TACCONTROLLER_MESSAGE.fields_by_name['registered'].message_type = _TACCONTROLLER_REGISTERED -_TACCONTROLLER_MESSAGE.fields_by_name['unregistered'].message_type = _TACCONTROLLER_UNREGISTERED -_TACCONTROLLER_MESSAGE.fields_by_name['cancelled'].message_type = _TACCONTROLLER_CANCELLED -_TACCONTROLLER_MESSAGE.fields_by_name['game_data'].message_type = _TACCONTROLLER_GAMEDATA -_TACCONTROLLER_MESSAGE.fields_by_name['tx_confirmation'].message_type = _TACCONTROLLER_TRANSACTIONCONFIRMATION -_TACCONTROLLER_MESSAGE.fields_by_name['state_update'].message_type = _TACCONTROLLER_STATEUPDATE -_TACCONTROLLER_MESSAGE.fields_by_name['error'].message_type = _TACCONTROLLER_ERROR -_TACCONTROLLER_MESSAGE.containing_type = _TACCONTROLLER -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['registered']) -_TACCONTROLLER_MESSAGE.fields_by_name['registered'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['unregistered']) -_TACCONTROLLER_MESSAGE.fields_by_name['unregistered'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['cancelled']) -_TACCONTROLLER_MESSAGE.fields_by_name['cancelled'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['game_data']) -_TACCONTROLLER_MESSAGE.fields_by_name['game_data'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['tx_confirmation']) -_TACCONTROLLER_MESSAGE.fields_by_name['tx_confirmation'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['state_update']) -_TACCONTROLLER_MESSAGE.fields_by_name['state_update'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACCONTROLLER_MESSAGE.fields_by_name['error']) -_TACCONTROLLER_MESSAGE.fields_by_name['error'].containing_oneof = _TACCONTROLLER_MESSAGE.oneofs_by_name['msg'] -_TACCONTROLLER_SIGNEDMESSAGE.containing_type = _TACCONTROLLER -_TACAGENT_REGISTER.containing_type = _TACAGENT -_TACAGENT_UNREGISTER.containing_type = _TACAGENT -_TACAGENT_TRANSACTION.fields_by_name['quantities'].message_type = _STRINTPAIR -_TACAGENT_TRANSACTION.containing_type = _TACAGENT -_TACAGENT_GETSTATEUPDATE.containing_type = _TACAGENT -_TACAGENT_MESSAGE.fields_by_name['register'].message_type = _TACAGENT_REGISTER -_TACAGENT_MESSAGE.fields_by_name['unregister'].message_type = _TACAGENT_UNREGISTER -_TACAGENT_MESSAGE.fields_by_name['transaction'].message_type = _TACAGENT_TRANSACTION -_TACAGENT_MESSAGE.fields_by_name['get_state_update'].message_type = _TACAGENT_GETSTATEUPDATE -_TACAGENT_MESSAGE.containing_type = _TACAGENT -_TACAGENT_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACAGENT_MESSAGE.fields_by_name['register']) -_TACAGENT_MESSAGE.fields_by_name['register'].containing_oneof = _TACAGENT_MESSAGE.oneofs_by_name['msg'] -_TACAGENT_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACAGENT_MESSAGE.fields_by_name['unregister']) -_TACAGENT_MESSAGE.fields_by_name['unregister'].containing_oneof = _TACAGENT_MESSAGE.oneofs_by_name['msg'] -_TACAGENT_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACAGENT_MESSAGE.fields_by_name['transaction']) -_TACAGENT_MESSAGE.fields_by_name['transaction'].containing_oneof = _TACAGENT_MESSAGE.oneofs_by_name['msg'] -_TACAGENT_MESSAGE.oneofs_by_name['msg'].fields.append( - _TACAGENT_MESSAGE.fields_by_name['get_state_update']) -_TACAGENT_MESSAGE.fields_by_name['get_state_update'].containing_oneof = _TACAGENT_MESSAGE.oneofs_by_name['msg'] -_TACAGENT_SIGNEDMESSAGE.containing_type = _TACAGENT -DESCRIPTOR.message_types_by_name['StrIntPair'] = _STRINTPAIR -DESCRIPTOR.message_types_by_name['StrStrPair'] = _STRSTRPAIR -DESCRIPTOR.message_types_by_name['TACController'] = _TACCONTROLLER -DESCRIPTOR.message_types_by_name['TACAgent'] = _TACAGENT - -StrIntPair = _reflection.GeneratedProtocolMessageType('StrIntPair', (_message.Message,), dict( - DESCRIPTOR = _STRINTPAIR, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.StrIntPair) - )) -_sym_db.RegisterMessage(StrIntPair) - -StrStrPair = _reflection.GeneratedProtocolMessageType('StrStrPair', (_message.Message,), dict( - DESCRIPTOR = _STRSTRPAIR, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.StrStrPair) - )) -_sym_db.RegisterMessage(StrStrPair) - -TACController = _reflection.GeneratedProtocolMessageType('TACController', (_message.Message,), dict( - - Registered = _reflection.GeneratedProtocolMessageType('Registered', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_REGISTERED, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.Registered) - )) - , - - Unregistered = _reflection.GeneratedProtocolMessageType('Unregistered', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_UNREGISTERED, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.Unregistered) - )) - , - - Cancelled = _reflection.GeneratedProtocolMessageType('Cancelled', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_CANCELLED, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.Cancelled) - )) - , - - GameData = _reflection.GeneratedProtocolMessageType('GameData', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_GAMEDATA, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.GameData) - )) - , - - TransactionConfirmation = _reflection.GeneratedProtocolMessageType('TransactionConfirmation', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_TRANSACTIONCONFIRMATION, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.TransactionConfirmation) - )) - , - - StateUpdate = _reflection.GeneratedProtocolMessageType('StateUpdate', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_STATEUPDATE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.StateUpdate) - )) - , - - Error = _reflection.GeneratedProtocolMessageType('Error', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_ERROR, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.Error) - )) - , - - Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_MESSAGE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.Message) - )) - , - - SignedMessage = _reflection.GeneratedProtocolMessageType('SignedMessage', (_message.Message,), dict( - DESCRIPTOR = _TACCONTROLLER_SIGNEDMESSAGE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController.SignedMessage) - )) - , - DESCRIPTOR = _TACCONTROLLER, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACController) - )) -_sym_db.RegisterMessage(TACController) -_sym_db.RegisterMessage(TACController.Registered) -_sym_db.RegisterMessage(TACController.Unregistered) -_sym_db.RegisterMessage(TACController.Cancelled) -_sym_db.RegisterMessage(TACController.GameData) -_sym_db.RegisterMessage(TACController.TransactionConfirmation) -_sym_db.RegisterMessage(TACController.StateUpdate) -_sym_db.RegisterMessage(TACController.Error) -_sym_db.RegisterMessage(TACController.Message) -_sym_db.RegisterMessage(TACController.SignedMessage) - -TACAgent = _reflection.GeneratedProtocolMessageType('TACAgent', (_message.Message,), dict( - - Register = _reflection.GeneratedProtocolMessageType('Register', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_REGISTER, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.Register) - )) - , - - Unregister = _reflection.GeneratedProtocolMessageType('Unregister', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_UNREGISTER, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.Unregister) - )) - , - - Transaction = _reflection.GeneratedProtocolMessageType('Transaction', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_TRANSACTION, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.Transaction) - )) - , - - GetStateUpdate = _reflection.GeneratedProtocolMessageType('GetStateUpdate', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_GETSTATEUPDATE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.GetStateUpdate) - )) - , - - Message = _reflection.GeneratedProtocolMessageType('Message', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_MESSAGE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.Message) - )) - , - - SignedMessage = _reflection.GeneratedProtocolMessageType('SignedMessage', (_message.Message,), dict( - DESCRIPTOR = _TACAGENT_SIGNEDMESSAGE, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent.SignedMessage) - )) - , - DESCRIPTOR = _TACAGENT, - __module__ = 'tac_pb2' - # @@protoc_insertion_point(class_scope:fetch.oef.pb.TACAgent) - )) -_sym_db.RegisterMessage(TACAgent) -_sym_db.RegisterMessage(TACAgent.Register) -_sym_db.RegisterMessage(TACAgent.Unregister) -_sym_db.RegisterMessage(TACAgent.Transaction) -_sym_db.RegisterMessage(TACAgent.GetStateUpdate) -_sym_db.RegisterMessage(TACAgent.Message) -_sym_db.RegisterMessage(TACAgent.SignedMessage) - - -# @@protoc_insertion_point(module_scope) From 2348a359a5131b3b94295d44aa142eb68278282e Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Sun, 25 Aug 2019 11:02:14 +0100 Subject: [PATCH 082/107] Further fixes. --- tac/agents/controller/base/handlers.py | 20 ++++++++++---------- tac/agents/controller/base/states.py | 3 +-- tac/platform/game/base.py | 10 +++++----- tests/test_controller.py | 2 +- tests/test_game.py | 3 +-- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 6717bb71..e960b22a 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -112,19 +112,19 @@ def handle(self, message: TACMessage, sender: Address) -> None: logger.error("[{}]: Agent name not in whitelist: '{}'".format(self.controller_agent.name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) if sender in self.controller_agent.game_handler.registered_agents: logger.error("[{}]: Agent already registered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender])) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_PBK_ALREADY_REGISTERED) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) if agent_name in self.controller_agent.game_handler.agent_pbk_to_name.values(): logger.error("[{}]: Agent with this name already registered: '{}'".format(self.controller_agent.name, agent_name)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) try: self.controller_agent.game_handler.monitor.dashboard.agent_pbk_to_name.update({sender: agent_name}) @@ -158,7 +158,7 @@ def handle(self, message: TACMessage, sender: Address) -> None: logger.error("[{}]: Agent not registered: '{}'".format(self.controller_agent.name, sender)) tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.AGENT_NOT_REGISTERED) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) else: logger.debug("[{}]: Agent unregistered: '{}'".format(self.controller_agent.name, self.controller_agent.game_handler.agent_pbk_to_name[sender])) self.controller_agent.game_handler.registered_agents.remove(sender) @@ -188,7 +188,7 @@ def handle(self, message: TACMessage, sender: Address) -> None: # if transaction arrives first time then put it into the pending pool if message.get("transaction_id") not in self._pending_transaction_requests: - if self.controller_agent.game_handler.current_game.is_transaction_valid(message): + if self.controller_agent.game_handler.current_game.is_transaction_valid(transaction): logger.debug("[{}]: Put transaction TACMessage in the pool: {}".format(self.controller_agent.name, message.get("transaction_id"))) self._pending_transaction_requests[message.get("transaction_id")] = transaction else: @@ -228,8 +228,8 @@ def _handle_valid_transaction(self, message: TACMessage, sender: Address, transa # send the transaction confirmation. tac_msg = TACMessage(tac_type=TACMessage.Type.TRANSACTION_CONFIRMATION, transaction_id=message.get("transaction_id")) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) - self.controller_agent.outbox.put_message(to=message.get("counterparty"), sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) + self.controller_agent.outbox.put_message(to=message.get("counterparty"), sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) # log messages logger.debug("[{}]: Transaction '{}' settled successfully.".format(self.controller_agent.name, message.get("transaction_id"))) @@ -240,13 +240,13 @@ def _handle_invalid_transaction(self, message: TACMessage, sender: Address) -> N """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")}) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) def _handle_non_matching_transaction(self, message: TACMessage, sender: Address) -> None: """Handle non-matching transaction.""" tac_msg = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.TRANSACTION_NOT_MATCHING) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) class GetStateUpdateHandler(TACMessageHandler): @@ -274,7 +274,7 @@ def handle(self, message: TACMessage, sender: Address) -> None: initial_game_data = self.controller_agent.game_handler.game_data_per_participant[sender] # type: Dict tac_msg = TACMessage(tac_type=TACMessage.Type.STATE_UPDATE, initial_state=initial_game_data, transactions=transactions) tac_bytes = TACSerializer().encode(tac_msg) - self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=message.protocol_id, message=tac_bytes) + self.controller_agent.mailbox.outbox.put_message(to=sender, sender=self.controller_agent.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) class AgentMessageDispatcher(object): diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index 690d51ff..3a4392fb 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -29,7 +29,6 @@ import logging from typing import List, Dict, Any -from aea.crypto.base import Crypto from aea.mail.base import Address from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState, Transaction @@ -511,7 +510,7 @@ def to_dict(self) -> Dict[str, Any]: } @classmethod - def from_dict(cls, d: Dict[str, Any], crypto: Crypto) -> 'Game': + def from_dict(cls, d: Dict[str, Any]) -> 'Game': """Get class instance from dictionary.""" configuration = GameConfiguration.from_dict(d["configuration"]) initialization = GameInitialization.from_dict(d["initialization"]) diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index 429fd2d2..3eb9a5d6 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -220,23 +220,23 @@ def __init__(self, transaction_id: TransactionId, is_sender_buyer: bool, counter self.counterparty = counterparty self.amount = amount self.quantities_by_good_pbk = quantities_by_good_pbk - self.sender = sender + self._sender = sender self._check_consistency() @property - def sender(self): + def sender(self) -> Address: """Get the sender public key.""" - return self.sender + return self._sender @property - def buyer_pbk(self) -> str: + def buyer_pbk(self) -> Address: """Get the publick key of the buyer.""" result = self.sender if self.is_sender_buyer else self.counterparty return result @property - def seller_pbk(self) -> str: + def seller_pbk(self) -> Address: """Get the publick key of the seller.""" result = self.counterparty if self.is_sender_buyer else self.sender return result diff --git a/tests/test_controller.py b/tests/test_controller.py index c604d018..61b3aa88 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -101,6 +101,6 @@ def test_agent_receives_cancelled_message(self): counter = 0 while not self.agent1.inbox.empty(): counter += 1 - msg = self.inbox.get_nowait() + msg = self.agent1.inbox.get_nowait() assert msg is not None assert counter == 1 diff --git a/tests/test_game.py b/tests/test_game.py index b72f3cd3..5f21f7eb 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -21,7 +21,6 @@ import pytest -from aea.crypto.base import Crypto from tac.agents.controller.base.states import GameInitialization, Game from tac.agents.participant.v1.base.states import AgentState from tac.platform.game.base import GameConfiguration, GoodState, Transaction @@ -615,7 +614,7 @@ def test_from_dict(self): expected_game.settle_transaction(transaction_1) expected_game.settle_transaction(transaction_2) - actual_game = Game.from_dict(expected_game.to_dict(), Crypto()) + actual_game = Game.from_dict(expected_game.to_dict()) assert actual_game == expected_game From 0424e50a5d5dc6409b0e969f80db0a6302a5420f Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sun, 25 Aug 2019 19:16:25 +0100 Subject: [PATCH 083/107] fix controller and game examples. --- tac/agents/controller/base/states.py | 4 ++-- tac/platform/game/base.py | 11 +++++++++++ tests/common.py | 4 ---- tests/test_controller.py | 17 +++++++---------- tests/test_game.py | 7 ++++--- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/tac/agents/controller/base/states.py b/tac/agents/controller/base/states.py index 3a4392fb..75350ae0 100644 --- a/tac/agents/controller/base/states.py +++ b/tac/agents/controller/base/states.py @@ -48,7 +48,7 @@ class GameInitialization: """Class containing the game initialization of a TAC instance.""" def __init__(self, - initial_money_amounts: List[int], + initial_money_amounts: List[float], endowments: List[Endowment], utility_params: List[UtilityParams], eq_prices: List[float], @@ -80,7 +80,7 @@ def __init__(self, self._check_consistency() @property - def initial_money_amounts(self) -> List[int]: + def initial_money_amounts(self) -> List[float]: """Get list of the initial amount of money of every agent.""" return self._initial_money_amounts diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index 3eb9a5d6..c4498533 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -327,6 +327,17 @@ def matches(self, other: 'Transaction') -> bool: return result + def __eq__(self, other): + return isinstance(other, Transaction) \ + and self.transaction_id == other.transaction_id \ + and self.is_sender_buyer == other.is_sender_buyer \ + and self.counterparty == other.counterparty \ + and self.amount == other.amount \ + and self.quantities_by_good_pbk == other.quantities_by_good_pbk \ + and self.sender == other.sender \ + and self.buyer_pbk == other.buyer_pbk \ + and self.seller_pbk == other.seller_pbk + class GameData: """Convenience representation of the game data.""" diff --git a/tests/common.py b/tests/common.py index 814e7047..f49c3ba0 100644 --- a/tests/common.py +++ b/tests/common.py @@ -24,7 +24,3 @@ class TOEFAgent(OEFMailBox): """An OEF agent for testing.""" - - def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): - """Initialize.""" - super().__init__(public_key, oef_addr, oef_port) diff --git a/tests/test_controller.py b/tests/test_controller.py index 61b3aa88..f14d8bc2 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -73,24 +73,21 @@ def setup_class(cls): tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) + job = Thread(target=cls.controller_agent.start) + job.start() + crypto = Crypto() cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000) cls.agent1.connect() - job = Thread(target=cls.controller_agent.start) - job.start() - agent_job = Thread(target=cls.agent1.run) - agent_job.start() - - tac_msg = TACMessage(type=TACMessage.Type.REGISTER, agent_name='agent_name') + tac_msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name='agent_name') tac_bytes = TACSerializer().encode(tac_msg) cls.agent1.outbox.put_message(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) - time.sleep(10.0) + time.sleep(5.0) job.join() - cls.agent1.stop() - agent_job.join() + cls.agent1.disconnect() def test_only_one_agent_registered(self): """Test exactly one agent is registered.""" @@ -102,5 +99,5 @@ def test_agent_receives_cancelled_message(self): while not self.agent1.inbox.empty(): counter += 1 msg = self.agent1.inbox.get_nowait() - assert msg is not None + assert msg is not None and msg.sender == self.controller_agent.crypto.public_key assert counter == 1 diff --git a/tests/test_game.py b/tests/test_game.py index 5f21f7eb..5c4d9fed 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -18,6 +18,7 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the game module.""" +from pprint import pprint import pytest @@ -569,7 +570,7 @@ def test_from_dict(self): tx_fee = 1.0 agent_pbk_to_name = {'tac_agent_0_pbk': 'tac_agent_0', 'tac_agent_1_pbk': 'tac_agent_1', 'tac_agent_2_pbk': 'tac_agent_2'} good_pbk_to_name = {'tac_good_0_pbk': 'tac_good_0', 'tac_good_1_pbk': 'tac_good_1', 'tac_good_2_pbk': 'tac_good_2'} - money_amounts = [20, 20, 20] + money_amounts = [20.0, 20.0, 20.0] endowments = [ [1, 1, 1], [2, 1, 1], @@ -609,8 +610,8 @@ def test_from_dict(self): tx_id = 'some_tx_id' sender_pbk = 'tac_agent_0_pbk' counterparty_pbk = 'tac_agent_1_pbk' - transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) - transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10, {'tac_good_0_pbk': 1}, sender_pbk) + transaction_1 = Transaction(tx_id, True, counterparty_pbk, 10.0, {'tac_good_0_pbk': 1}, sender_pbk) + transaction_2 = Transaction(tx_id, False, counterparty_pbk, 10.0, {'tac_good_0_pbk': 1}, sender_pbk) expected_game.settle_transaction(transaction_1) expected_game.settle_transaction(transaction_2) From 7b160c188ca645972f7baf6b0b0a86cae26758c6 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sun, 25 Aug 2019 19:48:27 +0100 Subject: [PATCH 084/107] fix stylecheck. --- tac/platform/game/base.py | 1 + tests/test_game.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tac/platform/game/base.py b/tac/platform/game/base.py index c4498533..acf12a72 100644 --- a/tac/platform/game/base.py +++ b/tac/platform/game/base.py @@ -328,6 +328,7 @@ def matches(self, other: 'Transaction') -> bool: return result def __eq__(self, other): + """Compare to another object.""" return isinstance(other, Transaction) \ and self.transaction_id == other.transaction_id \ and self.is_sender_buyer == other.is_sender_buyer \ diff --git a/tests/test_game.py b/tests/test_game.py index 5c4d9fed..92a3be28 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -18,7 +18,6 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the game module.""" -from pprint import pprint import pytest From d7dde6df2201434b98498f8468deca864bc996c7 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Mon, 26 Aug 2019 23:57:58 +0100 Subject: [PATCH 085/107] bump oef sdk version to 0.6.2. --- Pipfile | 2 +- Pipfile.lock | 24 +++----- tac/agents/controller/agent.py | 1 + tac/agents/controller/base/handlers.py | 2 + tac/platform/oef_health_check.py | 76 +++++++++++++++++++++----- tests/test_controller.py | 22 +++++--- 6 files changed, 89 insertions(+), 38 deletions(-) diff --git a/Pipfile b/Pipfile index 3914eed8..c94bde93 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ flask-restful = "*" nbsphinx = "*" numpy = "*" matplotlib = "*" -oef = {version = "==0.6.0",index = "test-pypi"} +oef = {version = "==0.6.2",index = "test-pypi"} python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 60afc58b..71aa2e60 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "bd24006788c89b0a5678e335640502b980c3d377d6d6e65c26505b57cc568093" + "sha256": "e6dd69d9dff1b803678bd42f0a3e8d83f9183b31bb6dcba1248baa70dbfa191a" }, "pipfile-spec": 6, "requires": { @@ -24,7 +24,7 @@ "aea": { "editable": true, "git": "https://github.com/fetchai/agents-aea.git", - "ref": "ed2389ca853e5259c22cf9df6385eab769b05112" + "ref": "26926d2a94a5e2fdcebc82baf27316beceaafacc" }, "alabaster": { "hashes": [ @@ -405,11 +405,11 @@ }, "oef": { "hashes": [ - "sha256:1944b11632c4af841e373f52e3e96941821f6b4bb4dbc430f790c1b436880618", - "sha256:cab9e251bfd1476f332de5046a83e0c01d0ce75ddfefb1bd614359a2b904d726" + "sha256:1f2dbe6b8815fe5aec927e0c97dd9b9b286c8105284c94013e477ea8b697864e", + "sha256:5f9f2badc79d18fc8716795a6db8dc0c3b5b287dd1d7d25389f88a6b04fde78a" ], "index": "test-pypi", - "version": "==0.6.0" + "version": "==0.6.2" }, "packaging": { "hashes": [ @@ -739,14 +739,6 @@ } }, "develop": { - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -1371,10 +1363,10 @@ }, "zipp": { "hashes": [ - "sha256:4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", - "sha256:8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec" + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" ], - "version": "==0.5.2" + "version": "==0.6.0" } } } diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 4d4a47ac..53f12127 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -166,6 +166,7 @@ def stop(self) -> None: logger.debug("[{}]: Stopping myself...".format(self.name)) if self.game_handler.game_phase == GamePhase.GAME or self.game_handler.game_phase == GamePhase.GAME_SETUP: self.game_handler.notify_competition_cancelled() + time.sleep(2.0) super().stop() def start(self) -> None: diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index e960b22a..0ebd4ef7 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -36,6 +36,7 @@ import json import logging import os +import time from abc import ABC, abstractmethod from collections import defaultdict from typing import Any, Dict, Optional, List, Set, TYPE_CHECKING @@ -465,6 +466,7 @@ def notify_competition_cancelled(self): tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED) tac_bytes = TACSerializer().encode(tac_msg) self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) + time.sleep(1.0) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index 872d2422..133da044 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -22,10 +22,13 @@ """This script waits until the OEF is up and running.""" import argparse +import asyncio import logging +from typing import Optional from aea.crypto.base import Crypto from aea.channel.oef import OEFMailBox +from oef.core import AsyncioCore, Connection logger = logging.getLogger(__name__) @@ -38,35 +41,80 @@ class OEFHealthCheck(object): """A health check class.""" - def __init__(self, oef_addr: str, oef_port: int): + def __init__(self, oef_addr: str, oef_port: int, loop: Optional[asyncio.AbstractEventLoop] = None): """ Initialize. :param oef_addr: IP address of the OEF node. :param oef_port: Port of the OEF node. """ - crypto = Crypto() - self.mailbox = OEFMailBox(crypto.public_key, oef_addr=oef_addr, oef_port=oef_port) self.oef_addr = oef_addr self.oef_port = oef_port - def run(self) -> bool: + self._result = False + self._stop = False + self._conn = None + self._loop = asyncio.get_event_loop() if loop is None else loop + self._loop.set_exception_handler(self.exception_handler) + self._core = AsyncioCore(loop=self._loop) + + def exception_handler(self, loop, d): + """Handle exception during a connection attempt.""" + print("An error occurred. Details: {}".format(d)) + self._stop = True + + def on_connect_ok(self, conn=None, url=None, ex=None, conn_name=None): + """Handle a successful connection.""" + print("Connection OK!") + self._result = True + self._stop = True + + def on_connect_fail(self, conn=None, url=None, ex=None, conn_name=None): + """Handle a connection failure.""" + print("Connection failed. {}".format(ex)) + self._result = False + self._stop = True + + async def _run(self) -> bool: """ - Run the check. + Run the check, asynchronously. - :return: + :return: True if the check is successful, False otherwise. """ - result = False + self._result = False + self._stop = False + pbk = 'check' + seconds = 3.0 try: - print("Connecting to {}:{}".format(self.oef_addr, self.oef_port)) - self.mailbox.connect() - self.mailbox.disconnect() - print("OK!") - result = True - return result + print("Connecting to {}:{}...".format(self.oef_addr, self.oef_port)) + + self._conn = Connection(self._core) + self._conn.connect(url="127.0.0.1:10000", success=self.on_connect_ok, + failure=self.on_connect_fail, + public_key=pbk) + + while not self._stop and seconds > 0: + await asyncio.sleep(1.0) + seconds -= 1.0 + + if self._result: + print("Connection established. Tearing down connection...") + self._core.call_soon_async(self._conn.do_stop) + await asyncio.sleep(1.0) + else: + print("A problem occurred. Exiting...") + except Exception as e: print(str(e)) - return result + finally: + return self._result + + def run(self) -> bool: + """Run the check. + + :return: True if the check is successful, False otherwise. + """ + return self._loop.run_until_complete(self._run()) def main(oef_addr, oef_port): diff --git a/tests/test_controller.py b/tests/test_controller.py index f14d8bc2..913d2ce4 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -57,7 +57,10 @@ def test_no_registered_agents(self): job.join(10.0) assert not job.is_alive() assert len(self.controller_agent.game_handler.registered_agents) == 0 - self.controller_agent.stop() + + @classmethod + def teardown_class(cls): + cls.controller_agent.stop() class TestCompetitionStopsTooFewAgentRegistered: @@ -73,25 +76,25 @@ def setup_class(cls): tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) + cls.controller_agent.mailbox.connect() job = Thread(target=cls.controller_agent.start) job.start() - crypto = Crypto() - cls.agent1 = TOEFAgent(crypto.public_key, oef_addr='127.0.0.1', oef_port=10000) + cls.crypto = Crypto() + cls.agent1 = TOEFAgent(cls.crypto.public_key, oef_addr='127.0.0.1', oef_port=10000) cls.agent1.connect() tac_msg = TACMessage(tac_type=TACMessage.Type.REGISTER, agent_name='agent_name') tac_bytes = TACSerializer().encode(tac_msg) - cls.agent1.outbox.put_message(to=cls.controller_agent.crypto.public_key, sender=crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) - - time.sleep(5.0) + cls.agent1.outbox.put_message(to=cls.controller_agent.crypto.public_key, sender=cls.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) job.join() - cls.agent1.disconnect() def test_only_one_agent_registered(self): """Test exactly one agent is registered.""" assert len(self.controller_agent.game_handler.registered_agents) == 1 + agent_pbk = next(iter(self.controller_agent.game_handler.registered_agents)) + assert agent_pbk == self.crypto.public_key def test_agent_receives_cancelled_message(self): """Test the agent receives a cancelled message.""" @@ -101,3 +104,8 @@ def test_agent_receives_cancelled_message(self): msg = self.agent1.inbox.get_nowait() assert msg is not None and msg.sender == self.controller_agent.crypto.public_key assert counter == 1 + + @classmethod + def teardown_class(cls): + cls.controller_agent.stop() + cls.agent1.disconnect() From 8ab92389f45ff1aa4ec8f802938bb952ebb34b08 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 27 Aug 2019 00:07:13 +0100 Subject: [PATCH 086/107] wip. --- Pipfile | 2 +- Pipfile.lock | 58 +++++++++++--------------- tac/agents/controller/agent.py | 25 +++++------ tac/agents/controller/base/handlers.py | 23 +++++----- tests/test_controller.py | 53 ++++++++++++++++++++++- 5 files changed, 104 insertions(+), 57 deletions(-) diff --git a/Pipfile b/Pipfile index c94bde93..489d3b21 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ flask-restful = "*" nbsphinx = "*" numpy = "*" matplotlib = "*" -oef = {version = "==0.6.2",index = "test-pypi"} +oef = {index = "https://test.pypi.org/simple/",version = "==0.6.3"} python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 71aa2e60..7c9e4d9c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e6dd69d9dff1b803678bd42f0a3e8d83f9183b31bb6dcba1248baa70dbfa191a" + "sha256": "4b47dbf0e5072aced0b959e92c05509aa05678f8912e5789ea5cc1c53e193440" }, "pipfile-spec": 6, "requires": { @@ -24,7 +24,7 @@ "aea": { "editable": true, "git": "https://github.com/fetchai/agents-aea.git", - "ref": "26926d2a94a5e2fdcebc82baf27316beceaafacc" + "ref": "7a552b56dfe30d3e5fc2ee6d935e02ac9789a207" }, "alabaster": { "hashes": [ @@ -383,33 +383,32 @@ }, "numpy": { "hashes": [ - "sha256:03e311b0a4c9f5755da7d52161280c6a78406c7be5c5cc7facfbcebb641efb7e", - "sha256:0cdd229a53d2720d21175012ab0599665f8c9588b3b8ffa6095dd7b90f0691dd", - "sha256:312bb18e95218bedc3563f26fcc9c1c6bfaaf9d453d15942c0839acdd7e4c473", - "sha256:464b1c48baf49e8505b1bb754c47a013d2c305c5b14269b5c85ea0625b6a988a", - "sha256:5adfde7bd3ee4864536e230bcab1c673f866736698724d5d28c11a4d63672658", - "sha256:7724e9e31ee72389d522b88c0d4201f24edc34277999701ccd4a5392e7d8af61", - "sha256:8d36f7c53ae741e23f54793ffefb2912340b800476eb0a831c6eb602e204c5c4", - "sha256:910d2272403c2ea8a52d9159827dc9f7c27fb4b263749dca884e2e4a8af3b302", - "sha256:951fefe2fb73f84c620bec4e001e80a80ddaa1b84dce244ded7f1e0cbe0ed34a", - "sha256:9588c6b4157f493edeb9378788dcd02cb9e6a6aeaa518b511a1c79d06cbd8094", - "sha256:9ce8300950f2f1d29d0e49c28ebfff0d2f1e2a7444830fbb0b913c7c08f31511", - "sha256:be39cca66cc6806652da97103605c7b65ee4442c638f04ff064a7efd9a81d50a", - "sha256:c3ab2d835b95ccb59d11dfcd56eb0480daea57cdf95d686d22eff35584bc4554", - "sha256:eb0fc4a492cb896346c9e2c7a22eae3e766d407df3eb20f4ce027f23f76e4c54", - "sha256:ec0c56eae6cee6299f41e780a0280318a93db519bbb2906103c43f3e2be1206c", - "sha256:f4e4612de60a4f1c4d06c8c2857cdcb2b8b5289189a12053f37d3f41f06c60d0" + "sha256:03f2ebcbffcce2dec8860633b89a93e80c6a239d21a77ae8b241450dc21e8c35", + "sha256:078c8025da5ab9e8657edc9c2a1e9642e06e953bc7baa2e65c1aa9d9dfb7e98b", + "sha256:0fbfa98c5d5c3c6489cc1e852ec94395d51f35d9ebe70c6850e47f465038cdf4", + "sha256:1c841033f4fe6801648180c3033c45b3235a8bbd09bc7249010f99ea27bb6790", + "sha256:2c0984a01ddd0aeec89f0ce46ef21d64761048cd76c0074d0658c91f9131f154", + "sha256:4c166dcb0fff7cb3c0bbc682dfb5061852a2547efb6222e043a7932828c08fb5", + "sha256:8c2d98d0623bd63fb883b65256c00454d5f53127a5a7bcdaa8bdc582814e8cb4", + "sha256:8cb4b6ae45aad6d26712a1ce0a3f2556c5e1484867f9649e03496e45d6a5eba4", + "sha256:93050e73c446c82065b7410221b07682e475ac51887cd9368227a5d944afae80", + "sha256:a3f6b3024f8826d8b1490e6e2a9b99e841cd2c375791b1df62991bd8f4c00b89", + "sha256:bede70fd8699695363f39e86c1e869b2c8b74fb5ef135a67b9e1eeebff50322a", + "sha256:c304b2221f33489cd15a915237a84cdfe9420d7e4d4828c78a0820f9d990395c", + "sha256:f11331530f0eff69a758d62c2461cd98cdc2eae0147279d8fc86e0464eb7e8ca", + "sha256:fa5f2a8ef1e07ba258dc07d4dd246de23ef4ab920ae0f3fa2a1cc5e90f0f1888", + "sha256:fb6178b0488b0ce6a54bc4accbdf5225e937383586555604155d64773f6beb2b", + "sha256:fd5e830d4dc31658d61a6452cd3e842213594d8c15578cdae6829e36ad9c0930" ], "index": "pypi", - "version": "==1.17.0" + "version": "==1.17.1" }, "oef": { "hashes": [ - "sha256:1f2dbe6b8815fe5aec927e0c97dd9b9b286c8105284c94013e477ea8b697864e", - "sha256:5f9f2badc79d18fc8716795a6db8dc0c3b5b287dd1d7d25389f88a6b04fde78a" + "sha256:9ede6588109edb4346bf89e3804b8654e460a7070e353f762f3d9402781d45ab", + "sha256:d5254a61e3cdc88e0d8c254c72dcf4b72bda048b055f661937c5bc4c7748f40c" ], - "index": "test-pypi", - "version": "==0.6.2" + "version": "==0.6.3" }, "packaging": { "hashes": [ @@ -865,18 +864,11 @@ }, "flake8-docstrings": { "hashes": [ - "sha256:3ad372b641f4c8e70c7465f067aed4ff8bf1e9347fce14f9eb71ed816db36257", - "sha256:d8d72ccd5807c1ab9ff1466cb9bece0c4d94b8669e9bc4f472abc80dbc5d399e" + "sha256:1666dd069c9c457ee57e80af3c1a6b37b00cc1801c6fde88e455131bb2e186cd", + "sha256:9c0db5a79a1affd70fdf53b8765c8a26bf968e59e0252d7f2fc546b41c0cda06" ], "index": "pypi", - "version": "==1.3.1" - }, - "flake8-polyfill": { - "hashes": [ - "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", - "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda" - ], - "version": "==1.0.2" + "version": "==1.4.0" }, "idna": { "hashes": [ diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index 53f12127..f4074c2d 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -137,18 +137,19 @@ def react(self) -> None: :return: None """ - counter = 0 - while (not self.inbox.empty() and counter < self.max_reactions): - counter += 1 - envelope = self.inbox.get_nowait() # type: Optional[Envelope] - if envelope is not None: - if envelope.protocol_id == 'oef': - self.oef_handler.handle_oef_message(envelope) - elif envelope.protocol_id == 'tac': - self.agent_message_dispatcher.handle_agent_message(envelope) - self.last_activity = datetime.datetime.now() - else: - raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) + pass + # counter = 0 + # while (not self.inbox.empty() and counter < self.max_reactions): + # counter += 1 + # envelope = self.inbox.get_nowait() # type: Optional[Envelope] + # if envelope is not None: + # if envelope.protocol_id == 'oef': + # self.oef_handler.handle_oef_message(envelope) + # elif envelope.protocol_id == 'tac': + # self.agent_message_dispatcher.handle_agent_message(envelope) + # self.last_activity = datetime.datetime.now() + # else: + # raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) def update(self) -> None: """ diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 0ebd4ef7..13d246c5 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -309,20 +309,23 @@ def handle_agent_message(self, envelope: Envelope) -> None: assert envelope.protocol_id == "tac" tac_msg = TACSerializer().decode(envelope.message) logger.debug("[{}] on_message: origin={}" .format(self.controller_agent.name, envelope.sender)) - handle_tac_message = self.handlers.get(TACMessage.Type(tac_msg.get("type")), None) # type: TACMessageHandler + tac_msg_type = tac_msg.get("type") + handle_tac_message = self.handlers.get(TACMessage.Type(tac_msg_type), None) # type: TACMessageHandler if handle_tac_message is None: logger.debug("[{}]: Unknown message from {}".format(self.controller_agent.name, envelope.sender)) - tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.REQUEST_NOT_VALID) - tac_bytes = TACSerializer().encode(tac_error) - self.controller_agent.mailbox.outbox.put_message(to=envelope.sender, sender=self.controller_agent.crypto.public_key, protocol_id=tac_error.protocol_id, message=tac_bytes) - try: - handle_tac_message(tac_msg, envelope.sender) - except Exception as e: - logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) - logger.exception(e) - tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.GENERIC_ERROR) + tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.REQUEST_NOT_VALID.value) tac_bytes = TACSerializer().encode(tac_error) self.controller_agent.mailbox.outbox.put_message(to=envelope.sender, sender=self.controller_agent.crypto.public_key, protocol_id=tac_error.protocol_id, message=tac_bytes) + return + else: + try: + handle_tac_message(tac_msg, envelope.sender) + except Exception as e: + logger.debug("[{}]: Error caught: {}".format(self.controller_agent.name, str(e))) + logger.exception(e) + tac_error = TACMessage(tac_type=TACMessage.Type.TAC_ERROR, error_code=TACMessage.ErrorCode.GENERIC_ERROR.value) + tac_bytes = TACSerializer().encode(tac_error) + self.controller_agent.mailbox.outbox.put_message(to=envelope.sender, sender=self.controller_agent.crypto.public_key, protocol_id=tac_error.protocol_id, message=tac_bytes) class GameHandler: diff --git a/tests/test_controller.py b/tests/test_controller.py index 913d2ce4..1d66daf1 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -22,6 +22,8 @@ import datetime import logging # import multiprocessing +from pprint import pprint + import pytest import time from threading import Thread @@ -54,7 +56,7 @@ def test_no_registered_agents(self): """Test no agent is registered.""" job = Thread(target=self.controller_agent.start) job.start() - job.join(10.0) + job.join(20.0) assert not job.is_alive() assert len(self.controller_agent.game_handler.registered_agents) == 0 @@ -109,3 +111,52 @@ def test_agent_receives_cancelled_message(self): def teardown_class(cls): cls.controller_agent.stop() cls.agent1.disconnect() + + +class TestControllerError: + """Test that the controller returns a TAC error.""" + + @pytest.fixture(autouse=True) + def _start_oef_node(self, network_node): + pass + + @classmethod + def setup_class(cls): + """""" + tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) + cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) + + job = Thread(target=cls.controller_agent.start) + job.start() + + tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') + tac_bytes = TACSerializer().encode(tac_msg) + cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, + sender=cls.controller_agent.crypto.public_key, + protocol_id=TACMessage.protocol_id, message=tac_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') + tac_bytes = TACSerializer().encode(tac_msg) + cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, + sender=cls.controller_agent.crypto.public_key, + protocol_id=TACMessage.protocol_id, message=tac_bytes) + tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') + tac_bytes = TACSerializer().encode(tac_msg) + cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, + sender=cls.controller_agent.crypto.public_key, + protocol_id=TACMessage.protocol_id, message=tac_bytes) + + job.join() + + def test_get_message(self): + """test that the controller can send a message to himself.""" + + counter = 0 + while not self.controller_agent.inbox.empty(): + counter += 1 + msg = self.controller_agent.inbox.get_nowait() + assert msg is not None and msg.sender == self.controller_agent.crypto.public_key + assert counter == 3 + + @classmethod + def teardown_class(cls): + cls.controller_agent.stop() From cee179aff63455d99bc0a5e2c04950192f7bf636 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 27 Aug 2019 20:57:56 +0100 Subject: [PATCH 087/107] update Pipfile and Pipfile.lock. --- Pipfile | 2 +- Pipfile.lock | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Pipfile b/Pipfile index 489d3b21..7c03df3f 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ flask-restful = "*" nbsphinx = "*" numpy = "*" matplotlib = "*" -oef = {index = "https://test.pypi.org/simple/",version = "==0.6.3"} +oef = {index = "test-pypi",version = "==0.6.3"} python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 7c9e4d9c..603267b0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4b47dbf0e5072aced0b959e92c05509aa05678f8912e5789ea5cc1c53e193440" + "sha256": "333259b3f3958b9e54d7eac0e8ca397ab28e2e8e29b6fce8369ab775a792efcd" }, "pipfile-spec": 6, "requires": { @@ -24,7 +24,7 @@ "aea": { "editable": true, "git": "https://github.com/fetchai/agents-aea.git", - "ref": "7a552b56dfe30d3e5fc2ee6d935e02ac9789a207" + "ref": "68727db42830dc1034819858648e1c53e76a9017" }, "alabaster": { "hashes": [ @@ -408,6 +408,7 @@ "sha256:9ede6588109edb4346bf89e3804b8654e460a7070e353f762f3d9402781d45ab", "sha256:d5254a61e3cdc88e0d8c254c72dcf4b72bda048b055f661937c5bc4c7748f40c" ], + "index": "test-pypi", "version": "==0.6.3" }, "packaging": { From 5a4b8867e956e94ad7def1562e9236fb5544a35c Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 27 Aug 2019 21:09:18 +0100 Subject: [PATCH 088/107] fix 'Agent' object constructor. Fix 'Agent' object constructor calls to comply with the latest changes in the aea package. In particular, remove the 'oef_addr' and 'oef_port' constructor parameters. --- tac/agents/controller/agent.py | 27 ++++++------- tac/agents/participant/v1/agent.py | 2 +- templates/v1/expert.py | 2 +- tests/test_agent/test_agent_state.py | 4 +- tests/test_agent/test_misc.py | 2 +- tests/test_controller.py | 60 ++-------------------------- 6 files changed, 22 insertions(+), 75 deletions(-) diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index f4074c2d..d3a18628 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -71,7 +71,7 @@ def __init__(self, name: str, :param private_key_pem: the path to a private key in PEM format. :param debug: if True, run the agent in debug mode. """ - super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) + super().__init__(name, private_key_pem, agent_timeout, debug=debug) self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) self.oef_handler = OEFHandler(self.crypto, self.liveness, self.mailbox, self.name) @@ -137,19 +137,18 @@ def react(self) -> None: :return: None """ - pass - # counter = 0 - # while (not self.inbox.empty() and counter < self.max_reactions): - # counter += 1 - # envelope = self.inbox.get_nowait() # type: Optional[Envelope] - # if envelope is not None: - # if envelope.protocol_id == 'oef': - # self.oef_handler.handle_oef_message(envelope) - # elif envelope.protocol_id == 'tac': - # self.agent_message_dispatcher.handle_agent_message(envelope) - # self.last_activity = datetime.datetime.now() - # else: - # raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) + counter = 0 + while (not self.inbox.empty() and counter < self.max_reactions): + counter += 1 + envelope = self.inbox.get_nowait() # type: Optional[Envelope] + if envelope is not None: + if envelope.protocol_id == 'oef': + self.oef_handler.handle_oef_message(envelope) + elif envelope.protocol_id == 'tac': + self.agent_message_dispatcher.handle_agent_message(envelope) + self.last_activity = datetime.datetime.now() + else: + raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) def update(self) -> None: """ diff --git a/tac/agents/participant/v1/agent.py b/tac/agents/participant/v1/agent.py index 6d902940..4da317c0 100644 --- a/tac/agents/participant/v1/agent.py +++ b/tac/agents/participant/v1/agent.py @@ -65,7 +65,7 @@ def __init__(self, name: str, :param private_key_pem: the path to a private key in PEM format. :param debug: if True, run the agent in debug mode. """ - super().__init__(name, oef_addr, oef_port, private_key_pem, agent_timeout, debug=debug) + super().__init__(name, private_key_pem, agent_timeout, debug=debug) self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) self._game_instance = GameInstance(name, strategy, self.mailbox.mail_stats, services_interval, pending_transaction_timeout, dashboard) # type: Optional[GameInstance] diff --git a/templates/v1/expert.py b/templates/v1/expert.py index 4c0bc721..78658a0d 100644 --- a/templates/v1/expert.py +++ b/templates/v1/expert.py @@ -48,7 +48,7 @@ class MyAgent(Agent): def __init__(self, name: str, oef_addr: str, oef_port: int, agent_timeout: float = 1.0, private_key_pem_path: Optional[str] = None): """Agent initialization.""" - super().__init__(name, oef_addr, oef_port, private_key_pem_path, agent_timeout) + super().__init__(name, private_key_pem_path, agent_timeout) self.mailbox = OEFMailBox(self.crypto.public_key, oef_addr, oef_port) raise NotImplementedError("Your agent must implement the interface defined in Agent.") diff --git a/tests/test_agent/test_agent_state.py b/tests/test_agent/test_agent_state.py index bd8fe461..000247cf 100644 --- a/tests/test_agent/test_agent_state.py +++ b/tests/test_agent/test_agent_state.py @@ -32,7 +32,7 @@ class TAgent(Agent): def __init__(self): """Initialize the test agent.""" - super().__init__("test_agent", "127.0.0.1", 10000) + super().__init__("test_agent") self.mailbox = OEFMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: @@ -55,7 +55,7 @@ def update(self) -> None: def test_agent_initiated(): """Test that when the agent is initiated, her state is AgentState.INITIATED.""" - test_agent = Agent("test_agent", "127.0.0.1", 10000) + test_agent = TAgent() assert test_agent.agent_state == AgentState.INITIATED diff --git a/tests/test_agent/test_misc.py b/tests/test_agent/test_misc.py index e3310388..94bd2f1d 100644 --- a/tests/test_agent/test_misc.py +++ b/tests/test_agent/test_misc.py @@ -31,7 +31,7 @@ class TAgent(Agent): def __init__(self, **kwargs): """Initialize the test agent.""" - super().__init__("test_agent", "127.0.0.1", 10000, **kwargs) + super().__init__("test_agent", **kwargs) self.mailbox = OEFMailBox(self.crypto.public_key, "127.0.0.1", 10000) def setup(self) -> None: diff --git a/tests/test_controller.py b/tests/test_controller.py index 1d66daf1..03f6aadb 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -21,20 +21,17 @@ import datetime import logging -# import multiprocessing -from pprint import pprint - -import pytest -import time from threading import Thread +import pytest +from aea.crypto.base import Crypto from aea.protocols.tac.message import TACMessage from aea.protocols.tac.serialization import TACSerializer -from .common import TOEFAgent -from aea.crypto.base import Crypto from tac.agents.controller.agent import ControllerAgent from tac.agents.controller.base.tac_parameters import TACParameters +from .common import TOEFAgent + logger = logging.getLogger(__name__) @@ -111,52 +108,3 @@ def test_agent_receives_cancelled_message(self): def teardown_class(cls): cls.controller_agent.stop() cls.agent1.disconnect() - - -class TestControllerError: - """Test that the controller returns a TAC error.""" - - @pytest.fixture(autouse=True) - def _start_oef_node(self, network_node): - pass - - @classmethod - def setup_class(cls): - """""" - tac_parameters = TACParameters(min_nb_agents=2, start_time=datetime.datetime.now(), registration_timeout=5) - cls.controller_agent = ControllerAgent('controller', '127.0.0.1', 10000, tac_parameters) - - job = Thread(target=cls.controller_agent.start) - job.start() - - tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') - tac_bytes = TACSerializer().encode(tac_msg) - cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, - sender=cls.controller_agent.crypto.public_key, - protocol_id=TACMessage.protocol_id, message=tac_bytes) - tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') - tac_bytes = TACSerializer().encode(tac_msg) - cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, - sender=cls.controller_agent.crypto.public_key, - protocol_id=TACMessage.protocol_id, message=tac_bytes) - tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED, agent_name='agent_name') - tac_bytes = TACSerializer().encode(tac_msg) - cls.controller_agent.outbox.put_message(to=cls.controller_agent.crypto.public_key, - sender=cls.controller_agent.crypto.public_key, - protocol_id=TACMessage.protocol_id, message=tac_bytes) - - job.join() - - def test_get_message(self): - """test that the controller can send a message to himself.""" - - counter = 0 - while not self.controller_agent.inbox.empty(): - counter += 1 - msg = self.controller_agent.inbox.get_nowait() - assert msg is not None and msg.sender == self.controller_agent.crypto.public_key - assert counter == 3 - - @classmethod - def teardown_class(cls): - cls.controller_agent.stop() From 32434e4dd1706fbf48c937ab169349ad7fd60e29 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 27 Aug 2019 21:20:05 +0100 Subject: [PATCH 089/107] fix codestyle checks. --- tac/platform/oef_health_check.py | 2 -- tests/test_controller.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index 133da044..ef232081 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -26,8 +26,6 @@ import logging from typing import Optional -from aea.crypto.base import Crypto -from aea.channel.oef import OEFMailBox from oef.core import AsyncioCore, Connection logger = logging.getLogger(__name__) diff --git a/tests/test_controller.py b/tests/test_controller.py index 03f6aadb..26379965 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -59,6 +59,7 @@ def test_no_registered_agents(self): @classmethod def teardown_class(cls): + """Teardown test class.""" cls.controller_agent.stop() @@ -106,5 +107,6 @@ def test_agent_receives_cancelled_message(self): @classmethod def teardown_class(cls): + """Teardown test class.""" cls.controller_agent.stop() cls.agent1.disconnect() From a4baf2e7e4c1bef95959495ef45329393a4ad887 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Tue, 27 Aug 2019 23:46:36 +0100 Subject: [PATCH 090/107] fix import statement in the oef health check module. --- tac/platform/oef_health_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index ef232081..5f09218b 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -26,7 +26,7 @@ import logging from typing import Optional -from oef.core import AsyncioCore, Connection +from oef.agents import AsyncioCore, Connection logger = logging.getLogger(__name__) From 6da3e0eba83c07c99b791db6255a7c94a98573b2 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 10:23:44 +0100 Subject: [PATCH 091/107] address PR comment Add comments to explain some newly introduced instructions. --- tac/agents/controller/agent.py | 1 - tac/agents/controller/base/handlers.py | 3 +- tests/test_localnode.py | 102 ------------------------- 3 files changed, 2 insertions(+), 104 deletions(-) delete mode 100644 tests/test_localnode.py diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index d3a18628..e64acbba 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -166,7 +166,6 @@ def stop(self) -> None: logger.debug("[{}]: Stopping myself...".format(self.name)) if self.game_handler.game_phase == GamePhase.GAME or self.game_handler.game_phase == GamePhase.GAME_SETUP: self.game_handler.notify_competition_cancelled() - time.sleep(2.0) super().stop() def start(self) -> None: diff --git a/tac/agents/controller/base/handlers.py b/tac/agents/controller/base/handlers.py index 13d246c5..4f346cbd 100644 --- a/tac/agents/controller/base/handlers.py +++ b/tac/agents/controller/base/handlers.py @@ -469,7 +469,8 @@ def notify_competition_cancelled(self): tac_msg = TACMessage(tac_type=TACMessage.Type.CANCELLED) tac_bytes = TACSerializer().encode(tac_msg) self.mailbox.outbox.put_message(to=agent_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) - time.sleep(1.0) + # wait some time to make sure the connection delivers the messages + time.sleep(2.0) self._game_phase = GamePhase.POST_GAME def simulation_dump(self) -> None: diff --git a/tests/test_localnode.py b/tests/test_localnode.py deleted file mode 100644 index cdac7a32..00000000 --- a/tests/test_localnode.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2018-2019 Fetch.AI Limited -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ------------------------------------------------------------------------------ - -"""This module contains the tests of the local OEF node implementation.""" -import time - -from aea.channel.local import LocalNode, OEFLocalConnection -from aea.mail.base import Envelope, MailBox -from aea.protocols.default.message import DefaultMessage -from aea.protocols.default.serialization import DefaultSerializer -from aea.protocols.fipa.message import FIPAMessage -from aea.protocols.fipa.serialization import FIPASerializer - - -def test_connection(): - """Test that two mailbox can connect to the node.""" - node = LocalNode() - mailbox1 = MailBox(OEFLocalConnection("mailbox1", node)) - mailbox2 = MailBox(OEFLocalConnection("mailbox2", node)) - - mailbox1.connect() - mailbox2.connect() - - mailbox1.disconnect() - mailbox2.disconnect() - - -def test_communication(): - """Test that two mailbox can communicate through the node.""" - node = LocalNode() - mailbox1 = MailBox(OEFLocalConnection("mailbox1", node)) - mailbox2 = MailBox(OEFLocalConnection("mailbox2", node)) - - mailbox1.connect() - mailbox2.connect() - - msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") - msg_bytes = DefaultSerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=DefaultMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.CFP, query=None) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.PROPOSE, proposal=[]) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.ACCEPT) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - msg = FIPAMessage(0, 0, 0, FIPAMessage.Performative.DECLINE) - msg_bytes = FIPASerializer().encode(msg) - envelope = Envelope(to="mailbox2", sender="mailbox1", protocol_id=FIPAMessage.protocol_id, message=msg_bytes) - mailbox1.send(envelope) - - time.sleep(5.0) - - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = DefaultSerializer().decode(envelope.message) - assert envelope.protocol_id == "default" - assert msg.get("content") == b"hello" - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.CFP - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.PROPOSE - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.ACCEPT - envelope = mailbox2.inbox.get(block=True, timeout=1.0) - msg = FIPASerializer().decode(envelope.message) - assert envelope.protocol_id == "fipa" - assert msg.get("performative") == FIPAMessage.Performative.DECLINE - - mailbox1.disconnect() - mailbox2.disconnect() From 91e683d05b77c345e184c0a8da1bcec65c0e40e9 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 11:20:35 +0100 Subject: [PATCH 092/107] bump oef sdk version to 0.6.4. --- Pipfile | 2 +- Pipfile.lock | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Pipfile b/Pipfile index 7c03df3f..67c1fe7d 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ flask-restful = "*" nbsphinx = "*" numpy = "*" matplotlib = "*" -oef = {index = "test-pypi",version = "==0.6.3"} +oef = {index = "test-pypi",version = "==0.6.4"} python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 603267b0..987e515f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "333259b3f3958b9e54d7eac0e8ca397ab28e2e8e29b6fce8369ab775a792efcd" + "sha256": "331da37d0749378faeded473e56279f8164d9c5e73a1fe0b233e656952e89eeb" }, "pipfile-spec": 6, "requires": { @@ -24,7 +24,7 @@ "aea": { "editable": true, "git": "https://github.com/fetchai/agents-aea.git", - "ref": "68727db42830dc1034819858648e1c53e76a9017" + "ref": "86f31a781d1b9a1805c2442a82cc65aaec4d0076" }, "alabaster": { "hashes": [ @@ -131,13 +131,6 @@ ], "version": "==7.0" }, - "colorlog": { - "hashes": [ - "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", - "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981" - ], - "version": "==4.0.2" - }, "cryptography": { "hashes": [ "sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", @@ -405,11 +398,11 @@ }, "oef": { "hashes": [ - "sha256:9ede6588109edb4346bf89e3804b8654e460a7070e353f762f3d9402781d45ab", - "sha256:d5254a61e3cdc88e0d8c254c72dcf4b72bda048b055f661937c5bc4c7748f40c" + "sha256:99ab0836a383a87bbaa7d9de8be8dfac3feafa070d63ba6e8fded7592d760f91", + "sha256:f228b9a220cc867fb8840a1ab7d3e44da3c73bbcc595a5e81ec1a2c27aa51937" ], "index": "test-pypi", - "version": "==0.6.3" + "version": "==0.6.4" }, "packaging": { "hashes": [ From 066d8ceae25e1cd0f86a7aa9e4655e383670592e Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 11:20:41 +0100 Subject: [PATCH 093/107] make oef health check more readable. --- tac/platform/oef_health_check.py | 63 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index 5f09218b..62d528cf 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -24,9 +24,11 @@ import argparse import asyncio import logging +import time +from threading import Thread, Timer from typing import Optional -from oef.agents import AsyncioCore, Connection +from oef.agents import AsyncioCore, Connection, OEFAgent logger = logging.getLogger(__name__) @@ -51,29 +53,30 @@ def __init__(self, oef_addr: str, oef_port: int, loop: Optional[asyncio.Abstract self._result = False self._stop = False - self._conn = None - self._loop = asyncio.get_event_loop() if loop is None else loop - self._loop.set_exception_handler(self.exception_handler) - self._core = AsyncioCore(loop=self._loop) + self._core = AsyncioCore() + self.agent = OEFAgent("check", core=self._core, oef_addr=self.oef_addr, oef_port=self.oef_port) + self.agent.on_connect_success = self.on_connect_ok + self.agent.on_connection_terminated = self.on_connect_terminated + self.agent.on_connect_failed = self.exception_handler - def exception_handler(self, loop, d): + def exception_handler(self, url=None, ex=None): """Handle exception during a connection attempt.""" - print("An error occurred. Details: {}".format(d)) + print("An error occurred. Exception: {}".format(ex)) self._stop = True - def on_connect_ok(self, conn=None, url=None, ex=None, conn_name=None): + def on_connect_ok(self, url=None): """Handle a successful connection.""" print("Connection OK!") self._result = True self._stop = True - def on_connect_fail(self, conn=None, url=None, ex=None, conn_name=None): + def on_connect_terminated(self, url=None): """Handle a connection failure.""" - print("Connection failed. {}".format(ex)) - self._result = False + print("Connection terminated.") self._stop = True - async def _run(self) -> bool: + + def run(self) -> bool: """ Run the check, asynchronously. @@ -81,39 +84,37 @@ async def _run(self) -> bool: """ self._result = False self._stop = False - pbk = 'check' - seconds = 3.0 + + def stop_connection_attempt(self): + if self.agent.state == "connecting": + self.agent.state = "failed" + + t = Timer(1.5, stop_connection_attempt, args=(self, )) + try: print("Connecting to {}:{}...".format(self.oef_addr, self.oef_port)) + self._core.run_threaded() - self._conn = Connection(self._core) - self._conn.connect(url="127.0.0.1:10000", success=self.on_connect_ok, - failure=self.on_connect_fail, - public_key=pbk) - - while not self._stop and seconds > 0: - await asyncio.sleep(1.0) - seconds -= 1.0 + t.start() + self._result = self.agent.connect() + self._stop = True if self._result: print("Connection established. Tearing down connection...") - self._core.call_soon_async(self._conn.do_stop) - await asyncio.sleep(1.0) + self.agent.disconnect() + t.cancel() else: print("A problem occurred. Exiting...") except Exception as e: print(str(e)) finally: + t.join(1.0) + self.agent.stop() + self.agent.disconnect() + self._core.stop() return self._result - def run(self) -> bool: - """Run the check. - - :return: True if the check is successful, False otherwise. - """ - return self._loop.run_until_complete(self._run()) - def main(oef_addr, oef_port): """Launch the health check.""" From 3c02a67c7d8f8fe57c030f2ef62140c088ab3cc4 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 11:38:29 +0100 Subject: [PATCH 094/107] fix codestyle checks. --- tac/platform/oef_health_check.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tac/platform/oef_health_check.py b/tac/platform/oef_health_check.py index 62d528cf..39243fcf 100644 --- a/tac/platform/oef_health_check.py +++ b/tac/platform/oef_health_check.py @@ -24,11 +24,10 @@ import argparse import asyncio import logging -import time -from threading import Thread, Timer +from threading import Timer from typing import Optional -from oef.agents import AsyncioCore, Connection, OEFAgent +from oef.agents import AsyncioCore, OEFAgent logger = logging.getLogger(__name__) @@ -75,7 +74,6 @@ def on_connect_terminated(self, url=None): print("Connection terminated.") self._stop = True - def run(self) -> bool: """ Run the check, asynchronously. From 4338d3cc75c6a7cab81d8dc2e48a7387d22a5ace Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 23:30:26 +0100 Subject: [PATCH 095/107] add Docker image for agent execution. --- docker-agent-image/Dockerfile | 19 ++++++++++++++ docker-agent-image/README.md | 45 ++++++++++++++++++++++++++++++++ docker-agent-image/docker-env.sh | 6 +++++ docker-agent-image/scripts | 1 + 4 files changed, 71 insertions(+) create mode 100644 docker-agent-image/Dockerfile create mode 100644 docker-agent-image/README.md create mode 100755 docker-agent-image/docker-env.sh create mode 120000 docker-agent-image/scripts diff --git a/docker-agent-image/Dockerfile b/docker-agent-image/Dockerfile new file mode 100644 index 00000000..184a3c50 --- /dev/null +++ b/docker-agent-image/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.7-alpine + +RUN apk add --no-cache make git +RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev # cryptography: https://cryptography.io/en/latest/installation/#alpine +RUN apk add --update --no-cache py3-numpy py3-scipy py3-pillow py3-zmq + +# # https://stackoverflow.com/a/57485724 +ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages" + +RUN pip install --upgrade pip +RUN pip install protobuf colorlog graphviz # other oef dependences +RUN pip install --index-url https://test.pypi.org/simple/ oef==0.6.4 --no-deps + +WORKDIR /build +COPY . /build + +RUN pip install . + +ENTRYPOINT [] diff --git a/docker-agent-image/README.md b/docker-agent-image/README.md new file mode 100644 index 00000000..d5301b8b --- /dev/null +++ b/docker-agent-image/README.md @@ -0,0 +1,45 @@ +# Docker agent image + +Lightweight Docker image for agent execution, based on [`python:3.7-alpine`](https://hub.docker.com/r/jfloff/alpine-python/). + +## Build + +From the root of the repository: + + ./docker-agent-image/scripts/docker-build.sh + +## Run + + ./docker-agent-image/scripts/docker-run.sh -- sh + +## Example + +- run an OEF Node: +``` +curl https://raw.githubusercontent.com/fetchai/oef-search-pluto/master/scripts/node_config.json > node_config.json + +docker run -it -v $(pwd):/config -v $(pwd):/app/fetch-logs \ + -p 20000:20000 -p 10000:10000 \ + -p 40000:40000 -p 7500 \ + fetchai/oef-search:latest /config/node_config.json +``` +- Run a Visdom server: +``` +pipenv shell +python -m visdom.server +``` +- Run the controller: +``` +./docker-agent-image/scripts/docker-run.sh --network host -- sh +python tac/agents/controller/agent.py --nb-agents 2 --dashboard +``` +- Run Agent 1: +``` +./docker-agent-image/scripts/docker-run.sh --network host -- sh +python templates/v1/basic.py --name agent1 +``` +- Run Agent 2: +``` +./docker-agent-image/scripts/docker-run.sh --network host -- sh +python templates/v1/basic.py --name agent2 +``` diff --git a/docker-agent-image/docker-env.sh b/docker-agent-image/docker-env.sh new file mode 100755 index 00000000..b39bb333 --- /dev/null +++ b/docker-agent-image/docker-env.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +DOCKER_IMAGE_TAG=docker-agent-image:latest +DOCKER_BUILD_CONTEXT_DIR=.. +DOCKERFILE=./Dockerfile +WORKDIR=/build diff --git a/docker-agent-image/scripts b/docker-agent-image/scripts new file mode 120000 index 00000000..179f9f57 --- /dev/null +++ b/docker-agent-image/scripts @@ -0,0 +1 @@ +../docker-images/scripts \ No newline at end of file From c0c696edb8e18d5fa68e82afcf714d9cb43e0fee Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 23:46:19 +0100 Subject: [PATCH 096/107] add bash as entrypoint. --- docker-agent-image/Dockerfile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docker-agent-image/Dockerfile b/docker-agent-image/Dockerfile index 184a3c50..f3ad0d05 100644 --- a/docker-agent-image/Dockerfile +++ b/docker-agent-image/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.7-alpine -RUN apk add --no-cache make git +RUN apk add --no-cache make git bash RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl-dev # cryptography: https://cryptography.io/en/latest/installation/#alpine RUN apk add --update --no-cache py3-numpy py3-scipy py3-pillow py3-zmq @@ -10,10 +10,6 @@ ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages" RUN pip install --upgrade pip RUN pip install protobuf colorlog graphviz # other oef dependences RUN pip install --index-url https://test.pypi.org/simple/ oef==0.6.4 --no-deps +RUN pip install git+https://github.com/fetchai/agents-tac.git@develop#egg=tac -WORKDIR /build -COPY . /build - -RUN pip install . - -ENTRYPOINT [] +ENTRYPOINT ["bash", "-c"] From e7bd249d8ffafcf2b863bee5a0fdb713b82ea616 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 23:23:37 +0100 Subject: [PATCH 097/107] do misc changes. - Remove duplicated call to 'teardown' method of the controller agent. - Remove obsolete function in GameStats class to plot scoe history. - Remove matplotlib usage in game_stats module. - Add entry in .gitignore --- .gitignore | 1 + tac/agents/controller/agent.py | 4 +--- tac/platform/game/stats.py | 43 +--------------------------------- 3 files changed, 3 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 39e5298b..c2f3efb6 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,7 @@ tac/gui/.visdom_env/ sandbox/TAC sandbox/data +simulation/v1/data/ *.pem !tests/data/priv.pem` diff --git a/tac/agents/controller/agent.py b/tac/agents/controller/agent.py index e64acbba..9a17b214 100644 --- a/tac/agents/controller/agent.py +++ b/tac/agents/controller/agent.py @@ -29,7 +29,7 @@ import time from typing import Union, Optional -import dateutil +import dateutil.parser from aea.agent import Agent from aea.channel.oef import OEFMailBox from aea.mail.base import Envelope @@ -123,12 +123,10 @@ def act(self) -> None: if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: logger.debug("[{}]: Inactivity timeout expired. Terminating...".format(self.name)) self.stop() - self.teardown() return elif current_time > self.game_handler.tac_parameters.end_time: logger.debug("[{}]: Competition timeout expired. Terminating...".format(self.name)) self.stop() - self.teardown() return def react(self) -> None: diff --git a/tac/platform/game/stats.py b/tac/platform/game/stats.py index 66c94ab6..e65802c5 100644 --- a/tac/platform/game/stats.py +++ b/tac/platform/game/stats.py @@ -23,16 +23,10 @@ from typing import Any, Dict, Optional, Tuple, List import numpy as np -import matplotlib -import os -import pylab as plt -from aea.crypto.base import Crypto from tac.agents.controller.base.states import Game from tac.agents.participant.v1.base.states import AgentState -matplotlib.use('agg') - class GameStats: """A class to query statistics about a game.""" @@ -50,7 +44,7 @@ def __init__(self, game: Optional[Game]) -> None: @classmethod def from_json(cls, d: Dict[str, Any]): """Read from json.""" - game = Game.from_dict(d, Crypto()) # any crypto object will do here + game = Game.from_dict(d) return GameStats(game) def holdings_history(self): @@ -146,29 +140,6 @@ def price_history(self) -> np.ndarray: return result - def plot_score_history(self, output_path: Optional[str] = None) -> None: - """ - Plot the history of the scores, for every agent, by transaction. - - :param output_path: an optional output path where to save the figure generated. - - :return: None - """ - keys, history = self.score_history() - - plt.clf() - plt.plot(history) - plt.legend([self.game.configuration.agent_pbk_to_name[agent_pbk] for agent_pbk in keys], loc="best") - plt.xlabel("Transactions") - plt.ylabel("Score") - - plt.gca().xaxis.set_major_locator(plt.MaxNLocator(integer=True)) - - if output_path is None: - plt.show() - else: - plt.savefig(output_path) - def eq_vs_mean_price(self) -> Tuple[List[str], np.ndarray]: """ Compute the mean price of each good and display it together with the equilibrium price. @@ -285,15 +256,3 @@ def adjusted_score(self) -> Tuple[List[str], np.ndarray]: result = np.transpose(result) return keys, result - - def dump(self, directory: str, experiment_name: str) -> None: - """ - Dump the plot. - - :param directory: the directory where experiments details are listed. - :param experiment_name: the name of the folder where the data about experiment will be saved. - - :return: None. - """ - experiment_dir = directory + "/" + experiment_name - self.plot_score_history(os.path.join(experiment_dir, "plot.png")) From d7836b70ecd672cccc78c55b92e5e1a98623565c Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 23:25:52 +0100 Subject: [PATCH 098/107] update dependencies in setup.py. - Remove unused dependencies like matplotlib, base58 and fetch-ledger-api. - Export dependencies for the GUIs by creating a 'gui' setuptools extension. --- setup.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index c7d3aeb7..15ad7952 100644 --- a/setup.py +++ b/setup.py @@ -96,6 +96,14 @@ def _fix_import_statements_in_protobuf_module(self, filename): readme = f.read() +extras = { + "gui": [ + "flask", + "flask_restful", + "wtforms" + ] +} + setup( name=about['__title__'], description=about['__description__'], @@ -116,19 +124,14 @@ def _fix_import_statements_in_protobuf_module(self, filename): 'Programming Language :: Python :: 3.7', ], install_requires=[ + "aea @ git+https://github.com/fetchai/agents-aea.git@develop#egg=aea", "oef", "numpy", - "matplotlib", - "flask", - "flask_restful", - "wtforms", "python-dateutil", - "visdom", - "cryptography", - "fetchai-ledger-api @ git+https://github.com/fetchai/ledger-api-py.git#egg=fetchai-ledger-api", - "base58" + "visdom" ], tests_require=["tox"], + extras_require=extras, zip_safe=False, include_package_data=True, data_files=[ From 3e8cd8c707e0bb06209f1f5911732dd7084ece4b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Wed, 28 Aug 2019 23:32:22 +0100 Subject: [PATCH 099/107] remove unused build subcommand in setup.py. --- setup.py | 60 -------------------------------------------------------- 1 file changed, 60 deletions(-) diff --git a/setup.py b/setup.py index 15ad7952..ce72c530 100644 --- a/setup.py +++ b/setup.py @@ -30,63 +30,6 @@ PACKAGE_NAME = "tac" - -class protoc(Command): - """A custom command to generate Python Protobuf modules from oef-core-protocol""" - - description = "Generate Python Protobuf modules from protobuf files specifications." - user_options = [ - ("proto-path", None, "Path to the `oef-core-protocol` folder.") - ] - - def run(self): - command = self._build_command() - self._run_command(command) - self._fix_import_statements_in_all_protobuf_modules() - - def _run_command(self, command): - self.announce("Running %s" % str(command)) - subprocess.check_call(command) - - def initialize_options(self): - """Set default values for options.""" - self.proto_path = os.path.join("proto") - - def finalize_options(self): - """Post-process options.""" - assert os.path.exists(self.proto_path), ('Directory %s does not exist.' % self.proto_path) - - def _find_protoc_executable_path(self): - result = shutil.which("protoc") - - if result is None or result == "": - raise EnvironmentError("protoc compiler not found.") - return result - - def _build_command(self): - protoc_executable_path = self._find_protoc_executable_path() - command = [protoc_executable_path] + self._get_arguments() - return command - - def _get_arguments(self): - arguments = [] - arguments.append("--proto_path=%s" % self.proto_path) - arguments.append("--python_out=tac") - arguments += glob.glob(os.path.join(self.proto_path, "*.proto")) - return arguments - - def _fix_import_statements_in_all_protobuf_modules(self): - generated_protobuf_python_modules = glob.glob(os.path.join("tac", "*_pb2.py")) - for filepath in generated_protobuf_python_modules: - self._fix_import_statements_in_protobuf_module(filepath) - - def _fix_import_statements_in_protobuf_module(self, filename): - for line in fileinput.input(filename, inplace=True): - line = re.sub("^(import \w*_pb2)", "from . \g<1>", line) - # stdout redirected to the file (fileinput.input with inplace=True) - sys.stdout.write(line) - - here = os.path.abspath(os.path.dirname(__file__)) about = {} with open(os.path.join(here, PACKAGE_NAME, '__version__.py'), 'r') as f: @@ -112,9 +55,6 @@ def _fix_import_statements_in_protobuf_module(self, filename): url=about['__url__'], long_description=readme, packages=find_packages(include=["tac*"]), - cmdclass={ - 'protoc': protoc, - }, classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', From a9ad515914e9cf1885fc85b8dc4257938abf50ef Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 29 Aug 2019 10:30:29 +0100 Subject: [PATCH 100/107] add temporary 'colorlog' dependency. --- Pipfile | 1 + Pipfile.lock | 12 ++++++++++-- setup.py | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 67c1fe7d..1d485a79 100644 --- a/Pipfile +++ b/Pipfile @@ -36,6 +36,7 @@ sphinx = "*" wtforms = "*" visdom = "*" aea = {editable = true,git = "https://github.com/fetchai/agents-aea.git",ref = "develop"} +colorlog = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 987e515f..39383e5d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "331da37d0749378faeded473e56279f8164d9c5e73a1fe0b233e656952e89eeb" + "sha256": "c104a4860ddd2511c5a6510c10724b3bff7c843eb3e5b1c664919525c497e3f4" }, "pipfile-spec": 6, "requires": { @@ -131,6 +131,14 @@ ], "version": "==7.0" }, + "colorlog": { + "hashes": [ + "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", + "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981" + ], + "index": "pypi", + "version": "==4.0.2" + }, "cryptography": { "hashes": [ "sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c", @@ -191,7 +199,7 @@ }, "fetchai-ledger-api": { "git": "https://github.com/fetchai/ledger-api-py.git", - "ref": "a5f513a9247524ee767e1dd1519aad1a1f4383e3" + "ref": "514e17c209932357d000be599e85362a1cbeb8a0" }, "flask": { "hashes": [ diff --git a/setup.py b/setup.py index ce72c530..aec810a9 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ install_requires=[ "aea @ git+https://github.com/fetchai/agents-aea.git@develop#egg=aea", "oef", + "colorlog", # TODO 'oef' dependency, to be fixed. "numpy", "python-dateutil", "visdom" From 981ddfe8ef291ef384f541d46c76cc65529af60b Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Thu, 29 Aug 2019 10:47:45 +0100 Subject: [PATCH 101/107] update docker agent image tag to dev:v0.0.1. --- docker-agent-image/docker-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-agent-image/docker-env.sh b/docker-agent-image/docker-env.sh index b39bb333..045f296e 100755 --- a/docker-agent-image/docker-env.sh +++ b/docker-agent-image/docker-env.sh @@ -1,6 +1,6 @@ #!/bin/bash -DOCKER_IMAGE_TAG=docker-agent-image:latest +DOCKER_IMAGE_TAG=docker-agent-image/dev:v0.0.1 DOCKER_BUILD_CONTEXT_DIR=.. DOCKERFILE=./Dockerfile WORKDIR=/build From 8a857ca14f83e4656bf57d0d52062dc99e794c0d Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 30 Aug 2019 00:39:57 +0100 Subject: [PATCH 102/107] remove endpoint in 'docker-agent-image'. --- docker-agent-image/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-agent-image/Dockerfile b/docker-agent-image/Dockerfile index f3ad0d05..9b5f125c 100644 --- a/docker-agent-image/Dockerfile +++ b/docker-agent-image/Dockerfile @@ -10,6 +10,6 @@ ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages" RUN pip install --upgrade pip RUN pip install protobuf colorlog graphviz # other oef dependences RUN pip install --index-url https://test.pypi.org/simple/ oef==0.6.4 --no-deps -RUN pip install git+https://github.com/fetchai/agents-tac.git@develop#egg=tac +RUN pip install -e git+https://github.com/fetchai/agents-tac.git@develop#egg=tac -ENTRYPOINT ["bash", "-c"] +ENTRYPOINT [] From cda4b39a84a7cd91fbde6707acdc6a57045fabb0 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Fri, 30 Aug 2019 15:58:43 +0100 Subject: [PATCH 103/107] Fix Visdom arguments forwarding to agent templates. --- templates/v1/advanced.py | 2 +- templates/v1/basic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/v1/advanced.py b/templates/v1/advanced.py index 9902977e..27503194 100644 --- a/templates/v1/advanced.py +++ b/templates/v1/advanced.py @@ -121,7 +121,7 @@ def main(): args = parse_arguments() if args.dashboard: - agent_dashboard = AgentDashboard(agent_name=args.name, env_name=args.name) + agent_dashboard = AgentDashboard(agent_name=args.name, visdom_addr=args.visdom_addr, visdom_port=args.visdom_port, env_name=args.name) else: agent_dashboard = None diff --git a/templates/v1/basic.py b/templates/v1/basic.py index a86c7516..d881c2d7 100644 --- a/templates/v1/basic.py +++ b/templates/v1/basic.py @@ -59,7 +59,7 @@ def main(): args = parse_arguments() if args.dashboard: - agent_dashboard = AgentDashboard(agent_name=args.name, env_name=args.name) + agent_dashboard = AgentDashboard(agent_name=args.name, visdom_addr=args.visdom_addr, visdom_port=args.visdom_port, env_name=args.name) else: agent_dashboard = None From 6f495ac2d7618f97c77997c10a1301a3ad6fa181 Mon Sep 17 00:00:00 2001 From: MarcoFavorito Date: Sat, 31 Aug 2019 14:31:58 +0100 Subject: [PATCH 104/107] fix participant reaction to request a state update. the 'GetStateUpdate' message was not serialized correctly. --- tac/agents/participant/v1/base/reactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tac/agents/participant/v1/base/reactions.py b/tac/agents/participant/v1/base/reactions.py index 89abf62a..18ef3cb5 100644 --- a/tac/agents/participant/v1/base/reactions.py +++ b/tac/agents/participant/v1/base/reactions.py @@ -189,7 +189,7 @@ def _request_state_update(self) -> None: :return: None """ tac_msg = TACMessage(tac_type=TACMessage.Type.GET_STATE_UPDATE) - tac_bytes = TACSerializer.encode(tac_msg) + tac_bytes = TACSerializer().encode(tac_msg) self.mailbox.outbox.put_message(to=self.game_instance.controller_pbk, sender=self.crypto.public_key, protocol_id=TACMessage.protocol_id, message=tac_bytes) From a7105e30b2d544f08f3113b4dcb7e69e1aeb6325 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 5 Sep 2019 15:23:37 +0100 Subject: [PATCH 105/107] Bump to aea version 0.1.1 --- Pipfile | 2 +- Pipfile.lock | 77 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/Pipfile b/Pipfile index 1d485a79..70cc6c02 100644 --- a/Pipfile +++ b/Pipfile @@ -35,7 +35,7 @@ sphinxcontrib-apidoc = "*" sphinx = "*" wtforms = "*" visdom = "*" -aea = {editable = true,git = "https://github.com/fetchai/agents-aea.git",ref = "develop"} +aea = {index = "test-pypi",version = "==0.1.1"} colorlog = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 39383e5d..95f09dda 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c104a4860ddd2511c5a6510c10724b3bff7c843eb3e5b1c664919525c497e3f4" + "sha256": "3fb412afb795592215aa45220db7ed90bcfbfe8326e04f450b91b493765dbb29" }, "pipfile-spec": 6, "requires": { @@ -22,9 +22,12 @@ }, "default": { "aea": { - "editable": true, - "git": "https://github.com/fetchai/agents-aea.git", - "ref": "86f31a781d1b9a1805c2442a82cc65aaec4d0076" + "hashes": [ + "sha256:5a61ef8df80647b3a5021e48359594b8a3dde20f12359f67f2bb1a07e190c1cd", + "sha256:adb8185b7c948baebc5d277f14a3e764f1c61c6ad9a37e9453ea2af9f023a0a9" + ], + "index": "test-pypi", + "version": "==0.1.1" }, "alabaster": { "hashes": [ @@ -199,7 +202,7 @@ }, "fetchai-ledger-api": { "git": "https://github.com/fetchai/ledger-api-py.git", - "ref": "514e17c209932357d000be599e85362a1cbeb8a0" + "ref": "0b92886f6aaeae5ae299001a0f2e8598f64c5a48" }, "flask": { "hashes": [ @@ -427,10 +430,10 @@ }, "pbr": { "hashes": [ - "sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc", - "sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf" + "sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8", + "sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9" ], - "version": "==5.4.2" + "version": "==5.4.3" }, "pillow": { "hashes": [ @@ -593,9 +596,9 @@ }, "snowballstemmer": { "hashes": [ - "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" + "sha256:da7525a92df56deb788f65493f41720eb76aa5f7f9502e72eb24e97ad3895226" ], - "version": "==1.9.0" + "version": "==1.9.1" }, "sphinx": { "hashes": [ @@ -725,10 +728,10 @@ }, "werkzeug": { "hashes": [ - "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", - "sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6" + "sha256:00d32beac38fcd48d329566f80d39f10ec2ed994efbecfb8dd4b320062d05902", + "sha256:0a24d43be6a7dce81bae05292356176d6c46d63e42a0dd3f9504b210a9cfaa43" ], - "version": "==0.15.5" + "version": "==0.15.6" }, "wtforms": { "hashes": [ @@ -740,6 +743,14 @@ } }, "develop": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "markers": "sys_platform == 'darwin'", + "version": "==0.1.0" + }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -881,11 +892,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" + "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", + "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" ], "markers": "python_version < '3.8'", - "version": "==0.19" + "version": "==0.20" }, "ipykernel": { "hashes": [ @@ -896,11 +907,11 @@ }, "ipython": { "hashes": [ - "sha256:1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e", - "sha256:537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d" + "sha256:c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", + "sha256:dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1" ], "index": "pypi", - "version": "==7.7.0" + "version": "==7.8.0" }, "ipython-genutils": { "hashes": [ @@ -1164,11 +1175,11 @@ }, "pytest": { "hashes": [ - "sha256:95b1f6db806e5b1b5b443efeb58984c24945508f93a866c1719e1a507a957d7c", - "sha256:c3d5020755f70c82eceda3feaf556af9a341334414a8eca521a18f463bcead88" + "sha256:95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", + "sha256:b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.1.2" }, "pytest-cov": { "hashes": [ @@ -1218,10 +1229,10 @@ }, "qtconsole": { "hashes": [ - "sha256:40f53ab58ef77aa4b53aca1ee314eed873d05952dec22d2ab84d20660943b7de", - "sha256:756bdcb6de6900dc50b14430accff2e47b846c3e7820e04075d4067b4c0ab52f" + "sha256:40d5d8e00d070ea266dbf6f0da74c4b9597b8b8d67cd8233c3ffd8debf923703", + "sha256:b91e7412587e6cfe1644696538f73baf5611e837be5406633218443b2827c6d9" ], - "version": "==4.5.4" + "version": "==4.5.5" }, "requests": { "hashes": [ @@ -1246,9 +1257,9 @@ }, "snowballstemmer": { "hashes": [ - "sha256:9f3b9ffe0809d174f7047e121431acf99c89a7040f0ca84f94ba53a498e6d0c9" + "sha256:da7525a92df56deb788f65493f41720eb76aa5f7f9502e72eb24e97ad3895226" ], - "version": "==1.9.0" + "version": "==1.9.1" }, "terminado": { "hashes": [ @@ -1285,11 +1296,11 @@ }, "tox": { "hashes": [ - "sha256:dab0b0160dd187b654fc33d690ee1d7bf328bd5b8dc6ef3bb3cc468969c659ba", - "sha256:ee35ffce74933a6c6ac10c9a0182e41763140a5a5070e21b114feca56eaccdcd" + "sha256:0bc216b6a2e6afe764476b4a07edf2c1dab99ed82bb146a1130b2e828f5bff5e", + "sha256:c4f6b319c20ba4913dbfe71ebfd14ff95d1853c4231493608182f66e566ecfe1" ], "index": "pypi", - "version": "==3.13.2" + "version": "==3.14.0" }, "tox-pipenv": { "hashes": [ @@ -1315,10 +1326,10 @@ }, "virtualenv": { "hashes": [ - "sha256:94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", - "sha256:f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d" + "sha256:680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", + "sha256:f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2" ], - "version": "==16.7.4" + "version": "==16.7.5" }, "virtualenv-clone": { "hashes": [ From 89a0645ca526169eda3fd95e6c86b341cce0ee70 Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 5 Sep 2019 15:30:20 +0100 Subject: [PATCH 106/107] Updates Dockerfile to new version of aea. --- docker-agent-image/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-agent-image/Dockerfile b/docker-agent-image/Dockerfile index 9b5f125c..15949a26 100644 --- a/docker-agent-image/Dockerfile +++ b/docker-agent-image/Dockerfile @@ -10,6 +10,6 @@ ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages" RUN pip install --upgrade pip RUN pip install protobuf colorlog graphviz # other oef dependences RUN pip install --index-url https://test.pypi.org/simple/ oef==0.6.4 --no-deps -RUN pip install -e git+https://github.com/fetchai/agents-tac.git@develop#egg=tac +RUN pip install --index-url https://test.pypi.org/simple/ aea==0.1.1 --no-deps ENTRYPOINT [] From d44b71674f58769366fbf67939b8f5e37de0211e Mon Sep 17 00:00:00 2001 From: David Minarsch Date: Thu, 5 Sep 2019 15:38:56 +0100 Subject: [PATCH 107/107] Bumps the oef SDK version and the framework version. --- HISTORY.rst | 6 ++++++ Pipfile | 2 +- Pipfile.lock | 10 ++++++---- tac/__version__.py | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6accece0..74f9c94d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -23,3 +23,9 @@ Release History - Improved documentation by adding detailed guides - Added more tools/features to support the developer (e.g. the launcher app) +0.1.4 (2019-09-05) +------------------- + +- Updated to OEF SDK version 0.6.7 +- Transition to AEA architecture and extraction of AEA framework. +- Updated to AEA version 0.1.1 diff --git a/Pipfile b/Pipfile index 70cc6c02..2fbcb057 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ flask-restful = "*" nbsphinx = "*" numpy = "*" matplotlib = "*" -oef = {index = "test-pypi",version = "==0.6.4"} +oef = {index = "test-pypi",version = "==0.6.7"} python-dateutil = "*" sphinxcontrib-mermaid = "*" sphinxcontrib-apidoc = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 95f09dda..9f9aba1f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3fb412afb795592215aa45220db7ed90bcfbfe8326e04f450b91b493765dbb29" + "sha256": "ee04486054d0aa817ac8df1d35ec006c1e79cf7e6843c46788b522028930897c" }, "pipfile-spec": 6, "requires": { @@ -409,11 +409,13 @@ }, "oef": { "hashes": [ - "sha256:99ab0836a383a87bbaa7d9de8be8dfac3feafa070d63ba6e8fded7592d760f91", - "sha256:f228b9a220cc867fb8840a1ab7d3e44da3c73bbcc595a5e81ec1a2c27aa51937" + "sha256:023105a1d219579e4ffe1726f553652fbfbecdc0c5e45228d7cdf5b628ac37b7", + "sha256:14ddb1ec43cacae74b1916852714c9fe7a95ab7438608752a4d837e356d00129", + "sha256:6e51d2bb3d4df513a49630541be21dbf5e8ec845cdb3e2a24dc8a9daf1936810", + "sha256:ee33d6155542a31829370b2682dc9920adf075e74d5a039006fad34447e699d6" ], "index": "test-pypi", - "version": "==0.6.4" + "version": "==0.6.7" }, "packaging": { "hashes": [ diff --git a/tac/__version__.py b/tac/__version__.py index d346aab8..ad9b86a6 100644 --- a/tac/__version__.py +++ b/tac/__version__.py @@ -23,7 +23,7 @@ __title__ = 'tac' __description__ = 'Trading Agent Competition agents' __url__ = 'https://github.com/fetchai/agents-tac.git' -__version__ = '0.1.3' +__version__ = '0.1.4' __author__ = 'Fetch.AI Limited' __license__ = 'Apache 2.0' __copyright__ = '2019 Fetch.AI Limited'