diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 06d521f9f1..1c5929a3bc 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -32,10 +32,12 @@ jobs: run: | tox -e py3.8 -- --aea-loop sync -m 'not integration and not unstable' - sync_aea_loop_integrational_tests: + all_integrational_tests: continue-on-error: True runs-on: ubuntu-latest + timeout-minutes: 40 + steps: - uses: actions/checkout@master - uses: actions/setup-python@master @@ -52,10 +54,12 @@ jobs: pip install pipenv pip install tox sudo apt-get install -y protobuf-compiler - - name: Integrational tests and coverage + - name: Sync AEA loop integrational tests and coverage run: | tox -e py3.8 -- --aea-loop sync -m 'integration and not unstable and not ledger' - + - name: Async Integration tests + run: tox -e py3.8 -- -m 'integration and not unstable and not ledger' + common_checks: runs-on: ubuntu-latest continue-on-error: True @@ -82,6 +86,8 @@ jobs: tar xvfz go-ipfs.tar.gz sudo mv go-ipfs/ipfs /usr/local/bin/ipfs ipfs init + - name: Pipenv lock + run: pipenv lock - name: Security Check run: tox -e bandit - name: Safety Check @@ -111,27 +117,6 @@ jobs: - name: Generate Documentation run: tox -e docs - integration_checks: - continue-on-error: True - runs-on: ubuntu-latest - - timeout-minutes: 40 - - steps: - - uses: actions/checkout@master - - uses: actions/setup-python@master - with: - python-version: 3.7 - - name: Install dependencies (ubuntu-latest) - run: | - sudo apt-get update --fix-missing - sudo apt-get autoremove - sudo apt-get autoclean - pip install pipenv - pip install tox - - name: Integration tests - run: tox -e py3.7 -- -m 'integration and not unstable and not ledger' - integration_checks_ledger: continue-on-error: True runs-on: ubuntu-latest diff --git a/HISTORY.md b/HISTORY.md index 596f9b2171..39ffdbca8d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,27 @@ # Release History +## 0.5.3 (2020-08-05) + +- Adds support for re-starting agent after stopping it +- Adds full test coverage for protocols generator +- Adds support for dynamically adding handlers +- Improves p2p connection startup reliability +- Addresses p2p connection race condition with long running processes +- Adds connection states in connections +- Applies consistent logger usage throughout +- Adds key rotation and randomised locations for integration tests +- Adds request delays in SOEF connection to avoid request limits +- Exposes runtime states on agent and removes agent liveness object +- Adds readme files in protocols and connections +- Improves edge case handling in dialogue models +- Adds support for cosmwasm message signing +- Adds test coverage for test tools +- Adds dialogues models in all connections where required +- Transitions erc1155 skills and simple search to SOEF and p2p +- Adds full test coverage for skills modules +- Multiple docs updates +- Multiple additional tests and test stability fixes + ## 0.5.2 (2020-07-21) - Transitions demos to agent-land test network, P2P and SOEF connections diff --git a/MANIFEST.in b/MANIFEST.in index 5eb868e9f3..2b10871f8e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include README.md LICENSE HISTORY.md AUTHORS.md SECURITY.md CODE_OF_CONDUCT.md Pipfile mkdocs.yml tox.ini pytest.ini strategy.ini -recursive-include aea *.json *.yaml *.proto *.ico *png *.html *.js *.css +recursive-include aea *.json *.yaml *.proto *.ico *png *.html *.js *.css *.md recursive-include docs * recursive-include examples * recursive-include packages * diff --git a/Makefile b/Makefile index 222d6572bc..905922a689 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ lint: .PHONY: pylint pylint: - pylint aea benchmark examples packages scripts + pylint aea benchmark packages scripts examples/* .PHONY: security security: diff --git a/Pipfile b/Pipfile index baa3f28804..ee8e85647e 100644 --- a/Pipfile +++ b/Pipfile @@ -39,7 +39,6 @@ openapi-spec-validator = "==0.2.8" pexpect = "==4.8.0" psutil = "==5.7.0" pydocstyle = "==3.0.0" -pydoc-markdown = "==3.3.0" pygments = "==2.5.2" pylint = "==2.5.2" pymdown-extensions = "==6.3" diff --git a/README.md b/README.md index 7b2fcbb1d8..e1323ae025 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,51 @@ -# AEA Framework +

+ AEA Framework +

-[![PyPI](https://img.shields.io/pypi/v/aea)](https://pypi.org/project/aea/) -![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aea) -![PyPI - Wheel](https://img.shields.io/pypi/wheel/aea) -[![](https://img.shields.io/pypi/l/aea)](https://github.com/fetchai/agents-aea/blob/master/LICENSE) -![PyPI - Downloads](https://img.shields.io/pypi/dm/aea) -[![](https://img.shields.io/badge/slack-fetchai-black.svg)](https://fetch-ai.slack.com/join/shared_invite/enQtNDI2MDYwMjE3OTQwLWY0ZjAyYjM0NGQzNWRhNDMxMzdjYmVhYTE3NDNhNTAyMTE0YWRkY2VmOWRmMGQ3ODM1N2NjOWUwNDExM2U3YjY) - -![AEA framework sanity checks and tests](https://github.com/fetchai/agents-aea/workflows/AEA%20framework%20sanity%20checks%20and%20tests/badge.svg?branch=master) -![Codecov](https://img.shields.io/codecov/c/github/fetchai/agents-aea) +

+ + PyPI + + + PyPI - Python Version + + + PyPI - Wheel + + + License + + + License + + + Slack + +

+

+ + AEA framework sanity checks and tests + + + Codecov + + + flake8 + + + mypy + + + Black + + + mypy + +

+

A framework for autonomous economic agent (AEA) development +

AEA Description diff --git a/aea/__version__.py b/aea/__version__.py index 884d0cd850..9c4e84a1b2 100644 --- a/aea/__version__.py +++ b/aea/__version__.py @@ -22,7 +22,7 @@ __title__ = "aea" __description__ = "Autonomous Economic Agent framework" __url__ = "https://github.com/fetchai/agents-aea.git" -__version__ = "0.5.2" +__version__ = "0.5.3" __author__ = "Fetch.AI Limited" __license__ = "Apache-2.0" __copyright__ = "2019 Fetch.AI Limited" diff --git a/aea/aea.py b/aea/aea.py index ed396d4c26..fdb96bcd82 100644 --- a/aea/aea.py +++ b/aea/aea.py @@ -34,7 +34,7 @@ from aea.exceptions import AEAException from aea.helpers.exception_policy import ExceptionPolicyEnum from aea.helpers.exec_timeout import ExecTimeoutThreadGuard, TimeoutException -from aea.helpers.logging import AgentLoggerAdapter +from aea.helpers.logging import AgentLoggerAdapter, WithLogger from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message @@ -45,10 +45,8 @@ from aea.skills.error.handlers import ErrorHandler from aea.skills.tasks import TaskManager -logger = logging.getLogger(__name__) - -class AEA(Agent): +class AEA(Agent, WithLogger): """This class implements an autonomous economic agent.""" RUN_LOOPS: Dict[str, Type[BaseAgentLoop]] = { @@ -108,6 +106,10 @@ def __init__( loop_mode=loop_mode, runtime_mode=runtime_mode, ) + aea_logger = AgentLoggerAdapter( + logger=logging.getLogger(__name__), agent_name=identity.name + ) + WithLogger.__init__(self, logger=cast(logging.Logger, aea_logger)) self.max_reactions = max_reactions self._task_manager = TaskManager() @@ -256,14 +258,14 @@ def _handle(self, envelope: Envelope) -> None: :param envelope: the envelope to handle. :return: None """ - logger.debug("Handling envelope: {}".format(envelope)) + self.logger.debug("Handling envelope: {}".format(envelope)) protocol = self.resources.get_protocol(envelope.protocol_id) # TODO specify error handler in config and make this work for different skill/protocol versions. error_handler = self._get_error_handler() if error_handler is None: - logger.warning("ErrorHandler not initialized. Stopping AEA!") + self.logger.warning("ErrorHandler not initialized. Stopping AEA!") self.stop() return error_handler = cast(ErrorHandler, error_handler) @@ -278,9 +280,11 @@ def _handle(self, envelope: Envelope) -> None: else: msg = protocol.serializer.decode(envelope.message) msg.counterparty = envelope.sender + msg.sender = envelope.sender + # msg.to = envelope.to msg.is_incoming = True except Exception as e: # pylint: disable=broad-except # thats ok, because we send the decoding error back - logger.warning("Decoding error. Exception: {}".format(str(e))) + self.logger.warning("Decoding error. Exception: {}".format(str(e))) error_handler.send_decoding_error(envelope) return @@ -334,13 +338,13 @@ def _execution_control( """ # docstyle: ignore def log_exception(e, fn, component): - logger.exception(f"<{e}> raised during `{fn}` call of `{component}`") + self.logger.exception(f"<{e}> raised during `{fn}` call of `{component}`") try: with ExecTimeoutThreadGuard(self._execution_timeout): return fn(*(args or []), **(kwargs or {})) except TimeoutException: - logger.warning( + self.logger.warning( "`{}` of `{}` was terminated as its execution exceeded the timeout of {} seconds. Please refactor your code!".format( fn, component, self._execution_timeout ) @@ -383,8 +387,7 @@ def teardown(self) -> None: :return: None """ - logger.debug("[{}]: Calling teardown method...".format(self.name)) - self.liveness.stop() + self.logger.debug("[{}]: Calling teardown method...".format(self.name)) self.decision_maker.stop() self.task_manager.stop() self.resources.teardown() diff --git a/aea/aea_builder.py b/aea/aea_builder.py index e404bdbd65..d1c5f71b36 100644 --- a/aea/aea_builder.py +++ b/aea/aea_builder.py @@ -71,12 +71,7 @@ ) from aea.configurations.loader import ConfigLoader from aea.contracts import contract_registry -from aea.crypto.helpers import ( - IDENTIFIER_TO_KEY_FILES, - create_private_key, - try_validate_private_key_path, -) -from aea.crypto.registries import crypto_registry +from aea.crypto.helpers import verify_or_create_private_keys from aea.crypto.wallet import Wallet from aea.decision_maker.base import DecisionMakerHandler from aea.decision_maker.default import ( @@ -1301,7 +1296,9 @@ def from_aea_project( """ aea_project_path = Path(aea_project_path) cls._try_to_load_agent_configuration_file(aea_project_path) - _verify_or_create_private_keys(aea_project_path) + verify_or_create_private_keys( + aea_project_path=aea_project_path, exit_on_error=False + ) builder = AEABuilder(with_default_packages=False) # TODO isolate environment @@ -1343,11 +1340,17 @@ def _load_and_add_components( if configuration in self._component_instances[component_type].keys(): component = self._component_instances[component_type][configuration] + if configuration.component_type != ComponentType.SKILL: + component.logger = cast( + logging.Logger, make_logger(configuration, agent_name) + ) else: configuration = deepcopy(configuration) - component = load_component_from_config(configuration, **kwargs) + _logger = make_logger(configuration, agent_name) + component = load_component_from_config( + configuration, logger=_logger, **kwargs + ) - _set_logger_to_component(component, configuration, agent_name) resources.add_component(component) def _populate_contract_registry(self): @@ -1394,58 +1397,19 @@ def _check_we_can_build(self): ) -def _set_logger_to_component( - component: Component, configuration: ComponentConfiguration, agent_name: str, -) -> None: +def make_logger( + configuration: ComponentConfiguration, agent_name: str, +) -> Optional[logging.Logger]: """ - Set the logger to the component. + Make the logger for a component. - :param component: the component instance. :param configuration: the component configuration :param agent_name: the agent name - :return: None + :return: the logger. """ if configuration.component_type == ComponentType.SKILL: # skip because skill object already have their own logger from the skill context. - return + return None logger_name = f"aea.packages.{configuration.author}.{configuration.component_type.to_plural()}.{configuration.name}" - logger = AgentLoggerAdapter(logging.getLogger(logger_name), agent_name) - component.logger = logger - - -# TODO this function is repeated in 'aea.cli.utils.package_utils.py' -def _verify_or_create_private_keys(aea_project_path: Path) -> None: - """Verify or create private keys.""" - path_to_configuration = aea_project_path / DEFAULT_AEA_CONFIG_FILE - agent_loader = ConfigLoader("aea-config_schema.json", AgentConfig) - fp_read = path_to_configuration.open(mode="r", encoding="utf-8") - agent_configuration = agent_loader.load(fp_read) - - for identifier, _value in agent_configuration.private_key_paths.read_all(): - if identifier not in crypto_registry.supported_ids: - raise ValueError(f"Item not registered with id '{identifier}'.") - - for identifier, private_key_path in IDENTIFIER_TO_KEY_FILES.items(): - config_private_key_path = agent_configuration.private_key_paths.read(identifier) - if config_private_key_path is None: - create_private_key( - identifier, private_key_file=str(aea_project_path / private_key_path) - ) - agent_configuration.private_key_paths.update(identifier, private_key_path) - else: - try: - try_validate_private_key_path( - identifier, - str(aea_project_path / private_key_path), - exit_on_error=False, - ) - except FileNotFoundError: # pragma: no cover - logger.error( - "File {} for private key {} not found.".format( - repr(private_key_path), identifier, - ) - ) - raise - - fp_write = path_to_configuration.open(mode="w", encoding="utf-8") - agent_loader.dump(agent_configuration, fp_write) + _logger = AgentLoggerAdapter(logging.getLogger(logger_name), agent_name) + return cast(logging.Logger, _logger) diff --git a/aea/agent.py b/aea/agent.py index a5a139334b..19c2622321 100644 --- a/aea/agent.py +++ b/aea/agent.py @@ -21,55 +21,18 @@ import logging from abc import ABC, abstractmethod from asyncio import AbstractEventLoop -from enum import Enum from typing import Dict, List, Optional, Type from aea.agent_loop import BaseAgentLoop, SyncAgentLoop from aea.connections.base import Connection from aea.identity.base import Identity from aea.multiplexer import InBox, Multiplexer, OutBox -from aea.runtime import AsyncRuntime, BaseRuntime, ThreadedRuntime +from aea.runtime import AsyncRuntime, BaseRuntime, RuntimeStates, ThreadedRuntime logger = logging.getLogger(__name__) -class AgentState(Enum): - """Enumeration for an agent state. - - 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. - """ - - 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 - - def start(self) -> None: - """Start the liveness.""" - self._is_stopped = False - - def stop(self) -> None: - """Stop the liveness.""" - self._is_stopped = True - - class Agent(ABC): """This class provides an abstract base class for a generic agent.""" @@ -111,7 +74,6 @@ def __init__( self._multiplexer = Multiplexer(self._connections, loop=loop) self._inbox = InBox(self._multiplexer) self._outbox = OutBox(self._multiplexer, identity.address) - self._liveness = Liveness() self._timeout = timeout self._tick = 0 @@ -185,11 +147,6 @@ def name(self) -> str: """Get the agent name.""" return self.identity.name - @property - def liveness(self) -> Liveness: - """Get the liveness.""" - return self._liveness - @property def tick(self) -> int: """ @@ -204,26 +161,6 @@ def timeout(self) -> float: """Get the time in (fractions of) seconds to time out an agent between act and react.""" return self._timeout - @property - def agent_state(self) -> AgentState: - """ - Get the state of the agent. - - :raises ValueError: if the state does not satisfy any of the foreseen conditions. - :return: None - """ - if ( - self.multiplexer is not None - and not self.multiplexer.connection_status.is_connected - ): - return AgentState.INITIATED - elif self.multiplexer.connection_status.is_connected and not self.is_running: - return AgentState.CONNECTED - elif self.multiplexer.connection_status.is_connected and self.is_running: - return AgentState.RUNNING - else: - raise ValueError("Agent state not recognized.") # pragma: no cover - @property def loop_mode(self) -> str: """Get the agent loop mode.""" @@ -240,7 +177,7 @@ def runtime(self) -> BaseRuntime: return self._runtime def setup_multiplexer(self) -> None: - """Set up the multiplexer""" + """Set up the multiplexer.""" pass def start(self) -> None: @@ -278,7 +215,6 @@ def start_setup(self) -> None: """ logger.debug("[{}]: Calling setup method...".format(self.name)) self.setup() - self.liveness.start() def stop(self) -> None: """ @@ -333,3 +269,12 @@ def teardown(self) -> None: :return: None """ + + @property + def state(self) -> RuntimeStates: + """ + Get state of the agent's runtime. + + :return: RuntimeStates + """ + return self._runtime.state diff --git a/aea/agent_loop.py b/aea/agent_loop.py index 1266a3e852..29d319e746 100644 --- a/aea/agent_loop.py +++ b/aea/agent_loop.py @@ -252,7 +252,7 @@ def _create_tasks(self) -> List[Task]: tasks = [ self._task_process_inbox(), self._task_process_internal_messages(), - self._task_process_new_behaviours(), + self._task_process_new_skill_components(), self._task_wait_for_error(), ] return list(map(self._loop.create_task, tasks)) # type: ignore # some issue with map and create_task @@ -275,11 +275,12 @@ async def _task_process_internal_messages(self) -> None: msg ) - async def _task_process_new_behaviours(self) -> None: + async def _task_process_new_skill_components(self) -> None: """Process new behaviours added to skills in runtime.""" while self.is_running: # TODO: better handling internal messages for skills internal updates self._agent.filter._handle_new_behaviours() # pylint: disable=protected-access # TODO: refactoring! + self._agent.filter._handle_new_handlers() # pylint: disable=protected-access # TODO: refactoring! self._register_all_behaviours() # re register, cause new may appear await asyncio.sleep(self.NEW_BEHAVIOURS_PROCESS_SLEEP) diff --git a/aea/cli/generate_key.py b/aea/cli/generate_key.py index faece7ca93..1781c0fd5b 100644 --- a/aea/cli/generate_key.py +++ b/aea/cli/generate_key.py @@ -20,6 +20,7 @@ """Implementation of the 'aea generate_key' subcommand.""" from pathlib import Path +from typing import Optional import click @@ -34,24 +35,36 @@ type=click.Choice([*list(crypto_registry.supported_ids), "all"]), required=True, ) -def generate_key(type_): +@click.argument( + "file", + metavar="FILE", + type=click.Path(exists=False, file_okay=True, dir_okay=False, readable=True), + required=False, +) +def generate_key(type_, file): """Generate private keys.""" - _generate_private_key(type_) + _generate_private_key(type_, file) -def _generate_private_key(type_: str) -> None: +def _generate_private_key(type_: str, file: Optional[str] = None) -> None: """ Generate private key. :param type_: type. + :param file: path to file. :return: None """ - types = list(IDENTIFIER_TO_KEY_FILES.keys()) if type_ == "all" else [type_] + if type_ == "all" and file is not None: + raise click.ClickException("Type all cannot be used in combination with file.") + elif type_ == "all": + types = list(IDENTIFIER_TO_KEY_FILES.keys()) + else: + types = [type_] for type_ in types: - private_key_file = IDENTIFIER_TO_KEY_FILES[type_] + private_key_file = IDENTIFIER_TO_KEY_FILES[type_] if file is None else file if _can_write(private_key_file): - create_private_key(type_) + create_private_key(type_, private_key_file) def _can_write(path) -> bool: diff --git a/aea/cli/generate_wealth.py b/aea/cli/generate_wealth.py index 9485936a85..fc96215077 100644 --- a/aea/cli/generate_wealth.py +++ b/aea/cli/generate_wealth.py @@ -20,29 +20,30 @@ """Implementation of the 'aea generate_wealth' subcommand.""" import time -from typing import cast +from typing import Dict, Optional, cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import try_get_balance, verify_or_create_private_keys -from aea.crypto.helpers import ( - IDENTIFIER_TO_FAUCET_APIS, - TESTNETS, - try_generate_testnet_wealth, +from aea.cli.utils.package_utils import ( + try_get_balance, + verify_or_create_private_keys_ctx, ) +from aea.configurations.base import AgentConfig +from aea.crypto.helpers import try_generate_testnet_wealth +from aea.crypto.registries import faucet_apis_registry, make_faucet_api_cls from aea.crypto.wallet import Wallet -FUNDS_RELEASE_TIMEOUT = 10 +FUNDS_RELEASE_TIMEOUT = 30 @click.command() @click.argument( "type_", metavar="TYPE", - type=click.Choice(list(IDENTIFIER_TO_FAUCET_APIS.keys())), + type=click.Choice(list(faucet_apis_registry.supported_ids)), required=True, ) @click.option( @@ -55,18 +56,29 @@ def generate_wealth(click_context, sync, type_): _try_generate_wealth(click_context, type_, sync) -def _try_generate_wealth(click_context, type_, sync): +def _try_generate_wealth( + click_context: click.core.Context, type_: str, sync: bool +) -> None: + """ + Try generate wealth for the provided network identifier. + + :param click_context: the click context + :param type_: the network type + :param sync: whether to sync or not + :return: None + """ ctx = cast(Context, click_context.obj) - verify_or_create_private_keys(ctx) + verify_or_create_private_keys_ctx(ctx=ctx) private_key_paths = { config_pair[0]: config_pair[1] for config_pair in ctx.agent_config.private_key_paths.read_all() - } + } # type: Dict[str, Optional[str]] wallet = Wallet(private_key_paths) try: address = wallet.addresses[type_] - testnet = TESTNETS[type_] + faucet_api_cls = make_faucet_api_cls(type_) + testnet = faucet_api_cls.network_name click.echo( "Requesting funds for address {} on test network '{}'".format( address, testnet @@ -80,10 +92,22 @@ def _try_generate_wealth(click_context, type_, sync): raise click.ClickException(str(e)) -def _wait_funds_release(agent_config, wallet, type_): +def _wait_funds_release(agent_config: AgentConfig, wallet: Wallet, type_: str) -> None: + """ + Wait for the funds to be released. + + :param agent_config: the agent config + :param wallet: the wallet + :param type_: the network type + """ start_balance = try_get_balance(agent_config, wallet, type_) end_time = time.time() + FUNDS_RELEASE_TIMEOUT + has_hit_timeout = True while time.time() < end_time: - if start_balance != try_get_balance(agent_config, wallet, type_): + current_balance = try_get_balance(agent_config, wallet, type_) + if start_balance != current_balance: + has_hit_timeout = False break # pragma: no cover time.sleep(1) + if has_hit_timeout: + raise ValueError("Timeout hit. Syncing did not finish.") diff --git a/aea/cli/get_address.py b/aea/cli/get_address.py index 2cf3f570c4..632353e26a 100644 --- a/aea/cli/get_address.py +++ b/aea/cli/get_address.py @@ -25,7 +25,7 @@ from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import verify_or_create_private_keys +from aea.cli.utils.package_utils import verify_or_create_private_keys_ctx from aea.crypto.registries import crypto_registry from aea.crypto.wallet import Wallet @@ -55,7 +55,7 @@ def _try_get_address(click_context, type_): :return: address. """ ctx = cast(Context, click_context.obj) - verify_or_create_private_keys(ctx) + verify_or_create_private_keys_ctx(ctx=ctx) private_key_paths = { config_pair[0]: config_pair[1] diff --git a/aea/cli/get_wealth.py b/aea/cli/get_wealth.py index 0a828db4f2..021c48faa1 100644 --- a/aea/cli/get_wealth.py +++ b/aea/cli/get_wealth.py @@ -19,20 +19,26 @@ """Implementation of the 'aea get_wealth' subcommand.""" -from typing import cast +from typing import Dict, Optional, cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import try_get_balance, verify_or_create_private_keys -from aea.crypto.ledger_apis import SUPPORTED_LEDGER_APIS +from aea.cli.utils.package_utils import ( + try_get_balance, + verify_or_create_private_keys_ctx, +) +from aea.crypto.registries import ledger_apis_registry from aea.crypto.wallet import Wallet @click.command() @click.argument( - "type_", metavar="TYPE", type=click.Choice(SUPPORTED_LEDGER_APIS), required=True, + "type_", + metavar="TYPE", + type=click.Choice(ledger_apis_registry.supported_ids), + required=True, ) @click.pass_context @check_aea_project @@ -42,12 +48,12 @@ def get_wealth(click_context, type_): click.echo(wealth) -def _try_get_wealth(click_context, type_): +def _try_get_wealth(click_context: click.core.Context, type_: str): ctx = cast(Context, click_context.obj) - verify_or_create_private_keys(ctx) + verify_or_create_private_keys_ctx(ctx=ctx) private_key_paths = { config_pair[0]: config_pair[1] for config_pair in ctx.agent_config.private_key_paths.read_all() - } + } # type: Dict[str, Optional[str]] wallet = Wallet(private_key_paths) return try_get_balance(ctx.agent_config, wallet, type_) diff --git a/aea/cli/interact.py b/aea/cli/interact.py index c7eaf03a65..0761b1a26e 100644 --- a/aea/cli/interact.py +++ b/aea/cli/interact.py @@ -134,7 +134,7 @@ def _try_construct_envelope(agent_name: str, sender: str) -> Optional[Envelope]: performative_str = "bytes" performative = DefaultMessage.Performative(performative_str) click.echo( - "Provide message of protocol fetchai/default:0.3.0 for performative {}:".format( + "Provide message of protocol fetchai/default:0.4.0 for performative {}:".format( performative_str ) ) @@ -150,10 +150,12 @@ def _try_construct_envelope(agent_name: str, sender: str) -> Optional[Envelope]: else: message = message_escaped # pragma: no cover msg = DefaultMessage(performative=performative, content=message) + msg.counterparty = agent_name + msg.sender = sender envelope = Envelope( - to=agent_name, - sender=sender, - protocol_id=DefaultMessage.protocol_id, # PublicId.from_str(protocol_id), + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) except InterruptInputException: diff --git a/aea/cli/utils/package_utils.py b/aea/cli/utils/package_utils.py index 84e8ce44c9..e199097ca9 100644 --- a/aea/cli/utils/package_utils.py +++ b/aea/cli/utils/package_utils.py @@ -42,51 +42,27 @@ _get_default_configuration_file_name_from_type, ) from aea.configurations.loader import ConfigLoader -from aea.crypto.helpers import ( - IDENTIFIER_TO_KEY_FILES, - create_private_key, - try_validate_private_key_path, -) +from aea.crypto.helpers import verify_or_create_private_keys from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis -from aea.crypto.registries import crypto_registry from aea.crypto.wallet import Wallet +ROOT = Path(".") + -def verify_or_create_private_keys(ctx: Context) -> None: +def verify_or_create_private_keys_ctx( + ctx: Context, aea_project_path: Path = ROOT, exit_on_error: bool = True, +) -> None: """ - Verify or create private keys. + Verify or create private keys with ctx provided. :param ctx: Context """ - path = Path(DEFAULT_AEA_CONFIG_FILE) - agent_loader = ConfigLoader("aea-config_schema.json", AgentConfig) - fp = path.open(mode="r", encoding="utf-8") - aea_conf = agent_loader.load(fp) - - for identifier, _value in aea_conf.private_key_paths.read_all(): - if identifier not in crypto_registry.supported_ids: - ValueError("Unsupported identifier in private key paths.") - - for identifier, private_key_path in IDENTIFIER_TO_KEY_FILES.items(): - config_private_key_path = aea_conf.private_key_paths.read(identifier) - if config_private_key_path is None: - create_private_key(identifier) - aea_conf.private_key_paths.update(identifier, private_key_path) - else: - try: - try_validate_private_key_path(identifier, private_key_path) - except FileNotFoundError: # pragma: no cover - raise click.ClickException( - "File {} for private key {} not found.".format( - repr(private_key_path), identifier, - ) - ) - - # update aea config - path = Path(DEFAULT_AEA_CONFIG_FILE) - fp = path.open(mode="w", encoding="utf-8") - agent_loader.dump(aea_conf, fp) - ctx.agent_config = aea_conf + try: + agent_config = verify_or_create_private_keys(aea_project_path, exit_on_error) + if ctx is not None: + ctx.agent_config = agent_config + except ValueError as e: # pragma: nocover + click.ClickException(str(e)) def validate_package_name(package_name: str): @@ -430,9 +406,8 @@ def try_get_balance(agent_config: AgentConfig, wallet: Wallet, type_: str) -> in try: if type_ not in DEFAULT_LEDGER_CONFIGS: # pragma: no cover raise ValueError("No ledger api config for {} available.".format(type_)) - ledger_apis = LedgerApis(DEFAULT_LEDGER_CONFIGS, agent_config.default_ledger) address = wallet.addresses[type_] - balance = ledger_apis.get_balance(type_, address) + balance = LedgerApis.get_balance(type_, address) if balance is None: # pragma: no cover raise ValueError("No balance returned!") return balance diff --git a/aea/components/base.py b/aea/components/base.py index 166defa01d..0455e736f2 100644 --- a/aea/components/base.py +++ b/aea/components/base.py @@ -42,6 +42,7 @@ def __init__( self, configuration: Optional[ComponentConfiguration] = None, is_vendor: bool = False, + **kwargs ): """ Initialize a package. @@ -49,7 +50,7 @@ def __init__( :param configuration: the package configuration. :param is_vendor: whether the package is vendorized. """ - WithLogger.__init__(self) + WithLogger.__init__(self, **kwargs) self._configuration = configuration self._directory = None # type: Optional[Path] self._is_vendor = is_vendor diff --git a/aea/configurations/constants.py b/aea/configurations/constants.py index 89b557e3f1..2ac9890c4a 100644 --- a/aea/configurations/constants.py +++ b/aea/configurations/constants.py @@ -25,13 +25,13 @@ from aea.crypto.cosmos import CosmosCrypto from aea.crypto.helpers import COSMOS_PRIVATE_KEY_FILE -DEFAULT_CONNECTION = PublicId.from_str("fetchai/stub:0.6.0") -DEFAULT_PROTOCOL = PublicId.from_str("fetchai/default:0.3.0") -DEFAULT_SKILL = PublicId.from_str("fetchai/error:0.3.0") +DEFAULT_CONNECTION = PublicId.from_str("fetchai/stub:0.7.0") +DEFAULT_PROTOCOL = PublicId.from_str("fetchai/default:0.4.0") +DEFAULT_SKILL = PublicId.from_str("fetchai/error:0.4.0") DEFAULT_LEDGER = CosmosCrypto.identifier DEFAULT_PRIVATE_KEY_FILE = COSMOS_PRIVATE_KEY_FILE DEFAULT_REGISTRY_PATH = DRP DEFAULT_LICENSE = DL -SIGNING_PROTOCOL = PublicId.from_str("fetchai/signing:0.1.0") -STATE_UPDATE_PROTOCOL = PublicId.from_str("fetchai/state_update:0.1.0") +SIGNING_PROTOCOL = PublicId.from_str("fetchai/signing:0.2.0") +STATE_UPDATE_PROTOCOL = PublicId.from_str("fetchai/state_update:0.2.0") LOCAL_PROTOCOLS = [DEFAULT_PROTOCOL, SIGNING_PROTOCOL, STATE_UPDATE_PROTOCOL] diff --git a/aea/connections/base.py b/aea/connections/base.py index eb7888333b..f04dd49021 100644 --- a/aea/connections/base.py +++ b/aea/connections/base.py @@ -16,13 +16,13 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """The base connection package.""" import inspect import logging import re from abc import ABC, abstractmethod from asyncio import AbstractEventLoop +from enum import Enum from pathlib import Path from typing import Optional, Set, TYPE_CHECKING, cast @@ -34,6 +34,7 @@ PublicId, ) from aea.crypto.wallet import CryptoStore +from aea.helpers.async_utils import AsyncState from aea.helpers.base import load_aea_package, load_module from aea.identity.base import Identity @@ -45,15 +46,13 @@ logger = logging.getLogger(__name__) -# TODO refactoring: this should be an enum -# but beware of backward-compatibility. -class ConnectionStatus: - """The connection status class.""" +class ConnectionStates(Enum): + """Connection states enum.""" - def __init__(self): - """Initialize the connection status.""" - self.is_connected = False # type: bool - self.is_connecting = False # type: bool + connected = "connected" + connecting = "connecting" + disconnecting = "disconnecting" + disconnected = "disconnected" class Connection(Component, ABC): @@ -68,6 +67,7 @@ def __init__( crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, + **kwargs ): """ Initialize the connection. @@ -82,12 +82,12 @@ def __init__( :param excluded_protocols: the set of protocols ids that we want to exclude for this connection. """ assert configuration is not None, "The configuration must be provided." - super().__init__(configuration) + super().__init__(configuration, **kwargs) assert ( super().public_id == self.connection_id ), "Connection ids in configuration and class not matching." self._loop: Optional[AbstractEventLoop] = None - self._connection_status = ConnectionStatus() + self._state = AsyncState(ConnectionStates.disconnected) self._identity = identity self._crypto_store = crypto_store @@ -164,9 +164,9 @@ def excluded_protocols(self) -> Set[PublicId]: # pragma: nocover return self.configuration.excluded_protocols @property - def connection_status(self) -> ConnectionStatus: + def state(self) -> ConnectionStates: """Get the connection status.""" - return self._connection_status + return self._state.get() @abstractmethod async def connect(self): @@ -195,7 +195,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: @classmethod def from_dir( - cls, directory: str, identity: Identity, crypto_store: CryptoStore + cls, directory: str, identity: Identity, crypto_store: CryptoStore, **kwargs ) -> "Connection": """ Load the connection from a directory. @@ -210,7 +210,7 @@ def from_dir( ComponentConfiguration.load(ComponentType.CONNECTION, Path(directory)), ) configuration.directory = Path(directory) - return Connection.from_config(configuration, identity, crypto_store) + return Connection.from_config(configuration, identity, crypto_store, **kwargs) @classmethod def from_config( @@ -218,6 +218,7 @@ def from_config( configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, + **kwargs ) -> "Connection": """ Load a connection from a configuration. @@ -249,5 +250,18 @@ def from_config( connection_class_name ) return connection_class( - configuration=configuration, identity=identity, crypto_store=crypto_store + configuration=configuration, + identity=identity, + crypto_store=crypto_store, + **kwargs ) + + @property + def is_connected(self) -> bool: + """Return is connected state.""" + return self.state == ConnectionStates.connected + + @property + def is_disconnected(self) -> bool: + """Return is disconnected state.""" + return self.state == ConnectionStates.disconnected diff --git a/aea/connections/scaffold/connection.yaml b/aea/connections/scaffold/connection.yaml index 21393b32e4..c1e655bae3 100644 --- a/aea/connections/scaffold/connection.yaml +++ b/aea/connections/scaffold/connection.yaml @@ -8,6 +8,7 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj connection.py: QmT7MNg8gkmWMzthN3k77i6UVhwXBeC2bGiNrUmXQcjWit + readme.md: Qmdt71SaCCwAG1c24VktXDm4pxgUBiPMg4bWfUTiqorypf fingerprint_ignore_patterns: [] protocols: [] class_name: MyScaffoldConnection diff --git a/aea/connections/scaffold/readme.md b/aea/connections/scaffold/readme.md new file mode 100644 index 0000000000..6359d72d7b --- /dev/null +++ b/aea/connections/scaffold/readme.md @@ -0,0 +1,5 @@ +# Scaffold connection +The scaffold connection acts as a boilerplate for a newly created connection. + +## Usage +Create a scaffold connection with the `aea scaffold connection {NAME}` command and implement your own connection. diff --git a/aea/connections/stub/connection.py b/aea/connections/stub/connection.py index 71fdef4769..4aa3fe28b4 100644 --- a/aea/connections/stub/connection.py +++ b/aea/connections/stub/connection.py @@ -30,7 +30,7 @@ from typing import AsyncIterable, IO, List, Optional from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.helpers import file_lock from aea.helpers.base import exception_log_and_reraise from aea.mail.base import Envelope @@ -44,7 +44,7 @@ DEFAULT_OUTPUT_FILE_NAME = "./output_file" SEPARATOR = b"," -PUBLIC_ID = PublicId.from_str("fetchai/stub:0.6.0") +PUBLIC_ID = PublicId.from_str("fetchai/stub:0.7.0") def _encode(e: Envelope, separator: bytes = SEPARATOR): @@ -251,19 +251,19 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: async def connect(self) -> None: """Set up the connection.""" - if self.connection_status.is_connected: + if self.is_connected: return - self._loop = asyncio.get_event_loop() + + self._state.set(ConnectionStates.connecting) + try: - # initialize the queue here because the queue - # must be initialized with the right event loop - # which is known only at connection time. + self._loop = asyncio.get_event_loop() self.in_queue = asyncio.Queue() self._read_envelopes_task = self._loop.create_task(self.read_envelopes()) - finally: - self.connection_status.is_connected = False - - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) + except Exception: # pragma: no cover + self._state.set(ConnectionStates.disconnected) + raise async def _stop_read_envelopes(self) -> None: """ @@ -292,14 +292,16 @@ async def disconnect(self) -> None: In this type of connection there's no channel to disconnect. """ - if not self.connection_status.is_connected: + if self.is_disconnected: return assert self.in_queue is not None, "Input queue not initialized." + + self._state.set(ConnectionStates.disconnecting) await self._stop_read_envelopes() self._write_pool.shutdown(wait=False) self.in_queue.put_nowait(None) - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: Envelope) -> None: """ diff --git a/aea/connections/stub/connection.yaml b/aea/connections/stub/connection.yaml index b066d6281c..6a9045d90e 100644 --- a/aea/connections/stub/connection.yaml +++ b/aea/connections/stub/connection.yaml @@ -1,13 +1,14 @@ name: stub author: fetchai -version: 0.6.0 +version: 0.7.0 description: The stub connection implements a connection stub which reads/writes messages from/to file. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmWwepN9Fy9gHAp39vUGFSLdnB9JZjdyE3STnbowSUhJkC - connection.py: QmSTtyR9GAeTRpby8dWNXwLQ2XHQdVhxKpLesruUJKsw9v + connection.py: QmVkdNVgxBLMcTbPqUF1aTz1hcjF5bW8HXkXrcc8MyfRFz + readme.md: Qmdh2bmWqSCTZPGoLomuG4Gfbfcktz3bR7hVTLJTpVH9Xn fingerprint_ignore_patterns: [] protocols: [] class_name: StubConnection diff --git a/aea/connections/stub/readme.md b/aea/connections/stub/readme.md new file mode 100644 index 0000000000..a534a8e5b9 --- /dev/null +++ b/aea/connections/stub/readme.md @@ -0,0 +1,7 @@ +# stub connection +A simple connection for communication with an AEA, using the file system as a point of data exchange. + +## Usage +First, add the connection to your AEA project: `aea add connection fetchai/stub:0.7.0`. (If you have created your AEA project with `aea create` then the connection will already be available by default.) + +Optionally, in the `connection.yaml` file under `config` set the `input_file` and `output_file` to the desired file path. The `stub` connection reads encoded envelopes from the `input_file` and writes encoded envelopes to the `output_file`. diff --git a/aea/context/base.py b/aea/context/base.py index 778ee507d8..f789a9d6c3 100644 --- a/aea/context/base.py +++ b/aea/context/base.py @@ -24,10 +24,9 @@ from typing import Any, Dict, Optional from aea.configurations.base import PublicId -from aea.connections.base import ConnectionStatus from aea.identity.base import Identity from aea.mail.base import Address -from aea.multiplexer import OutBox +from aea.multiplexer import ConnectionStatus, OutBox from aea.skills.tasks import TaskManager diff --git a/aea/contracts/base.py b/aea/contracts/base.py index 9f53aa0e61..275944b9ad 100644 --- a/aea/contracts/base.py +++ b/aea/contracts/base.py @@ -43,13 +43,13 @@ class Contract(Component, ABC): contract_interface: Any = None - def __init__(self, contract_config: ContractConfig): + def __init__(self, contract_config: ContractConfig, **kwargs): """ Initialize the contract. :param contract_config: the contract configurations. """ - super().__init__(contract_config) + super().__init__(contract_config, **kwargs) @property def id(self) -> ContractId: @@ -76,7 +76,7 @@ def get_instance( """ @classmethod - def from_dir(cls, directory: str) -> "Contract": + def from_dir(cls, directory: str, **kwargs) -> "Contract": """ Load the protocol from a directory. @@ -88,10 +88,10 @@ def from_dir(cls, directory: str) -> "Contract": ComponentConfiguration.load(ComponentType.CONTRACT, Path(directory)), ) configuration.directory = Path(directory) - return Contract.from_config(configuration) + return Contract.from_config(configuration, **kwargs) @classmethod - def from_config(cls, configuration: ContractConfig) -> "Contract": + def from_config(cls, configuration: ContractConfig, **kwargs) -> "Contract": """ Load contract from configuration. @@ -120,4 +120,64 @@ def from_config(cls, configuration: ContractConfig) -> "Contract": # with open(path, "r") as interface_file: # contract_interface = json.load(interface_file) - return contract_class(configuration) + return contract_class(configuration, **kwargs) + + @classmethod + def get_deploy_transaction(cls, ledger_api: LedgerApi, **kwargs) -> bytes: + """ + Handler method for the 'GET_DEPLOY_TRANSACTION' requests. + + Implement this method in the sub class if you want + to handle the contract requests manually. + + :param ledger_api: the ledger apis. + :param kwargs: keyword arguments. + :return: the bytes representing the state. + """ + raise NotImplementedError + + @classmethod + def get_raw_transaction( + cls, ledger_api: LedgerApi, contract_address: str, **kwargs + ) -> bytes: + """ + Handler method for the 'GET_RAW_TRANSACTION' requests. + + Implement this method in the sub class if you want + to handle the contract requests manually. + + :param ledger_api: the ledger apis. + :param contract_address: the contract address. + :return: the bytes representing the state. + """ + raise NotImplementedError + + @classmethod + def get_raw_message( + cls, ledger_api: LedgerApi, contract_address: str, **kwargs + ) -> bytes: + """ + Handler method for the 'GET_RAW_MESSAGE' requests. + + Implement this method in the sub class if you want + to handle the contract requests manually. + + :param ledger_api: the ledger apis. + :param contract_address: the contract address. + :return: the bytes representing the state. + """ + raise NotImplementedError + + @classmethod + def get_state(cls, ledger_api: LedgerApi, contract_address: str, **kwargs) -> bytes: + """ + Handler method for the 'GET_STATE' requests. + + Implement this method in the sub class if you want + to handle the contract requests manually. + + :param ledger_api: the ledger apis. + :param contract_address: the contract address. + :return: the bytes representing the state. + """ + raise NotImplementedError diff --git a/aea/crypto/__init__.py b/aea/crypto/__init__.py index e493ef4f70..0a07586c50 100644 --- a/aea/crypto/__init__.py +++ b/aea/crypto/__init__.py @@ -22,7 +22,11 @@ from aea.crypto.cosmos import CosmosCrypto from aea.crypto.ethereum import EthereumCrypto from aea.crypto.fetchai import FetchAICrypto -from aea.crypto.registries import register_crypto, register_ledger_api # noqa +from aea.crypto.registries import ( + register_crypto, + register_faucet_api, + register_ledger_api, +) # noqa register_crypto( id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAICrypto" @@ -34,6 +38,16 @@ id_=CosmosCrypto.identifier, entry_point="aea.crypto.cosmos:CosmosCrypto" ) +register_faucet_api( + id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAIFaucetApi" +) +register_faucet_api( + id_=EthereumCrypto.identifier, entry_point="aea.crypto.ethereum:EthereumFaucetApi" +) +register_faucet_api( + id_=CosmosCrypto.identifier, entry_point="aea.crypto.cosmos:CosmosFaucetApi" +) + register_ledger_api( id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAIApi", ) diff --git a/aea/crypto/base.py b/aea/crypto/base.py index 90a0e0eab9..b404e2f6a7 100644 --- a/aea/crypto/base.py +++ b/aea/crypto/base.py @@ -202,6 +202,16 @@ def recover_message( :return: the recovered addresses """ + @staticmethod + @abstractmethod + def get_hash(message: bytes) -> str: + """ + Get the hash of a message. + + :param message: the message to be hashed. + :return: the hash of the message. + """ + class LedgerApi(Helper, ABC): """Interface for ledger APIs.""" @@ -283,6 +293,7 @@ class FaucetApi(ABC): """Interface for testnet faucet APIs.""" identifier = "base" # type: str + network_name = "testnet" # type: str @abstractmethod def get_wealth(self, address: Address) -> None: diff --git a/aea/crypto/cosmos.py b/aea/crypto/cosmos.py index 96e6de1452..c76956cb01 100644 --- a/aea/crypto/cosmos.py +++ b/aea/crypto/cosmos.py @@ -42,6 +42,7 @@ _COSMOS = "cosmos" COSMOS_TESTNET_FAUCET_URL = "https://faucet-agent-land.prod.fetch-ai.com:443/claim" +TESTNET_NAME = "testnet" DEFAULT_ADDRESS = "https://rest-agent-land.prod.fetch-ai.com:443" DEFAULT_CURRENCY_DENOM = "atestfet" DEFAULT_CHAIN_ID = "agent-land" @@ -117,17 +118,19 @@ def sign_message(self, message: bytes, is_deprecated_mode: bool = False) -> str: signature_base64_str = base64.b64encode(signature_compact).decode("utf-8") return signature_base64_str - def sign_transaction(self, transaction: Any) -> Any: + @staticmethod + def format_default_transaction( + transaction: Any, signature: str, base64_pbk: str + ) -> Any: """ - Sign a transaction in bytes string form. + Format default CosmosSDK transaction and add signature - :param transaction: the transaction to be signed - :return: signed transaction + :param transaction: the transaction to be formatted + :param signature: the transaction signature + :param base64_pbk: the base64 formatted public key + + :return: formatted transaction with signature """ - transaction_str = json.dumps(transaction, separators=(",", ":"), sort_keys=True) - transaction_bytes = transaction_str.encode("utf-8") - signed_transaction = self.sign_message(transaction_bytes) - base64_pbk = base64.b64encode(bytes.fromhex(self.public_key)).decode("utf-8") pushable_tx = { "tx": { "msg": transaction["msgs"], @@ -135,7 +138,7 @@ def sign_transaction(self, transaction: Any) -> Any: "memo": transaction["memo"], "signatures": [ { - "signature": signed_transaction, + "signature": signature, "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": base64_pbk, @@ -149,6 +152,66 @@ def sign_transaction(self, transaction: Any) -> Any: } return pushable_tx + @staticmethod + def format_wasm_transaction( + transaction: Any, signature: str, base64_pbk: str + ) -> Any: + """ + Format CosmWasm transaction and add signature + + :param transaction: the transaction to be formatted + :param signature: the transaction signature + :param base64_pbk: the base64 formatted public key + + :return: formatted transaction with signature + """ + + pushable_tx = { + "type": "cosmos-sdk/StdTx", + "value": { + "msg": transaction["msgs"], + "fee": transaction["fee"], + "signatures": [ + { + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": base64_pbk, + }, + "signature": signature, + } + ], + "memo": transaction["memo"], + }, + } + return pushable_tx + + def sign_transaction(self, transaction: Any) -> Any: + """ + Sign a transaction in bytes string form. + + :param transaction: the transaction to be signed + :return: signed transaction + """ + + transaction_str = json.dumps(transaction, separators=(",", ":"), sort_keys=True) + transaction_bytes = transaction_str.encode("utf-8") + signed_transaction = self.sign_message(transaction_bytes) + base64_pbk = base64.b64encode(bytes.fromhex(self.public_key)).decode("utf-8") + + if ( + "msgs" in transaction + and len(transaction["msgs"]) == 1 + and "type" in transaction["msgs"][0] + and "wasm" in transaction["msgs"][0]["type"] + ): + return self.format_wasm_transaction( + transaction, signed_transaction, base64_pbk + ) + else: + return self.format_default_transaction( + transaction, signed_transaction, base64_pbk + ) + @classmethod def generate_private_key(cls) -> SigningKey: """Generate a key pair for cosmos network.""" @@ -268,6 +331,17 @@ def recover_message( ] return tuple(addresses) + @staticmethod + def get_hash(message: bytes) -> str: + """ + Get the hash of a message. + + :param message: the message to be hashed. + :return: the hash of the message. + """ + digest = hashlib.sha256(message).hexdigest() + return digest + class CosmosApi(LedgerApi, CosmosHelper): """Class to interact with the Cosmos SDK via a HTTP APIs.""" @@ -456,6 +530,7 @@ class CosmosFaucetApi(FaucetApi): """Cosmos testnet faucet API.""" identifier = _COSMOS + testnet_name = TESTNET_NAME def get_wealth(self, address: Address) -> None: """ diff --git a/aea/crypto/ethereum.py b/aea/crypto/ethereum.py index 51937bfd1d..cca52d9442 100644 --- a/aea/crypto/ethereum.py +++ b/aea/crypto/ethereum.py @@ -44,6 +44,7 @@ _ETHEREUM = "ethereum" GAS_ID = "gwei" ETHEREUM_TESTNET_FAUCET_URL = "https://faucet.ropsten.be/donate/" +TESTNET_NAME = "ropsten" DEFAULT_ADDRESS = "https://ropsten.infura.io/v3/f00f7b3ba0e848ddbdc8941c527447fe" DEFAULT_CHAIN_ID = 3 DEFAULT_GAS_PRICE = "50" @@ -244,6 +245,17 @@ def recover_message( ) return (address,) + @staticmethod + def get_hash(message: bytes) -> str: + """ + Get the hash of a message. + + :param message: the message to be hashed. + :return: the hash of the message. + """ + digest = Web3.keccak(message).hex() + return digest + class EthereumApi(LedgerApi, EthereumHelper): """Class to interact with the Ethereum Web3 APIs.""" @@ -419,6 +431,7 @@ class EthereumFaucetApi(FaucetApi): """Ethereum testnet faucet API.""" identifier = _ETHEREUM + testnet_name = TESTNET_NAME def get_wealth(self, address: Address) -> None: """ diff --git a/aea/crypto/fetchai.py b/aea/crypto/fetchai.py index 9e091f2997..1c35352faf 100644 --- a/aea/crypto/fetchai.py +++ b/aea/crypto/fetchai.py @@ -48,6 +48,7 @@ DEFAULT_NETWORK = "testnet" SUCCESSFUL_TERMINAL_STATES = ("Executed", "Submitted") FETCHAI_TESTNET_FAUCET_URL = "https://explore-testnet.fetch.ai/api/v1/send_tokens/" +TESTNET_NAME = "testnet" class FetchAICrypto(Crypto[Entity]): @@ -241,6 +242,17 @@ def recover_message( ] return tuple(addresses) + @staticmethod + def get_hash(message: bytes) -> str: + """ + Get the hash of a message. + + :param message: the message to be hashed. + :return: the hash of the message. + """ + digest = sha256_hash(message) + return digest.hex() + class FetchAIApi(LedgerApi, FetchAIHelper): """Class to interact with the Fetch ledger APIs.""" @@ -366,6 +378,7 @@ class FetchAIFaucetApi(FaucetApi): """Fetchai testnet faucet API.""" identifier = _FETCHAI + testnet_name = TESTNET_NAME def get_wealth(self, address: Address) -> None: """ diff --git a/aea/crypto/helpers.py b/aea/crypto/helpers.py index feb326b2bb..ad6e1bf970 100644 --- a/aea/crypto/helpers.py +++ b/aea/crypto/helpers.py @@ -21,35 +21,73 @@ import logging import sys -from typing import Optional +from pathlib import Path -from aea.crypto.cosmos import CosmosCrypto, CosmosFaucetApi -from aea.crypto.ethereum import EthereumCrypto, EthereumFaucetApi -from aea.crypto.fetchai import FetchAICrypto, FetchAIFaucetApi -from aea.crypto.registries import make_crypto +from aea.configurations.base import AgentConfig, DEFAULT_AEA_CONFIG_FILE +from aea.configurations.loader import ConfigLoader +from aea.crypto.cosmos import CosmosCrypto +from aea.crypto.ethereum import EthereumCrypto +from aea.crypto.fetchai import FetchAICrypto +from aea.crypto.registries import crypto_registry, make_crypto, make_faucet_api COSMOS_PRIVATE_KEY_FILE = "cosmos_private_key.txt" FETCHAI_PRIVATE_KEY_FILE = "fet_private_key.txt" ETHEREUM_PRIVATE_KEY_FILE = "eth_private_key.txt" -TESTNETS = { - FetchAICrypto.identifier: "testnet", - EthereumCrypto.identifier: "ropsten", - CosmosCrypto.identifier: "testnet", -} IDENTIFIER_TO_KEY_FILES = { CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE, EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_FILE, FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_FILE, } -IDENTIFIER_TO_FAUCET_APIS = { - CosmosCrypto.identifier: CosmosFaucetApi(), - EthereumCrypto.identifier: EthereumFaucetApi(), - FetchAICrypto.identifier: FetchAIFaucetApi(), -} logger = logging.getLogger(__name__) +def verify_or_create_private_keys( + aea_project_path: Path, exit_on_error: bool = True, +) -> AgentConfig: + """ + Verify or create private keys. + + :param ctx: Context + """ + path_to_aea_config = aea_project_path / DEFAULT_AEA_CONFIG_FILE + agent_loader = ConfigLoader("aea-config_schema.json", AgentConfig) + fp = path_to_aea_config.open(mode="r", encoding="utf-8") + aea_conf = agent_loader.load(fp) + + for identifier, _value in aea_conf.private_key_paths.read_all(): + if identifier not in crypto_registry.supported_ids: # pragma: nocover + ValueError("Unsupported identifier in private key paths.") + + for identifier, private_key_path in IDENTIFIER_TO_KEY_FILES.items(): + config_private_key_path = aea_conf.private_key_paths.read(identifier) + if config_private_key_path is None: + if identifier == aea_conf.default_ledger: # pragma: nocover + create_private_key( + identifier, + private_key_file=str(aea_project_path / private_key_path), + ) + aea_conf.private_key_paths.update(identifier, private_key_path) + else: + try: + try_validate_private_key_path( + identifier, + str(aea_project_path / private_key_path), + exit_on_error=exit_on_error, + ) + except FileNotFoundError: # pragma: no cover + raise ValueError( + "File {} for private key {} not found.".format( + repr(private_key_path), identifier, + ) + ) + + # update aea config + fp = path_to_aea_config.open(mode="w", encoding="utf-8") + agent_loader.dump(aea_conf, fp) + return aea_conf + + def try_validate_private_key_path( ledger_id: str, private_key_path: str, exit_on_error: bool = True ) -> None: @@ -77,16 +115,15 @@ def try_validate_private_key_path( raise -def create_private_key(ledger_id: str, private_key_file: Optional[str] = None) -> None: +def create_private_key(ledger_id: str, private_key_file: str) -> None: """ Create a private key for the specified ledger identifier. :param ledger_id: the ledger identifier. + :param private_key_file: the private key file. :return: None :raises: ValueError if the identifier is invalid. """ - if private_key_file is None: - private_key_file = IDENTIFIER_TO_KEY_FILES[ledger_id] crypto = make_crypto(ledger_id) crypto.dump(open(private_key_file, "wb")) @@ -99,6 +136,6 @@ def try_generate_testnet_wealth(identifier: str, address: str) -> None: :param address: the address to check for :return: None """ - faucet_api = IDENTIFIER_TO_FAUCET_APIS.get(identifier, None) + faucet_api = make_faucet_api(identifier) if faucet_api is not None: faucet_api.get_wealth(address) diff --git a/aea/crypto/ledger_apis.py b/aea/crypto/ledger_apis.py index a411553bf8..29aa10cf2f 100644 --- a/aea/crypto/ledger_apis.py +++ b/aea/crypto/ledger_apis.py @@ -19,22 +19,22 @@ """Module wrapping all the public and private keys cryptography.""" import logging -from typing import Any, Dict, Optional, Type, Union +from typing import Any, Dict, Optional, Tuple, Union +from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.base import LedgerApi from aea.crypto.cosmos import CosmosApi from aea.crypto.cosmos import DEFAULT_ADDRESS as COSMOS_DEFAULT_ADDRESS from aea.crypto.ethereum import DEFAULT_ADDRESS as ETHEREUM_DEFAULT_ADDRESS from aea.crypto.ethereum import DEFAULT_CHAIN_ID, EthereumApi from aea.crypto.fetchai import DEFAULT_NETWORK, FetchAIApi -from aea.crypto.registries import make_ledger_api +from aea.crypto.registries import ( + ledger_apis_registry, + make_ledger_api, + make_ledger_api_cls, +) from aea.mail.base import Address -SUPPORTED_LEDGER_APIS = { - CosmosApi.identifier: CosmosApi, - EthereumApi.identifier: EthereumApi, - FetchAIApi.identifier: FetchAIApi, -} # type: Dict[str, Type[LedgerApi]] DEFAULT_LEDGER_CONFIGS = { CosmosApi.identifier: {"address": COSMOS_DEFAULT_ADDRESS}, EthereumApi.identifier: { @@ -50,55 +50,24 @@ class LedgerApis: """Store all the ledger apis we initialise.""" - def __init__( - self, - ledger_api_configs: Dict[str, Dict[str, Union[str, int]]], - default_ledger_id: str, - ): - """ - Instantiate a wallet object. - - :param ledger_api_configs: the ledger api configs. - :param default_ledger_id: the default ledger id. - """ - apis = {} # type: Dict[str, LedgerApi] - for identifier, config in ledger_api_configs.items(): - api = make_ledger_api(identifier, **config) - apis[identifier] = api - self._apis = apis - self._configs = ledger_api_configs - self._default_ledger_id = default_ledger_id - - @property - def configs(self) -> Dict[str, Dict[str, Union[str, int]]]: - """Get the configs.""" - return self._configs - - @property - def apis(self) -> Dict[str, LedgerApi]: - """Get the apis.""" - return self._apis + ledger_api_configs: Dict[str, Dict[str, Union[str, int]]] = DEFAULT_LEDGER_CONFIGS - def has_ledger(self, identifier: str) -> bool: - """Check if it has a .""" - return identifier in self.apis + @staticmethod + def has_ledger(identifier: str) -> bool: + """Check if it has the api.""" + return identifier in ledger_apis_registry.supported_ids - def get_api(self, identifier: str) -> LedgerApi: + @classmethod + def get_api(cls, identifier: str) -> LedgerApi: """Get the ledger API.""" - assert self.has_ledger(identifier), "Ledger API not instantiated!" - return self.apis[identifier] - - @property - def has_default_ledger(self) -> bool: - """Check if it has the default ledger API.""" - return self.default_ledger_id in self.apis.keys() - - @property - def default_ledger_id(self) -> str: - """Get the default ledger id.""" - return self._default_ledger_id + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) + return api - def get_balance(self, identifier: str, address: str) -> Optional[int]: + @classmethod + def get_balance(cls, identifier: str, address: str) -> Optional[int]: """ Get the token balance. @@ -106,13 +75,16 @@ def get_balance(self, identifier: str, address: str) -> Optional[int]: :param address: the address to check for :return: the token balance """ - assert identifier in self.apis.keys(), "Not a registered ledger api identifier." - api = self.apis[identifier] + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) balance = api.get_balance(address) return balance + @classmethod def get_transfer_transaction( - self, + cls, identifier: str, sender_address: str, destination_address: str, @@ -133,14 +105,17 @@ def get_transfer_transaction( :return: tx """ - assert identifier in self.apis.keys(), "Not a registered ledger api identifier." - api = self.apis[identifier] + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx = api.get_transfer_transaction( sender_address, destination_address, amount, tx_fee, tx_nonce, **kwargs, ) return tx - def send_signed_transaction(self, identifier: str, tx_signed: Any) -> Optional[str]: + @classmethod + def send_signed_transaction(cls, identifier: str, tx_signed: Any) -> Optional[str]: """ Send a signed transaction and wait for confirmation. @@ -148,12 +123,15 @@ def send_signed_transaction(self, identifier: str, tx_signed: Any) -> Optional[s :param tx_signed: the signed transaction :return: the tx_digest, if present """ - assert identifier in self.apis.keys(), "Not a registered ledger api identifier." - api = self.apis[identifier] + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx_digest = api.send_signed_transaction(tx_signed) return tx_digest - def get_transaction_receipt(self, identifier: str, tx_digest: str) -> Optional[Any]: + @classmethod + def get_transaction_receipt(cls, identifier: str, tx_digest: str) -> Optional[Any]: """ Get the transaction receipt for a transaction digest. @@ -161,12 +139,15 @@ def get_transaction_receipt(self, identifier: str, tx_digest: str) -> Optional[A :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ - assert identifier in self.apis.keys(), "Not a registered ledger api identifier." - api = self.apis[identifier] + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx_receipt = api.get_transaction_receipt(tx_digest) return tx_receipt - def get_transaction(self, identifier: str, tx_digest: str) -> Optional[Any]: + @classmethod + def get_transaction(cls, identifier: str, tx_digest: str) -> Optional[Any]: """ Get the transaction for a transaction digest. @@ -174,8 +155,10 @@ def get_transaction(self, identifier: str, tx_digest: str) -> Optional[Any]: :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ - assert identifier in self.apis.keys(), "Not a registered ledger api identifier." - api = self.apis[identifier] + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api = make_ledger_api(identifier, **cls.ledger_api_configs[identifier]) tx = api.get_transaction(tx_digest) return tx @@ -189,9 +172,9 @@ def is_transaction_settled(identifier: str, tx_receipt: Any) -> bool: :return: True if correctly settled, False otherwise """ assert ( - identifier in SUPPORTED_LEDGER_APIS.keys() + identifier in ledger_apis_registry.supported_ids ), "Not a registered ledger api identifier." - api_class = SUPPORTED_LEDGER_APIS[identifier] + api_class = make_ledger_api_cls(identifier) is_settled = api_class.is_transaction_settled(tx_receipt) return is_settled @@ -216,9 +199,9 @@ def is_transaction_valid( :return: True if is valid , False otherwise """ assert ( - identifier in SUPPORTED_LEDGER_APIS.keys() + identifier in ledger_apis_registry.supported_ids ), "Not a registered ledger api identifier." - api_class = SUPPORTED_LEDGER_APIS[identifier] + api_class = make_ledger_api_cls(identifier) is_valid = api_class.is_transaction_valid(tx, seller, client, tx_nonce, amount) return is_valid @@ -233,8 +216,51 @@ def generate_tx_nonce(identifier: str, seller: Address, client: Address) -> str: :return: return the hash in hex. """ assert ( - identifier in SUPPORTED_LEDGER_APIS.keys() + identifier in ledger_apis_registry.supported_ids ), "Not a registered ledger api identifier." - api_class = SUPPORTED_LEDGER_APIS[identifier] + api_class = make_ledger_api_cls(identifier) tx_nonce = api_class.generate_tx_nonce(seller=seller, client=client) return tx_nonce + + @staticmethod + def recover_message( + identifier: str, + message: bytes, + signature: str, + is_deprecated_mode: bool = False, + ) -> Tuple[Address, ...]: + """ + Recover the addresses from the hash. + + :param identifier: ledger identifier. + :param message: the message we expect + :param signature: the transaction signature + :param is_deprecated_mode: if the deprecated signing was used + :return: the recovered addresses + """ + assert ( + identifier in ledger_apis_registry.supported_ids + ), "Not a registered ledger api identifier." + api_class = make_ledger_api_cls(identifier) + addresses = api_class.recover_message( + message=message, signature=signature, is_deprecated_mode=is_deprecated_mode + ) + return addresses + + @staticmethod + def get_hash(identifier: str, message: bytes) -> str: + """ + Get the hash of a message. + + :param identifier: ledger identifier. + :param message: the message to be hashed. + :return: the hash of the message. + """ + identifier = ( + identifier + if identifier in ledger_apis_registry.supported_ids + else DEFAULT_LEDGER + ) + api_class = make_ledger_api_cls(identifier) + digest = api_class.get_hash(message=message) + return digest diff --git a/aea/crypto/registries/__init__.py b/aea/crypto/registries/__init__.py index f5d755e8f1..21c94d61cc 100644 --- a/aea/crypto/registries/__init__.py +++ b/aea/crypto/registries/__init__.py @@ -18,9 +18,9 @@ # ------------------------------------------------------------------------------ """This module contains the crypto and the ledger APIs registries.""" -from typing import Callable +from typing import Callable, Type -from aea.crypto.base import Crypto, LedgerApi +from aea.crypto.base import Crypto, FaucetApi, LedgerApi from aea.crypto.registries.base import Registry crypto_registry: Registry[Crypto] = Registry[Crypto]() @@ -30,3 +30,9 @@ ledger_apis_registry: Registry[LedgerApi] = Registry[LedgerApi]() register_ledger_api = ledger_apis_registry.register make_ledger_api: Callable[..., LedgerApi] = ledger_apis_registry.make +make_ledger_api_cls: Callable[..., Type[LedgerApi]] = ledger_apis_registry.make_cls + +faucet_apis_registry: Registry[FaucetApi] = Registry[FaucetApi]() +register_faucet_api = faucet_apis_registry.register +make_faucet_api: Callable[..., FaucetApi] = faucet_apis_registry.make +make_faucet_api_cls: Callable[..., Type[FaucetApi]] = faucet_apis_registry.make_cls diff --git a/aea/crypto/registries/base.py b/aea/crypto/registries/base.py index 7b9c172dc1..f1bab9d9f6 100644 --- a/aea/crypto/registries/base.py +++ b/aea/crypto/registries/base.py @@ -134,11 +134,20 @@ def make(self, **kwargs) -> ItemType: """ _kwargs = self._kwargs.copy() _kwargs.update(kwargs) + cls = self.get_class() + item = cls(**_kwargs) # type: ignore + return item + + def get_class(self) -> Type[ItemType]: + """ + Get the class of the item with class variables instantiated. + + :return: an item class + """ cls = self.entry_point.load() for key, value in self._class_kwargs.items(): setattr(cls, key, value) - item = cls(**_kwargs) # type: ignore - return item + return cls class Registry(Generic[ItemType]): @@ -201,6 +210,29 @@ def make( item = spec.make(**kwargs) return item + def make_cls( + self, id_: Union[ItemId, str], module: Optional[str] = None + ) -> Type[ItemType]: + """ + Load a class of the associated type item id. + + :param id_: the id of the item class. Make sure it has been registered earlier + before calling this function. + :param module: dotted path to a module. + whether a module should be loaded before creating the object. + this argument is useful when the item might not be registered + beforehand, and loading the specified module will make the registration. + E.g. suppose the call to 'register' for a custom object + is located in some_package/__init__.py. By providing module="some_package", + the call to 'register' in such module gets triggered and + the make can then find the identifier. + :return: the new item class. + """ + item_id = ItemId(id_) + spec = self._get_spec(item_id, module=module) + cls = spec.get_class() + return cls + def has_spec(self, item_id: ItemId) -> bool: """ Check whether there exist a spec associated with an item id. diff --git a/aea/crypto/wallet.py b/aea/crypto/wallet.py index d1028d3aa7..c4971d0e59 100644 --- a/aea/crypto/wallet.py +++ b/aea/crypto/wallet.py @@ -45,16 +45,19 @@ def __init__( crypto_objects = {} # type: Dict[str, Crypto] public_keys = {} # type: Dict[str, str] addresses = {} # type: Dict[str, str] + private_keys = {} # type: Dict[str, str] for identifier, path in crypto_id_to_path.items(): crypto = make_crypto(identifier, private_key_path=path) crypto_objects[identifier] = crypto public_keys[identifier] = cast(str, crypto.public_key) addresses[identifier] = cast(str, crypto.address) + private_keys[identifier] = cast(str, crypto.private_key) self._crypto_objects = crypto_objects self._public_keys = public_keys self._addresses = addresses + self._private_keys = private_keys @property def public_keys(self) -> Dict[str, str]: @@ -71,6 +74,11 @@ def addresses(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._addresses + @property + def private_keys(self) -> Dict[str, str]: + """Get the crypto addresses.""" + return self._private_keys + class Wallet: """ @@ -114,6 +122,11 @@ def addresses(self) -> Dict[str, str]: """Get the crypto addresses.""" return self._main_cryptos.addresses + @property + def private_keys(self) -> Dict[str, str]: + """Get the crypto addresses.""" + return self._main_cryptos.private_keys + @property def main_cryptos(self) -> CryptoStore: """Get the main crypto store.""" diff --git a/aea/decision_maker/base.py b/aea/decision_maker/base.py index 51d5429cae..3cc826502f 100644 --- a/aea/decision_maker/base.py +++ b/aea/decision_maker/base.py @@ -17,10 +17,11 @@ # # ------------------------------------------------------------------------------ """This module contains the decision maker class.""" + +import copy import hashlib import logging import threading -import uuid from abc import ABC, abstractmethod from queue import Queue from threading import Thread @@ -296,7 +297,7 @@ def __init__( :param decision_maker_handler: the decision maker handler """ self._agent_name = decision_maker_handler.identity.name - self._queue_access_code = uuid.uuid4().hex + self._queue_access_code = uuid4().hex self._message_in_queue = ProtectedQueue( self._queue_access_code ) # type: ProtectedQueue @@ -376,6 +377,10 @@ def handle(self, message: Message) -> None: :param message: the internal message :return: None """ - message.counterparty = uuid4().hex # TODO: temporary fix only - message.is_incoming = True - self.decision_maker_handler.handle(message) + # TODO: remove next three lines + copy_message = copy.copy(message) + copy_message.counterparty = message.sender + copy_message.sender = message.sender + # copy_message.to = message.to + copy_message.is_incoming = True + self.decision_maker_handler.handle(copy_message) diff --git a/aea/helpers/async_utils.py b/aea/helpers/async_utils.py index 3eec78fad0..4f17212912 100644 --- a/aea/helpers/async_utils.py +++ b/aea/helpers/async_utils.py @@ -31,6 +31,7 @@ Any, Awaitable, Callable, + Container, List, Optional, Sequence, @@ -63,37 +64,54 @@ def ensure_list(value: Any) -> List: class AsyncState: """Awaitable state.""" - def __init__(self, initial_state: Any = None): + def __init__( + self, initial_state: Any = None, states_enum: Optional[Container[Any]] = None + ): """Init async state. :param initial_state: state to set on start. + :param states_enum: container of valid states if not provided state not checked on set. """ self._state = initial_state self._watchers: Set[Future] = set() - - @property - def state(self) -> Any: - """Return current state.""" - return self.get() - - @state.setter - def state(self, state: Any) -> None: - """Set state.""" - self.set(state) + self._callbacks: List[Callable[[Any], None]] = [] + self._states_enum = states_enum def set(self, state: Any) -> None: """Set state.""" + if self._states_enum is not None and state not in self._states_enum: + raise ValueError( + f"Unsupported state: {state}. Valid states are {self._states_enum}" + ) + if self._state == state: # pragma: no cover return + self._state_changed(state) self._state = state + def add_callback(self, callback_fn: Callable[[Any], None]) -> None: + """ + Add callback to track state changes. + + :param callback_fn: callable object to be called on state changed. + + :return: None + """ + self._callbacks.append(callback_fn) + def get(self) -> Any: """Get state.""" return self._state def _state_changed(self, state: Any) -> None: """Fulfill watchers for state.""" + for callback_fn in self._callbacks: + try: + callback_fn(state) + except Exception: # pylint: disable=broad-except + logger.exception(f"Exception on calling {callback_fn}") + for watcher in list(self._watchers): if state not in watcher._states: # type: ignore # pylint: disable=protected-access # pragma: nocover continue diff --git a/aea/helpers/dialogue/base.py b/aea/helpers/dialogue/base.py index 5c12d7444a..556cdf095e 100644 --- a/aea/helpers/dialogue/base.py +++ b/aea/helpers/dialogue/base.py @@ -28,7 +28,7 @@ import itertools from abc import ABC, abstractmethod from enum import Enum -from typing import Callable, Dict, FrozenSet, List, Optional, Tuple, Type, cast +from typing import Callable, Dict, FrozenSet, List, Optional, Set, Tuple, Type, cast from aea.mail.base import Address from aea.protocols.base import Message @@ -124,6 +124,15 @@ def from_json(cls, obj: Dict[str, str]) -> "DialogueLabel": ) return dialogue_label + def get_incomplete_version(self) -> "DialogueLabel": + """Get the incomplete version of the label.""" + dialogue_label = DialogueLabel( + (self.dialogue_starter_reference, Dialogue.OPPONENT_STARTER_REFERENCE), + self.dialogue_opponent_addr, + self.dialogue_starter_addr, + ) + return dialogue_label + def __str__(self): """Get the string representation.""" return "{}_{}_{}_{}".format( @@ -133,6 +142,22 @@ def __str__(self): self.dialogue_starter_addr, ) + @classmethod + def from_str(cls, obj: str) -> "DialogueLabel": + """Get the dialogue label from string representation.""" + ( + dialogue_starter_reference, + dialogue_responder_reference, + dialogue_opponent_addr, + dialogue_starter_addr, + ) = obj.split("_") + dialogue_label = DialogueLabel( + (dialogue_starter_reference, dialogue_responder_reference), + dialogue_opponent_addr, + dialogue_starter_addr, + ) + return dialogue_label + class Dialogue(ABC): """The dialogue class maintains state of a dialogue and manages it.""" @@ -239,6 +264,7 @@ def __init__( :return: None """ self._agent_address = agent_address + self._incomplete_dialogue_label = dialogue_label.get_incomplete_version() self._dialogue_label = dialogue_label self._role = role @@ -264,6 +290,24 @@ def dialogue_label(self) -> DialogueLabel: """ return self._dialogue_label + @property + def incomplete_dialogue_label(self) -> DialogueLabel: + """ + Get the dialogue label. + + :return: The incomplete dialogue label + """ + return self._incomplete_dialogue_label + + @property + def dialogue_labels(self) -> Set[DialogueLabel]: + """ + Get the dialogue labels (incomplete and complete, if it exists) + + :return: the dialogue labels + """ + return {self._dialogue_label, self._incomplete_dialogue_label} + @property def agent_address(self) -> Address: """ @@ -392,24 +436,39 @@ def is_empty(self) -> bool: def update(self, message: Message) -> bool: """ - Extend the list of incoming/outgoing messages with 'message', if 'message' is valid. + Extend the list of incoming/outgoing messages with 'message', if 'message' belongs to dialogue and is valid. :param message: a message to be added :return: True if message successfully added, false otherwise """ - if ( - message.is_incoming - and self.last_message is not None - and self.last_message.message_id == self.STARTING_MESSAGE_ID - and self.dialogue_label.dialogue_reference[1] - == self.OPPONENT_STARTER_REFERENCE - ): - self._update_self_initiated_dialogue_label_on_second_message(message) + if not message.is_incoming: + message.sender = self.agent_address + self.ensure_counterparty(message) + + if not self.is_belonging_to_dialogue(message): + return False + + is_extendable = self.is_valid_next_message(message) + if is_extendable: + if message.is_incoming: + self._incoming_messages.extend([message]) + else: + self._outgoing_messages.extend([message]) + return is_extendable + + def ensure_counterparty(self, message: Message) -> None: + """ + Ensure the counterparty is set (set if not) correctly. + + :param message: a message + :return: None + """ counterparty = None # type: Optional[str] try: counterparty = message.counterparty except AssertionError: + # assume message belongs to dialogue message.counterparty = self.dialogue_label.dialogue_opponent_addr if counterparty is not None: @@ -417,13 +476,26 @@ def update(self, message: Message) -> bool: message.counterparty == self.dialogue_label.dialogue_opponent_addr ), "The counterparty specified in the message is different from the opponent in this dialogue." - is_extendable = self.is_valid_next_message(message) - if is_extendable: - if message.is_incoming: - self._incoming_messages.extend([message]) - else: - self._outgoing_messages.extend([message]) - return is_extendable + def is_belonging_to_dialogue(self, message: Message) -> bool: + """ + Check if the message is belonging to the dialogue. + + :param message: the message + :return: Ture if message is part of the dialogue, False otherwise + """ + if self.is_self_initiated: + self_initiated_dialogue_label = DialogueLabel( + (message.dialogue_reference[0], Dialogue.OPPONENT_STARTER_REFERENCE), + message.counterparty, + self.agent_address, + ) + result = self_initiated_dialogue_label in self.dialogue_labels + else: + other_initiated_dialogue_label = DialogueLabel( + message.dialogue_reference, message.counterparty, message.counterparty + ) + result = other_initiated_dialogue_label in self.dialogue_labels + return result def reply(self, target_message: Message, performative, **kwargs) -> Message: """ @@ -455,49 +527,6 @@ def reply(self, target_message: Message, performative, **kwargs) -> Message: else: raise Exception("Invalid message from performative and contents.") - def _update_self_initiated_dialogue_label_on_second_message( - self, second_message: Message - ) -> None: - """ - Update this (self initiated) dialogue's dialogue_label with a complete dialogue reference from counterparty's first message. - - :param second_message: The second message in the dialogue (the first by the counterparty) - :return: None - """ - dialogue_reference = second_message.dialogue_reference - - self_initiated_dialogue_reference = ( - dialogue_reference[0], - self.OPPONENT_STARTER_REFERENCE, - ) - self_initiated_dialogue_label = DialogueLabel( - self_initiated_dialogue_reference, - second_message.counterparty, - self.agent_address, - ) - - if self.last_message is not None: - if ( - self.dialogue_label == self_initiated_dialogue_label - and self.last_message.message_id == 1 - and second_message.message_id == 2 - and second_message.is_incoming - ): - updated_dialogue_label = DialogueLabel( - dialogue_reference, - self_initiated_dialogue_label.dialogue_opponent_addr, - self_initiated_dialogue_label.dialogue_starter_addr, - ) - self.update_dialogue_label(updated_dialogue_label) - else: - raise Exception( - "Invalid call to update dialogue's reference. This call must be made only after receiving dialogue's second message by the counterparty." - ) - else: - raise Exception( - "Cannot update dialogue's reference after the first message." - ) - def is_valid_next_message(self, message: Message) -> bool: """ Check whether 'message' is a valid next message in this dialogue. @@ -699,7 +728,10 @@ def __init__( :param end_states: the list of dialogue endstates :return: None """ - self._dialogues = {} # type: Dict[DialogueLabel, Dialogue] + self._dialogues_by_dialogue_label = {} # type: Dict[DialogueLabel, Dialogue] + self._incomplete_to_complete_dialogue_labels = ( + {} + ) # type: Dict[DialogueLabel, DialogueLabel] self._agent_address = agent_address self._dialogue_nonce = 0 self._dialogue_stats = DialogueStats(end_states) @@ -722,7 +754,7 @@ def __init__( @property def dialogues(self) -> Dict[DialogueLabel, Dialogue]: """Get dictionary of dialogues in which the agent engages.""" - return self._dialogues + return self._dialogues_by_dialogue_label @property def agent_address(self) -> Address: @@ -745,7 +777,7 @@ def new_self_initiated_dialogue_reference(self) -> Tuple[str, str]: :return: the next nonce """ - return str(self._dialogue_nonce + 1), Dialogue.OPPONENT_STARTER_REFERENCE + return str(self._next_dialogue_nonce()), Dialogue.OPPONENT_STARTER_REFERENCE def create( self, counterparty: Address, performative: Message.Performative, **kwargs, @@ -774,13 +806,14 @@ def create( dialogue = self._create_self_initiated( dialogue_opponent_addr=counterparty, + dialogue_reference=initial_message.dialogue_reference, role=self._role_from_first_message(initial_message), ) successfully_updated = dialogue.update(initial_message) if not successfully_updated: - self._dialogues.pop(dialogue.dialogue_label) + self._dialogues_by_dialogue_label.pop(dialogue.dialogue_label) self._dialogue_nonce -= 1 raise Exception( "Cannot create the a dialogue with the specified performative and contents." @@ -801,58 +834,81 @@ def update(self, message: Message) -> Optional[Dialogue]: """ dialogue_reference = message.dialogue_reference - if ( # new dialogue by other + if not message.has_counterparty: + raise ValueError( + "The message counterparty field is not set {}".format(message) + ) + if message.is_incoming and not message.has_sender: + raise ValueError("The message sender field is not set {}".format(message)) + + is_invalid_label = ( + dialogue_reference[0] == Dialogue.OPPONENT_STARTER_REFERENCE + and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE + ) + is_new_dialogue = ( dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE - and message.is_incoming - ): + and message.message_id == 1 + and message.target == 0 + ) + is_incomplete_label_and_non_initial_msg = ( + dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE + and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE + and message.message_id > 1 + ) + if is_invalid_label: + dialogue = None # type: Optional[Dialogue] + elif is_new_dialogue and message.is_incoming: # new dialogue by other dialogue = self._create_opponent_initiated( dialogue_opponent_addr=message.counterparty, dialogue_reference=dialogue_reference, role=self._role_from_first_message(message), - ) # type: Optional[Dialogue] - elif ( # new dialogue by self - dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE - and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE - and not message.is_incoming - ): - assert ( - message.counterparty is not None - ), "The message counter-party field is not set {}".format(message) + ) + elif is_new_dialogue and not message.is_incoming: # new dialogue by self dialogue = self._create_self_initiated( dialogue_opponent_addr=message.counterparty, + dialogue_reference=dialogue_reference, role=self._role_from_first_message(message), ) - else: # existing dialogue - self._update_self_initiated_dialogue_label_on_second_message(message) + elif ( # non-initial message with incomplete label + is_incomplete_label_and_non_initial_msg + ): + # we can allow a dialogue to have incomplete reference + # as multiple messages can be sent before one is received with complete reference + dialogue = self.get_dialogue(message) + else: + self._update_self_initiated_dialogue_label_on_message_with_complete_reference( + message + ) dialogue = self.get_dialogue(message) - if dialogue is not None: - dialogue.update(message) + if dialogue is not None and dialogue.update(message): result = dialogue # type: Optional[Dialogue] - else: # couldn't find the dialogue + else: # couldn't find the dialogue or invalid message result = None return result - def _update_self_initiated_dialogue_label_on_second_message( - self, second_message: Message + def _update_self_initiated_dialogue_label_on_message_with_complete_reference( + self, message: Message ) -> None: """ Update a self initiated dialogue label with a complete dialogue reference from counterparty's first message. - :param second_message: The second message in the dialogue (the first by the counterparty in a self initiated dialogue) + :param message: A message in the dialogue (the first by the counterparty with a complete reference) :return: None """ - dialogue_reference = second_message.dialogue_reference + dialogue_reference = message.dialogue_reference + assert ( + dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE + and dialogue_reference[1] != Dialogue.OPPONENT_STARTER_REFERENCE + ), "Only complete dialogue references allowed." self_initiated_dialogue_reference = ( dialogue_reference[0], Dialogue.OPPONENT_STARTER_REFERENCE, ) self_initiated_dialogue_label = DialogueLabel( - self_initiated_dialogue_reference, - second_message.counterparty, - self.agent_address, + self_initiated_dialogue_reference, message.counterparty, self.agent_address, ) if self_initiated_dialogue_label in self.dialogues: @@ -865,10 +921,15 @@ def _update_self_initiated_dialogue_label_on_second_message( self_initiated_dialogue.update_dialogue_label(final_dialogue_label) assert ( self_initiated_dialogue.dialogue_label not in self.dialogues - ), "DialogueLabel already present." + and self_initiated_dialogue_label + not in self._incomplete_to_complete_dialogue_labels + ), "DialogueLabel already present in dialogues." self.dialogues.update( {self_initiated_dialogue.dialogue_label: self_initiated_dialogue} ) + self._incomplete_to_complete_dialogue_labels[ + self_initiated_dialogue_label + ] = final_dialogue_label def get_dialogue(self, message: Message) -> Optional[Dialogue]: """ @@ -887,15 +948,33 @@ def get_dialogue(self, message: Message) -> Optional[Dialogue]: dialogue_reference, counterparty, counterparty ) - if other_initiated_dialogue_label in self.dialogues: - result = self.dialogues[ - other_initiated_dialogue_label - ] # type: Optional[Dialogue] - elif self_initiated_dialogue_label in self.dialogues: - result = self.dialogues[self_initiated_dialogue_label] - else: - result = None + self_initiated_dialogue_label = self.get_latest_label( + self_initiated_dialogue_label + ) + other_initiated_dialogue_label = self.get_latest_label( + other_initiated_dialogue_label + ) + self_initiated_dialogue = self.get_dialogue_from_label( + self_initiated_dialogue_label + ) + other_initiated_dialogue = self.get_dialogue_from_label( + other_initiated_dialogue_label + ) + + result = self_initiated_dialogue or other_initiated_dialogue + return result + + def get_latest_label(self, dialogue_label: DialogueLabel) -> DialogueLabel: + """ + Retrieve the latest dialogue label if present otherwise return same label. + + :param dialogue_label: the dialogue label + :return dialogue_label: the dialogue label + """ + result = self._incomplete_to_complete_dialogue_labels.get( + dialogue_label, dialogue_label + ) return result def get_dialogue_from_label( @@ -911,7 +990,10 @@ def get_dialogue_from_label( return result def _create_self_initiated( - self, dialogue_opponent_addr: Address, role: Dialogue.Role, + self, + dialogue_opponent_addr: Address, + dialogue_reference: Tuple[str, str], + role: Dialogue.Role, ) -> Dialogue: """ Create a self initiated dialogue. @@ -921,25 +1003,14 @@ def _create_self_initiated( :return: the created dialogue. """ - dialogue_reference = ( - str(self._next_dialogue_nonce()), - Dialogue.OPPONENT_STARTER_REFERENCE, - ) - dialogue_label = DialogueLabel( + assert ( + dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE + and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE + ), "Cannot initiate dialogue with preassigned dialogue_responder_reference!" + incomplete_dialogue_label = DialogueLabel( dialogue_reference, dialogue_opponent_addr, self.agent_address ) - if self._message_class is not None and self._dialogue_class is not None: - dialogue = self._dialogue_class( - dialogue_label=dialogue_label, - message_class=self._message_class, - agent_address=self.agent_address, - role=role, - ) - else: - dialogue = self.create_dialogue( - dialogue_label=dialogue_label, role=role, - ) # pragma: no cover - self.dialogues.update({dialogue_label: dialogue}) + dialogue = self._create(incomplete_dialogue_label, role) return dialogue def _create_opponent_initiated( @@ -961,15 +1032,50 @@ def _create_opponent_initiated( dialogue_reference[0] != Dialogue.OPPONENT_STARTER_REFERENCE and dialogue_reference[1] == Dialogue.OPPONENT_STARTER_REFERENCE ), "Cannot initiate dialogue with preassigned dialogue_responder_reference!" + incomplete_dialogue_label = DialogueLabel( + dialogue_reference, dialogue_opponent_addr, dialogue_opponent_addr + ) new_dialogue_reference = ( dialogue_reference[0], str(self._next_dialogue_nonce()), ) - dialogue_label = DialogueLabel( + complete_dialogue_label = DialogueLabel( new_dialogue_reference, dialogue_opponent_addr, dialogue_opponent_addr ) + dialogue = self._create( + incomplete_dialogue_label, role, complete_dialogue_label + ) + return dialogue + + def _create( + self, + incomplete_dialogue_label: DialogueLabel, + role: Dialogue.Role, + complete_dialogue_label: Optional[DialogueLabel] = None, + ) -> Dialogue: + """ + Create a dialogue from label and role. + + :param incomplete_dialogue_label: the dialogue label (incomplete) + :param role: the agent's role + :param complete_dialogue_label: the dialogue label (complete) - assert dialogue_label not in self.dialogues + :return: the created dialogue + """ + assert ( + incomplete_dialogue_label + not in self._incomplete_to_complete_dialogue_labels + ), "Incomplete dialogue label already present." + if complete_dialogue_label is None: + dialogue_label = incomplete_dialogue_label + else: + self._incomplete_to_complete_dialogue_labels[ + incomplete_dialogue_label + ] = complete_dialogue_label + dialogue_label = complete_dialogue_label + assert ( + dialogue_label not in self.dialogues + ), "Dialogue label already present in dialogues." if self._message_class is not None and self._dialogue_class is not None: dialogue = self._dialogue_class( dialogue_label=dialogue_label, @@ -978,11 +1084,11 @@ def _create_opponent_initiated( role=role, ) else: + # TODO: remove this approach dialogue = self.create_dialogue( dialogue_label=dialogue_label, role=role, ) # pragma: no cover self.dialogues.update({dialogue_label: dialogue}) - return dialogue @abstractmethod diff --git a/aea/helpers/exec_timeout.py b/aea/helpers/exec_timeout.py index 371a5aa70e..c70d172e86 100644 --- a/aea/helpers/exec_timeout.py +++ b/aea/helpers/exec_timeout.py @@ -203,7 +203,9 @@ def start(cls) -> None: cls._loop = asyncio.new_event_loop() cls._stopped_future = Future(loop=cls._loop) - cls._supervisor_thread = threading.Thread(target=cls._supervisor_event_loop) + cls._supervisor_thread = threading.Thread( + target=cls._supervisor_event_loop, daemon=True + ) cls._supervisor_thread.start() @classmethod @@ -227,6 +229,7 @@ def stop(cls, force: bool = False) -> None: if cls._supervisor_thread and cls._supervisor_thread.is_alive(): cls._supervisor_thread.join() cls._supervisor_thread = None + cls._start_count = 0 @classmethod def _supervisor_event_loop(cls) -> None: diff --git a/aea/helpers/logging.py b/aea/helpers/logging.py index 6cc46866ba..8a085528a5 100644 --- a/aea/helpers/logging.py +++ b/aea/helpers/logging.py @@ -19,7 +19,7 @@ """Logging helpers.""" import logging from logging import Logger, LoggerAdapter -from typing import Any, MutableMapping, Optional, Tuple, Union +from typing import Any, MutableMapping, Optional, Tuple, cast class AgentLoggerAdapter(LoggerAdapter): @@ -44,9 +44,7 @@ class WithLogger: """Interface to endow subclasses with a logger.""" def __init__( - self, - logger: Optional[Union[Logger, LoggerAdapter]] = None, - default_logger_name: str = "aea", + self, logger: Optional[Logger] = None, default_logger_name: str = "aea", ): """ Initialize the logger. @@ -54,19 +52,19 @@ def __init__( :param logger: the logger object. :param default_logger_name: the default logger name, if a logger is not provided. """ - self._logger = logger + self._logger: Optional[Logger] = logger self._default_logger_name = default_logger_name @property - def logger(self) -> Union[Logger, LoggerAdapter]: + def logger(self) -> Logger: """Get the component logger.""" if self._logger is None: # if not set (e.g. programmatic instantiation) # return a default one with the default logger name. return logging.getLogger(self._default_logger_name) - return self._logger + return cast(Logger, self._logger) @logger.setter - def logger(self, logger: Union[Logger, LoggerAdapter]): + def logger(self, logger: Optional[Logger]): """Set the logger.""" self._logger = logger diff --git a/aea/helpers/transaction/base.py b/aea/helpers/transaction/base.py index f96abe34ff..f31533db05 100644 --- a/aea/helpers/transaction/base.py +++ b/aea/helpers/transaction/base.py @@ -19,8 +19,12 @@ """This module contains terms related classes.""" +import collections +import copy import pickle # nosec -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple + +from aea.crypto.ledger_apis import LedgerApis Address = str @@ -398,13 +402,14 @@ def __init__( counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], - is_sender_payable_tx_fee: bool, nonce: str, + is_sender_payable_tx_fee: bool = True, fee_by_currency_id: Optional[Dict[str, int]] = None, + is_strict: bool = False, **kwargs, ): """ - Instantiate terms. + Instantiate terms of a transaction. :param ledger_id: the ledger on which the terms are to be settled. :param sender_address: the sender address of the transaction. @@ -414,6 +419,7 @@ def __init__( :param is_sender_payable_tx_fee: whether the sender or counterparty pays the tx fee. :param nonce: nonce to be included in transaction to discriminate otherwise identical transactions. :param fee_by_currency_id: the fee associated with the transaction. + :param is_strict: whether or not terms must have quantities and amounts of opposite signs. """ self._ledger_id = ledger_id self._sender_address = sender_address @@ -422,9 +428,42 @@ def __init__( self._quantities_by_good_id = quantities_by_good_id self._is_sender_payable_tx_fee = is_sender_payable_tx_fee self._nonce = nonce - self._fee_by_currency_id = fee_by_currency_id + self._fee_by_currency_id = ( + fee_by_currency_id if fee_by_currency_id is not None else {} + ) + self._is_strict = is_strict self._kwargs = kwargs if kwargs is not None else {} self._check_consistency() + ( + good_ids, + sender_supplied_quantities, + counterparty_supplied_quantities, + ) = self._get_lists() + self._good_ids = good_ids + self._sender_supplied_quantities = sender_supplied_quantities + self._counterparty_supplied_quantities = counterparty_supplied_quantities + self._sender_hash = self.get_hash( + self.ledger_id, + sender_address=self.sender_address, + counterparty_address=self.counterparty_address, + good_ids=self.good_ids, + sender_supplied_quantities=self.sender_supplied_quantities, + counterparty_supplied_quantities=self.counterparty_supplied_quantities, + sender_payable_amount=self.sender_payable_amount, + counterparty_payable_amount=self.counterparty_payable_amount, + nonce=self.nonce, + ) + self._counterparty_hash = self.get_hash( + self.ledger_id, + sender_address=self.counterparty_address, + counterparty_address=self.sender_address, + good_ids=self.good_ids, + sender_supplied_quantities=self.counterparty_supplied_quantities, + counterparty_supplied_quantities=self.sender_supplied_quantities, + sender_payable_amount=self.counterparty_payable_amount, + counterparty_payable_amount=self.sender_payable_amount, + nonce=self.nonce, + ) def _check_consistency(self) -> None: """Check consistency of the object.""" @@ -445,21 +484,6 @@ def _check_consistency(self) -> None: for key, value in self._quantities_by_good_id.items() ] ), "quantities_by_good_id must be a dictionary with str keys and int values." - pos_amounts = all( - [amount >= 0 for amount in self._amount_by_currency_id.values()] - ) - neg_amounts = all( - [amount <= 0 for amount in self._amount_by_currency_id.values()] - ) - pos_quantities = all( - [quantity >= 0 for quantity in self._quantities_by_good_id.values()] - ) - neg_quantities = all( - [quantity <= 0 for quantity in self._quantities_by_good_id.values()] - ) - assert (pos_amounts and neg_quantities) or ( - neg_amounts and pos_quantities - ), "quantities and amounts do not constitute valid terms." assert isinstance( self._is_sender_payable_tx_fee, bool ), "is_sender_payable_tx_fee must be bool" @@ -468,11 +492,48 @@ def _check_consistency(self) -> None: isinstance(self._fee_by_currency_id, dict) and all( [ - isinstance(key, str) and isinstance(value, int) + isinstance(key, str) and isinstance(value, int) and value >= 0 for key, value in self._fee_by_currency_id.items() ] ) - ), "fee must be None or Dict[str, int]" + ), "fee must be None or Dict[str, int] with positive fees only." + assert all( + [ + key in self._amount_by_currency_id + for key in self._fee_by_currency_id.keys() + ] + ), "Fee dictionary has keys which are not present in amount dictionary." + if self._is_strict: + is_pos_amounts = all( + [amount >= 0 for amount in self._amount_by_currency_id.values()] + ) + is_neg_amounts = all( + [amount <= 0 for amount in self._amount_by_currency_id.values()] + ) + is_pos_quantities = all( + [quantity >= 0 for quantity in self._quantities_by_good_id.values()] + ) + is_neg_quantities = all( + [quantity <= 0 for quantity in self._quantities_by_good_id.values()] + ) + assert (is_pos_amounts and is_neg_quantities) or ( + is_neg_amounts and is_pos_quantities + ), "quantities and amounts do not constitute valid terms. All quantities must be of same sign. All amounts must be of same sign. Quantities and amounts must be of different sign." + + @property + def id(self) -> str: + """Get hash of the terms.""" + return self.sender_hash + + @property + def sender_hash(self) -> str: + """Get the sender hash.""" + return self._sender_hash + + @property + def counterparty_hash(self) -> str: + """Get the sender hash.""" + return self._counterparty_hash @property def ledger_id(self) -> str: @@ -498,33 +559,101 @@ def counterparty_address(self, counterparty_address: Address) -> None: @property def amount_by_currency_id(self) -> Dict[str, int]: """Get the amount by currency id.""" - return self._amount_by_currency_id + return copy.copy(self._amount_by_currency_id) + + @property + def is_sender_payable_tx_fee(self) -> bool: + """Bool indicating whether the tx fee is paid by sender or counterparty.""" + return self._is_sender_payable_tx_fee + + @property + def is_single_currency(self) -> bool: + """Check whether a single currency is used for payment.""" + return ( + len(self._amount_by_currency_id) == 1 and len(self._fee_by_currency_id) <= 1 + ) + + @property + def is_empty_currency(self) -> bool: + """Check whether a single currency is used for payment.""" + return len(self._amount_by_currency_id) == 0 + + @property + def currency_id(self) -> str: + """Get the amount the sender must pay.""" + assert self.is_single_currency, "More than one currency id, cannot get id." + value = next(iter(self._amount_by_currency_id.keys())) + return value @property def sender_payable_amount(self) -> int: """Get the amount the sender must pay.""" assert ( - len(self._amount_by_currency_id) == 1 + self.is_single_currency or self.is_empty_currency ), "More than one currency id, cannot get amount." - return -next(iter(self._amount_by_currency_id.values())) + value = ( + next(iter(self._amount_by_currency_id.values())) + if not self.is_empty_currency + else 0 + ) + payable = -value if value <= 0 else 0 + return payable + + @property + def sender_payable_amount_incl_fee(self) -> int: + """Get the amount the sender must pay inclusive fee.""" + assert ( + self.is_single_currency or self.is_empty_currency + ), "More than one currency id, cannot get amount." + payable = self.sender_payable_amount + if self.is_sender_payable_tx_fee and len(self._fee_by_currency_id) == 1: + payable += next(iter(self._fee_by_currency_id.values())) + return payable @property def counterparty_payable_amount(self) -> int: """Get the amount the counterparty must pay.""" assert ( - len(self._amount_by_currency_id) == 1 + self.is_single_currency or self.is_empty_currency ), "More than one currency id, cannot get amount." - return next(iter(self._amount_by_currency_id.values())) + value = ( + next(iter(self._amount_by_currency_id.values())) + if not self.is_empty_currency + else 0 + ) + payable = value if value >= 0 else 0 + return payable + + @property + def counterparty_payable_amount_incl_fee(self) -> int: + """Get the amount the counterparty must pay.""" + assert ( + self.is_single_currency or self.is_empty_currency + ), "More than one currency id, cannot get amount." + payable = self.counterparty_payable_amount + if not self.is_sender_payable_tx_fee and len(self._fee_by_currency_id) == 1: + payable += next(iter(self._fee_by_currency_id.values())) + return payable @property def quantities_by_good_id(self) -> Dict[str, int]: """Get the quantities by good id.""" - return self._quantities_by_good_id + return copy.copy(self._quantities_by_good_id) @property - def is_sender_payable_tx_fee(self) -> bool: - """Bool indicating whether the tx fee is paid by sender or counterparty.""" - return self._is_sender_payable_tx_fee + def good_ids(self) -> List[str]: + """Get the (ordered) good ids.""" + return self._good_ids + + @property + def sender_supplied_quantities(self) -> List[int]: + """Get the (ordered) quantities supplied by the sender.""" + return self._sender_supplied_quantities + + @property + def counterparty_supplied_quantities(self) -> List[int]: + """Get the (ordered) quantities supplied by the counterparty.""" + return self._counterparty_supplied_quantities @property def nonce(self) -> str: @@ -534,28 +663,117 @@ def nonce(self) -> str: @property def has_fee(self) -> bool: """Check if fee is set.""" - return self._fee_by_currency_id is not None + return self.fee_by_currency_id != {} @property def fee(self) -> int: """Get the fee.""" - assert self._fee_by_currency_id is not None, "fee_by_currency_id not set." + assert self.has_fee, "fee_by_currency_id not set." assert ( - len(self._fee_by_currency_id) == 1 + len(self.fee_by_currency_id) == 1 ), "More than one currency id, cannot get fee." return next(iter(self._fee_by_currency_id.values())) + @property + def sender_fee(self) -> int: + """Get the sender fee.""" + value = self.fee if self.is_sender_payable_tx_fee else 0 + return value + + @property + def counterparty_fee(self) -> int: + """Get the counterparty fee.""" + value = 0 if self.is_sender_payable_tx_fee else self.fee + return value + @property def fee_by_currency_id(self) -> Dict[str, int]: """Get fee by currency.""" - assert self._fee_by_currency_id is not None, "fee_by_currency_id not set." - return self._fee_by_currency_id + return copy.copy(self._fee_by_currency_id) @property def kwargs(self) -> Dict[str, Any]: """Get the kwargs.""" return self._kwargs + def _get_lists(self) -> Tuple[List[str], List[int], List[int]]: + ordered = collections.OrderedDict(sorted(self.quantities_by_good_id.items())) + good_ids = [] # type: List[str] + sender_supplied_quantities = [] # type: List[int] + counterparty_supplied_quantities = [] # type: List[int] + for good_id, quantity in ordered.items(): + good_ids.append(good_id) + if quantity >= 0: + sender_supplied_quantities.append(quantity) + counterparty_supplied_quantities.append(0) + else: + sender_supplied_quantities.append(0) + counterparty_supplied_quantities.append(-quantity) + return good_ids, sender_supplied_quantities, counterparty_supplied_quantities + + @staticmethod + def get_hash( + ledger_id: str, + sender_address: str, + counterparty_address: str, + good_ids: List[str], + sender_supplied_quantities: List[int], + counterparty_supplied_quantities: List[int], + sender_payable_amount: int, + counterparty_payable_amount: int, + nonce: str, + ) -> str: + """ + Generate a hash from transaction information. + + :param sender_addr: the sender address + :param counterparty_addr: the counterparty address + :param good_ids: the list of good ids + :param sender_supplied_quantities: the quantities supplied by the sender (must all be positive) + :param counterparty_supplied_quantities: the quantities supplied by the counterparty (must all be positive) + :param sender_payable_amount: the amount payable by the sender + :param counterparty_payable_amount: the amount payable by the counterparty + :param tx_nonce: the nonce of the transaction + :return: the hash + """ + if len(good_ids) == 0: + aggregate_hash = LedgerApis.get_hash(ledger_id, b"") + else: + aggregate_hash = LedgerApis.get_hash( + ledger_id, + b"".join( + [ + good_ids[0].encode("utf-8"), + sender_supplied_quantities[0].to_bytes(32, "big"), + counterparty_supplied_quantities[0].to_bytes(32, "big"), + ] + ), + ) + for idx, good_id in enumerate(good_ids): + if idx == 0: + continue + aggregate_hash = LedgerApis.get_hash( + ledger_id, + b"".join( + [ + aggregate_hash.encode("utf-8"), + good_id.encode("utf-8"), + sender_supplied_quantities[idx].to_bytes(32, "big"), + counterparty_supplied_quantities[idx].to_bytes(32, "big"), + ] + ), + ) + + m_list = [] # type: List[bytes] + m_list.append(sender_address.encode("utf-8")) + m_list.append(counterparty_address.encode("utf-8")) + m_list.append(aggregate_hash.encode("utf-8")) + m_list.append(sender_payable_amount.to_bytes(32, "big")) + m_list.append(counterparty_payable_amount.to_bytes(32, "big")) + m_list.append(nonce.encode("utf-8")) + digest = LedgerApis.get_hash(ledger_id, b"".join(m_list)) + return digest + @staticmethod def encode(terms_protobuf_object, terms_object: "Terms") -> None: """ diff --git a/aea/launcher.py b/aea/launcher.py index dc36c86897..0f7901fdde 100644 --- a/aea/launcher.py +++ b/aea/launcher.py @@ -105,7 +105,7 @@ def stop_event_thread(): agent.start() except KeyboardInterrupt: # pragma: nocover logger.debug("_run_agent: keyboard interrupt") - except BaseException as e: + except BaseException as e: # pragma: nocover logger.exception("exception in _run_agent") exc = AEAException(f"Raised {type(e)}({e})") exc.__traceback__ = e.__traceback__ diff --git a/aea/multiplexer.py b/aea/multiplexer.py index 071cf4a109..e305929c77 100644 --- a/aea/multiplexer.py +++ b/aea/multiplexer.py @@ -25,7 +25,7 @@ from typing import Collection, Dict, List, Optional, Sequence, Tuple, cast from aea.configurations.base import PublicId -from aea.connections.base import Connection, ConnectionStatus +from aea.connections.base import Connection, ConnectionStates from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.async_utils import ThreadedAsyncRunner, cancel_and_wait from aea.helpers.logging import WithLogger @@ -40,6 +40,19 @@ from aea.protocols.base import Message +# TODO refactoring: this should be an enum +# but beware of backward-compatibility. + + +class ConnectionStatus: + """The connection status class.""" + + def __init__(self): + """Initialize the connection status.""" + self.is_connected = False # type: bool + self.is_connecting = False # type: bool + + class AsyncMultiplexer(WithLogger): """This class can handle multiple connections at once.""" @@ -158,7 +171,7 @@ def connections(self) -> Tuple[Connection, ...]: @property def is_connected(self) -> bool: """Check whether the multiplexer is processing envelopes.""" - return all(c.connection_status.is_connected for c in self._connections) + return all(c.is_connected for c in self._connections) @property def default_routing(self) -> Dict[PublicId, PublicId]: @@ -235,7 +248,7 @@ async def _stop(self) -> None: for connection in [ c for c in self.connections - if c.connection_status.is_connected or c.connection_status.is_connecting + if c.state in (ConnectionStates.connecting, ConnectionStates.connected) ]: await connection.disconnect() self.logger.debug("Multiplexer stopped.") @@ -268,7 +281,7 @@ async def _connect_one(self, connection_id: PublicId) -> None: """ connection = self._id_to_connection[connection_id] self.logger.debug("Processing connection {}".format(connection.connection_id)) - if connection.connection_status.is_connected: + if connection.is_connected: self.logger.debug( "Connection {} already established.".format(connection.connection_id) ) @@ -303,7 +316,7 @@ async def _disconnect_one(self, connection_id: PublicId) -> None: """ connection = self._id_to_connection[connection_id] self.logger.debug("Processing connection {}".format(connection.connection_id)) - if not connection.connection_status.is_connected: + if not connection.is_connected: self.logger.debug( "Connection {} already disconnected.".format(connection.connection_id) ) @@ -365,7 +378,7 @@ async def _receiving_loop(self) -> None: # reinstantiate receiving task, but only if the connection is still up. connection = task_to_connection.pop(task) - if connection.connection_status.is_connected: + if connection.is_connected: new_task = asyncio.ensure_future(connection.receive()) task_to_connection[new_task] = connection @@ -711,34 +724,43 @@ def put(self, envelope: Envelope) -> None: envelope.context, ) ) - assert isinstance( - envelope.message, Message - ), "Only Message type allowed in envelope message field when putting into outbox." + if not isinstance(envelope.message, Message): + raise ValueError( + "Only Message type allowed in envelope message field when putting into outbox." + ) + message = cast(Message, envelope.message) + if not message.has_counterparty: + raise ValueError("Provided message has message.counterparty not set.") + if not message.has_sender: + raise ValueError("Provided message has message.sender not set.") self._multiplexer.put(envelope) def put_message( self, message: Message, - sender: Optional[Address] = None, context: Optional[EnvelopeContext] = None, + sender: Optional[str] = None, ) -> None: """ Put a message in the outbox. This constructs an envelope with the input arguments. - :param sender: the sender of the envelope (optional field only necessary when the non-default address is used for sending). - :param message: the message. + "sender" is a deprecated kwarg and will be removed in the next version + + :param message: the message :param context: the envelope context :return: None """ - assert isinstance(message, Message), "Provided message not of type Message." - assert ( - message.counterparty - ), "Provided message has message.counterparty not set." + if not isinstance(message, Message): + raise ValueError("Provided message not of type Message.") + if not message.has_counterparty: + raise ValueError("Provided message has message.counterparty not set.") + if not message.has_sender and sender is None: + raise ValueError("Provided message has message.sender not set.") envelope = Envelope( to=message.counterparty, - sender=sender or self._default_address, + sender=sender or message.sender, # TODO: remove "sender" protocol_id=message.protocol_id, message=message, context=context, diff --git a/aea/protocols/base.py b/aea/protocols/base.py index 52c42b6596..dcd602293a 100644 --- a/aea/protocols/base.py +++ b/aea/protocols/base.py @@ -65,6 +65,8 @@ def __init__(self, body: Optional[Dict] = None, **kwargs): :param body: the dictionary of values to hold. :param kwargs: any additional value to add to the body. It will overwrite the body values. """ + self._to = None # type: Optional[Address] + self._sender = None # type: Optional[Address] self._counterparty = None # type: Optional[Address] self._body = copy(body) if body else {} # type: Dict[str, Any] self._body.update(kwargs) @@ -74,6 +76,49 @@ def __init__(self, body: Optional[Dict] = None, **kwargs): except Exception as e: # pylint: disable=broad-except logger.error(e) + @property + def has_sender(self) -> bool: + """Check if it has a sender.""" + return self._sender is not None + + @property + def sender(self) -> Address: + """ + Get the sender of the message in Address form. + + :return the address + """ + assert self._sender is not None, "Sender must not be None." + return self._sender + + @sender.setter + def sender(self, sender: Address) -> None: + """Set the sender of the message.""" + # assert self._sender is None, "Sender already set." + self._sender = sender + + @property + def has_to(self) -> bool: + """Check if it has a sender.""" + return self._to is not None + + @property + def to(self) -> Address: + """Get address of receiver.""" + assert self._to is not None, "To must not be None." + return self._to + + @to.setter + def to(self, to: Address) -> None: + """Set address of receiver.""" + assert self._to is None, "To already set." + self._to = to + + @property + def has_counterparty(self) -> bool: + """Check if the counterparty is set.""" + return self._counterparty is not None + @property def counterparty(self) -> Address: """ @@ -293,14 +338,16 @@ class Protocol(Component): It includes a serializer to encode/decode a message. """ - def __init__(self, configuration: ProtocolConfig, message_class: Type[Message]): + def __init__( + self, configuration: ProtocolConfig, message_class: Type[Message], **kwargs + ): """ Initialize the protocol manager. :param configuration: the protocol configurations. - :param serializer: the serializer. + :param message_class: the message class. """ - super().__init__(configuration) + super().__init__(configuration, **kwargs) self._message_class = message_class @@ -310,7 +357,7 @@ def serializer(self) -> Type[Serializer]: return self._message_class.serializer @classmethod - def from_dir(cls, directory: str) -> "Protocol": + def from_dir(cls, directory: str, **kwargs) -> "Protocol": """ Load the protocol from a directory. @@ -322,10 +369,10 @@ def from_dir(cls, directory: str) -> "Protocol": ComponentConfiguration.load(ComponentType.PROTOCOL, Path(directory)), ) configuration.directory = Path(directory) - return Protocol.from_config(configuration) + return Protocol.from_config(configuration, **kwargs) @classmethod - def from_config(cls, configuration: ProtocolConfig) -> "Protocol": + def from_config(cls, configuration: ProtocolConfig, **kwargs) -> "Protocol": """ Load the protocol from configuration. @@ -366,4 +413,4 @@ def from_config(cls, configuration: ProtocolConfig) -> "Protocol": serialize_class = serializer_classes[0][1] message_class.serializer = serialize_class - return Protocol(configuration, message_class) + return Protocol(configuration, message_class, **kwargs) diff --git a/examples/protocol_specification_ex/default.yaml b/aea/protocols/default/README.md similarity index 68% rename from examples/protocol_specification_ex/default.yaml rename to aea/protocols/default/README.md index 72347ae5a4..1a4e445a58 100644 --- a/examples/protocol_specification_ex/default.yaml +++ b/aea/protocols/default/README.md @@ -1,7 +1,26 @@ +# Default Protocol + +**Name:** default + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: A protocol for exchanging any bytes message. + +**License**: Apache-2.0 + +## Description + +This is a protocol for two agents exchanging any bytes messages. + +## Specification + +```yaml --- name: default author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -33,3 +52,6 @@ termination: [bytes, error] roles: {agent} end_states: [successful, failed] ... +``` + +## Links diff --git a/aea/protocols/default/dialogues.py b/aea/protocols/default/dialogues.py index e833fdd5e3..402304c5f3 100644 --- a/aea/protocols/default/dialogues.py +++ b/aea/protocols/default/dialogues.py @@ -126,7 +126,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> DefaultDialogue: """ - Create an instance of fipa dialogue. + Create an instance of default dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/aea/protocols/default/message.py b/aea/protocols/default/message.py index 5f4fd1b0d7..4bba6f7706 100644 --- a/aea/protocols/default/message.py +++ b/aea/protocols/default/message.py @@ -35,7 +35,7 @@ class DefaultMessage(Message): """A protocol for exchanging any bytes message.""" - protocol_id = ProtocolId("fetchai", "default", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/default:0.4.0") ErrorCode = CustomErrorCode diff --git a/aea/protocols/default/protocol.yaml b/aea/protocols/default/protocol.yaml index 15c9d68f6b..fcfa1bd6b4 100644 --- a/aea/protocols/default/protocol.yaml +++ b/aea/protocols/default/protocol.yaml @@ -1,16 +1,17 @@ name: default author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmfMJ6iNNLWJLqHwjvypw5pyjWXwjDH9rnrrhQizLxDYKZ __init__.py: QmPMtKUrzVJp594VqNuapJzCesWLQ6Awjqv2ufG3wKNRmH custom_types.py: QmRcgwDdTxkSHyfF9eoMtsb5P5GJDm4oyLq5W6ZBko1MFU default.proto: QmNzMUvXkBm5bbitR5Yi49ADiwNn1FhCvXqSKKoqAPZyXv default_pb2.py: QmSRFi1s3jcqnPuk4yopJeNuC6o58RL7dvEdt85uns3B3N - dialogues.py: QmP2K2GZedU4o9khkdeB3LCGxxZek7TiT8jJnmcvWAh11j - message.py: QmapJFvDxeyrM7c5yGwxH1caREkJwaJ6MGmD71FrjUfLZR + dialogues.py: QmS3gaDAaoTD9s3YLGvXVxv9RV864TNN8Q87xQ5CALkMBm + message.py: QmbC95LcUY1pwbWtgx9no88Tuh8j2TfNQfvU9x4DjACmBR serialization.py: QmRnajc9BNCftjGkYTKCP9LnD3rq197jM3Re1GDVJTHh2y fingerprint_ignore_patterns: [] dependencies: diff --git a/aea/protocols/generator/base.py b/aea/protocols/generator/base.py index e3e784d40b..3e87db8eb8 100644 --- a/aea/protocols/generator/base.py +++ b/aea/protocols/generator/base.py @@ -95,14 +95,14 @@ def __init__( self, path_to_protocol_specification: str, output_path: str = ".", - path_to_protocol_package: Optional[str] = None, + dotted_path_to_protocol_package: Optional[str] = None, ) -> None: """ Instantiate a protocol generator. :param path_to_protocol_specification: path to protocol specification file :param output_path: the path to the location in which the protocol module is to be generated. - :param path_to_protocol_package: the path to the protocol package + :param dotted_path_to_protocol_package: the path to the protocol package :return: None """ @@ -128,9 +128,9 @@ def __init__( self.path_to_generated_protocol_package = os.path.join( output_path, self.protocol_specification.name ) - self.path_to_protocol_package = ( - path_to_protocol_package + self.protocol_specification.name - if path_to_protocol_package is not None + self.dotted_path_to_protocol_package = ( + dotted_path_to_protocol_package + self.protocol_specification.name + if dotted_path_to_protocol_package is not None else "{}.{}.protocols.{}".format( PATH_TO_PACKAGES, self.protocol_specification.author, @@ -211,7 +211,7 @@ def _import_from_custom_types_module(self) -> str: else: for custom_class in self.spec.all_custom_types: import_str += "from {}.custom_types import {} as Custom{}\n".format( - self.path_to_protocol_package, custom_class, custom_class, + self.dotted_path_to_protocol_package, custom_class, custom_class, ) import_str = import_str[:-1] return import_str @@ -628,10 +628,13 @@ def _message_class_str(self) -> str: ) # Class attributes - cls_str += self.indent + 'protocol_id = ProtocolId("{}", "{}", "{}")\n'.format( - self.protocol_specification.author, - self.protocol_specification.name, - self.protocol_specification.version, + cls_str += ( + self.indent + + 'protocol_id = ProtocolId.from_str("{}/{}:{}")\n'.format( + self.protocol_specification.author, + self.protocol_specification.name, + self.protocol_specification.version, + ) ) for custom_type in self.spec.all_custom_types: cls_str += "\n" @@ -989,7 +992,8 @@ def _dialogue_class_str(self) -> str: cls_str += self.indent + "from aea.mail.base import Address\n" cls_str += self.indent + "from aea.protocols.base import Message\n\n" cls_str += self.indent + "from {}.message import {}Message\n".format( - self.path_to_protocol_package, self.protocol_specification_in_camel_case, + self.dotted_path_to_protocol_package, + self.protocol_specification_in_camel_case, ) # Class Header @@ -1163,7 +1167,9 @@ def _dialogue_class_str(self) -> str: ) self._change_indent(1) cls_str += self.indent + '"""\n' - cls_str += self.indent + "Create an instance of {} dialogue.\n\n" + cls_str += self.indent + "Create an instance of {} dialogue.\n\n".format( + self.protocol_specification.name + ) cls_str += ( self.indent + ":param dialogue_label: the identifier of the dialogue\n" ) @@ -1203,9 +1209,6 @@ def _custom_types_module_str(self) -> str: # Module docstring cls_str += '"""This module contains class representations corresponding to every custom type in the protocol specification."""\n' - if len(self.spec.all_custom_types) == 0: - return cls_str - # class code per custom type for custom_type in self.spec.all_custom_types: cls_str += self.indent + "\n\nclass {}:\n".format(custom_type) @@ -1240,7 +1243,7 @@ def _custom_types_module_str(self) -> str: ) cls_str += ( self.indent - + "The protocol buffer object in the {}_protobuf_object argument must be matched with the instance of this class in the '{}_object' argument.\n\n".format( + + "The protocol buffer object in the {}_protobuf_object argument is matched with the instance of this class in the '{}_object' argument.\n\n".format( _camel_case_to_snake_case(custom_type), _camel_case_to_snake_case(custom_type), ) @@ -1277,7 +1280,7 @@ def _custom_types_module_str(self) -> str: ) cls_str += ( self.indent - + "A new instance of this class must be created that matches the protocol buffer object in the '{}_protobuf_object' argument.\n\n".format( + + "A new instance of this class is created that matches the protocol buffer object in the '{}_protobuf_object' argument.\n\n".format( _camel_case_to_snake_case(custom_type) ) ) @@ -1529,17 +1532,18 @@ def _serialization_class_str(self) -> str: cls_str += MESSAGE_IMPORT + "\n" cls_str += SERIALIZER_IMPORT + "\n\n" cls_str += self.indent + "from {} import (\n {}_pb2,\n)\n".format( - self.path_to_protocol_package, self.protocol_specification.name, + self.dotted_path_to_protocol_package, self.protocol_specification.name, ) for custom_type in self.spec.all_custom_types: cls_str += ( self.indent + "from {}.custom_types import (\n {},\n)\n".format( - self.path_to_protocol_package, custom_type, + self.dotted_path_to_protocol_package, custom_type, ) ) cls_str += self.indent + "from {}.message import (\n {}Message,\n)\n".format( - self.path_to_protocol_package, self.protocol_specification_in_camel_case, + self.dotted_path_to_protocol_package, + self.protocol_specification_in_camel_case, ) # Class Header @@ -1921,14 +1925,12 @@ def _init_str(self) -> str: init_str += '"""This module contains the support resources for the {} protocol."""\n\n'.format( self.protocol_specification.name ) - init_str += "from packages.{}.protocols.{}.message import {}Message\n".format( - self.protocol_specification.author, - self.protocol_specification.name, + init_str += "from {}.message import {}Message\n".format( + self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) - init_str += "from packages.{}.protocols.{}.serialization import {}Serializer\n\n".format( - self.protocol_specification.author, - self.protocol_specification.name, + init_str += "from {}.serialization import {}Serializer\n".format( + self.dotted_path_to_protocol_package, self.protocol_specification_in_camel_case, ) init_str += "{}Message.serializer = {}Serializer\n".format( diff --git a/aea/protocols/generator/common.py b/aea/protocols/generator/common.py index 4fb683b28b..ba03d82894 100644 --- a/aea/protocols/generator/common.py +++ b/aea/protocols/generator/common.py @@ -36,6 +36,13 @@ "pt:union", "pt:optional", ] +PYTHON_COMPOSITIONAL_TYPES = [ + "FrozenSet", + "Tuple", + "Dict", + "Union", + "Optional", +] MESSAGE_IMPORT = "from aea.protocols.base import Message" SERIALIZER_IMPORT = "from aea.protocols.base import Serializer" @@ -77,6 +84,55 @@ def _camel_case_to_snake_case(text: str) -> str: return re.sub(r"(? int: + """ + Give the index of the matching close bracket for the opening bracket at 'index_of_open_bracket' in the input 'text'. + + :param text: the text containing the brackets. + :param index_of_open_bracket: the index of the opening bracket. + + :return: the index of the matching closing bracket (if any). + :raises SyntaxError if there are no matching closing bracket. + """ + if text[index_of_open_bracket] != "[": + raise SyntaxError( + "Index {} in 'text' is not an open bracket '['. It is {}".format( + index_of_open_bracket, text[index_of_open_bracket], + ) + ) + + open_bracket_stack = [] + for index in range(index_of_open_bracket, len(text)): + if text[index] == "[": + open_bracket_stack.append(text[index]) + elif text[index] == "]": + open_bracket_stack.pop() + if not open_bracket_stack: + return index + raise SyntaxError( + "No matching closing bracket ']' for the opening bracket '[' at {} " + + str(index_of_open_bracket) + ) + + +def _has_matched_brackets(text: str) -> bool: + """ + Evaluate whether every opening bracket '[' in the 'text' has a matching closing bracket ']'. + + :param text: the text. + :return: Boolean result, and associated message. + """ + open_bracket_stack = [] + for index, _ in enumerate(text): + if text[index] == "[": + open_bracket_stack.append(index) + elif text[index] == "]": + if len(open_bracket_stack) == 0: + return False + open_bracket_stack.pop() + return len(open_bracket_stack) == 0 + + def _get_sub_types_of_compositional_types(compositional_type: str) -> Tuple[str, ...]: """ Extract the sub-types of compositional types. @@ -87,82 +143,66 @@ def _get_sub_types_of_compositional_types(compositional_type: str) -> Tuple[str, :return: tuple containing all extracted sub-types. """ sub_types_list = list() - if compositional_type.startswith("Optional") or compositional_type.startswith( - "pt:optional" + for valid_compositional_type in ( + SPECIFICATION_COMPOSITIONAL_TYPES + PYTHON_COMPOSITIONAL_TYPES ): - sub_type1 = compositional_type[ - compositional_type.index("[") + 1 : compositional_type.rindex("]") - ].strip() - sub_types_list.append(sub_type1) - if ( - compositional_type.startswith("FrozenSet") - or compositional_type.startswith("pt:set") - or compositional_type.startswith("pt:list") - ): - sub_type1 = compositional_type[ - compositional_type.index("[") + 1 : compositional_type.rindex("]") - ].strip() - sub_types_list.append(sub_type1) - if compositional_type.startswith("Tuple"): - sub_type1 = compositional_type[ - compositional_type.index("[") + 1 : compositional_type.rindex("]") - ].strip() - sub_type1 = sub_type1[:-5] - sub_types_list.append(sub_type1) - if compositional_type.startswith("Dict") or compositional_type.startswith( - "pt:dict" - ): - sub_type1 = compositional_type[ - compositional_type.index("[") + 1 : compositional_type.index(",") - ].strip() - sub_type2 = compositional_type[ - compositional_type.index(",") + 1 : compositional_type.rindex("]") - ].strip() - sub_types_list.extend([sub_type1, sub_type2]) - if compositional_type.startswith("Union") or compositional_type.startswith( - "pt:union" - ): - inside_union = compositional_type[ - compositional_type.index("[") + 1 : compositional_type.rindex("]") - ].strip() - while inside_union != "": - if inside_union.startswith("Dict") or inside_union.startswith("pt:dict"): - sub_type = inside_union[: inside_union.index("]") + 1].strip() - rest_of_inside_union = inside_union[ - inside_union.index("]") + 1 : - ].strip() - if rest_of_inside_union.find(",") == -1: - # it is the last sub-type - inside_union = rest_of_inside_union.strip() - else: - # it is not the last sub-type - inside_union = rest_of_inside_union[ - rest_of_inside_union.index(",") + 1 : + if compositional_type.startswith(valid_compositional_type): + inside_string = compositional_type[ + compositional_type.index("[") + 1 : compositional_type.rindex("]") + ].strip() + while inside_string != "": + do_not_add = False + if inside_string.find(",") == -1: # No comma; this is the last sub-type + provisional_sub_type = inside_string.strip() + if ( + provisional_sub_type == "..." + ): # The sub-string is ... used for Tuple, e.g. Tuple[int, ...] + do_not_add = True + else: + sub_type = provisional_sub_type + inside_string = "" + else: # There is a comma; this MAY not be the last sub-type + sub_string_until_comma = inside_string[ + : inside_string.index(",") ].strip() - elif inside_union.startswith("Tuple"): - sub_type = inside_union[: inside_union.index("]") + 1].strip() - rest_of_inside_union = inside_union[ - inside_union.index("]") + 1 : - ].strip() - if rest_of_inside_union.find(",") == -1: - # it is the last sub-type - inside_union = rest_of_inside_union.strip() - else: - # it is not the last sub-type - inside_union = rest_of_inside_union[ - rest_of_inside_union.index(",") + 1 : - ].strip() - else: - if inside_union.find(",") == -1: - # it is the last sub-type - sub_type = inside_union.strip() - inside_union = "" - else: - # it is not the last sub-type - sub_type = inside_union[: inside_union.index(",")].strip() - inside_union = inside_union[inside_union.index(",") + 1 :].strip() - sub_types_list.append(sub_type) - return tuple(sub_types_list) + if ( + sub_string_until_comma.find("[") == -1 + ): # No open brackets; this is a primitive type and NOT the last sub-type + sub_type = sub_string_until_comma + inside_string = inside_string[ + inside_string.index(",") + 1 : + ].strip() + else: # There is an open bracket'['; this is a compositional type + try: + closing_bracket_index = _match_brackets( + inside_string, inside_string.index("[") + ) + except SyntaxError: + raise SyntaxError( + "Bad formatting. No matching close bracket ']' for the open bracket at {}".format( + inside_string[ + : inside_string.index("[") + 1 + ].strip() + ) + ) + sub_type = inside_string[: closing_bracket_index + 1].strip() + the_rest_of_inside_string = inside_string[ + closing_bracket_index + 1 : + ].strip() + if ( + the_rest_of_inside_string.find(",") == -1 + ): # No comma; this is the last sub-type + inside_string = the_rest_of_inside_string.strip() + else: # There is a comma; this is not the last sub-type + inside_string = the_rest_of_inside_string[ + the_rest_of_inside_string.index(",") + 1 : + ].strip() + if not do_not_add: + sub_types_list.append(sub_type) + return tuple(sub_types_list) + raise SyntaxError( + "{} is not a valid compositional type.".format(compositional_type) + ) def _union_sub_type_to_protobuf_variable_name( diff --git a/aea/protocols/generator/validate.py b/aea/protocols/generator/validate.py index 93742dca24..073fb18d94 100644 --- a/aea/protocols/generator/validate.py +++ b/aea/protocols/generator/validate.py @@ -26,6 +26,7 @@ SPECIFICATION_COMPOSITIONAL_TYPES, SPECIFICATION_PRIMITIVE_TYPES, _get_sub_types_of_compositional_types, + _has_matched_brackets, ) # The following names are reserved for standard message fields and cannot be @@ -36,7 +37,7 @@ PERFORMATIVE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" CONTENT_NAME_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" -CT_CONTENT_REGEX_PATTERN = "^ct:([A-Z]+[a-z]*)+$" # or maybe "ct:(?:[A-Z][a-z]+)+" or # "^ct:[A-Z][a-zA-Z0-9]*$" +CT_CONTENT_TYPE_REGEX_PATTERN = "^ct:([A-Z]+[a-z]*)+$" # or maybe "ct:(?:[A-Z][a-z]+)+" or # "^ct:[A-Z][a-zA-Z0-9]*$" ROLE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" END_STATE_REGEX_PATTERN = "^[a-zA-Z0-9]+$|^[a-zA-Z0-9]+(_?[a-zA-Z0-9]+)+$" @@ -69,29 +70,62 @@ def _is_valid_regex(regex_pattern: str, text: str) -> bool: def _has_brackets(content_type: str) -> bool: + """ + Evaluate whether a compositional content type in a protocol specification is valid has corresponding brackets. + + :param content_type: an 'set' content type. + :return: Boolean result + """ for compositional_type in SPECIFICATION_COMPOSITIONAL_TYPES: if content_type.startswith(compositional_type): content_type = content_type[len(compositional_type) :] - return content_type[0] == "[" and content_type[len(content_type) - 1] == "]" + if len(content_type) < 2: + return False + else: + return ( + content_type[0] == "[" + and content_type[len(content_type) - 1] == "]" + ) raise SyntaxError("Content type must be a compositional type!") def _is_valid_ct(content_type: str) -> bool: + """ + Evaluate whether the format of a 'ct' content type in a protocol specification is valid. + + :param content_type: a 'ct' content type. + :return: Boolean result + """ content_type = content_type.strip() - return _is_valid_regex(CT_CONTENT_REGEX_PATTERN, content_type) + return _is_valid_regex(CT_CONTENT_TYPE_REGEX_PATTERN, content_type) def _is_valid_pt(content_type: str) -> bool: + """ + Evaluate whether the format of a 'pt' content type in a protocol specification is valid. + + :param content_type: a 'pt' content type. + :return: Boolean result + """ content_type = content_type.strip() return content_type in SPECIFICATION_PRIMITIVE_TYPES def _is_valid_set(content_type: str) -> bool: + """ + Evaluate whether the format of a 'set' content type in a protocol specification is valid. + + :param content_type: a 'set' content type. + :return: Boolean result + """ content_type = content_type.strip() if not content_type.startswith("pt:set"): return False + if not _has_matched_brackets(content_type): + return False + if not _has_brackets(content_type): return False @@ -104,11 +138,20 @@ def _is_valid_set(content_type: str) -> bool: def _is_valid_list(content_type: str) -> bool: + """ + Evaluate whether the format of a 'list' content type in a protocol specification is valid. + + :param content_type: a 'list' content type. + :return: Boolean result + """ content_type = content_type.strip() if not content_type.startswith("pt:list"): return False + if not _has_matched_brackets(content_type): + return False + if not _has_brackets(content_type): return False @@ -121,11 +164,20 @@ def _is_valid_list(content_type: str) -> bool: def _is_valid_dict(content_type: str) -> bool: + """ + Evaluate whether the format of a 'dict' content type in a protocol specification is valid. + + :param content_type: a 'dict' content type. + :return: Boolean result + """ content_type = content_type.strip() if not content_type.startswith("pt:dict"): return False + if not _has_matched_brackets(content_type): + return False + if not _has_brackets(content_type): return False @@ -139,15 +191,33 @@ def _is_valid_dict(content_type: str) -> bool: def _is_valid_union(content_type: str) -> bool: + """ + Evaluate whether the format of a 'union' content type in a protocol specification is valid. + + :param content_type: an 'union' content type. + :return: Boolean result + """ content_type = content_type.strip() if not content_type.startswith("pt:union"): return False + if not _has_matched_brackets(content_type): + return False + if not _has_brackets(content_type): return False sub_types = _get_sub_types_of_compositional_types(content_type) + # check there are at least two subtypes in the union + if len(sub_types) < 2: + return False + + # check there are no duplicate subtypes in the union + sub_types_set = set(sub_types) + if len(sub_types) != len(sub_types_set): + return False + for sub_type in sub_types: if not ( _is_valid_ct(sub_type) @@ -162,11 +232,20 @@ def _is_valid_union(content_type: str) -> bool: def _is_valid_optional(content_type: str) -> bool: + """ + Evaluate whether the format of an 'optional' content type in a protocol specification is valid. + + :param content_type: an 'optional' content type. + :return: Boolean result + """ content_type = content_type.strip() if not content_type.startswith("pt:optional"): return False + if not _has_matched_brackets(content_type): + return False + if not _has_brackets(content_type): return False @@ -186,6 +265,12 @@ def _is_valid_optional(content_type: str) -> bool: def _is_valid_content_type_format(content_type: str) -> bool: + """ + Evaluate whether the format of a content type in a protocol specification is valid. + + :param content_type: a content type. + :return: Boolean result + """ return ( _is_valid_ct(content_type) or _is_valid_pt(content_type) @@ -232,7 +317,7 @@ def _validate_content_name(content_name: str, performative: str) -> Tuple[bool, :return: Boolean result, and associated message. """ - if not _is_valid_regex(PERFORMATIVE_REGEX_PATTERN, content_name): + if not _is_valid_regex(CONTENT_NAME_REGEX_PATTERN, content_name): return ( False, "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( @@ -296,6 +381,14 @@ def _validate_speech_acts_section( custom_types_set = set() performatives_set = set() + if len(protocol_specification.speech_acts.read_all()) == 0: + return ( + False, + "Speech-acts cannot be empty!", + None, + None, + ) + for ( performative, speech_act_content_config, @@ -397,6 +490,12 @@ def _validate_initiation( :return: Boolean result, and associated message. """ + if len(initiation) == 0 or initiation is None: + return ( + False, + "At least one initial performative for this dialogue must be specified.", + ) + for performative in initiation: if performative not in performatives_set: return ( @@ -454,6 +553,12 @@ def _validate_termination( :return: Boolean result, and associated message. """ + if len(termination) == 0 or termination is None: + return ( + False, + "At least one terminal performative for this dialogue must be specified.", + ) + for performative in termination: if performative not in performatives_set: return ( @@ -473,6 +578,14 @@ def _validate_roles(roles: Set[str]) -> Tuple[bool, str]: :param roles: Set of roles of a dialogue. :return: Boolean result, and associated message. """ + if not 1 <= len(roles) <= 2: + return ( + False, + "There must be either 1 or 2 roles defined in this dialogue. Found {}".format( + len(roles) + ), + ) + for role in roles: if not _is_valid_regex(ROLE_REGEX_PATTERN, role): return ( diff --git a/aea/protocols/scaffold/message.py b/aea/protocols/scaffold/message.py index b620d8a8e3..ecb37e9f06 100644 --- a/aea/protocols/scaffold/message.py +++ b/aea/protocols/scaffold/message.py @@ -21,7 +21,7 @@ from enum import Enum -from aea.configurations.base import PublicId +from aea.configurations.base import ProtocolId from aea.protocols.base import Message from aea.protocols.scaffold.serialization import MyScaffoldSerializer @@ -29,7 +29,7 @@ class MyScaffoldMessage(Message): """The scaffold message class.""" - protocol_id = PublicId("fetchai", "scaffold", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/scaffold:0.1.0") serializer = MyScaffoldSerializer class Performative(Enum): diff --git a/aea/protocols/scaffold/protocol.yaml b/aea/protocols/scaffold/protocol.yaml index 4f365f7a80..bd236f8c6b 100644 --- a/aea/protocols/scaffold/protocol.yaml +++ b/aea/protocols/scaffold/protocol.yaml @@ -6,7 +6,7 @@ license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmedGZfo1UqT6UJoRkHys9kmquia9BQcK17y2touwSENDU - message.py: QmR9baHynNkr4mLvEdzJQpiNzPEfsPm2gzYa1H9jT3TxTQ + message.py: QmQBEHSHTH19n3dBr2WKAW9vqjytCTHCB2avExs9wMGPxw serialization.py: QmNjyzqmoYnCxiLoBeZjXMhYkQzJpbDSFm7A9wytyRa2Xn fingerprint_ignore_patterns: [] dependencies: {} diff --git a/examples/protocol_specification_ex/signing.yaml b/aea/protocols/signing/README.md similarity index 82% rename from examples/protocol_specification_ex/signing.yaml rename to aea/protocols/signing/README.md index 438c1f13c3..7875b0552f 100644 --- a/examples/protocol_specification_ex/signing.yaml +++ b/aea/protocols/signing/README.md @@ -1,7 +1,26 @@ +# Signing Protocol + +**Name:** signing + +**Author**: fetchai + +**Version**: 0.2.0 + +**Short Description**: A protocol for communication between skills and decision maker. + +**License**: Apache-2.0 + +## Description + +This is a protocol for communication between a skill and a decision maker. + +## Specification + +```yaml --- name: signing author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -59,3 +78,6 @@ termination: [signed_transaction, signed_message, error] roles: {skill, decision_maker} end_states: [successful, failed] ... +``` + +## Links diff --git a/aea/protocols/signing/dialogues.py b/aea/protocols/signing/dialogues.py index c5a0f52016..40bde9680b 100644 --- a/aea/protocols/signing/dialogues.py +++ b/aea/protocols/signing/dialogues.py @@ -145,7 +145,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> SigningDialogue: """ - Create an instance of fipa dialogue. + Create an instance of signing dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/aea/protocols/signing/message.py b/aea/protocols/signing/message.py index 559a0dc56b..5150a27d5a 100644 --- a/aea/protocols/signing/message.py +++ b/aea/protocols/signing/message.py @@ -42,7 +42,7 @@ class SigningMessage(Message): """A protocol for communication between skills and decision maker.""" - protocol_id = ProtocolId("fetchai", "signing", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/signing:0.2.0") ErrorCode = CustomErrorCode diff --git a/aea/protocols/signing/protocol.yaml b/aea/protocols/signing/protocol.yaml index 1265c0fc90..15ce140129 100644 --- a/aea/protocols/signing/protocol.yaml +++ b/aea/protocols/signing/protocol.yaml @@ -1,14 +1,15 @@ name: signing author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmSYzpWru7bswJGW1DBtuLgYUrbF5MXZ3KkDwnwxfB2yYk __init__.py: QmcCL3TTdvd8wxYKzf2d3cgKEtY9RzLjPCn4hex4wmb6h6 custom_types.py: Qmc7sAyCQbAaVs5dZf9hFkTrB2BG8VAioWzbyKBAybrQ1J - dialogues.py: QmdQz9MJNXSaXxWPfmGKgbfYHittDap9BbBW7WZZifQ8RF - message.py: QmeyubdB5wTu6S1PMVCb5WDweNNvYi6GUDnoTSXY9qBDjG + dialogues.py: QmaoSYB1baPGuVa64H7xtMkNjTybQguPbFBSzBwLsTVT8x + message.py: QmRXGbAy2oYWecxXmdxfQW9dNspinwhxVuSK4RqR4WZTvE serialization.py: QmPUWHUpQ9pst42s1naM5nTbsxxko5HxPi2gB86FQnMGnL signing.proto: QmT59ZVsevFoJ51uiuAzCgHGowmwfo3bLAKRSgXV1qyXFo signing_pb2.py: QmPZFneKLZUipxAZ3usnmUm1br6VvetzvBpid6GU4JjR39 diff --git a/examples/protocol_specification_ex/state_update.yaml b/aea/protocols/state_update/README.md similarity index 67% rename from examples/protocol_specification_ex/state_update.yaml rename to aea/protocols/state_update/README.md index 2f88d847e2..4caf292f7f 100644 --- a/examples/protocol_specification_ex/state_update.yaml +++ b/aea/protocols/state_update/README.md @@ -1,7 +1,26 @@ +# State Update Protocol + +**Name:** state_update + +**Author**: fetchai + +**Version**: 0.2.0 + +**Short Description**: A protocol for state updates to the decision maker state. + +**License**: Apache-2.0 + +## Description + +This is a protocol for updating the state of a decision maker. + +## Specification + +```yaml --- name: state_update author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -16,10 +35,6 @@ speech_acts: quantities_by_good_id: pt:dict[pt:str, pt:int] ... --- -ct:StateUpdate: | - bytes state_update = 1; -... ---- initiation: [initialize] reply: initialize: [apply] @@ -28,3 +43,6 @@ termination: [apply] roles: {skill, decision_maker} end_states: [successful] ... +``` + +## Links diff --git a/aea/protocols/state_update/dialogues.py b/aea/protocols/state_update/dialogues.py index 48292d5a56..ad8d959dad 100644 --- a/aea/protocols/state_update/dialogues.py +++ b/aea/protocols/state_update/dialogues.py @@ -122,7 +122,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> StateUpdateDialogue: """ - Create an instance of fipa dialogue. + Create an instance of state_update dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/aea/protocols/state_update/message.py b/aea/protocols/state_update/message.py index 1c247f0d57..cd11ba9a48 100644 --- a/aea/protocols/state_update/message.py +++ b/aea/protocols/state_update/message.py @@ -34,7 +34,7 @@ class StateUpdateMessage(Message): """A protocol for state updates to the decision maker state.""" - protocol_id = ProtocolId("fetchai", "state_update", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/state_update:0.2.0") class Performative(Enum): """Performatives for the state_update protocol.""" diff --git a/aea/protocols/state_update/protocol.yaml b/aea/protocols/state_update/protocol.yaml index 4be82dbb24..34dd71115a 100644 --- a/aea/protocols/state_update/protocol.yaml +++ b/aea/protocols/state_update/protocol.yaml @@ -1,13 +1,14 @@ name: state_update author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmQ2sQno2YXxkHx9copJsb9wwhwfryz3oJmnXnarP4Pcsb __init__.py: Qma2opyN54gwTpkVV1E14jjeMmMfoqgE6XMM9LsvGuTdkm - dialogues.py: QmPk4bgw1o5Uon2cpnRH6Y5WzJKUDcvMgFfDt2qQVUdJex - message.py: QmPHEGuepwmrLsNhe8JVLKcdPmNGaziDfdeqshirRJhAKY + dialogues.py: QmUo2zDoSShCy6dY6HWR1i2yb7rfBSSyHK1xpAp1WmMpLT + message.py: QmZo4tX6fjyJmcRezrVC8EQ882iV1y3sm2RyWK6ByhTUdY serialization.py: QmQDdbN4pgfdL1LUhV4J7xMUhdqUJ2Tamz7Nheca3yGw2G state_update.proto: QmdmEUSa7PDxJ98ZmGE7bLFPmUJv8refgbkHPejw6uDdwD state_update_pb2.py: QmQr5KXhapRv9AnfQe7Xbr5bBqYWp9DEMLjxX8UWmK75Z4 diff --git a/aea/registries/base.py b/aea/registries/base.py index a316a6455a..673daa188f 100644 --- a/aea/registries/base.py +++ b/aea/registries/base.py @@ -18,6 +18,8 @@ # ------------------------------------------------------------------------------ """This module contains registries.""" + +import copy import logging from abc import ABC, abstractmethod from typing import Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast @@ -48,12 +50,15 @@ def __init__(self): super().__init__(logger) @abstractmethod - def register(self, item_id: ItemId, item: Item) -> None: + def register( + self, item_id: ItemId, item: Item, is_dynamically_added: bool = False + ) -> None: """ Register an item. :param item_id: the public id of the item. :param item: the item. + :param is_dynamicall_added: whether or not the item is dynamicall added. :return: None :raises: ValueError if an item is already registered with that item id. """ @@ -116,13 +121,17 @@ def __init__(self) -> None: self._registered_keys: Set[ComponentId] = set() def register( - self, component_id: ComponentId, component: Component + self, + component_id: ComponentId, + component: Component, + is_dynamically_added: bool = False, ) -> None: # pylint: disable=arguments-differ """ Register a component. :param component_id: the id of the component. :param component: the component object. + :param is_dynamicall_added: whether or not the item is dynamicall added. """ if component_id in self._registered_keys: raise ValueError( @@ -227,8 +236,7 @@ def teardown(self) -> None: :return: None """ - self._components_by_type = {} - self._registered_keys = set() + pass class ComponentRegistry( @@ -244,13 +252,20 @@ def __init__(self) -> None: """ super().__init__() self._items = {} # type: Dict[SkillId, Dict[str, SkillComponentType]] + self._dynamically_added = {} # type: Dict[SkillId, Set[str]] - def register(self, item_id: Tuple[SkillId, str], item: SkillComponentType) -> None: + def register( + self, + item_id: Tuple[SkillId, str], + item: SkillComponentType, + is_dynamically_added: bool = False, + ) -> None: """ Register a item. :param item_id: a pair (skill id, item name). :param item: the item to register. + :param is_dynamicall_added: whether or not the item is dynamicall added. :return: None :raises: ValueError if an item is already registered with that item id. """ @@ -263,11 +278,25 @@ def register(self, item_id: Tuple[SkillId, str], item: SkillComponentType) -> No ) ) self._items.setdefault(skill_id, {})[item_name] = item + if is_dynamically_added: + self._dynamically_added.setdefault(skill_id, set()).add(item_name) def unregister(self, item_id: Tuple[SkillId, str]) -> None: """ Unregister a item. + :param item_id: a pair (skill id, item name). + :return: None + :raises: ValueError if no item registered with that item id. + """ + self._unregister_from_main_index(item_id) + + def _unregister_from_main_index( + self, item_id: Tuple[SkillId, str] + ) -> SkillComponentType: + """ + Unregister a item. + :param item_id: a pair (skill id, item name). :return: None :raises: ValueError if no item registered with that item id. @@ -280,11 +309,17 @@ def unregister(self, item_id: Tuple[SkillId, str]) -> None: "No item registered with component id '{}'".format(item_id) ) self.logger.debug("Unregistering item with id {}".format(item_id)) - name_to_item.pop(item_name) - + item = name_to_item.pop(item_name) if len(name_to_item) == 0: self._items.pop(skill_id, None) + items = self._dynamically_added.get(skill_id, None) + if items is not None: + items.remove(item_name) + if len(items) == 0: + self._dynamically_added.pop(skill_id, None) + return item + def fetch(self, item_id: Tuple[SkillId, str]) -> Optional[SkillComponentType]: """ Fetch an item. @@ -313,6 +348,7 @@ def unregister_by_skill(self, skill_id: SkillId) -> None: "No component of skill {} present in the registry.".format(skill_id) ) self._items.pop(skill_id, None) + self._dynamically_added.pop(skill_id, None) def setup(self) -> None: """ @@ -351,6 +387,10 @@ def teardown(self) -> None: skill_id, type(item).__name__, str(e) ) ) + _dynamically_added = copy.deepcopy(self._dynamically_added) + for skill_id, items_names in _dynamically_added.items(): + for item_name in items_names: + self.unregister((skill_id, item_name)) class HandlerRegistry(ComponentRegistry[Handler]): @@ -367,16 +407,22 @@ def __init__(self) -> None: {} ) # type: Dict[ProtocolId, Dict[SkillId, Handler]] - def register(self, item_id: Tuple[SkillId, str], item: Handler) -> None: + def register( + self, + item_id: Tuple[SkillId, str], + item: Handler, + is_dynamically_added: bool = False, + ) -> None: """ Register a handler. :param item_id: the item id. :param item: the handler. + :param is_dynamicall_added: whether or not the item is dynamicall added. :return: None :raises ValueError: if the protocol is None, or an item with pair (skill_id, protocol_id_ already exists. """ - super().register(item_id, item) + super().register(item_id, item, is_dynamically_added=is_dynamically_added) skill_id = item_id[0] @@ -411,21 +457,8 @@ def unregister(self, item_id: Tuple[SkillId, str]) -> None: :return: None :raises: ValueError if no item is registered with that item id. """ - # remove from main index skill_id = item_id[0] - item_name = item_id[1] - name_to_item = self._items.get(skill_id, {}) - if item_name not in name_to_item: - raise ValueError( - "No item registered with component id '{}'".format(item_id) - ) - self.logger.debug( # pylint: disable=no-member - "Unregistering item with id {}".format(item_id) - ) - handler = name_to_item.pop(item_name) - - if len(name_to_item) == 0: - self._items.pop(skill_id, None) + handler = super()._unregister_from_main_index(item_id) # remove from index by protocol and skill protocol_id = cast(ProtocolId, handler.SUPPORTED_PROTOCOL) @@ -443,6 +476,9 @@ def unregister_by_skill(self, skill_id: SkillId) -> None: raise ValueError( "No component of skill {} present in the registry.".format(skill_id) ) + + self._dynamically_added.pop(skill_id, None) + handlers = self._items.pop(skill_id).values() # unregister from the protocol-skill index diff --git a/aea/registries/filter.py b/aea/registries/filter.py index 562dde7ff8..d362568890 100644 --- a/aea/registries/filter.py +++ b/aea/registries/filter.py @@ -19,6 +19,7 @@ """This module contains registries.""" +import copy import logging import queue from queue import Queue @@ -100,8 +101,9 @@ def handle_internal_messages(self) -> None: :return: None """ self._handle_decision_maker_out_queue() - # get new behaviours from the agent skills + # get new behaviours and handlers from the agent skills self._handle_new_behaviours() + self._handle_new_handlers() def _handle_decision_maker_out_queue(self) -> None: """Process descision maker's messages.""" @@ -136,12 +138,29 @@ def _handle_new_behaviours(self) -> None: self.resources.behaviour_registry.register( (skill.skill_context.skill_id, new_behaviour.name), new_behaviour, + is_dynamically_added=True, ) except ValueError as e: logger.warning( "Error when trying to add a new behaviour: {}".format(str(e)) ) + def _handle_new_handlers(self) -> None: + """Register new handlers added to skills.""" + for skill in self.resources.get_all_skills(): + while not skill.skill_context.new_handlers.empty(): + new_handler = skill.skill_context.new_handlers.get() + try: + self.resources.handler_registry.register( + (skill.skill_context.skill_id, new_handler.name), + new_handler, + is_dynamically_added=True, + ) + except ValueError as e: + logger.warning( + "Error when trying to add a new handler: {}".format(str(e)) + ) + def _handle_signing_message(self, signing_message: SigningMessage): """Handle transaction message from the Decision Maker.""" skill_callback_ids = [ @@ -150,15 +169,22 @@ def _handle_signing_message(self, signing_message: SigningMessage): ] for skill_id in skill_callback_ids: handler = self.resources.handler_registry.fetch_by_protocol_and_skill( - signing_message.protocol_id, skill_id + signing_message.protocol_id, + skill_id, # TODO: route based on component id specified on message ) if handler is not None: logger.debug( "Calling handler {} of skill {}".format(type(handler), skill_id) ) - signing_message.counterparty = "decision_maker" # TODO: temp fix - signing_message.is_incoming = True - handler.handle(cast(Message, signing_message)) + # TODO: remove next three lines + copy_signing_message = copy.copy( + signing_message + ) # we do a shallow copy as we only need the message object to be copied; not its referenced objects + copy_signing_message.counterparty = signing_message.sender + copy_signing_message.sender = signing_message.sender + # copy_signing_message.to = signing_message.to + copy_signing_message.is_incoming = True + handler.handle(cast(Message, copy_signing_message)) else: logger.warning( "No internal handler fetched for skill_id={}".format(skill_id) diff --git a/aea/registries/resources.py b/aea/registries/resources.py index 9ba282fb5a..d136a7678c 100644 --- a/aea/registries/resources.py +++ b/aea/registries/resources.py @@ -19,7 +19,7 @@ """This module contains the resources class.""" from contextlib import suppress -from typing import Dict, List, Optional, cast +from typing import List, Optional, cast from aea.components.base import Component from aea.configurations.base import ( @@ -244,28 +244,6 @@ def add_skill(self, skill: Skill) -> None: if skill.models is not None: for model in skill.models.values(): self._model_registry.register((skill.public_id, model.name), model) - self.inject_contracts(skill) - - def inject_contracts(self, skill: Skill) -> None: - """ - Inject contracts into a skill context. - - :param skill: a skill - :return: None - """ - if skill.config.contracts is not None: - # check all contracts are present - contracts = {} # type: Dict[str, Contract] - for contract_id in skill.config.contracts: - contract = self._component_registry.fetch( - ComponentId(ComponentType.CONTRACT, contract_id) - ) - if contract is None: - raise ValueError( - "Missing contract for contract id {}".format(contract_id) - ) - contracts[contract_id.name] = cast(Contract, contract) - skill.inject_contracts(contracts) def get_skill(self, skill_id: SkillId) -> Optional[Skill]: """ diff --git a/aea/runtime.py b/aea/runtime.py index dadf82f494..895decc423 100644 --- a/aea/runtime.py +++ b/aea/runtime.py @@ -25,7 +25,7 @@ from asyncio.events import AbstractEventLoop from contextlib import suppress from enum import Enum -from typing import Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING, cast from aea.agent_loop import AsyncState from aea.helpers.async_utils import ensure_loop @@ -41,12 +41,11 @@ class RuntimeStates(Enum): """Runtime states.""" - initial = "not_started" starting = "starting" - started = "started" - loop_stopped = "loop_stopped" + running = "running" stopping = "stopping" stopped = "stopped" + error = "error" class BaseRuntime(ABC): @@ -66,17 +65,33 @@ def __init__( self._loop = ensure_loop( loop ) # TODO: decide who constructs loop: agent, runtime, multiplexer. - self._state: AsyncState = AsyncState(RuntimeStates.initial) + self._state: AsyncState = AsyncState(RuntimeStates.stopped, RuntimeStates) + self._state.add_callback(self._log_runtime_state) + self._was_started = False + + def _log_runtime_state(self, state) -> None: + logger.debug(f"[{self._agent.name}]: Runtime state changed to {state}.") def start(self) -> None: """Start agent using runtime.""" - if self._state.get() is RuntimeStates.started: # pragma: nocover - logger.error("[{}]: Runtime already started".format(self._agent.name)) + if self._state.get() is not RuntimeStates.stopped: + logger.error( + "[{}]: Runtime is not stopped. Please stop it and start after.".format( + self._agent.name + ) + ) return + self._was_started = True self._start() def stop(self) -> None: """Stop agent and runtime.""" + if self._state.get() in (RuntimeStates.stopped, RuntimeStates.stopping): + logger.error( + "[{}]: Runtime is already stopped or stopping.".format(self._agent.name) + ) + return + logger.debug("[{}]: Runtime stopping...".format(self._agent.name)) self._teardown() self._stop() @@ -100,12 +115,12 @@ def _stop(self) -> None: # pragma: nocover @property def is_running(self) -> bool: """Get running state of the runtime.""" - return self._state.get() == RuntimeStates.started + return self._state.get() == RuntimeStates.running @property def is_stopped(self) -> bool: """Get stopped state of the runtime.""" - return self._state.get() in [RuntimeStates.stopped, RuntimeStates.initial] + return self._state.get() in [RuntimeStates.stopped] def set_loop(self, loop: AbstractEventLoop) -> None: """ @@ -116,6 +131,15 @@ def set_loop(self, loop: AbstractEventLoop) -> None: self._loop = loop asyncio.set_event_loop(self._loop) + @property + def state(self) -> RuntimeStates: + """ + Get runtime state. + + :return: RuntimeStates + """ + return cast(RuntimeStates, self._state.get()) + class AsyncRuntime(BaseRuntime): """Asynchronous runtime: uses asyncio loop for multiplexer and async agent main loop.""" @@ -156,28 +180,24 @@ def _start(self) -> None: """ self.set_loop(self._loop) - self._state.set(RuntimeStates.started) - logger.debug(f"Start runtime event loop {self._loop}: {id(self._loop)}") self._task = self._loop.create_task(self.run_runtime()) try: self._loop.run_until_complete(self._task) - self._state.set(RuntimeStates.loop_stopped) logger.debug("Runtime loop stopped!") except Exception: logger.exception("Exception raised during runtime processing") + self._state.set(RuntimeStates.error) raise finally: self._stopping_task = None async def run_runtime(self) -> None: """Run agent and starts multiplexer.""" + self._state.set(RuntimeStates.starting) try: - self._state.set(RuntimeStates.starting) - self._agent.setup_multiplexer() await self._start_multiplexer() - self._agent.start_setup() await self._start_agent_loop() except Exception: logger.exception("AsyncRuntime exception during run:") @@ -192,12 +212,14 @@ async def _multiplexer_disconnect(self) -> None: async def _start_multiplexer(self) -> None: """Call multiplexer connect asynchronous way.""" + self._agent.setup_multiplexer() await AsyncMultiplexer.connect(self._agent.multiplexer) async def _start_agent_loop(self) -> None: """Start agent main loop asynchronous way.""" logger.debug("[{}]: Runtime started".format(self._agent.name)) - self._state.set(RuntimeStates.started) + self._agent.start_setup() + self._state.set(RuntimeStates.running) await self._agent.main_loop.run_loop() async def _stop_runtime(self) -> None: @@ -214,7 +236,10 @@ async def _stop_runtime(self) -> None: async with self._async_stop_lock: - if self._state.get() is not RuntimeStates.started: + if self._state.get() in ( + RuntimeStates.stopped, + RuntimeStates.stopping, + ): # pragma: nocover return self._state.set(RuntimeStates.stopping) @@ -276,10 +301,15 @@ def _start_agent_loop(self) -> None: """Start aget's main loop.""" logger.debug("[{}]: Runtime started".format(self._agent.name)) try: - self._state.set(RuntimeStates.started) + self._state.set(RuntimeStates.running) self._agent.main_loop.start() - finally: - self._state.set(RuntimeStates.loop_stopped) + logger.debug("[{}]: Runtime stopped".format(self._agent.name)) + except KeyboardInterrupt: + raise + except BaseException: # pragma: nocover + logger.exception("Runtime exception during stop:") + self._state.set(RuntimeStates.error) + raise def _stop(self) -> None: """Implement runtime stop function here.""" diff --git a/aea/skills/base.py b/aea/skills/base.py index ae11fa8703..0fc2c26004 100644 --- a/aea/skills/base.py +++ b/aea/skills/base.py @@ -25,11 +25,11 @@ import queue import re from abc import ABC, abstractmethod -from logging import Logger, LoggerAdapter +from logging import Logger from pathlib import Path from queue import Queue from types import SimpleNamespace -from typing import Any, Dict, Optional, Sequence, Set, Tuple, Type, Union, cast +from typing import Any, Dict, Optional, Sequence, Set, Tuple, Type, cast from aea.components.base import Component from aea.configurations.base import ( @@ -40,18 +40,16 @@ SkillComponentConfiguration, SkillConfig, ) -from aea.connections.base import ConnectionStatus from aea.context.base import AgentContext -from aea.contracts.base import Contract from aea.exceptions import AEAException from aea.helpers.base import load_aea_package, load_module from aea.helpers.logging import AgentLoggerAdapter from aea.mail.base import Address -from aea.multiplexer import OutBox +from aea.multiplexer import ConnectionStatus, OutBox from aea.protocols.base import Message from aea.skills.tasks import TaskManager -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class SkillContext: @@ -74,17 +72,18 @@ def __init__( self._is_active = True # type: bool self._new_behaviours_queue = queue.Queue() # type: Queue - self._logger: Optional[Union[Logger, LoggerAdapter]] = None + self._new_handlers_queue = queue.Queue() # type: Queue + self._logger: Optional[Logger] = None @property - def logger(self) -> Union[Logger, LoggerAdapter]: + def logger(self) -> Logger: """Get the logger.""" if self._logger is None: - return logging.getLogger("aea") + return _default_logger return self._logger @logger.setter - def logger(self, logger_: Union[Logger, AgentLoggerAdapter]) -> None: + def logger(self, logger_: Logger) -> None: assert self._logger is None, "Logger already set." self._logger = logger_ @@ -122,14 +121,14 @@ def is_active(self) -> bool: def is_active(self, value: bool) -> None: """Set the status of the skill (active/not active).""" self._is_active = value - logger.debug( + self.logger.debug( "New status of skill {}: is_active={}".format( self.skill_id, self._is_active ) ) @property - def new_behaviours(self) -> Queue: + def new_behaviours(self) -> "Queue[Behaviour]": """ Queue for the new behaviours. @@ -140,6 +139,18 @@ def new_behaviours(self) -> Queue: """ return self._new_behaviours_queue + @property + def new_handlers(self) -> "Queue[Handler]": + """ + Queue for the new handlers. + + This queue can be used to send messages to the framework + to request the registration of a handler. + + :return the queue of new handlers. + """ + return self._new_handlers_queue + @property def agent_addresses(self) -> Dict[str, str]: """Get addresses.""" @@ -200,12 +211,6 @@ def behaviours(self) -> SimpleNamespace: assert self._skill is not None, "Skill not initialized." return SimpleNamespace(**self._skill.behaviours) - @property - def contracts(self) -> SimpleNamespace: - """Get contracts the skill has access to.""" - assert self._skill is not None, "Skill not initialized." - return SimpleNamespace(**self._skill.contracts) - @property def namespace(self) -> SimpleNamespace: """Get the agent context namespace.""" @@ -242,7 +247,7 @@ def __init__( self._name = name self._context = skill_context if len(kwargs) != 0: - logger.warning( + self.context.logger.warning( "The kwargs={} passed to {} have not been set!".format(kwargs, name) ) @@ -381,6 +386,7 @@ def parse_module( name_to_class = dict(behaviours_classes) _print_warning_message_for_non_declared_skill_components( + skill_context, set(name_to_class.keys()), { behaviour_config.class_name @@ -392,13 +398,15 @@ def parse_module( for behaviour_id, behaviour_config in behaviour_configs.items(): behaviour_class_name = cast(str, behaviour_config.class_name) - logger.debug("Processing behaviour {}".format(behaviour_class_name)) + skill_context.logger.debug( + "Processing behaviour {}".format(behaviour_class_name) + ) assert ( behaviour_id.isidentifier() ), "'{}' is not a valid identifier.".format(behaviour_id) behaviour_class = name_to_class.get(behaviour_class_name, None) if behaviour_class is None: - logger.warning( + skill_context.logger.warning( "Behaviour '{}' cannot be found.".format(behaviour_class_name) ) else: @@ -462,6 +470,7 @@ def parse_module( name_to_class = dict(handler_classes) _print_warning_message_for_non_declared_skill_components( + skill_context, set(name_to_class.keys()), {handler_config.class_name for handler_config in handler_configs.values()}, "handlers", @@ -469,13 +478,15 @@ def parse_module( ) for handler_id, handler_config in handler_configs.items(): handler_class_name = cast(str, handler_config.class_name) - logger.debug("Processing handler {}".format(handler_class_name)) + skill_context.logger.debug( + "Processing handler {}".format(handler_class_name) + ) assert handler_id.isidentifier(), "'{}' is not a valid identifier.".format( handler_id ) handler_class = name_to_class.get(handler_class_name, None) if handler_class is None: - logger.warning( + skill_context.logger.warning( "Handler '{}' cannot be found.".format(handler_class_name) ) else: @@ -534,7 +545,7 @@ def parse_module( ) for module_path in module_paths: - logger.debug("Trying to load module {}".format(module_path)) + skill_context.logger.debug("Trying to load module {}".format(module_path)) module_name = module_path.replace(".py", "") model_module = load_module(module_name, Path(module_path)) classes = inspect.getmembers(model_module, inspect.isclass) @@ -555,6 +566,7 @@ def parse_module( _check_duplicate_classes(models) name_to_class = dict(models) _print_warning_message_for_non_declared_skill_components( + skill_context, set(name_to_class.keys()), {model_config.class_name for model_config in model_configs.values()}, "models", @@ -562,7 +574,7 @@ def parse_module( ) for model_id, model_config in model_configs.items(): model_class_name = model_config.class_name - logger.debug( + skill_context.logger.debug( "Processing model id={}, class={}".format(model_id, model_class_name) ) assert model_id.isidentifier(), "'{}' is not a valid identifier.".format( @@ -570,7 +582,9 @@ def parse_module( ) model = name_to_class.get(model_class_name, None) if model is None: - logger.warning("Model '{}' cannot be found.".format(model_class_name)) + skill_context.logger.warning( + "Model '{}' cannot be found.".format(model_class_name) + ) else: model_instance = model( name=model_id, @@ -635,19 +649,8 @@ def __init__( ) # type: Dict[str, Behaviour] self._models = {} if models is None else models # type: Dict[str, Model] - self._contracts = {} # type: Dict[str, Contract] - self._skill_context._skill = self - @property - def contracts(self) -> Dict[str, Contract]: - """Get the contracts associated with the skill.""" - return self._contracts - - def inject_contracts(self, contracts: Dict[str, Contract]) -> None: - """Add the contracts to the skill.""" - self._contracts = contracts - @property def skill_context(self) -> SkillContext: """Get the skill context.""" @@ -670,7 +673,7 @@ def models(self) -> Dict[str, Model]: return self._models @classmethod - def from_dir(cls, directory: str, agent_context: AgentContext) -> "Skill": + def from_dir(cls, directory: str, agent_context: AgentContext, **kwargs) -> "Skill": """ Load the skill from a directory. @@ -686,7 +689,7 @@ def from_dir(cls, directory: str, agent_context: AgentContext) -> "Skill": return Skill.from_config(configuration, agent_context) @property - def logger(self) -> Union[Logger, LoggerAdapter]: + def logger(self) -> Logger: """ Get the logger. @@ -698,11 +701,11 @@ def logger(self) -> Union[Logger, LoggerAdapter]: @logger.setter def logger(self, *args) -> None: """Set the logger.""" - raise ValueError("Cannot set logger to a skill component..") + raise ValueError("Cannot set logger to a skill component.") @classmethod def from_config( - cls, configuration: SkillConfig, agent_context: AgentContext + cls, configuration: SkillConfig, agent_context: AgentContext, **kwargs ) -> "Skill": """ Load the skill from configuration. @@ -721,10 +724,10 @@ def from_config( skill_context = SkillContext() skill_context.set_agent_context(agent_context) logger_name = f"aea.packages.{configuration.author}.skills.{configuration.name}" - logger = AgentLoggerAdapter( + _logger = AgentLoggerAdapter( logging.getLogger(logger_name), agent_context.agent_name ) - skill_context.logger = logger + skill_context.logger = cast(Logger, _logger) skill = Skill(configuration, skill_context) @@ -753,11 +756,15 @@ def from_config( def _print_warning_message_for_non_declared_skill_components( - classes: Set[str], config_components: Set[str], item_type, skill_path + skill_context: SkillContext, + classes: Set[str], + config_components: Set[str], + item_type, + skill_path, ): """Print a warning message if a skill component is not declared in the config files.""" for class_name in classes.difference(config_components): - logger.warning( + skill_context.logger.warning( "Class {} of type {} found but not declared in the configuration file {}.".format( class_name, item_type, skill_path ) diff --git a/aea/skills/error/handlers.py b/aea/skills/error/handlers.py index 5905313c37..9ff282306a 100644 --- a/aea/skills/error/handlers.py +++ b/aea/skills/error/handlers.py @@ -82,6 +82,7 @@ def send_unsupported_protocol(self, envelope: Envelope) -> None: }, ) reply.counterparty = envelope.sender + reply.sender = self.context.agent_address self.context.outbox.put_message(message=reply) def send_decoding_error(self, envelope: Envelope) -> None: @@ -107,6 +108,7 @@ def send_decoding_error(self, envelope: Envelope) -> None: error_data={"envelope": encoded_envelope}, ) reply.counterparty = envelope.sender + reply.sender = self.context.agent_address self.context.outbox.put_message(message=reply) def send_unsupported_skill(self, envelope: Envelope) -> None: @@ -139,4 +141,5 @@ def send_unsupported_skill(self, envelope: Envelope) -> None: error_data={"envelope": encoded_envelope}, ) reply.counterparty = envelope.sender + reply.sender = self.context.agent_address self.context.outbox.put_message(message=reply) diff --git a/aea/skills/error/skill.yaml b/aea/skills/error/skill.yaml index 9a4882704c..f089e39ade 100644 --- a/aea/skills/error/skill.yaml +++ b/aea/skills/error/skill.yaml @@ -1,16 +1,16 @@ name: error author: fetchai -version: 0.3.0 +version: 0.4.0 description: The error skill implements basic error handling required by all AEAs. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmYm7UaWVmRy2i35MBKZRnBrpWBJswLdEH6EY1QQKXdQES - handlers.py: QmV1yRiqVZr5fKd6xbDVxtE68kjcWvrH7UEcxKd82jLM68 + handlers.py: QmTHJ2EFdyRPdDb93po118eqkVMpxxVLZeF4XitLq76yPx fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: [] behaviours: {} handlers: diff --git a/aea/skills/scaffold/behaviours.py b/aea/skills/scaffold/behaviours.py index ecdc1fabc0..1df3678311 100644 --- a/aea/skills/scaffold/behaviours.py +++ b/aea/skills/scaffold/behaviours.py @@ -31,7 +31,7 @@ def setup(self) -> None: :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def act(self) -> None: """ @@ -39,7 +39,7 @@ def act(self) -> None: :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def teardown(self) -> None: """ @@ -47,4 +47,4 @@ def teardown(self) -> None: :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError diff --git a/aea/skills/scaffold/handlers.py b/aea/skills/scaffold/handlers.py index 406c615d4f..1ed480fead 100644 --- a/aea/skills/scaffold/handlers.py +++ b/aea/skills/scaffold/handlers.py @@ -37,7 +37,7 @@ def setup(self) -> None: :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def handle(self, message: Message) -> None: """ @@ -46,7 +46,7 @@ def handle(self, message: Message) -> None: :param message: the message :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def teardown(self) -> None: """ @@ -54,4 +54,4 @@ def teardown(self) -> None: :return: None """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError diff --git a/aea/skills/scaffold/skill.yaml b/aea/skills/scaffold/skill.yaml index 70320b482e..1f4ac58a33 100644 --- a/aea/skills/scaffold/skill.yaml +++ b/aea/skills/scaffold/skill.yaml @@ -6,8 +6,8 @@ license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta - behaviours.py: QmYa1rczhGTtMJBgCd1QR9uZhhkf45orm7TnGTE5Eizjpy - handlers.py: QmZYyTENRr6ecnxx1FeBdgjLiBhFLVn9mqarzUtFQmNUFn + behaviours.py: QmNgDDAmBzWBeBF7e5gUCny38kdqVVfpvHGaAZVZcMtm9Q + handlers.py: QmUFLmnyph4sstd5vxq4Q7up8PVbVMctiiZi8EZMsK1Kj6 my_model.py: QmPaZ6G37Juk63mJj88nParaEp71XyURts8AmmX1axs24V fingerprint_ignore_patterns: [] contracts: [] diff --git a/aea/skills/tasks.py b/aea/skills/tasks.py index 50a0e5a15b..7a01b025f4 100644 --- a/aea/skills/tasks.py +++ b/aea/skills/tasks.py @@ -27,14 +27,13 @@ from aea.helpers.logging import WithLogger -logger = logging.getLogger(__name__) - -class Task: +class Task(WithLogger): """This class implements an abstract task.""" def __init__(self, **kwargs): """Initialize a task.""" + super().__init__(**kwargs) self._is_executed = False # this is where we store the result. self._result = None @@ -57,7 +56,7 @@ def __call__(self, *args, **kwargs): self._result = self.execute(*args, **kwargs) return self except Exception as e: # pylint: disable=broad-except - logger.debug( + self.logger.debug( "Got exception of type {} with message '{}' while executing task.".format( type(e), str(e) ) @@ -83,10 +82,9 @@ def result(self) -> Any: raise ValueError("Task not executed yet.") return self._result - @abstractmethod def setup(self) -> None: """ - Implement the behaviour setup. + Implement the task setup. :return: None """ @@ -99,10 +97,9 @@ def execute(self, *args, **kwargs) -> None: :return: None """ - @abstractmethod def teardown(self) -> None: """ - Implement the behaviour teardown. + Implement the task teardown. :return: None """ @@ -125,7 +122,12 @@ def init_worker() -> None: class TaskManager(WithLogger): """A Task manager.""" - def __init__(self, nb_workers: int = 1, is_lazy_pool_start: bool = True): + def __init__( + self, + nb_workers: int = 1, + is_lazy_pool_start: bool = True, + logger: Optional[logging.Logger] = None, + ): """ Initialize the task manager. @@ -240,7 +242,7 @@ def _start_pool(self) -> None: :return: None """ if self._pool: - self.logger.debug("Pool was already started!.") + self.logger.debug("Pool was already started!") return self._pool = Pool(self._nb_workers, initializer=init_worker) diff --git a/aea/test_tools/click_testing.py b/aea/test_tools/click_testing.py index 160f7fbb70..bc6d80b50c 100644 --- a/aea/test_tools/click_testing.py +++ b/aea/test_tools/click_testing.py @@ -18,7 +18,7 @@ # ------------------------------------------------------------------------------ """ -This module contains an adaptation of click.testing.CliRunner +This module contains an adaptation of click.testing.CliRunner. In particular, it fixes an issue in CLIRunner.invoke, in the 'finally' clause. More precisely, before reading from @@ -37,7 +37,7 @@ class CliRunner(ClickCliRunner): - """Patch of click.testing.CliRunner""" + """Patch of click.testing.CliRunner.""" def invoke( self, @@ -49,12 +49,12 @@ def invoke( color=False, **extra ): - """Patch click.testing.CliRunner.invoke().""" + """Call a cli command with click.testing.CliRunner.invoke.""" exc_info = None - with self.isolation(input=input, env=env, color=color) as outstreams: - exception = None - exit_code = 0 + exception = None + exit_code = 0 + with self.isolation(input=input, env=env, color=color) as outstreams: if isinstance(args, string_types): args = shlex.split(args) @@ -68,10 +68,10 @@ def invoke( except SystemExit as e: exc_info = sys.exc_info() exit_code = e.code - if exit_code is None: + if exit_code is None: # pragma: nocover exit_code = 0 - if exit_code != 0: + if exit_code != 0: # pragma: nocover exception = e if not isinstance(exit_code, int): diff --git a/aea/test_tools/test_cases.py b/aea/test_tools/test_cases.py index 075a46a786..4889d8788b 100644 --- a/aea/test_tools/test_cases.py +++ b/aea/test_tools/test_cases.py @@ -16,7 +16,6 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This module contains test case classes based on pytest for AEA end-to-end testing.""" import copy import logging @@ -30,6 +29,7 @@ import tempfile import time from abc import ABC +from contextlib import suppress from filecmp import dircmp from io import TextIOWrapper from pathlib import Path @@ -88,6 +88,7 @@ class BaseAEATestCase(ABC): agents: Set[str] = set() # the set of created agents stdout: Dict[int, str] # dict of process.pid: string stdout stderr: Dict[int, str] # dict of process.pid: string stderr + _is_teardown_class_called: bool = False @classmethod def set_agent_context(cls, agent_name: str): @@ -100,18 +101,19 @@ def unset_agent_context(cls): cls.current_agent_context = "" @classmethod - def set_config(cls, dotted_path: str, value: Any, type_: str = "str") -> None: + def set_config(cls, dotted_path: str, value: Any, type_: str = "str") -> Result: """ Set a config. + Run from agent's directory. :param dotted_path: str dotted path to config param. :param value: a new value to set. :param type_: the type - :return: None + :return: Result """ - cls.run_cli_command( + return cls.run_cli_command( "config", "set", dotted_path, @@ -128,9 +130,10 @@ def force_set_config(cls, dotted_path: str, value: Any) -> None: force_set_config(dotted_path, value) @classmethod - def disable_aea_logging(cls): + def disable_aea_logging(cls) -> None: """ Disable AEA logging of specific agent. + Run from agent's directory. :return: None @@ -143,7 +146,7 @@ def disable_aea_logging(cls): cls.run_cli_command("config", "set", path, value, cwd=cls._get_cwd()) @classmethod - def run_cli_command(cls, *args: str, cwd: str = ".") -> None: + def run_cli_command(cls, *args: str, cwd: str = ".") -> Result: """ Run AEA CLI command. @@ -151,7 +154,7 @@ def run_cli_command(cls, *args: str, cwd: str = ".") -> None: :param cwd: the working directory from where to run the command. :raises AEATestingException: if command fails. - :return: None + :return: Result """ with cd(cwd): result = cls.runner.invoke( @@ -165,6 +168,7 @@ def run_cli_command(cls, *args: str, cwd: str = ".") -> None: args, result.exit_code, result.exception ) ) + return result @classmethod def _run_python_subprocess(cls, *args: str, cwd: str = ".") -> subprocess.Popen: @@ -199,7 +203,7 @@ def start_subprocess(cls, *args: str, cwd: str = ".") -> subprocess.Popen: return process @classmethod - def start_thread(cls, target: Callable, **kwargs) -> None: + def start_thread(cls, target: Callable, **kwargs) -> Thread: """ Start python Thread. @@ -214,6 +218,7 @@ def start_thread(cls, target: Callable, **kwargs) -> None: thread = Thread(target=target) thread.start() cls.threads.append(thread) + return thread @classmethod def create_agents(cls, *agents_names: str) -> None: @@ -251,7 +256,7 @@ def difference_to_fetched_agent(cls, public_id: str, agent_name: str) -> List[st :return: list of files differing in the projects """ - + # for pydocstyle def is_allowed_diff_in_agent_config( path_to_fetched_aea, path_to_manually_created_aea ) -> Tuple[bool, Dict[str, str], Dict[str, str]]: @@ -281,26 +286,30 @@ def is_allowed_diff_in_agent_config( path_to_manually_created_aea = os.path.join(cls.t, agent_name) new_cwd = os.path.join(cls.t, "fetch_dir") os.mkdir(new_cwd) - path_to_fetched_aea = os.path.join(new_cwd, agent_name) + fetched_agent_name = agent_name + path_to_fetched_aea = os.path.join(new_cwd, fetched_agent_name) registry_tmp_dir = os.path.join(new_cwd, cls.packages_dir_path) shutil.copytree(str(cls.package_registry_src), str(registry_tmp_dir)) with cd(new_cwd): - cls.run_cli_command("fetch", "--local", public_id, "--alias", agent_name) + cls.run_cli_command( + "fetch", "--local", public_id, "--alias", fetched_agent_name + ) comp = dircmp(path_to_manually_created_aea, path_to_fetched_aea) file_diff = comp.diff_files result, diff1, diff2 = is_allowed_diff_in_agent_config( path_to_fetched_aea, path_to_manually_created_aea ) if result: - file_diff.remove("aea-config.yaml") # won't match! + if "aea-config.yaml" in file_diff: # pragma: nocover + file_diff.remove("aea-config.yaml") # won't match! else: file_diff.append( "Difference in aea-config.yaml: " + str(diff1) + " vs. " + str(diff2) ) - try: + + with suppress(OSError, IOError): shutil.rmtree(new_cwd) - except (OSError, IOError): - pass + return file_diff @classmethod @@ -320,6 +329,7 @@ def delete_agents(cls, *agents_names: str) -> None: def run_agent(cls, *args: str) -> subprocess.Popen: """ Run agent as subprocess. + Run from agent's directory. :param args: CLI args @@ -337,6 +347,7 @@ def run_agent(cls, *args: str) -> subprocess.Popen: def run_interaction(cls) -> subprocess.Popen: """ Run interaction as subprocess. + Run from agent's directory. :param args: CLI args @@ -359,6 +370,7 @@ def terminate_agents( ) -> None: """ Terminate agent subprocesses. + Run from agent's directory. :param subprocesses: the subprocesses running the agents @@ -374,9 +386,7 @@ def terminate_agents( @classmethod def is_successfully_terminated(cls, *subprocesses: subprocess.Popen): - """ - Check if all subprocesses terminated successfully - """ + """Check if all subprocesses terminated successfully.""" if not subprocesses: subprocesses = tuple(cls.subprocesses) @@ -393,52 +403,58 @@ def initialize_aea(cls, author) -> None: cls.run_cli_command("init", "--local", "--author", author, cwd=cls._get_cwd()) @classmethod - def add_item(cls, item_type: str, public_id: str, local: bool = True) -> None: + def add_item(cls, item_type: str, public_id: str, local: bool = True) -> Result: """ Add an item to the agent. + Run from agent's directory. :param item_type: str item type. :param public_id: public id of the item. :param local: a flag for local folder add True by default. - :return: None + :return: Result """ cli_args = ["add", "--local", item_type, public_id] - if not local: + if not local: # pragma: nocover cli_args.remove("--local") - cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) + return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod - def scaffold_item(cls, item_type: str, name: str) -> None: + def scaffold_item(cls, item_type: str, name: str) -> Result: """ Scaffold an item for the agent. + Run from agent's directory. :param item_type: str item type. :param name: name of the item. - :return: None + :return: Result """ - cls.run_cli_command("scaffold", item_type, name, cwd=cls._get_cwd()) + return cls.run_cli_command("scaffold", item_type, name, cwd=cls._get_cwd()) @classmethod - def fingerprint_item(cls, item_type: str, public_id: str) -> None: + def fingerprint_item(cls, item_type: str, public_id: str) -> Result: """ - Scaffold an item for the agent. + Fingerprint an item for the agent. + Run from agent's directory. :param item_type: str item type. :param name: public id of the item. - :return: None + :return: Result """ - cls.run_cli_command("fingerprint", item_type, public_id, cwd=cls._get_cwd()) + return cls.run_cli_command( + "fingerprint", item_type, public_id, cwd=cls._get_cwd() + ) @classmethod - def eject_item(cls, item_type: str, public_id: str) -> None: + def eject_item(cls, item_type: str, public_id: str) -> Result: """ Eject an item in the agent. + Run from agent's directory. :param item_type: str item type. @@ -447,29 +463,37 @@ def eject_item(cls, item_type: str, public_id: str) -> None: :return: None """ cli_args = ["eject", item_type, public_id] - cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) + return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod - def run_install(cls): + def run_install(cls) -> Result: """ Execute AEA CLI install command. + Run from agent's directory. - :return: None + :return: Result """ - cls.run_cli_command("install", cwd=cls._get_cwd()) + return cls.run_cli_command("install", cwd=cls._get_cwd()) @classmethod - def generate_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER) -> None: + def generate_private_key( + cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_file: Optional[str] = None + ) -> Result: """ Generate AEA private key with CLI command. + Run from agent's directory. :param ledger_api_id: ledger API ID. + :param private_key_file: the private key file. - :return: None + :return: Result """ - cls.run_cli_command("generate-key", ledger_api_id, cwd=cls._get_cwd()) + cli_args = ["generate-key", ledger_api_id] + if private_key_file is not None: + cli_args.append(private_key_file) + return cls.run_cli_command(*cli_args, cwd=cls._get_cwd()) @classmethod def add_private_key( @@ -477,19 +501,20 @@ def add_private_key( ledger_api_id: str = DEFAULT_LEDGER, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE, connection: bool = False, - ) -> None: + ) -> Result: """ Add private key with CLI command. + Run from agent's directory. :param ledger_api_id: ledger API ID. :param private_key_filepath: private key filepath. :param connection: whether or not the private key filepath is for a connection. - :return: None + :return: Result """ if connection: - cls.run_cli_command( + return cls.run_cli_command( "add-key", ledger_api_id, private_key_filepath, @@ -497,7 +522,7 @@ def add_private_key( cwd=cls._get_cwd(), ) else: - cls.run_cli_command( + return cls.run_cli_command( "add-key", ledger_api_id, private_key_filepath, cwd=cls._get_cwd() ) @@ -514,21 +539,22 @@ def replace_private_key_in_file( :return: None :raises: exception if file does not exist """ - with cd(cls._get_cwd()): + with cd(cls._get_cwd()): # pragma: nocover with open(private_key_filepath, "wt") as f: f.write(private_key) @classmethod - def generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> None: + def generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> Result: """ Generate wealth with CLI command. + Run from agent's directory. :param ledger_api_id: ledger API ID. - :return: None + :return: Result """ - cls.run_cli_command( + return cls.run_cli_command( "generate-wealth", ledger_api_id, "--sync", cwd=cls._get_cwd() ) @@ -536,6 +562,7 @@ def generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> None: def get_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> str: """ Get wealth with CLI command. + Run from agent's directory. :param ledger_api_id: ledger API ID. @@ -547,15 +574,16 @@ def get_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> str: return str(cls.last_cli_runner_result.stdout_bytes, "utf-8") @classmethod - def replace_file_content(cls, src: Path, dest: Path) -> None: + def replace_file_content(cls, src: Path, dest: Path) -> None: # pragma: nocover """ Replace the content of the source file to the dest file. + :param src: the source file. :param dest: the destination file. :return: None """ assert src.is_file() and dest.is_file(), "Source or destination is not a file." - src.write_text(dest.read_text()) + dest.write_text(src.read_text()) @classmethod def change_directory(cls, path: Path) -> None: @@ -586,12 +614,12 @@ def _join_threads(cls): cls.threads = [] @classmethod - def _read_out(cls, process: subprocess.Popen): + def _read_out(cls, process: subprocess.Popen): # pragma: nocover # runs in thread! for line in TextIOWrapper(process.stdout, encoding="utf-8"): cls.stdout[process.pid] += line @classmethod - def _read_err(cls, process: subprocess.Popen): + def _read_err(cls, process: subprocess.Popen): # pragma: nocover # runs in thread! if process.stderr is not None: for line in TextIOWrapper(process.stderr, encoding="utf-8"): cls.stderr[process.pid] += line @@ -646,6 +674,7 @@ def missing_from_output( ) -> List[str]: """ Check if strings are present in process output. + Read process stdout in thread and terminate when all strings are present or timeout expired. @@ -719,11 +748,11 @@ def setup_class(cls): @classmethod def teardown_class(cls): """Teardown the test.""" + cls.change_directory(cls.old_cwd) cls.terminate_agents(*cls.subprocesses) cls._terminate_subprocesses() cls._join_threads() cls.unset_agent_context() - cls.change_directory(cls.old_cwd) cls.last_cli_runner_result = None cls.packages_dir_path = Path("packages") cls.use_packages_dir = True @@ -732,17 +761,16 @@ def teardown_class(cls): cls.package_registry_src = Path(ROOT_DIR, "packages") cls.stdout = {} cls.stderr = {} - try: + + with suppress(OSError, IOError): shutil.rmtree(cls.t) - except (OSError, IOError): - pass + + cls._is_teardown_class_called = True @pytest.mark.integration class UseOef: - """ - Inherit from this class to launch an OEF node. - """ + """Inherit from this class to launch an OEF node.""" @pytest.fixture(autouse=True) def _start_oef_node(self, network_node): @@ -759,7 +787,7 @@ class AEATestCaseEmpty(BaseAEATestCase): @classmethod def setup_class(cls): """Set up the test class.""" - BaseAEATestCase.setup_class() + super(AEATestCaseEmpty, cls).setup_class() cls.agent_name = "agent-" + "".join(random.choices(string.ascii_lowercase, k=5)) cls.create_agents(cls.agent_name) cls.set_agent_context(cls.agent_name) @@ -771,12 +799,12 @@ class AEATestCaseMany(BaseAEATestCase): @classmethod def setup_class(cls): """Set up the test class.""" - BaseAEATestCase.setup_class() + super(AEATestCaseMany, cls).setup_class() @classmethod def teardown_class(cls): """Teardown the test class.""" - BaseAEATestCase.teardown_class() + super(AEATestCaseMany, cls).teardown_class() class AEATestCase(BaseAEATestCase): @@ -809,8 +837,8 @@ def setup_class(cls): # this will create a temporary directory and move into it # TODO: decide whether to keep optionally: BaseAEATestCase.packages_dir_path = cls.packages_dir_path - BaseAEATestCase.use_packages_dir = False - BaseAEATestCase.setup_class() + cls.use_packages_dir = False + super(AEATestCase, cls).setup_class() # copy the content of the agent into the temporary directory shutil.copytree(str(cls.path_to_aea), str(cls.t / cls.agent_name)) @@ -822,4 +850,4 @@ def teardown_class(cls): cls.path_to_aea = Path(".") # TODO: decide whether to keep optionally: cls.packages_dir_path = Path("..", "packages") cls.agent_configuration = None - BaseAEATestCase.teardown_class() + super(AEATestCase, cls).teardown_class() diff --git a/benchmark/framework/fake_connection.py b/benchmark/framework/fake_connection.py index 8c152101a7..5ba0618ffe 100644 --- a/benchmark/framework/fake_connection.py +++ b/benchmark/framework/fake_connection.py @@ -20,7 +20,7 @@ import asyncio from typing import Optional -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope @@ -37,7 +37,7 @@ def __init__(self, envelope: Envelope, num: int, *args, **kwargs): Connection.__init__(self, *args, **kwargs) self.num = num self.envelope = envelope - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) async def connect(self) -> None: """ @@ -52,7 +52,7 @@ async def disconnect(self) -> None: :return: None """ - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: Envelope) -> None: """ diff --git a/deploy-image/docker-env.sh b/deploy-image/docker-env.sh index 649a323822..832182a1c4 100755 --- a/deploy-image/docker-env.sh +++ b/deploy-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-deploy:0.5.2 +DOCKER_IMAGE_TAG=aea-deploy:0.5.3 # DOCKER_IMAGE_TAG=aea-deploy:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/develop-image/docker-env.sh b/develop-image/docker-env.sh index 66af064978..7def9004c0 100755 --- a/develop-image/docker-env.sh +++ b/develop-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-develop:0.5.2 +DOCKER_IMAGE_TAG=aea-develop:0.5.3 # DOCKER_IMAGE_TAG=aea-develop:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/docs/acn.md b/docs/acn.md new file mode 100644 index 0000000000..ff5e4ec970 --- /dev/null +++ b/docs/acn.md @@ -0,0 +1,7 @@ + +

+

Note

+

Details coming soon. In the meantime check out this section.

+
+ +
diff --git a/docs/agent-vs-aea.md b/docs/agent-vs-aea.md index f5bc543e65..7160ea3ae4 100644 --- a/docs/agent-vs-aea.md +++ b/docs/agent-vs-aea.md @@ -67,6 +67,8 @@ class MyAgent(Agent): envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message) + envelope.message.sender = receiver + envelope.message.counterparty = sender print( "Received envelope from {} with protocol_id={}".format( sender, envelope.protocol_id @@ -122,7 +124,7 @@ We use the input and output text files to send an envelope to our agent and rece ``` python # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: f.write(message_text) @@ -196,6 +198,8 @@ class MyAgent(Agent): envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message) + envelope.message.sender = receiver + envelope.message.counterparty = sender print( "Received envelope from {} with protocol_id={}".format( sender, envelope.protocol_id @@ -241,7 +245,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: f.write(message_text) diff --git a/docs/api/aea.md b/docs/api/aea.md index c66b584623..0ddaa454cb 100644 --- a/docs/api/aea.md +++ b/docs/api/aea.md @@ -7,7 +7,7 @@ This module contains the implementation of an autonomous economic agent (AEA). ## AEA Objects ```python -class AEA(Agent) +class AEA(Agent, WithLogger) ``` This class implements an autonomous economic agent. diff --git a/docs/api/aea_builder.md b/docs/api/aea_builder.md index 4eb0737345..87258cd202 100644 --- a/docs/api/aea_builder.md +++ b/docs/api/aea_builder.md @@ -632,3 +632,21 @@ Construct the builder from an AEA project. an AEABuilder. + +#### make`_`logger + +```python +make_logger(configuration: ComponentConfiguration, agent_name: str) -> Optional[logging.Logger] +``` + +Make the logger for a component. + +**Arguments**: + +- `configuration`: the component configuration +- `agent_name`: the agent name + +**Returns**: + +the logger. + diff --git a/docs/api/agent.md b/docs/api/agent.md index 6e1052704f..a1c5153b10 100644 --- a/docs/api/agent.md +++ b/docs/api/agent.md @@ -3,67 +3,6 @@ This module contains the implementation of a generic agent. - -## AgentState Objects - -```python -class AgentState(Enum) -``` - -Enumeration for an agent state. - -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. - - -## Liveness Objects - -```python -class Liveness() -``` - -Determines the liveness of the agent. - - -#### `__`init`__` - -```python - | __init__() -``` - -Instantiate the liveness. - - -#### is`_`stopped - -```python - | @property - | is_stopped() -> bool -``` - -Check whether the liveness is stopped. - - -#### start - -```python - | start() -> None -``` - -Start the liveness. - - -#### stop - -```python - | stop() -> None -``` - -Stop the liveness. - ## Agent Objects @@ -171,16 +110,6 @@ Envelopes placed in the Outbox are processed by the Multiplexer. Get the agent name. - -#### liveness - -```python - | @property - | liveness() -> Liveness -``` - -Get the liveness. - #### tick @@ -203,24 +132,6 @@ Each agent loop (one call to each one of act(), react(), update()) increments th Get the time in (fractions of) seconds to time out an agent between act and react. - -#### agent`_`state - -```python - | @property - | agent_state() -> AgentState -``` - -Get the state of the agent. - -**Raises**: - -- `ValueError`: if the state does not satisfy any of the foreseen conditions. - -**Returns**: - -None - #### loop`_`mode @@ -258,7 +169,7 @@ Get the runtime. | setup_multiplexer() -> None ``` -Set up the multiplexer +Set up the multiplexer. #### start @@ -392,3 +303,17 @@ Tear down the agent. None + +#### state + +```python + | @property + | state() -> RuntimeStates +``` + +Get state of the agent's runtime. + +**Returns**: + +RuntimeStates + diff --git a/docs/api/components/base.md b/docs/api/components/base.md index cb767491b8..53f1e4b718 100644 --- a/docs/api/components/base.md +++ b/docs/api/components/base.md @@ -16,7 +16,7 @@ Abstract class for an agent component. #### `__`init`__` ```python - | __init__(configuration: Optional[ComponentConfiguration] = None, is_vendor: bool = False) + | __init__(configuration: Optional[ComponentConfiguration] = None, is_vendor: bool = False, **kwargs) ``` Initialize a package. diff --git a/docs/api/connections/base.md b/docs/api/connections/base.md index 2eed51ed94..704f128a97 100644 --- a/docs/api/connections/base.md +++ b/docs/api/connections/base.md @@ -3,23 +3,14 @@ The base connection package. - -## ConnectionStatus Objects + +## ConnectionStates Objects ```python -class ConnectionStatus() +class ConnectionStates(Enum) ``` -The connection status class. - - -#### `__`init`__` - -```python - | __init__() -``` - -Initialize the connection status. +Connection states enum. ## Connection Objects @@ -34,7 +25,7 @@ Abstract definition of a connection. #### `__`init`__` ```python - | __init__(configuration: ConnectionConfig, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None) + | __init__(configuration: ConnectionConfig, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs) ``` Initialize the connection. @@ -148,12 +139,12 @@ Get the ids of the protocols this connection is restricted to. Get the ids of the excluded protocols for this connection. - -#### connection`_`status + +#### state ```python | @property - | connection_status() -> ConnectionStatus + | state() -> ConnectionStates ``` Get the connection status. @@ -215,7 +206,7 @@ the received envelope, or None if an error occurred. ```python | @classmethod - | from_dir(cls, directory: str, identity: Identity, crypto_store: CryptoStore) -> "Connection" + | from_dir(cls, directory: str, identity: Identity, crypto_store: CryptoStore, **kwargs) -> "Connection" ``` Load the connection from a directory. @@ -235,7 +226,7 @@ the connection object. ```python | @classmethod - | from_config(cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore) -> "Connection" + | from_config(cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, **kwargs) -> "Connection" ``` Load a connection from a configuration. @@ -250,3 +241,23 @@ Load a connection from a configuration. an instance of the concrete connection class. + +#### is`_`connected + +```python + | @property + | is_connected() -> bool +``` + +Return is connected state. + + +#### is`_`disconnected + +```python + | @property + | is_disconnected() -> bool +``` + +Return is disconnected state. + diff --git a/docs/api/contracts/base.md b/docs/api/contracts/base.md index 0620975a70..b7d4bbd02f 100644 --- a/docs/api/contracts/base.md +++ b/docs/api/contracts/base.md @@ -16,7 +16,7 @@ Abstract definition of a contract. #### `__`init`__` ```python - | __init__(contract_config: ContractConfig) + | __init__(contract_config: ContractConfig, **kwargs) ``` Initialize the contract. @@ -70,7 +70,7 @@ the contract instance ```python | @classmethod - | from_dir(cls, directory: str) -> "Contract" + | from_dir(cls, directory: str, **kwargs) -> "Contract" ``` Load the protocol from a directory. @@ -88,7 +88,7 @@ the contract object. ```python | @classmethod - | from_config(cls, configuration: ContractConfig) -> "Contract" + | from_config(cls, configuration: ContractConfig, **kwargs) -> "Contract" ``` Load contract from configuration. @@ -101,3 +101,91 @@ Load contract from configuration. the contract object. + +#### get`_`deploy`_`transaction + +```python + | @classmethod + | get_deploy_transaction(cls, ledger_api: LedgerApi, **kwargs) -> bytes +``` + +Handler method for the 'GET_DEPLOY_TRANSACTION' requests. + +Implement this method in the sub class if you want +to handle the contract requests manually. + +**Arguments**: + +- `ledger_api`: the ledger apis. +- `kwargs`: keyword arguments. + +**Returns**: + +the bytes representing the state. + + +#### get`_`raw`_`transaction + +```python + | @classmethod + | get_raw_transaction(cls, ledger_api: LedgerApi, contract_address: str, **kwargs) -> bytes +``` + +Handler method for the 'GET_RAW_TRANSACTION' requests. + +Implement this method in the sub class if you want +to handle the contract requests manually. + +**Arguments**: + +- `ledger_api`: the ledger apis. +- `contract_address`: the contract address. + +**Returns**: + +the bytes representing the state. + + +#### get`_`raw`_`message + +```python + | @classmethod + | get_raw_message(cls, ledger_api: LedgerApi, contract_address: str, **kwargs) -> bytes +``` + +Handler method for the 'GET_RAW_MESSAGE' requests. + +Implement this method in the sub class if you want +to handle the contract requests manually. + +**Arguments**: + +- `ledger_api`: the ledger apis. +- `contract_address`: the contract address. + +**Returns**: + +the bytes representing the state. + + +#### get`_`state + +```python + | @classmethod + | get_state(cls, ledger_api: LedgerApi, contract_address: str, **kwargs) -> bytes +``` + +Handler method for the 'GET_STATE' requests. + +Implement this method in the sub class if you want +to handle the contract requests manually. + +**Arguments**: + +- `ledger_api`: the ledger apis. +- `contract_address`: the contract address. + +**Returns**: + +the bytes representing the state. + diff --git a/docs/api/crypto/base.md b/docs/api/crypto/base.md index c3bf1ab83f..425d185000 100644 --- a/docs/api/crypto/base.md +++ b/docs/api/crypto/base.md @@ -291,6 +291,25 @@ Recover the addresses from the hash. the recovered addresses + +#### get`_`hash + +```python + | @staticmethod + | @abstractmethod + | get_hash(message: bytes) -> str +``` + +Get the hash of a message. + +**Arguments**: + +- `message`: the message to be hashed. + +**Returns**: + +the hash of the message. + ## LedgerApi Objects diff --git a/docs/api/crypto/cosmos.md b/docs/api/crypto/cosmos.md index 4c7c4d0fd2..6c514792fa 100644 --- a/docs/api/crypto/cosmos.md +++ b/docs/api/crypto/cosmos.md @@ -103,6 +103,46 @@ Sign a message in bytes string form. signature of the message in string form + +#### format`_`default`_`transaction + +```python + | @staticmethod + | format_default_transaction(transaction: Any, signature: str, base64_pbk: str) -> Any +``` + +Format default CosmosSDK transaction and add signature + +**Arguments**: + +- `transaction`: the transaction to be formatted +- `signature`: the transaction signature +- `base64_pbk`: the base64 formatted public key + +**Returns**: + +formatted transaction with signature + + +#### format`_`wasm`_`transaction + +```python + | @staticmethod + | format_wasm_transaction(transaction: Any, signature: str, base64_pbk: str) -> Any +``` + +Format CosmWasm transaction and add signature + +**Arguments**: + +- `transaction`: the transaction to be formatted +- `signature`: the transaction signature +- `base64_pbk`: the base64 formatted public key + +**Returns**: + +formatted transaction with signature + #### sign`_`transaction @@ -253,6 +293,24 @@ Recover the addresses from the hash. the recovered addresses + +#### get`_`hash + +```python + | @staticmethod + | get_hash(message: bytes) -> str +``` + +Get the hash of a message. + +**Arguments**: + +- `message`: the message to be hashed. + +**Returns**: + +the hash of the message. + ## CosmosApi Objects diff --git a/docs/api/crypto/ethereum.md b/docs/api/crypto/ethereum.md index 063fdfc06c..2d207b4353 100644 --- a/docs/api/crypto/ethereum.md +++ b/docs/api/crypto/ethereum.md @@ -253,6 +253,24 @@ Recover the addresses from the hash. the recovered addresses + +#### get`_`hash + +```python + | @staticmethod + | get_hash(message: bytes) -> str +``` + +Get the hash of a message. + +**Arguments**: + +- `message`: the message to be hashed. + +**Returns**: + +the hash of the message. + ## EthereumApi Objects diff --git a/docs/api/crypto/fetchai.md b/docs/api/crypto/fetchai.md index 4d0f70f128..ffa7427790 100644 --- a/docs/api/crypto/fetchai.md +++ b/docs/api/crypto/fetchai.md @@ -253,6 +253,24 @@ Recover the addresses from the hash. the recovered addresses + +#### get`_`hash + +```python + | @staticmethod + | get_hash(message: bytes) -> str +``` + +Get the hash of a message. + +**Arguments**: + +- `message`: the message to be hashed. + +**Returns**: + +the hash of the message. + ## FetchAIApi Objects diff --git a/docs/api/crypto/helpers.md b/docs/api/crypto/helpers.md index 1e7b80e7a8..ac2323abfb 100644 --- a/docs/api/crypto/helpers.md +++ b/docs/api/crypto/helpers.md @@ -3,6 +3,19 @@ Module wrapping the helpers of public and private key cryptography. + +#### verify`_`or`_`create`_`private`_`keys + +```python +verify_or_create_private_keys(aea_project_path: Path, exit_on_error: bool = True) -> AgentConfig +``` + +Verify or create private keys. + +**Arguments**: + +- `ctx`: Context + #### try`_`validate`_`private`_`key`_`path @@ -26,7 +39,7 @@ None #### create`_`private`_`key ```python -create_private_key(ledger_id: str, private_key_file: Optional[str] = None) -> None +create_private_key(ledger_id: str, private_key_file: str) -> None ``` Create a private key for the specified ledger identifier. @@ -34,6 +47,7 @@ Create a private key for the specified ledger identifier. **Arguments**: - `ledger_id`: the ledger identifier. +- `private_key_file`: the private key file. **Returns**: diff --git a/docs/api/crypto/ledger_apis.md b/docs/api/crypto/ledger_apis.md index 8604498216..92b1960b1e 100644 --- a/docs/api/crypto/ledger_apis.md +++ b/docs/api/crypto/ledger_apis.md @@ -12,83 +12,32 @@ class LedgerApis() Store all the ledger apis we initialise. - -#### `__`init`__` - -```python - | __init__(ledger_api_configs: Dict[str, Dict[str, Union[str, int]]], default_ledger_id: str) -``` - -Instantiate a wallet object. - -**Arguments**: - -- `ledger_api_configs`: the ledger api configs. -- `default_ledger_id`: the default ledger id. - - -#### configs - -```python - | @property - | configs() -> Dict[str, Dict[str, Union[str, int]]] -``` - -Get the configs. - - -#### apis - -```python - | @property - | apis() -> Dict[str, LedgerApi] -``` - -Get the apis. - #### has`_`ledger ```python + | @staticmethod | has_ledger(identifier: str) -> bool ``` -Check if it has a . +Check if it has the api. #### get`_`api ```python - | get_api(identifier: str) -> LedgerApi + | @classmethod + | get_api(cls, identifier: str) -> LedgerApi ``` Get the ledger API. - -#### has`_`default`_`ledger - -```python - | @property - | has_default_ledger() -> bool -``` - -Check if it has the default ledger API. - - -#### default`_`ledger`_`id - -```python - | @property - | default_ledger_id() -> str -``` - -Get the default ledger id. - #### get`_`balance ```python - | get_balance(identifier: str, address: str) -> Optional[int] + | @classmethod + | get_balance(cls, identifier: str, address: str) -> Optional[int] ``` Get the token balance. @@ -106,7 +55,8 @@ the token balance #### get`_`transfer`_`transaction ```python - | get_transfer_transaction(identifier: str, sender_address: str, destination_address: str, amount: int, tx_fee: int, tx_nonce: str, **kwargs, ,) -> Optional[Any] + | @classmethod + | get_transfer_transaction(cls, identifier: str, sender_address: str, destination_address: str, amount: int, tx_fee: int, tx_nonce: str, **kwargs, ,) -> Optional[Any] ``` Get a transaction to transfer from self to destination. @@ -128,7 +78,8 @@ tx #### send`_`signed`_`transaction ```python - | send_signed_transaction(identifier: str, tx_signed: Any) -> Optional[str] + | @classmethod + | send_signed_transaction(cls, identifier: str, tx_signed: Any) -> Optional[str] ``` Send a signed transaction and wait for confirmation. @@ -146,7 +97,8 @@ the tx_digest, if present #### get`_`transaction`_`receipt ```python - | get_transaction_receipt(identifier: str, tx_digest: str) -> Optional[Any] + | @classmethod + | get_transaction_receipt(cls, identifier: str, tx_digest: str) -> Optional[Any] ``` Get the transaction receipt for a transaction digest. @@ -164,7 +116,8 @@ the tx receipt, if present #### get`_`transaction ```python - | get_transaction(identifier: str, tx_digest: str) -> Optional[Any] + | @classmethod + | get_transaction(cls, identifier: str, tx_digest: str) -> Optional[Any] ``` Get the transaction for a transaction digest. @@ -240,3 +193,43 @@ Generate a random str message. return the hash in hex. + +#### recover`_`message + +```python + | @staticmethod + | recover_message(identifier: str, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...] +``` + +Recover the addresses from the hash. + +**Arguments**: + +- `identifier`: ledger identifier. +- `message`: the message we expect +- `signature`: the transaction signature +- `is_deprecated_mode`: if the deprecated signing was used + +**Returns**: + +the recovered addresses + + +#### get`_`hash + +```python + | @staticmethod + | get_hash(identifier: str, message: bytes) -> str +``` + +Get the hash of a message. + +**Arguments**: + +- `identifier`: ledger identifier. +- `message`: the message to be hashed. + +**Returns**: + +the hash of the message. + diff --git a/docs/api/crypto/registries/base.md b/docs/api/crypto/registries/base.md index 5660e95c9d..e4b435a360 100644 --- a/docs/api/crypto/registries/base.md +++ b/docs/api/crypto/registries/base.md @@ -128,6 +128,19 @@ Instantiate an instance of the item object with appropriate arguments. an item + +#### get`_`class + +```python + | get_class() -> Type[ItemType] +``` + +Get the class of the item with class variables instantiated. + +**Returns**: + +an item class + ## Registry Objects @@ -203,6 +216,32 @@ the make can then find the identifier. the new item instance. + +#### make`_`cls + +```python + | make_cls(id_: Union[ItemId, str], module: Optional[str] = None) -> Type[ItemType] +``` + +Load a class of the associated type item id. + +**Arguments**: + +- `id_`: the id of the item class. Make sure it has been registered earlier +before calling this function. +- `module`: dotted path to a module. +whether a module should be loaded before creating the object. +this argument is useful when the item might not be registered +beforehand, and loading the specified module will make the registration. +E.g. suppose the call to 'register' for a custom object +is located in some_package/__init__.py. By providing module="some_package", +the call to 'register' in such module gets triggered and +the make can then find the identifier. + +**Returns**: + +the new item class. + #### has`_`spec diff --git a/docs/api/crypto/wallet.md b/docs/api/crypto/wallet.md index 0452d4e982..37dc2b21d4 100644 --- a/docs/api/crypto/wallet.md +++ b/docs/api/crypto/wallet.md @@ -56,6 +56,16 @@ Get the crypto objects (key pair). Get the crypto addresses. + +#### private`_`keys + +```python + | @property + | private_keys() -> Dict[str, str] +``` + +Get the crypto addresses. + ## Wallet Objects @@ -114,6 +124,16 @@ Get the crypto objects (key pair). Get the crypto addresses. + +#### private`_`keys + +```python + | @property + | private_keys() -> Dict[str, str] +``` + +Get the crypto addresses. + #### main`_`cryptos diff --git a/docs/api/helpers/async_utils.md b/docs/api/helpers/async_utils.md index 426be9b2e3..313cfdf4f7 100644 --- a/docs/api/helpers/async_utils.md +++ b/docs/api/helpers/async_utils.md @@ -25,7 +25,7 @@ Awaitable state. #### `__`init`__` ```python - | __init__(initial_state: Any = None) + | __init__(initial_state: Any = None, states_enum: Optional[Container[Any]] = None) ``` Init async state. @@ -33,35 +33,33 @@ Init async state. **Arguments**: - `initial_state`: state to set on start. +- `states_enum`: container of valid states if not provided state not checked on set. - -#### state + +#### set ```python - | @property - | state() -> Any + | set(state: Any) -> None ``` -Return current state. +Set state. - -#### state + +#### add`_`callback ```python - | @state.setter - | state(state: Any) -> None + | add_callback(callback_fn: Callable[[Any], None]) -> None ``` -Set state. +Add callback to track state changes. - -#### set +**Arguments**: -```python - | set(state: Any) -> None -``` +- `callback_fn`: callable object to be called on state changed. -Set state. +**Returns**: + +None #### get @@ -199,17 +197,6 @@ Wait for coroutine execution result. Cancel coroutine task execution in a target loop. - -#### future`_`cancel - -```python - | future_cancel() -> None -``` - -Cancel task waiting future. - -In this case future result will raise CanclledError not waiting for real task exit. - #### done diff --git a/docs/api/helpers/dialogue/base.md b/docs/api/helpers/dialogue/base.md index 3d920980b7..2ddad349bd 100644 --- a/docs/api/helpers/dialogue/base.md +++ b/docs/api/helpers/dialogue/base.md @@ -123,6 +123,15 @@ Return the JSON representation. Get dialogue label from json. + +#### get`_`incomplete`_`version + +```python + | get_incomplete_version() -> "DialogueLabel" +``` + +Get the incomplete version of the label. + #### `__`str`__` @@ -132,6 +141,16 @@ Get dialogue label from json. Get the string representation. + +#### from`_`str + +```python + | @classmethod + | from_str(cls, obj: str) -> "DialogueLabel" +``` + +Get the dialogue label from string representation. + ## Dialogue Objects @@ -298,6 +317,34 @@ Get the dialogue label. The dialogue label + +#### incomplete`_`dialogue`_`label + +```python + | @property + | incomplete_dialogue_label() -> DialogueLabel +``` + +Get the dialogue label. + +**Returns**: + +The incomplete dialogue label + + +#### dialogue`_`labels + +```python + | @property + | dialogue_labels() -> Set[DialogueLabel] +``` + +Get the dialogue labels (incomplete and complete, if it exists) + +**Returns**: + +the dialogue labels + #### agent`_`address @@ -464,7 +511,7 @@ True if empty, False otherwise | update(message: Message) -> bool ``` -Extend the list of incoming/outgoing messages with 'message', if 'message' is valid. +Extend the list of incoming/outgoing messages with 'message', if 'message' belongs to dialogue and is valid. **Arguments**: @@ -474,6 +521,40 @@ Extend the list of incoming/outgoing messages with 'message', if 'message' is va True if message successfully added, false otherwise + +#### ensure`_`counterparty + +```python + | ensure_counterparty(message: Message) -> None +``` + +Ensure the counterparty is set (set if not) correctly. + +**Arguments**: + +- `message`: a message + +**Returns**: + +None + + +#### is`_`belonging`_`to`_`dialogue + +```python + | is_belonging_to_dialogue(message: Message) -> bool +``` + +Check if the message is belonging to the dialogue. + +**Arguments**: + +- `message`: the message + +**Returns**: + +Ture if message is part of the dialogue, False otherwise + #### reply @@ -753,6 +834,20 @@ Retrieve the dialogue 'message' belongs to. the dialogue, or None in case such a dialogue does not exist + +#### get`_`latest`_`label + +```python + | get_latest_label(dialogue_label: DialogueLabel) -> DialogueLabel +``` + +Retrieve the latest dialogue label if present otherwise return same label. + +**Arguments**: + +- `dialogue_label`: the dialogue label +:return dialogue_label: the dialogue label + #### get`_`dialogue`_`from`_`label diff --git a/docs/api/helpers/test_cases.md b/docs/api/helpers/test_cases.md index 8a5c5f4d0a..cfbed4fda6 100644 --- a/docs/api/helpers/test_cases.md +++ b/docs/api/helpers/test_cases.md @@ -37,10 +37,11 @@ Unset the current agent context. ```python | @classmethod - | set_config(cls, dotted_path: str, value: Any, type_: str = "str") -> None + | set_config(cls, dotted_path: str, value: Any, type_: str = "str") -> Result ``` Set a config. + Run from agent's directory. **Arguments**: @@ -51,7 +52,7 @@ Run from agent's directory. **Returns**: -None +Result #### force`_`set`_`config @@ -68,10 +69,11 @@ Force set config. ```python | @classmethod - | disable_aea_logging(cls) + | disable_aea_logging(cls) -> None ``` Disable AEA logging of specific agent. + Run from agent's directory. **Returns**: @@ -83,7 +85,7 @@ None ```python | @classmethod - | run_cli_command(cls, *args: str, *, cwd: str = ".") -> None + | run_cli_command(cls, *args: str, *, cwd: str = ".") -> Result ``` Run AEA CLI command. @@ -99,7 +101,7 @@ Run AEA CLI command. **Returns**: -None +Result #### start`_`subprocess @@ -124,7 +126,7 @@ subprocess object. ```python | @classmethod - | start_thread(cls, target: Callable, **kwargs) -> None + | start_thread(cls, target: Callable, **kwargs) -> Thread ``` Start python Thread. @@ -221,6 +223,7 @@ None ``` Run agent as subprocess. + Run from agent's directory. **Arguments**: @@ -240,6 +243,7 @@ subprocess object. ``` Run interaction as subprocess. + Run from agent's directory. **Arguments**: @@ -259,6 +263,7 @@ subprocess object. ``` Terminate agent subprocesses. + Run from agent's directory. **Arguments**: @@ -275,7 +280,7 @@ Run from agent's directory. | is_successfully_terminated(cls, *subprocesses: subprocess.Popen) ``` -Check if all subprocesses terminated successfully +Check if all subprocesses terminated successfully. #### initialize`_`aea @@ -296,10 +301,11 @@ None ```python | @classmethod - | add_item(cls, item_type: str, public_id: str, local: bool = True) -> None + | add_item(cls, item_type: str, public_id: str, local: bool = True) -> Result ``` Add an item to the agent. + Run from agent's directory. **Arguments**: @@ -310,17 +316,18 @@ Run from agent's directory. **Returns**: -None +Result #### scaffold`_`item ```python | @classmethod - | scaffold_item(cls, item_type: str, name: str) -> None + | scaffold_item(cls, item_type: str, name: str) -> Result ``` Scaffold an item for the agent. + Run from agent's directory. **Arguments**: @@ -330,17 +337,18 @@ Run from agent's directory. **Returns**: -None +Result #### fingerprint`_`item ```python | @classmethod - | fingerprint_item(cls, item_type: str, public_id: str) -> None + | fingerprint_item(cls, item_type: str, public_id: str) -> Result ``` -Scaffold an item for the agent. +Fingerprint an item for the agent. + Run from agent's directory. **Arguments**: @@ -350,17 +358,18 @@ Run from agent's directory. **Returns**: -None +Result #### eject`_`item ```python | @classmethod - | eject_item(cls, item_type: str, public_id: str) -> None + | eject_item(cls, item_type: str, public_id: str) -> Result ``` Eject an item in the agent. + Run from agent's directory. **Arguments**: @@ -377,44 +386,48 @@ None ```python | @classmethod - | run_install(cls) + | run_install(cls) -> Result ``` Execute AEA CLI install command. + Run from agent's directory. **Returns**: -None +Result #### generate`_`private`_`key ```python | @classmethod - | generate_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER) -> None + | generate_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_file: Optional[str] = None) -> Result ``` Generate AEA private key with CLI command. + Run from agent's directory. **Arguments**: - `ledger_api_id`: ledger API ID. +- `private_key_file`: the private key file. **Returns**: -None +Result #### add`_`private`_`key ```python | @classmethod - | add_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE, connection: bool = False) -> None + | add_private_key(cls, ledger_api_id: str = DEFAULT_LEDGER, private_key_filepath: str = DEFAULT_PRIVATE_KEY_FILE, connection: bool = False) -> Result ``` Add private key with CLI command. + Run from agent's directory. **Arguments**: @@ -425,7 +438,7 @@ Run from agent's directory. **Returns**: -None +Result #### replace`_`private`_`key`_`in`_`file @@ -452,10 +465,11 @@ None ```python | @classmethod - | generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> None + | generate_wealth(cls, ledger_api_id: str = DEFAULT_LEDGER) -> Result ``` Generate wealth with CLI command. + Run from agent's directory. **Arguments**: @@ -464,7 +478,7 @@ Run from agent's directory. **Returns**: -None +Result #### get`_`wealth @@ -475,6 +489,7 @@ None ``` Get wealth with CLI command. + Run from agent's directory. **Arguments**: @@ -551,6 +566,7 @@ Read an envelope from an agent, using the stub connection. ``` Check if strings are present in process output. + Read process stdout in thread and terminate when all strings are present or timeout expired. diff --git a/docs/api/helpers/transaction/base.md b/docs/api/helpers/transaction/base.md index 2fead6fa2b..d7f9b47d5f 100644 --- a/docs/api/helpers/transaction/base.md +++ b/docs/api/helpers/transaction/base.md @@ -431,10 +431,10 @@ Class to represent the terms of a multi-currency & multi-token ledger transactio #### `__`init`__` ```python - | __init__(ledger_id: str, sender_address: Address, counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], is_sender_payable_tx_fee: bool, nonce: str, fee_by_currency_id: Optional[Dict[str, int]] = None, **kwargs, ,) + | __init__(ledger_id: str, sender_address: Address, counterparty_address: Address, amount_by_currency_id: Dict[str, int], quantities_by_good_id: Dict[str, int], nonce: str, is_sender_payable_tx_fee: bool = True, fee_by_currency_id: Optional[Dict[str, int]] = None, is_strict: bool = False, **kwargs, ,) ``` -Instantiate terms. +Instantiate terms of a transaction. **Arguments**: @@ -446,6 +446,37 @@ Instantiate terms. - `is_sender_payable_tx_fee`: whether the sender or counterparty pays the tx fee. - `nonce`: nonce to be included in transaction to discriminate otherwise identical transactions. - `fee_by_currency_id`: the fee associated with the transaction. +- `is_strict`: whether or not terms must have quantities and amounts of opposite signs. + + +#### id + +```python + | @property + | id() -> str +``` + +Get hash of the terms. + + +#### sender`_`hash + +```python + | @property + | sender_hash() -> str +``` + +Get the sender hash. + + +#### counterparty`_`hash + +```python + | @property + | counterparty_hash() -> str +``` + +Get the sender hash. #### ledger`_`id @@ -497,6 +528,46 @@ Set the counterparty address. Get the amount by currency id. + +#### is`_`sender`_`payable`_`tx`_`fee + +```python + | @property + | is_sender_payable_tx_fee() -> bool +``` + +Bool indicating whether the tx fee is paid by sender or counterparty. + + +#### is`_`single`_`currency + +```python + | @property + | is_single_currency() -> bool +``` + +Check whether a single currency is used for payment. + + +#### is`_`empty`_`currency + +```python + | @property + | is_empty_currency() -> bool +``` + +Check whether a single currency is used for payment. + + +#### currency`_`id + +```python + | @property + | currency_id() -> str +``` + +Get the amount the sender must pay. + #### sender`_`payable`_`amount @@ -507,6 +578,16 @@ Get the amount by currency id. Get the amount the sender must pay. + +#### sender`_`payable`_`amount`_`incl`_`fee + +```python + | @property + | sender_payable_amount_incl_fee() -> int +``` + +Get the amount the sender must pay inclusive fee. + #### counterparty`_`payable`_`amount @@ -517,6 +598,16 @@ Get the amount the sender must pay. Get the amount the counterparty must pay. + +#### counterparty`_`payable`_`amount`_`incl`_`fee + +```python + | @property + | counterparty_payable_amount_incl_fee() -> int +``` + +Get the amount the counterparty must pay. + #### quantities`_`by`_`good`_`id @@ -527,15 +618,35 @@ Get the amount the counterparty must pay. Get the quantities by good id. - -#### is`_`sender`_`payable`_`tx`_`fee + +#### good`_`ids ```python | @property - | is_sender_payable_tx_fee() -> bool + | good_ids() -> List[str] ``` -Bool indicating whether the tx fee is paid by sender or counterparty. +Get the (ordered) good ids. + + +#### sender`_`supplied`_`quantities + +```python + | @property + | sender_supplied_quantities() -> List[int] +``` + +Get the (ordered) quantities supplied by the sender. + + +#### counterparty`_`supplied`_`quantities + +```python + | @property + | counterparty_supplied_quantities() -> List[int] +``` + +Get the (ordered) quantities supplied by the counterparty. #### nonce @@ -567,6 +678,26 @@ Check if fee is set. Get the fee. + +#### sender`_`fee + +```python + | @property + | sender_fee() -> int +``` + +Get the sender fee. + + +#### counterparty`_`fee + +```python + | @property + | counterparty_fee() -> int +``` + +Get the counterparty fee. + #### fee`_`by`_`currency`_`id @@ -587,6 +718,31 @@ Get fee by currency. Get the kwargs. + +#### get`_`hash + +```python + | @staticmethod + | get_hash(ledger_id: str, sender_address: str, counterparty_address: str, good_ids: List[str], sender_supplied_quantities: List[int], counterparty_supplied_quantities: List[int], sender_payable_amount: int, counterparty_payable_amount: int, nonce: str) -> str +``` + +Generate a hash from transaction information. + +**Arguments**: + +- `sender_addr`: the sender address +- `counterparty_addr`: the counterparty address +- `good_ids`: the list of good ids +- `sender_supplied_quantities`: the quantities supplied by the sender (must all be positive) +- `counterparty_supplied_quantities`: the quantities supplied by the counterparty (must all be positive) +- `sender_payable_amount`: the amount payable by the sender +- `counterparty_payable_amount`: the amount payable by the counterparty +- `tx_nonce`: the nonce of the transaction + +**Returns**: + +the hash + #### encode diff --git a/docs/api/multiplexer.md b/docs/api/multiplexer.md index 7d0c4bbc97..f07ab99ca0 100644 --- a/docs/api/multiplexer.md +++ b/docs/api/multiplexer.md @@ -3,6 +3,24 @@ Module for the multiplexer class and related classes. + +## ConnectionStatus Objects + +```python +class ConnectionStatus() +``` + +The connection status class. + + +#### `__`init`__` + +```python + | __init__() +``` + +Initialize the connection status. + ## AsyncMultiplexer Objects @@ -484,17 +502,18 @@ None #### put`_`message ```python - | put_message(message: Message, sender: Optional[Address] = None, context: Optional[EnvelopeContext] = None) -> None + | put_message(message: Message, context: Optional[EnvelopeContext] = None, sender: Optional[str] = None) -> None ``` Put a message in the outbox. This constructs an envelope with the input arguments. +"sender" is a deprecated kwarg and will be removed in the next version + **Arguments**: -- `sender`: the sender of the envelope (optional field only necessary when the non-default address is used for sending). -- `message`: the message. +- `message`: the message - `context`: the envelope context **Returns**: diff --git a/docs/api/protocols/base.md b/docs/api/protocols/base.md index ed15507645..57170e2faf 100644 --- a/docs/api/protocols/base.md +++ b/docs/api/protocols/base.md @@ -44,6 +44,78 @@ Initialize a Message object. - `body`: the dictionary of values to hold. - `kwargs`: any additional value to add to the body. It will overwrite the body values. + +#### has`_`sender + +```python + | @property + | has_sender() -> bool +``` + +Check if it has a sender. + + +#### sender + +```python + | @property + | sender() -> Address +``` + +Get the sender of the message in Address form. + +:return the address + + +#### sender + +```python + | @sender.setter + | sender(sender: Address) -> None +``` + +Set the sender of the message. + + +#### has`_`to + +```python + | @property + | has_to() -> bool +``` + +Check if it has a sender. + + +#### to + +```python + | @property + | to() -> Address +``` + +Get address of receiver. + + +#### to + +```python + | @to.setter + | to(to: Address) -> None +``` + +Set address of receiver. + + +#### has`_`counterparty + +```python + | @property + | has_counterparty() -> bool +``` + +Check if the counterparty is set. + #### counterparty @@ -390,7 +462,7 @@ It includes a serializer to encode/decode a message. #### `__`init`__` ```python - | __init__(configuration: ProtocolConfig, message_class: Type[Message]) + | __init__(configuration: ProtocolConfig, message_class: Type[Message], **kwargs) ``` Initialize the protocol manager. @@ -398,7 +470,7 @@ Initialize the protocol manager. **Arguments**: - `configuration`: the protocol configurations. -- `serializer`: the serializer. +- `message_class`: the message class. #### serializer @@ -415,7 +487,7 @@ Get the serializer. ```python | @classmethod - | from_dir(cls, directory: str) -> "Protocol" + | from_dir(cls, directory: str, **kwargs) -> "Protocol" ``` Load the protocol from a directory. @@ -433,7 +505,7 @@ the protocol object. ```python | @classmethod - | from_config(cls, configuration: ProtocolConfig) -> "Protocol" + | from_config(cls, configuration: ProtocolConfig, **kwargs) -> "Protocol" ``` Load the protocol from configuration. diff --git a/docs/api/registries/base.md b/docs/api/registries/base.md index 483ba5b224..9306db7d64 100644 --- a/docs/api/registries/base.md +++ b/docs/api/registries/base.md @@ -26,7 +26,7 @@ Initialize the registry. ```python | @abstractmethod - | register(item_id: ItemId, item: Item) -> None + | register(item_id: ItemId, item: Item, is_dynamically_added: bool = False) -> None ``` Register an item. @@ -35,6 +35,7 @@ Register an item. - `item_id`: the public id of the item. - `item`: the item. +- `is_dynamicall_added`: whether or not the item is dynamicall added. **Returns**: @@ -146,7 +147,7 @@ None #### register ```python - | register(component_id: ComponentId, component: Component) -> None + | register(component_id: ComponentId, component: Component, is_dynamically_added: bool = False) -> None ``` Register a component. @@ -155,6 +156,7 @@ Register a component. - `component_id`: the id of the component. - `component`: the component object. +- `is_dynamicall_added`: whether or not the item is dynamicall added. #### unregister @@ -264,7 +266,7 @@ None #### register ```python - | register(item_id: Tuple[SkillId, str], item: SkillComponentType) -> None + | register(item_id: Tuple[SkillId, str], item: SkillComponentType, is_dynamically_added: bool = False) -> None ``` Register a item. @@ -273,6 +275,7 @@ Register a item. - `item_id`: a pair (skill id, item name). - `item`: the item to register. +- `is_dynamicall_added`: whether or not the item is dynamicall added. **Returns**: @@ -393,7 +396,7 @@ None #### register ```python - | register(item_id: Tuple[SkillId, str], item: Handler) -> None + | register(item_id: Tuple[SkillId, str], item: Handler, is_dynamically_added: bool = False) -> None ``` Register a handler. @@ -402,6 +405,7 @@ Register a handler. - `item_id`: the item id. - `item`: the handler. +- `is_dynamicall_added`: whether or not the item is dynamicall added. **Returns**: diff --git a/docs/api/registries/filter.md b/docs/api/registries/filter.md index c61269dd13..8f8fcfbeda 100644 --- a/docs/api/registries/filter.md +++ b/docs/api/registries/filter.md @@ -50,7 +50,7 @@ Get decision maker (out) queue. #### get`_`active`_`handlers ```python - | get_active_handlers(protocol_id: PublicId, skill_id: Optional[SkillId]) -> List[Handler] + | get_active_handlers(protocol_id: PublicId, skill_id: Optional[SkillId] = None) -> List[Handler] ``` Get active handlers based on protocol id and optional skill id. diff --git a/docs/api/registries/resources.md b/docs/api/registries/resources.md index b92e9f4dc3..45b48633fc 100644 --- a/docs/api/registries/resources.md +++ b/docs/api/registries/resources.md @@ -281,23 +281,6 @@ Add a skill to the set of resources. None - -#### inject`_`contracts - -```python - | inject_contracts(skill: Skill) -> None -``` - -Inject contracts into a skill context. - -**Arguments**: - -- `skill`: a skill - -**Returns**: - -None - #### get`_`skill diff --git a/docs/api/runtime.md b/docs/api/runtime.md index c3e91ca60b..50b3f3f33b 100644 --- a/docs/api/runtime.md +++ b/docs/api/runtime.md @@ -90,6 +90,20 @@ Set event loop to be used. - `loop`: event loop to use. + +#### state + +```python + | @property + | state() -> RuntimeStates +``` + +Get runtime state. + +**Returns**: + +RuntimeStates + ## AsyncRuntime Objects diff --git a/docs/api/skills/base.md b/docs/api/skills/base.md index ceac293166..73e024a191 100644 --- a/docs/api/skills/base.md +++ b/docs/api/skills/base.md @@ -29,7 +29,7 @@ Initialize a skill context. ```python | @property - | logger() -> Union[Logger, LoggerAdapter] + | logger() -> Logger ``` Get the logger. @@ -98,7 +98,7 @@ Set the status of the skill (active/not active). ```python | @property - | new_behaviours() -> Queue + | new_behaviours() -> "Queue[Behaviour]" ``` Queue for the new behaviours. @@ -108,6 +108,21 @@ to request the registration of a behaviour. :return the queue of new behaviours. + +#### new`_`handlers + +```python + | @property + | new_handlers() -> "Queue[Handler]" +``` + +Queue for the new handlers. + +This queue can be used to send messages to the framework +to request the registration of a handler. + +:return the queue of new handlers. + #### agent`_`addresses @@ -218,16 +233,6 @@ Get handlers of the skill. Get behaviours of the skill. - -#### contracts - -```python - | @property - | contracts() -> SimpleNamespace -``` - -Get contracts the skill has access to. - #### namespace @@ -573,25 +578,6 @@ Initialize a skill. - `behaviours`: dictionary of behaviours. - `models`: dictionary of models. - -#### contracts - -```python - | @property - | contracts() -> Dict[str, Contract] -``` - -Get the contracts associated with the skill. - - -#### inject`_`contracts - -```python - | inject_contracts(contracts: Dict[str, Contract]) -> None -``` - -Add the contracts to the skill. - #### skill`_`context @@ -637,7 +623,7 @@ Get the handlers. ```python | @classmethod - | from_dir(cls, directory: str, agent_context: AgentContext) -> "Skill" + | from_dir(cls, directory: str, agent_context: AgentContext, **kwargs) -> "Skill" ``` Load the skill from a directory. @@ -656,7 +642,7 @@ the skill object. ```python | @property - | logger() -> Union[Logger, LoggerAdapter] + | logger() -> Logger ``` Get the logger. @@ -679,7 +665,7 @@ Set the logger. ```python | @classmethod - | from_config(cls, configuration: SkillConfig, agent_context: AgentContext) -> "Skill" + | from_config(cls, configuration: SkillConfig, agent_context: AgentContext, **kwargs) -> "Skill" ``` Load the skill from configuration. diff --git a/docs/api/skills/tasks.md b/docs/api/skills/tasks.md index df6d8c9bd9..908cb9976e 100644 --- a/docs/api/skills/tasks.md +++ b/docs/api/skills/tasks.md @@ -7,7 +7,7 @@ This module contains the classes for tasks. ## Task Objects ```python -class Task() +class Task(WithLogger) ``` This class implements an abstract task. @@ -70,11 +70,10 @@ Get the result. #### setup ```python - | @abstractmethod | setup() -> None ``` -Implement the behaviour setup. +Implement the task setup. **Returns**: @@ -98,11 +97,10 @@ None #### teardown ```python - | @abstractmethod | teardown() -> None ``` -Implement the behaviour teardown. +Implement the task teardown. **Returns**: @@ -137,7 +135,7 @@ A Task manager. #### `__`init`__` ```python - | __init__(nb_workers: int = 1, is_lazy_pool_start: bool = True) + | __init__(nb_workers: int = 1, is_lazy_pool_start: bool = True, logger: Optional[logging.Logger] = None) ``` Initialize the task manager. diff --git a/docs/aries-cloud-agent-demo.md b/docs/aries-cloud-agent-demo.md index 46b06c199a..22e49d79f4 100644 --- a/docs/aries-cloud-agent-demo.md +++ b/docs/aries-cloud-agent-demo.md @@ -24,7 +24,7 @@ The aim of this demo is to illustrate how AEAs can connect to ACAs, thus gaining activate aaca activate aaea - Note right of aaea: Shows identity + Note right of aaea: Shows p2p ID faea->>faca: Request status? faca->>faea: status @@ -54,8 +54,10 @@ Each AEA is connected to its corresponding ACA: **Alice_AEA** to **Alice_ACA** a The following lists the sequence of interactions between the four agents: * **Alice_AEA**: starts - * **Alice_AEA**: shows its identity in the terminal and waits for an `invitation` detail from **Faber_AEA**. + * **Alice_AEA**: shows its p2p address in the terminal and waits for an `invitation` detail from **Faber_AEA**. + * **Alice_AEA**: registers itself on the SOEF. * **Faber_AEA**: starts + * **Faber_AEA**: searches the SOEF and finds **Alice_AEA**. * **Faber_AEA**: tests its connection to **Faber_ACA**. * **Faber_ACA**: responds to **Faber_AEA**. * **Faber_AEA**: requests **Faber_ACA** to create an invitation. @@ -66,11 +68,11 @@ The following lists the sequence of interactions between the four agents: All messages from an AEA to an ACA are http requests (using `http_client` connection). -All messages from an AEA to another AEA utilise the `oef` communication network accessed via the `oef` connection. +All messages from an AEA to another AEA utilise the p2p communication network accessed via the `p2p_libp2p` connection. All messages initiated from an ACA to an AEA are webhooks (using `webhook` connection). -This is the extent of the demo, at this point. The rest of the interactions require an instance of the Indy ledger to run. This is what will be implemented next. +This is the extent of the demo at this point. The rest of the interactions require an instance of the Indy ledger to run. This is what will be implemented next. The rest of the interactions are broadly as follows: @@ -146,105 +148,49 @@ Again, make sure the above ports are unused and take note of the specific IP add Now you can create **Alice_AEA** and **Faber_AEA** in terminals 3 and 4 respectively. -There are two methods for creating each AEA, constructing it piece by piece, or fetching the whole agent project. +### Alice_AEA -### Alice_AEA -- Method 1: Construct the Agent - -In the third terminal, create **Alice_AEA** and move into its project folder: +In the third terminal, fetch **Alice_AEA** and move into its project folder: ``` bash -aea create aries_alice +aea fetch fetchai/aries_alice:0.7.0 cd aries_alice ``` -#### Add and Configure the Skill - -Add the `aries_alice` skill: - -``` bash -aea add skill fetchai/aries_alice:0.3.0 -``` - -You now need to configure this skill to ensure `admin_host` and `admin_port` values in the skill's configuration file `alice/vendor/fetchai/skills/aries_alice/skill.yaml` match with the values you noted above for **Alice_ACA**. - -You can use the framework's handy `config` CLI command to set these values: - -``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_host 127.0.0.1 -``` -``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_port 8031 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_port 8031 -``` - -#### Add and Configure the Connections - -Add `http_client`, `oef` and `webhook` connections: - -``` bash -aea add connection fetchai/http_client:0.5.0 -aea add connection fetchai/webhook:0.4.0 -aea add connection fetchai/oef:0.6.0 -``` - -You now need to configure the `webhook` connection. - -First is ensuring the value of `webhook_port` in `webhook` connection's configuration file `alice/vendor/fetchai/connections/webhook/connection.yaml` matches with what you used above for **Alice_ACA**. - -``` bash -aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 -``` - -Next, make sure the value of `webhook_url_path` is `/webhooks/topic/{topic}/`. - -``` bash -aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ -``` - -#### Configure Alice_AEA: - -Now you must ensure **Alice_AEA**'s default connection is `oef`. - -``` bash -aea config set agent.default_connection fetchai/oef:0.6.0 -``` - -### Alice_AEA -- Method 2: Fetch the Agent - -Alternatively, in the third terminal, fetch **Alice_AEA** and move into its project folder: +
Alternatively, create from scratch. +

+The following steps create **Alice_AEA** from scratch: ``` bash -aea fetch fetchai/aries_alice:0.6.0 +aea create aries_alice cd aries_alice +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/http_client:0.6.0 +aea add connection fetchai/webhook:0.5.0 +aea add skill fetchai/aries_alice:0.4.0 ``` +

+
-#### Configure the skill and connections: +#### Configure the `aries_alice` skill: -You need to configure the `aries_alice` skill of the AEA to ensure `admin_host` and `admin_port` values in the skill's configuration file `alice/vendor/fetchai/skills/aries_alice/skill.yaml` match with the values you noted above for **Alice_ACA**. +(configuration file: `alice/vendor/fetchai/skills/aries_alice/skill.yaml`) -You can use the framework's handy `config` CLI command to set these values: +Ensure `admin_host` and `admin_port` values match with the values you noted above for **Alice_ACA**. You can use the framework's handy `config` CLI command to set these values: ``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_host 127.0.0.1 +aea config set vendor.fetchai.skills.aries_alice.behaviours.alice.args.admin_host 127.0.0.1 ``` ``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_port 8031 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_port 8031 +aea config set --type int vendor.fetchai.skills.aries_alice.behaviours.alice.args.admin_port 8031 ``` -You now need to configure the `webhook` connection. +#### Configure the `webhook` connection: + +(configuration file: `alice/vendor/fetchai/connections/webhook/connection.yaml`). -First is ensuring the value of `webhook_port` in `webhook` connection's configuration file `alice/vendor/fetchai/connections/webhook/connection.yaml` matches with what you used above for **Alice_ACA**. +First ensure the value of `webhook_port` matches with what you used above for **Alice_ACA**. ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 @@ -258,7 +204,7 @@ aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webho ### Install the Dependencies and Run Alice_AEA: -After creating **Alice_AEA** using either of the methods above, you must install all the dependencies: +Now install all the dependencies: ``` bash aea install @@ -270,67 +216,52 @@ Finally run **Alice_AEA**: aea run ``` -You should see **Alice_AEA** running and showing its identity on the terminal. For example: - -``` bash -My address is: YrP7H2qdCb3VyPwpQa53o39cWCDHhVcjwCtJLes6HKWM8FpVK -``` - -Take note of this value. We will refer to this as **Alice_AEA's address**. +Once you see a message of the form `My libp2p addresses: ['SOME_ADDRESS']` take note of the address. We will refer to this as **Alice_AEA's p2p address**. -### Faber_AEA -- Method 1: Construct the Agent +### Faber_AEA -In the fourth terminal, create **Faber_AEA** and move into its project folder: +In the fourth terminal, fetch **Faber_AEA** and move into its project folder: ``` bash -aea create aries_faber +aea fetch fetchai/aries_faber:0.7.0 cd aries_faber ``` -#### Add and Configure the Skill: - -Add the `aries_faber` skill: +
Alternatively, create from scratch. +

+The following steps create **Faber_AEA** from scratch: ``` bash +aea create aries_faber +cd aries_faber +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/http_client:0.6.0 +aea add connection fetchai/webhook:0.5.0 aea add skill fetchai/aries_faber:0.3.0 ``` -You now need to configure this skill to ensure `admin_host` and `admin_port` values in the skill's configuration file `faber/vendor/fetchai/skills/aries_alice/skill.yaml` match with the values you noted above for **Faber_ACA**. +

+
-``` bash -aea config set vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_host 127.0.0.1 -``` +#### Configure the `aries_faber` skill: -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` +(configuration file: `faber/vendor/fetchai/skills/aries_alice/skill.yaml`) -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_port 8021 -``` +Ensure `admin_host` and `admin_port` values match with those you noted above for **Faber_ACA**. ``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_port 8021 +aea config set vendor.fetchai.skills.aries_faber.behaviours.faber.args.admin_host 127.0.0.1 ``` -Additionally, make sure that the value of `alice_id` matches **Alice_AEA's address** as displayed in the third terminal. - ``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.alice_id +aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.faber.args.admin_port 8021 ``` -#### Add and Configure the Connections: - -Add `http_client`, `oef` and `webhook` connections: +#### Configure the `webhook` connection: -``` bash -aea add connection fetchai/http_client:0.5.0 -aea add connection fetchai/webhook:0.4.0 -aea add connection fetchai/oef:0.6.0 -``` +(configuration file: `faber/vendor/fetchai/connections/webhook/connection.yaml`). -You now need to configure the `webhook` connection. - -First is ensuring the value of `webhook_port` in `webhook` connection's configuration file `faber/vendor/fetchai/connections/webhook/connection.yaml` matches with what you used above for **Faber_ACA**. +First, ensure the value of `webhook_port` matches with what you used above for **Faber_ACA**. ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 @@ -342,66 +273,26 @@ Next, make sure the value of `webhook_url_path` is `/webhooks/topic/{topic}/`. aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` -#### Configure Faber_AEA: - -Now you must ensure **Faber_AEA**'s default connection is `http_client`. - -``` bash -aea config set agent.default_connection fetchai/http_client:0.5.0 -``` +#### Configure the `p2p_libp2p` connection: -### Alice_AEA -- Method 2: Fetch the Agent +(configuration file: `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) -Alternatively, in the fourth terminal, fetch **Faber_AEA** and move into its project folder: +Replace the `config` section with the following (note the changes in the URIs): -``` bash -aea fetch fetchai/aries_faber:0.6.0 -cd aries_faber +``` yaml +config: + delegate_uri: 127.0.0.1:11001 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9001 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9001 ``` -#### Configure the skill and connections: - -You need to configure the `aries_faber` skill of the AEA to ensure `admin_host` and `admin_port` values in the skill's configuration file `faber/vendor/fetchai/skills/aries_alice/skill.yaml` match with the values you noted above for **Faber_ACA**. - -``` bash -aea config set vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_host 127.0.0.1 -``` - -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` - -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_port 8021 -``` - -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_port 8021 -``` - -Additionally, make sure that the value of `alice_id` matches **Alice_AEA's address** as displayed in the third terminal. - -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.alice_id -``` - -You now need to configure the `webhook` connection. - -First is ensuring the value of `webhook_port` in `webhook` connection's configuration file `faber/vendor/fetchai/connections/webhook/connection.yaml` matches with what you used above for **Faber_ACA**. - -``` bash -aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 -``` - -Next, make sure the value of `webhook_url_path` is `/webhooks/topic/{topic}/`. - -``` bash -aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ -``` +where `SOME_ADDRESS` is **Alice_AEA's p2p address** as displayed in the third terminal. ### Install the Dependencies and Run Faber_AEA: -After creating **Faber_AEA** using either of the methods above, you must install all the dependencies: +Now install all the dependencies: ``` bash aea install diff --git a/docs/assets/aries-demo-alice.png b/docs/assets/aries-demo-alice.png index fa6fc2c081..e2b431fd77 100644 Binary files a/docs/assets/aries-demo-alice.png and b/docs/assets/aries-demo-alice.png differ diff --git a/docs/assets/aries-demo-faber.png b/docs/assets/aries-demo-faber.png index 019c4e3d59..3fb6fa88e8 100644 Binary files a/docs/assets/aries-demo-faber.png and b/docs/assets/aries-demo-faber.png differ diff --git a/docs/build-aea-programmatically.md b/docs/build-aea-programmatically.md index 06693b080c..c77fd8ebd0 100644 --- a/docs/build-aea-programmatically.md +++ b/docs/build-aea-programmatically.md @@ -32,7 +32,7 @@ OUTPUT_FILE = "output_file" We need a private key to populate the AEA's wallet. ``` python # Create a private key - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) ``` ## Clearing the input and output files @@ -46,7 +46,7 @@ We will use the stub connection to pass envelopes in and out of the AEA. Ensure ``` ## Initialise the AEA -We use the `AEABuilder` to readily build an AEA. By default, the `AEABuilder` adds the `fetchai/default:0.3.0` protocol, the `fetchai/stub:0.6.0` connection and the `fetchai/error:0.3.0` skill. +We use the `AEABuilder` to readily build an AEA. By default, the `AEABuilder` adds the `fetchai/default:0.4.0` protocol, the `fetchai/stub:0.7.0` connection and the `fetchai/error:0.4.0` skill. ``` python # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added @@ -120,7 +120,7 @@ We use the input and output text files to send an envelope to our AEA and receiv ``` python # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = ( - "my_aea,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "my_aea,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) @@ -147,8 +147,8 @@ Finally stop our AEA and wait for it to finish ## Running the AEA If you now run this python script file, you should see this output: - input message: my_aea,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello - output message: other_agent,my_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello + input message: my_aea,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello + output message: other_agent,my_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello ## Entire code listing @@ -175,7 +175,7 @@ OUTPUT_FILE = "output_file" def run(): # Create a private key - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): @@ -235,7 +235,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = ( - "my_aea,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "my_aea,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) diff --git a/docs/car-park-skills.md b/docs/car-park-skills.md index 8d5dd97d52..690ecee025 100644 --- a/docs/car-park-skills.md +++ b/docs/car-park-skills.md @@ -55,7 +55,7 @@ Follow the Preliminaries and @@ -89,7 +89,7 @@ default_routing: Then, fetch the car data client AEA: ``` bash -aea fetch fetchai/car_data_buyer:0.8.0 +aea fetch fetchai/car_data_buyer:0.9.0 cd car_data_buyer aea install ``` @@ -101,19 +101,19 @@ The following steps create the car data client from scratch: ``` bash aea create car_data_buyer cd car_data_buyer -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/carpark_client:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/carpark_client:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `car_data_buyer/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

diff --git a/docs/cli-how-to.md b/docs/cli-how-to.md index c636992d9e..ea990b74ac 100644 --- a/docs/cli-how-to.md +++ b/docs/cli-how-to.md @@ -2,13 +2,13 @@ The command line interface is the easiest way to build an AEA. ## Installation -The following installs the AEA cli package. +The following installs the AEA CLI package. ``` bash pip install aea[cli] ``` -The following installs the entire AEA package including the cli. +The following installs the entire AEA package including the CLI. ``` bash pip install aea[all] @@ -24,6 +24,19 @@ pip install 'aea[all]' ``` respectively. +Be sure that the `bin` folder of your Python environment +is in the `PATH` variable. If so, you can execute the CLI tool as: +``` bash +aea +``` + +You might find useful the execution of the `aea.cli` package +as a script: +``` bash +python -m aea.cli +``` +which is just an alternative entry-point to the CLI tool. + ## Troubleshooting To ensure no cache is used run. diff --git a/docs/cli-vs-programmatic-aeas.md b/docs/cli-vs-programmatic-aeas.md index 52b39017f5..3dc1894262 100644 --- a/docs/cli-vs-programmatic-aeas.md +++ b/docs/cli-vs-programmatic-aeas.md @@ -28,7 +28,7 @@ If you want to create the weather station AEA step by step you can follow this g Fetch the weather station AEA with the following command : ``` bash -aea fetch fetchai/weather_station:0.8.0 +aea fetch fetchai/weather_station:0.9.0 cd weather_station ``` @@ -92,6 +92,7 @@ SOEF_PORT = 9002 ENTRY_PEER_ADDRESS = ( "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmAzvu5uNbcnD2qaqrkSULhJsc6GJUg3iikWerJkoD72pr" ) +COSMOS_PRIVATE_KEY_FILE_CONNECTION = "cosmos_connection_private_key.txt" ROOT_DIR = os.getcwd() logger = logging.getLogger("aea") @@ -100,22 +101,25 @@ logging.basicConfig(stream=sys.stdout, level=logging.INFO) def run(): # Create a private key - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE_CONNECTION) # Set up the wallet, identity and (empty) resources wallet = Wallet( private_key_paths={CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE}, - connection_private_key_paths={CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE}, + connection_private_key_paths={ + CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_CONNECTION + }, ) identity = Identity("my_aea", address=wallet.addresses.get(CosmosCrypto.identifier)) resources = Resources() # specify the default routing for some protocols default_routing = { - PublicId.from_str("fetchai/ledger_api:0.1.0"): LedgerConnection.connection_id, - PublicId.from_str("fetchai/oef_search:0.3.0"): SOEFConnection.connection_id, + PublicId.from_str("fetchai/ledger_api:0.2.0"): LedgerConnection.connection_id, + PublicId.from_str("fetchai/oef_search:0.4.0"): SOEFConnection.connection_id, } - default_connection = SOEFConnection.connection_id + default_connection = P2PLibp2pConnection.connection_id # create the AEA my_aea = AEA( @@ -180,13 +184,8 @@ def run(): api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, - delegate_uri="127.0.0.1:11001", - entry_peers=[ENTRY_PEER_ADDRESS], - local_uri="127.0.0.1:9001", - log_file="libp2p_node.log", - public_uri="127.0.0.1:9001", ) soef_connection = SOEFConnection(configuration=configuration, identity=identity) resources.add_connection(soef_connection) diff --git a/docs/config.md b/docs/config.md index ba85de6e24..4acab54abe 100644 --- a/docs/config.md +++ b/docs/config.md @@ -21,13 +21,13 @@ aea_version: '>=0.5.0, <0.6.0' # AEA framework version(s) compa fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/error:0.3.0 -default_connection: fetchai/oef:0.6.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). +- fetchai/error:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: cosmos # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false diff --git a/docs/connect-a-frontend.md b/docs/connect-a-frontend.md index 5659b9d3d3..b3e2f13086 100644 --- a/docs/connect-a-frontend.md +++ b/docs/connect-a-frontend.md @@ -3,7 +3,7 @@ This demo discusses the options we have to connect a front-end to the AEA. The f How to connect frontend to your AEA ## Case 1 -The first option we have is to create a `Connection` that will handle the incoming requests from the rest API. In this scenario, the rest API communicates with the AEA and requests are handled by the `HTTP Server` Connection package. The rest API should send CRUD requests to the `HTTP Server` Connection (`fetchai/http_server:0.5.0`) which translates these into Envelopes to be consumed by the correct skill. +The first option we have is to create a `Connection` that will handle the incoming requests from the rest API. In this scenario, the rest API communicates with the AEA and requests are handled by the `HTTP Server` Connection package. The rest API should send CRUD requests to the `HTTP Server` Connection (`fetchai/http_server:0.6.0`) which translates these into Envelopes to be consumed by the correct skill. ## Case 2 -The other option we have is to create a stand-alone `Multiplexer` with an `OEF` connection (`fetchai/oef:0.6.0`). In this scenario, the front-end needs to incorporate a Multiplexer with an `OEF` Connection. Then the [OEF communication node](../oef-ledger) can be used to send Envelopes from the AEA to the front-end. +The other option we have is to create a stand-alone `Multiplexer` with a `P2P` connection (`fetchai/p2p_libp2p:0.6.0`). In this scenario, the front-end needs to incorporate a Multiplexer with an `P2P` Connection. Then the [Agent Communication Network](../acn) can be used to send Envelopes from the AEA to the front-end. diff --git a/docs/erc1155-skills.md b/docs/erc1155-skills.md index 8f5d7132e2..4937da3012 100644 --- a/docs/erc1155-skills.md +++ b/docs/erc1155-skills.md @@ -19,14 +19,6 @@ with a one-step atomic swap functionality. That means the trade between the two

This demo serves demonstrative purposes only. Since the AEA deploying the contract also has the ability to mint tokens, in reality the transfer of tokens from the AEA signing the transaction is worthless.

-### Launch an OEF node -In a separate terminal, launch a local OEF node (for search and discovery). -``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` - -Keep it running for all the following demos. - ## Demo ### Create the deployer AEA @@ -34,7 +26,7 @@ Keep it running for all the following demos. Fetch the AEA that will deploy the contract. ``` bash -aea fetch fetchai/erc1155_deployer:0.9.0 +aea fetch fetchai/erc1155_deployer:0.10.0 cd erc1155_deployer aea install ``` @@ -47,18 +39,20 @@ Create the AEA that will deploy the contract. ``` bash aea create erc1155_deployer cd erc1155_deployer -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/erc1155_deploy:0.9.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/erc1155_deploy:0.10.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` Then update the agent config (`aea-config.yaml`) with the default routing: ``` yaml default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` And change the default ledger: @@ -76,12 +70,18 @@ aea generate-key ethereum aea add-key ethereum eth_private_key.txt ``` +And one for the P2P connection: +``` bash +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt --connection +``` + ### Create the client AEA In another terminal, fetch the AEA that will get some tokens from the deployer. ``` bash -aea fetch fetchai/erc1155_client:0.9.0 +aea fetch fetchai/erc1155_client:0.10.0 cd erc1155_client aea install ``` @@ -94,18 +94,20 @@ Create the AEA that will get some tokens from the deployer. ``` bash aea create erc1155_client cd erc1155_client -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/erc1155_client:0.8.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/erc1155_client:0.9.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` Then update the agent config (`aea-config.yaml`) with the default routing: ``` yaml default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` And change the default ledger: @@ -123,6 +125,12 @@ aea generate-key ethereum aea add-key ethereum eth_private_key.txt ``` +And one for the P2P connection: +``` bash +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt --connection +``` + ### Fund the AEAs To create some wealth for your AEAs for the Ethereum `ropsten` network. Note that this needs to be executed from each AEA folder: @@ -142,6 +150,14 @@ aea get-wealth ethereum

If no wealth appears after a while, then try funding the private key directly using a web faucet.

+ +## Update SOEF configs for both AEAs + +To update the SOEF config, run in each AEA project: +``` bash +aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum +``` + ## Run the AEAs First, run the deployer AEA. diff --git a/docs/generic-skills-step-by-step.md b/docs/generic-skills-step-by-step.md index dba6e941d1..a1106ac01b 100644 --- a/docs/generic-skills-step-by-step.md +++ b/docs/generic-skills-step-by-step.md @@ -41,16 +41,16 @@ Follow the
Preliminaries and MetaMask Faucet and reques Run both AEAs from their respective terminals. ``` bash -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea run ``` diff --git a/docs/generic-skills.md b/docs/generic-skills.md index 04f67b8662..30b1fa258c 100644 --- a/docs/generic-skills.md +++ b/docs/generic-skills.md @@ -59,7 +59,7 @@ Follow the Preliminaries and @@ -93,7 +93,7 @@ default_routing: Then, fetch the buyer AEA: ``` bash -aea fetch fetchai/generic_buyer:0.5.0 --alias my_buyer_aea +aea fetch fetchai/generic_buyer:0.6.0 --alias my_buyer_aea cd my_buyer_aea aea install ``` @@ -105,19 +105,19 @@ The following steps create the buyer from scratch: ``` bash aea create my_buyer_aea cd my_buyer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/generic_buyer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/generic_buyer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_buyer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

diff --git a/docs/gym-skill.md b/docs/gym-skill.md index 503784d838..ff193cbbdc 100644 --- a/docs/gym-skill.md +++ b/docs/gym-skill.md @@ -19,7 +19,7 @@ Follow the
Preliminaries and None: @@ -120,7 +117,7 @@ class HttpHandler(Handler): bodyy=json.dumps({"tom": {"type": "cat", "age": 10}}).encode("utf-8"), ) self.context.logger.info( - "[{}] responding with: {}".format(self.context.agent_name, http_response) + "responding with: {}".format(http_response) ) http_response.counterparty = http_msg.counterparty self.context.outbox.put_message(message=http_response) @@ -144,7 +141,7 @@ class HttpHandler(Handler): bodyy=b"", ) self.context.logger.info( - "[{}] responding with: {}".format(self.context.agent_name, http_response) + "responding with: {}".format(http_response) ) http_response.counterparty = http_msg.counterparty self.context.outbox.put_message(message=http_response) @@ -169,7 +166,7 @@ handlers: Finally, we run the fingerprinter: ``` bash -aea fingerprint skill fetchai/http_echo:0.3.0 +aea fingerprint skill fetchai/http_echo:0.4.0 ``` Note, you will have to replace the author name with your author handle. diff --git a/docs/index.md b/docs/index.md index 0fb637039d..0924c12825 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,7 +18,7 @@ Autonomous Economic Agents are digital entities that run complex dynamic decisio AEAs are not: -* just any agents: AEAs have an express purpose to generate economic value. +* just any agents: AEAs have an express purpose to generate economic value in a multi-stakeholder environment. * APIs or sensors which do not have agency. * smart contracts which do not display any proactiveness and are purely reactive to external requests (=contract calls). * artificial general intelligence (AGI): AEAs can have a very narrow goal directed focus involving some economic gain and implemented via simple conditional logic. diff --git a/docs/language-agnostic-definition.md b/docs/language-agnostic-definition.md index b362045fda..5cec1d2406 100644 --- a/docs/language-agnostic-definition.md +++ b/docs/language-agnostic-definition.md @@ -104,7 +104,7 @@ message DefaultMessage{
  • It is recommended that it processes `Envelopes` asynchronously. Note, the specification regarding the processing of messages does not impose any particular implementation choice/constraint; for example, the AEA can process envelopes either synchronously and asynchronously. However, due to the high level of activity that an AEA might be subject to, other AEAs expect a certain minimum level of responsiveness and reactivity of an AEA's implementation, especially in the case of many concurrent dialogues with other peers. That could imply the need for asynchronous programming to make the AEA's implementation scalable.
  • -
  • It MUST have an identity in the form of, at a minimum, an address derived from a public key and its associated private key. +
  • It MUST have an identity in the form of, at a minimum, an address derived from a public key and its associated private key (where the eliptic curve must be of type SECP256k1).
  • It SHOULD implement handling of errors using the `default` protocol. The protobuf schema is given above.
  • diff --git a/docs/logging.md b/docs/logging.md index 432b1397be..9c2ac79ffd 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -18,17 +18,17 @@ author: fetchai version: 0.1.0 description: '' license: Apache-2.0 -aea_version: 0.5.2 +aea_version: 0.5.3 fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: -- fetchai/error:0.3.0 -default_connection: fetchai/stub:0.6.0 +- fetchai/error:0.4.0 +default_connection: fetchai/stub:0.7.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/docs/ml-skills.md b/docs/ml-skills.md index 087f76a5c2..fb75872311 100644 --- a/docs/ml-skills.md +++ b/docs/ml-skills.md @@ -62,7 +62,7 @@ Follow the Preliminaries and @@ -96,7 +96,7 @@ default_routing: Then, fetch the model trainer AEA: ``` bash -aea fetch fetchai/ml_model_trainer:0.8.0 +aea fetch fetchai/ml_model_trainer:0.9.0 cd ml_model_trainer aea install ``` @@ -108,19 +108,19 @@ The following steps create the model trainer from scratch: ``` bash aea create ml_model_trainer cd ml_model_trainer -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/ml_train:0.7.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/ml_train:0.8.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea install ``` In `ml_model_trainer/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    diff --git a/docs/multiplexer-standalone.md b/docs/multiplexer-standalone.md index 664ba6b514..59b47e0971 100644 --- a/docs/multiplexer-standalone.md +++ b/docs/multiplexer-standalone.md @@ -60,7 +60,7 @@ We use the input and output text files to send an envelope to our agent and rece ``` python # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) @@ -155,7 +155,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) diff --git a/docs/oef-ledger.md b/docs/oef-ledger.md index cad6d88126..2efd676972 100644 --- a/docs/oef-ledger.md +++ b/docs/oef-ledger.md @@ -21,7 +21,7 @@ The latter will be decentralized over time. ### Agent Communication Network (ACN) -The agent communication network is a peer-to-peer communication network for agents. It allows agents, in particular AEAs, to send and receive envelopes between each other. +The agent communication network is a
    peer-to-peer communication network for agents. It allows agents, in particular AEAs, to send and receive envelopes between each other. The implementation builds on the open-source libp2p library. A distributed hash table is used by all participating peers to maintain a mapping between agents' cryptographic addresses and their network addresses. @@ -33,9 +33,7 @@ A `simple OEF search node` allows agents to search a For two agents to be able to find each other, at least one must register themselves and the other must query the `simple OEF search node` for it. Detailed documentation is provided `here`. -### Deprecated alternative (for local development only) - -
    Click here for a local development alternative. + ## Ledgers @@ -72,3 +94,5 @@ The Python version of the AEA Framework currently integrates with three ledgers: - [Fetch.ai ledger](https://docs.fetch.ai/ledger/) - [Ethereum ledger](https://ethereum.org/build/) - [Cosmos ledger](https://cosmos.network/sdk) + +However, the framework makes it straightforward for further ledgers to be added by any developer. diff --git a/docs/orm-integration.md b/docs/orm-integration.md index 2716ff6a84..2b2a7da3d6 100644 --- a/docs/orm-integration.md +++ b/docs/orm-integration.md @@ -59,7 +59,7 @@ A demo to run a scenario with a true ledger transaction on Fetch.ai `testnet` ne First, fetch the seller AEA, which will provide data: ``` bash -aea fetch fetchai/thermometer_aea:0.6.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.7.0 --alias my_thermometer_aea cd my_thermometer_aea aea install ``` @@ -71,19 +71,19 @@ The following steps create the seller from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    @@ -94,7 +94,7 @@ default_routing: In another terminal, fetch the AEA that will query the seller AEA. ``` bash -aea fetch fetchai/thermometer_client:0.6.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.7.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` @@ -106,19 +106,19 @@ The following steps create the car data client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_buyer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    @@ -214,7 +214,7 @@ aea install Before being able to modify a package we need to eject it from vendor: ``` bash -aea eject skill fetchai/thermometer:0.7.0 +aea eject skill fetchai/thermometer:0.8.0 ``` This will move the package to your `skills` directory and reset the version to `0.1.0` and the author to your author handle. diff --git a/docs/p2p-connection.md b/docs/p2p-connection.md index e216500117..fd2c5deeea 100644 --- a/docs/p2p-connection.md +++ b/docs/p2p-connection.md @@ -1,4 +1,4 @@ -The `fetchai/p2p_libp2p:0.5.0` connection allows AEAs to create a peer-to-peer communication network. In particular, the connection creates an overlay network which maps agents' public keys to IP addresses. +The `fetchai/p2p_libp2p:0.6.0` connection allows AEAs to create a peer-to-peer communication network. In particular, the connection creates an overlay network which maps agents' public keys to IP addresses. ## Local demo @@ -9,9 +9,9 @@ Create one AEA as follows: ``` bash aea create my_genesis_aea cd my_genesis_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 -aea run --connections fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea run --connections fetchai/p2p_libp2p:0.6.0 ``` ### Create and run another AEA @@ -21,8 +21,8 @@ Create a second AEA: ``` bash aea create my_other_aea cd my_other_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` Provide the AEA with the information it needs to find the genesis by replacing the following block in `vendor/fetchai/connnections/p2p_libp2p/connection.yaml`: @@ -40,7 +40,7 @@ Here `MULTI_ADDRESSES` needs to be replaced with the list of multi addresses dis Run the AEA: ``` bash -aea run --connections fetchai/p2p_libp2p:0.5.0 +aea run --connections fetchai/p2p_libp2p:0.6.0 ``` You can inspect the `libp2p_node.log` log files of the AEA to see how they discover each other. @@ -48,78 +48,7 @@ You can inspect the `libp2p_node.log` log files of the AEA to see how they disco ## Local demo with skills -### Fetch the weather station and client - -Create one AEA as follows: - -``` bash -aea fetch fetchai/weather_station:0.8.0 -aea fetch fetchai/weather_client:0.8.0 -``` - -Then enter each project individually and execute the following to add the `p2p_libp2p` connection: -``` bash -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 -``` - -Then extend the `aea-config.yaml` of each project as follows: -``` yaml -default_routing: - ? "fetchai/oef_search:0.3.0" - : "fetchai/oef:0.6.0" -``` -### Run OEF - -Run the oef for search and discovery: -``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` - -### Run weather station - -Run the weather station first: -``` bash -aea run --connections "fetchai/p2p_libp2p:0.5.0,fetchai/oef:0.6.0" -``` -The weather station will form the genesis node. Wait until you see the lines: -``` bash -My libp2p addresses: ... -``` -Take note of these as the genesis' `MULTI_ADDRESSES = ["{addr1}", "{addr2}"]`. - -### Generate wealth for the weather client AEA - -The weather client needs to have some wealth to purchase the weather station information. - -First, create the private key for the weather client AEA based on the network you want to transact. To generate and add a private-public key pair for Fetch.ai use: -``` bash -aea generate-key fetchai -aea add-key fetchai fet_private_key.txt -``` - -Then, create some wealth for your weather client based on the network you want to transact with. On the Fetch.ai `testnet` network: -``` bash -aea generate-wealth fetchai -``` - -### Run the weather client - -Provide the weather client AEA with the information it needs to find the genesis by adding the following block to `vendor/fetchai/connnections/p2p_libp2p/connection.yaml`: -``` yaml -config: - delegate_uri: 127.0.0.1:11001 - entry_peers: MULTI_ADDRESSES - local_uri: 127.0.0.1:9001 - log_file: libp2p_node.log - public_uri: 127.0.0.1:9001 -``` -Here `MULTI_ADDRESSES` needs to be replaced with the list of multi addresses displayed in the log output of the weather station AEA. - -Now run the weather client: -``` bash -aea run --connections "fetchai/p2p_libp2p:0.5.0,fetchai/oef:0.6.0" -``` +Explore the demo section for further examples. ## Deployed agent communication network diff --git a/docs/package-imports.md b/docs/package-imports.md index 09b25cd1a5..63b8e02088 100644 --- a/docs/package-imports.md +++ b/docs/package-imports.md @@ -47,7 +47,7 @@ The `aea-config.yaml` is the top level configuration file of an AEA. It defines For the AEA to use a package, the `public_id` for the package must be listed in the `aea-config.yaml` file, e.g. ``` yaml connections: -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 ``` The above shows a part of the `aea-config.yaml`. If you see the connections, you will see that we follow a pattern of `author/name_package:version` to identify each package, also referred to as `public_id`. Here the `author` is the author of the package. diff --git a/docs/protocol.md b/docs/protocol.md index 8fa534845a..5191af75d2 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -66,9 +66,9 @@ The developer can generate custom protocols with the generic skills step by step guide. +For examples of the usage of the `fetchai/fipa:0.5.0` protocol check out the generic skills step by step guide. ### Fipa dialogue diff --git a/docs/questions-and-answers.md b/docs/questions-and-answers.md index 7dd4842d34..ddb0638b96 100644 --- a/docs/questions-and-answers.md +++ b/docs/questions-and-answers.md @@ -72,7 +72,7 @@ You can find more details about the CLI commands here
    When a new AEA is created, is the `vendor` folder populated with some default packages? -All AEA projects by default hold the `fetchai/stub:0.6.0` connection, the `fetchai/default:0.3.0` protocol and the `fetchai/error:0.3.0` skill. These (as all other packages installed from the registry) are placed in the vendor's folder. +All AEA projects by default hold the `fetchai/stub:0.7.0` connection, the `fetchai/default:0.4.0` protocol and the `fetchai/error:0.4.0` skill. These (as all other packages installed from the registry) are placed in the vendor's folder.

    You can find more details about the file structure
    here
    @@ -86,3 +86,7 @@ By default, envelopes of a given protocol get routed to all skills which have a The `URI` in the `EnvelopeContext` can be used to route envelopes of a given protocol to a specific skill. The `URI` path needs to be set to the skill's `public_id.to_uri_path`. + +
    Why does the AEA framework use its own package registry? +AEA packages could be described as personalized plugins for the AEA runtime. They are not like a library and therefore not suitable for distribution via PyPI. +
    diff --git a/docs/quickstart.md b/docs/quickstart.md index 5d2f9af968..d5e86fa734 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -5,6 +5,12 @@ you can use the Fetch.ai AEA framework. This example will take you through the simplest AEA in order to make you familiar with the framework. +## System Requirements + +The AEA framework can be used on `Windows`, `Ubuntu/Debian` and `MacOS`. + +You need Python 3.6 or higher as well as Go 1.14.2 or higher installed. + ## Preliminaries Create and enter into a new working directory. @@ -30,30 +36,6 @@ Once installed, create a new environment and open it (here we use Python 3.7 but touch Pipfile && pipenv --python 3.7 && pipenv shell ``` -### Installing docker - -
    -

    Note

    -

    For the purpose of the quickstart only, you can skip installation of docker.

    -
    - -At some point, you will need [Docker](https://www.docker.com/) installed on your machine -(e.g. to run an [OEF search and communication node](../oef-ledger)). - -### Download the scripts and examples directories - -
    -

    Note

    -

    For the purpose of the quickstart only, you can skip downloading the scripts and examples directories.

    -
    - -Download folders containing examples and scripts: -``` bash -svn export https://github.com/fetchai/agents-aea.git/trunk/examples -svn export https://github.com/fetchai/agents-aea.git/trunk/scripts -``` -You can install the `svn` command with (`brew install subversion` or `sudo apt-get install subversion`). - ## Installation The following installs the entire AEA package which also includes a [command-line interface (CLI)](../cli-commands). @@ -104,7 +86,7 @@ Confirm password: / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.5.2 +v0.5.3 AEA configurations successfully initialized: {'author': 'fetchai'} ``` @@ -121,7 +103,7 @@ The echo skill is a simple demo that introduces you to the main business logic c If you want to follow a step by step guide we show you how to do it at the end of the file. ``` bash -aea fetch fetchai/my_first_aea:0.7.0 +aea fetch fetchai/my_first_aea:0.8.0 cd my_first_aea ``` @@ -141,9 +123,9 @@ cd my_first_aea
    Second, add the echo skill to the project. ``` bash -aea add skill fetchai/echo:0.3.0 +aea add skill fetchai/echo:0.4.0 ``` -This copies the `fetchai/echo:0.3.0` skill code containing the "behaviours", and "handlers" into the project, ready to run. The identifier of the skill `fetchai/echo:0.3.0` consists of the name of the author of the skill, followed by the skill name and its version. +This copies the `fetchai/echo:0.4.0` skill code containing the "behaviours", and "handlers" into the project, ready to run. The identifier of the skill `fetchai/echo:0.4.0` consists of the name of the author of the skill, followed by the skill name and its version. ## Usage of the stub connection @@ -165,12 +147,12 @@ TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, For example: ``` bash -recipient_aea,sender_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello, +recipient_aea,sender_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello, ``` ## Run the AEA -Run the AEA with the default `fetchai/stub:0.6.0` connection. +Run the AEA with the default `fetchai/stub:0.7.0` connection. ``` bash aea run @@ -179,7 +161,7 @@ aea run or ``` bash -aea run --connections fetchai/stub:0.6.0 +aea run --connections fetchai/stub:0.7.0 ``` You will see the echo skill running in the terminal window. @@ -191,7 +173,7 @@ You will see the echo skill running in the terminal window. / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.5.2 +v0.5.3 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. @@ -212,7 +194,7 @@ Let's look at the `Handler` in more depth. From a different terminal and same directory, we send the AEA a message wrapped in an envelope via the input file. ``` bash -echo 'my_first_aea,sender_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello,' >> input_file +echo 'my_first_aea,sender_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello,' >> input_file ``` You will see the `Echo Handler` dealing with the envelope and responding with the same message to the `output_file`, and also decoding the Base64 encrypted message in this case. diff --git a/docs/simple-oef-usage.md b/docs/simple-oef-usage.md index 005d2322d7..6b94e5ab88 100644 --- a/docs/simple-oef-usage.md +++ b/docs/simple-oef-usage.md @@ -1,12 +1,12 @@ You can use the SOEF in the agent framework by using the SOEF connection as a package in your agent project. ## Add the SOEF package -Check out the CLI guide on details how to add a connection. You will want to add the `fetchai/soef:0.5.0` connection package. +Check out the CLI guide on details how to add a connection. You will want to add the `fetchai/soef:0.6.0` connection package. ## Register your agent and its services ### Register agent location -To register your agent's location, you have to send a message in the `fetchai/oef_search:0.3.0` protocol to the SOEF connection. +To register your agent's location, you have to send a message in the `fetchai/oef_search:0.4.0` protocol to the SOEF connection. First, define a data model for location data: ``` python diff --git a/docs/skill-guide.md b/docs/skill-guide.md index 80015abed6..3ad7e58088 100644 --- a/docs/skill-guide.md +++ b/docs/skill-guide.md @@ -70,7 +70,7 @@ class MySearchBehaviour(TickerBehaviour): :return: None """ self.context.logger.info( - "[{}]: setting up MySearchBehaviour".format(self.context.agent_name) + "setting up MySearchBehaviour" ) def act(self) -> None: @@ -89,8 +89,8 @@ class MySearchBehaviour(TickerBehaviour): query=self.query, ) self.context.logger.info( - "[{}]: sending search request to OEF search node, search_count={}".format( - self.context.agent_name, self.sent_search_count + "sending search request to OEF search node, search_count={}".format( + self.sent_search_count ) ) search_request.counterparty = self.context.search_service_address @@ -104,7 +104,7 @@ class MySearchBehaviour(TickerBehaviour): :return: None """ self.context.logger.info( - "[{}]: tearing down MySearchBehaviour".format(self.context.agent_name) + "tearing down MySearchBehaviour" ) ``` @@ -144,7 +144,7 @@ class MySearchHandler(Handler): def setup(self) -> None: """Set up the handler.""" self.context.logger.info( - "[{}]: setting up MySearchHandler".format(self.context.agent_name) + "setting up MySearchHandler" ) def handle(self, message: Message) -> None: @@ -215,13 +215,12 @@ class MySearchHandler(Handler): self.received_search_count += 1 nb_agents_found = len(oef_search_msg.agents) self.context.logger.info( - "[{}]: found number of agents={}, received search count={}".format( - self.context.agent_name, nb_agents_found, self.received_search_count + "found number of agents={}, received search count={}".format( + nb_agents_found, self.received_search_count ) ) self.context.logger.info( - "[{}]: number of search requests sent={} vs. number of search responses received={}".format( - self.context.agent_name, + "number of search requests sent={} vs. number of search responses received={}".format( self.context.behaviours.my_search_behaviour.sent_search_count, self.received_search_count, ) @@ -250,7 +249,7 @@ class MySearchHandler(Handler): :return: None """ self.context.logger.info( - "[{}]: tearing down MySearchHandler".format(self.context.agent_name) + "tearing down MySearchHandler" ) ``` @@ -341,7 +340,7 @@ fingerprint: {} fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: my_search_behaviour: @@ -385,23 +384,23 @@ Ensure, you use the correct author name to reference your skill (here we use `fe Our AEA does not have the oef protocol yet so let's add it. ``` bash -aea add protocol fetchai/oef_search:0.3.0 +aea add protocol fetchai/oef_search:0.4.0 ``` This adds the protocol to our AEA and makes it available on the path `packages.fetchai.protocols...`. We also need to add the soef and p2p connections and install the AEA's dependencies: ``` bash -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` Finally, in the `aea-config.yaml` add the following lines: ``` yaml default_routing: - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` This will ensure that search requests are processed by the correct connection. @@ -410,7 +409,7 @@ This will ensure that search requests are processed by the correct connection. In order to be able to find another AEA when searching, from a different terminal window, we fetch another finished AEA: ``` bash -aea fetch fetchai/simple_service_registration:0.8.0 && cd simple_service_registration +aea fetch fetchai/simple_service_registration:0.9.0 && cd simple_service_registration ``` This AEA will simply register a location service on the [SOEF search node](../simple-oef) so we can search for it. @@ -713,7 +712,7 @@ from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class OefSearchHandler(Handler): @@ -822,7 +821,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: service: diff --git a/docs/skill.md b/docs/skill.md index 2e61b90d6b..bb5214712a 100644 --- a/docs/skill.md +++ b/docs/skill.md @@ -57,6 +57,13 @@ A handler can be registered in one way: - By declaring it in the skill configuration file `skill.yaml` (see [below](#skill-config)) +It is possible to register new handlers dynamically by enqueuing new +`Handler` instances in the queue `context.new_handlers`, e.g. in a skill +component we can write: + +``` python +self.context.new_handlers.put(MyHandler(name="my_handler", skill_context=self.context)) +``` ### `behaviours.py` @@ -255,7 +262,7 @@ handlers: models: {} dependencies: {} protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 ``` @@ -268,7 +275,7 @@ All AEAs have a default `error` skill that contains error handling code for a nu * Envelopes with decoding errors * Invalid messages with respect to the registered protocol -The error skill relies on the `fetchai/default:0.3.0` protocol which provides error codes for the above. +The error skill relies on the `fetchai/default:0.4.0` protocol which provides error codes for the above.
    diff --git a/docs/standalone-transaction.md b/docs/standalone-transaction.md index 93025d0028..8f57ad2a70 100644 --- a/docs/standalone-transaction.md +++ b/docs/standalone-transaction.md @@ -7,7 +7,7 @@ import logging from aea.crypto.cosmos import CosmosCrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth -from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis +from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet @@ -40,14 +40,6 @@ Once we created the private keys we need to generate the wallets. wallet_2 = Wallet({CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_2}) ``` -## Create LedgerApis - -We need to create the LedgerApis object in order to be able to interact with the Fetch.ai `testnet` -``` python - # Set up the LedgerApis - ledger_apis = LedgerApis(DEFAULT_LEDGER_CONFIGS, CosmosCrypto.identifier) -``` - ## Generate wealth Since we want to send funds from `wallet_1` to `wallet_2`, we need to generate some wealth for the `wallet_1`. We can @@ -65,12 +57,12 @@ Finally, we create a transaction that sends the funds to the `wallet_2` ``` python # Create the transaction and send it to the ledger. - tx_nonce = ledger_apis.generate_tx_nonce( + tx_nonce = LedgerApis.generate_tx_nonce( CosmosCrypto.identifier, wallet_2.addresses.get(CosmosCrypto.identifier), wallet_1.addresses.get(CosmosCrypto.identifier), ) - transaction = ledger_apis.get_transfer_transaction( + transaction = LedgerApis.get_transfer_transaction( identifier=CosmosCrypto.identifier, sender_address=wallet_1.addresses.get(CosmosCrypto.identifier), destination_address=wallet_2.addresses.get(CosmosCrypto.identifier), @@ -79,7 +71,7 @@ Finally, we create a transaction that sends the funds to the `wallet_2` tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction(CosmosCrypto.identifier, transaction) - transaction_digest = ledger_apis.send_signed_transaction( + transaction_digest = LedgerApis.send_signed_transaction( CosmosCrypto.identifier, signed_transaction ) @@ -94,7 +86,7 @@ import logging from aea.crypto.cosmos import CosmosCrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth -from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis +from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet @@ -118,9 +110,6 @@ def run(): wallet_1 = Wallet({CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_1}) wallet_2 = Wallet({CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_2}) - # Set up the LedgerApis - ledger_apis = LedgerApis(DEFAULT_LEDGER_CONFIGS, CosmosCrypto.identifier) - # Generate some wealth try_generate_testnet_wealth( CosmosCrypto.identifier, wallet_1.addresses[CosmosCrypto.identifier] @@ -131,12 +120,12 @@ def run(): ) # Create the transaction and send it to the ledger. - tx_nonce = ledger_apis.generate_tx_nonce( + tx_nonce = LedgerApis.generate_tx_nonce( CosmosCrypto.identifier, wallet_2.addresses.get(CosmosCrypto.identifier), wallet_1.addresses.get(CosmosCrypto.identifier), ) - transaction = ledger_apis.get_transfer_transaction( + transaction = LedgerApis.get_transfer_transaction( identifier=CosmosCrypto.identifier, sender_address=wallet_1.addresses.get(CosmosCrypto.identifier), destination_address=wallet_2.addresses.get(CosmosCrypto.identifier), @@ -145,7 +134,7 @@ def run(): tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction(CosmosCrypto.identifier, transaction) - transaction_digest = ledger_apis.send_signed_transaction( + transaction_digest = LedgerApis.send_signed_transaction( CosmosCrypto.identifier, signed_transaction ) diff --git a/docs/tac-skills-contract.md b/docs/tac-skills-contract.md index 8f42eba869..c6ebeca19e 100644 --- a/docs/tac-skills-contract.md +++ b/docs/tac-skills-contract.md @@ -95,21 +95,13 @@ There is an equivalent diagram for seller AEAs set up to search for buyers and t Follow the Preliminaries and Installation sections from the AEA quick start. -### Launch an OEF search and communication node -In a separate terminal, launch a local [OEF search and communication node](../oef-ledger). -``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` - -Keep it running for the following demo. - ## Demo instructions: ### Create TAC controller AEA In the root directory, fetch the controller AEA: ``` bash -aea fetch fetchai/tac_controller_contract:0.6.0 +aea fetch fetchai/tac_controller_contract:0.7.0 cd tac_controller_contract aea install ``` @@ -121,10 +113,12 @@ The following steps create the controller from scratch: ``` bash aea create tac_controller_contract cd tac_controller_contract -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_control_contract:0.4.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_control_contract:0.5.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum ``` @@ -160,12 +154,12 @@ aea get-wealth ethereum In a separate terminal, in the root directory, fetch at least two participants: ``` bash -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_one cd tac_participant_one aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool cd .. -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_two cd tac_participant_two aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -184,11 +178,13 @@ aea create tac_participant_two Build participant one: ``` bash cd tac_participant_one -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -197,11 +193,13 @@ aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_con Then, build participant two: ``` bash cd tac_participant_two -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool diff --git a/docs/tac-skills.md b/docs/tac-skills.md index 3244989d6f..d13c2c3a52 100644 --- a/docs/tac-skills.md +++ b/docs/tac-skills.md @@ -94,21 +94,13 @@ There is an equivalent diagram for seller AEAs set up to search for buyers and t Follow the Preliminaries and Installation sections from the AEA quick start. -### Launch an OEF search and communication node -In a separate terminal, launch a local [OEF search and communication node](../oef-ledger). -``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` - -Keep it running for the following demo. - ## Demo instructions: ### Create TAC controller AEA In the root directory, fetch the controller AEA: ``` bash -aea fetch fetchai/tac_controller:0.5.0 +aea fetch fetchai/tac_controller:0.6.0 cd tac_controller aea install ``` @@ -120,11 +112,13 @@ The following steps create the controller from scratch: ``` bash aea create tac_controller cd tac_controller -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_control:0.3.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_control:0.4.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos ```

    @@ -134,8 +128,8 @@ aea config set agent.default_ledger ethereum In a separate terminal, in the root directory, fetch at least two participants: ``` bash -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_one -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_two cd tac_participant_two aea install ``` @@ -152,28 +146,41 @@ aea create tac_participant_two Build participant one: ``` bash cd tac_participant_one -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos ``` Then, build participant two: ``` bash cd tac_participant_two -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos ```

    +### Add keys for all AEAs + +Create the private key for the AEA for Fetch.ai `AgentLand`: +``` bash +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt +aea add-key cosmos cosmos_private_key.txt --connection +``` + ### Update the game parameters in the controller Navigate to the tac controller project, then use the command line to get and set the start time (set it to at least two minutes in the future): @@ -183,6 +190,38 @@ aea config get vendor.fetchai.skills.tac_control.models.parameters.args.start_ti aea config set vendor.fetchai.skills.tac_control.models.parameters.args.start_time '01 01 2020 00:01' ``` +### Update the connection params + +Briefly run the controller AEA: + +``` bash +aea run +``` + +Once you see a message of the form `My libp2p addresses: ['SOME_ADDRESS']` take note of the address. + +Then, update the configuration of the weather client AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: + +``` yaml +config: + delegate_uri: 127.0.0.1:11001 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9001 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9001 +``` + +``` yaml +config: + delegate_uri: 127.0.0.1:11002 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9002 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9002 +``` + +where `SOME_ADDRESS` is replaced accordingly. + ### Run the AEAs The CLI tool supports the launch of several agents @@ -259,7 +298,7 @@ models: class_name: Transactions args: pending_transaction_timeout: 30 -protocols: ['fetchai/oef_search:0.3.0', 'fetchai/fipa:0.4.0'] +protocols: ['fetchai/oef_search:0.4.0', 'fetchai/fipa:0.5.0'] ``` Above, you can see the registered `Behaviour` class name `GoodsRegisterAndSearchBehaviour` which implements register and search behaviour of an AEA for the `tac_negotiation` skill. diff --git a/docs/thermometer-skills.md b/docs/thermometer-skills.md index 50fa822b44..32fb841539 100644 --- a/docs/thermometer-skills.md +++ b/docs/thermometer-skills.md @@ -62,7 +62,7 @@ A demo to run the thermometer scenario with a true ledger transaction This demo First, fetch the thermometer AEA: ``` bash -aea fetch fetchai/thermometer_aea:0.6.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.7.0 --alias my_thermometer_aea cd thermometer_aea aea install ``` @@ -74,19 +74,19 @@ The following steps create the thermometer AEA from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    @@ -96,7 +96,7 @@ default_routing: Then, fetch the thermometer client AEA: ``` bash -aea fetch fetchai/thermometer_client:0.6.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.7.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` @@ -108,19 +108,19 @@ The following steps create the thermometer client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    diff --git a/docs/weather-skills.md b/docs/weather-skills.md index 01aaf5e7e8..bd3365837d 100644 --- a/docs/weather-skills.md +++ b/docs/weather-skills.md @@ -61,7 +61,7 @@ trusts the seller AEA to send the data upon successful payment. First, fetch the AEA that will provide weather measurements: ``` bash -aea fetch fetchai/weather_station:0.8.0 --alias my_weather_station +aea fetch fetchai/weather_station:0.9.0 --alias my_weather_station cd my_weather_station aea install ``` @@ -73,19 +73,19 @@ The following steps create the weather station from scratch: ``` bash aea create my_weather_station cd my_weather_station -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/weather_station:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/weather_station:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `weather_station/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    @@ -96,7 +96,7 @@ default_routing: In another terminal, fetch the AEA that will query the weather station: ``` bash -aea fetch fetchai/weather_client:0.8.0 --alias my_weather_client +aea fetch fetchai/weather_client:0.9.0 --alias my_weather_client cd my_weather_client aea install ``` @@ -108,19 +108,19 @@ The following steps create the weather client from scratch: ``` bash aea create my_weather_client cd my_weather_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/weather_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/weather_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` In `my_weather_client/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ```

    diff --git a/examples/gym_ex/gyms/env.py b/examples/gym_ex/gyms/env.py index c50fdc6d52..5d33566a20 100644 --- a/examples/gym_ex/gyms/env.py +++ b/examples/gym_ex/gyms/env.py @@ -70,7 +70,8 @@ def __init__( self.seed() # seed environment randomness - def reset(self) -> Observation: + @staticmethod + def reset() -> Observation: """ Reset the environment. diff --git a/examples/gym_ex/proxy/agent.py b/examples/gym_ex/proxy/agent.py index 7a0baffc74..6923d4a593 100644 --- a/examples/gym_ex/proxy/agent.py +++ b/examples/gym_ex/proxy/agent.py @@ -34,7 +34,9 @@ sys.modules["packages.fetchai.connections.gym"] = locate( "packages.fetchai.connections.gym" ) -from packages.fetchai.connections.gym.connection import GymConnection # noqa: E402 +from packages.fetchai.connections.gym.connection import ( # noqa: E402 # pylint: disable=wrong-import-position + GymConnection, +) ADDRESS = "some_address" diff --git a/examples/gym_ex/proxy/env.py b/examples/gym_ex/proxy/env.py index cc5bd97697..08c0e003ec 100755 --- a/examples/gym_ex/proxy/env.py +++ b/examples/gym_ex/proxy/env.py @@ -16,19 +16,19 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This contains the proxy gym environment.""" +import copy import sys import time from queue import Queue from threading import Thread -from typing import Any, Tuple, cast +from typing import Any, Optional, Tuple, cast import gym -from aea.configurations.base import PublicId from aea.helpers.base import locate +from aea.helpers.dialogue.base import Dialogue as BaseDialogue from aea.mail.base import Envelope from aea.protocols.base import Message @@ -36,9 +36,17 @@ "packages.fetchai.connections.gym" ) sys.modules["packages.fetchai.protocols.gym"] = locate("packages.fetchai.protocols.gym") -from packages.fetchai.protocols.gym.message import GymMessage # noqa: E402 +from packages.fetchai.protocols.gym.dialogues import ( # noqa: E402 # pylint: disable=wrong-import-position + GymDialogue as BaseGymDialogue, +) +from packages.fetchai.protocols.gym.dialogues import ( # noqa: E402 # pylint: disable=wrong-import-position + GymDialogues as BaseGymDialogues, +) +from packages.fetchai.protocols.gym.message import ( # noqa: E402 # pylint: disable=wrong-import-position + GymMessage, +) -from .agent import ProxyAgent # noqa: E402 +from .agent import ProxyAgent # noqa: E402 # pylint: disable=wrong-import-position Action = Any Observation = Any @@ -49,6 +57,23 @@ DEFAULT_GYM = "gym" +GymDialogue = BaseGymDialogue + +GymDialogues = BaseGymDialogues + + +@staticmethod +def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseGymDialogue.Role.AGENT + + +GymDialogues.role_from_first_message = role_from_first_message + class ProxyEnv(gym.Env): """This class implements a proxy gym environment.""" @@ -61,13 +86,22 @@ def __init__(self, gym_env: gym.Env) -> None: :return: None """ super().__init__() - self._queue = Queue() - self._action_counter = 0 + self._queue: Queue = Queue() + self._action_counter: int = 0 + self.gym_address = "fetchai/gym:0.5.0" self._agent = ProxyAgent( name="proxy", gym_env=gym_env, proxy_env_queue=self._queue ) self._agent_address = self._agent.identity.address self._agent_thread = Thread(target=self._agent.start) + self._active_dialogue = None # type: Optional[GymDialogue] + self.agent_address = "proxy" + self.gym_dialogues = GymDialogues(self.agent_address) + + @property + def active_dialogue(self) -> GymDialogue: + """Get the active dialogue.""" + return self._active_dialogue def step(self, action: Action) -> Feedback: """ @@ -105,7 +139,9 @@ def render(self, mode="human") -> None: :return: None """ # TODO: adapt this line to the new APIs. We no longer have a mailbox. - self._agent.mailbox._connection.channel.gym_env.render(mode) + self._agent.mailbox._connection.channel.gym_env.render( # pylint: disable=protected-access,no-member + mode + ) def reset(self) -> None: """ @@ -115,9 +151,20 @@ def reset(self) -> None: """ if not self._agent.multiplexer.is_connected: self._connect() - gym_msg = GymMessage(performative=GymMessage.Performative.RESET) - gym_msg.counterparty = DEFAULT_GYM - self._agent.outbox.put_message(message=gym_msg, sender=self._agent_address) + gym_msg = GymMessage( + dialogue_reference=self.gym_dialogues.new_self_initiated_dialogue_reference(), + performative=GymMessage.Performative.RESET, + ) + gym_msg.counterparty = self.gym_address + gym_dialogue = cast(Optional[GymDialogue], self.gym_dialogues.update(gym_msg)) + assert gym_dialogue is not None + self._active_dialogue = gym_dialogue + self._agent.outbox.put_message(message=gym_msg) + + # Wait (blocking!) for the response envelope from the environment + in_envelope = self._queue.get(block=True, timeout=None) # type: GymMessage + + self._decode_status(in_envelope) def close(self) -> None: """ @@ -125,9 +172,17 @@ def close(self) -> None: :return: None """ - gym_msg = GymMessage(performative=GymMessage.Performative.CLOSE) - gym_msg.counterparty = DEFAULT_GYM - self._agent.outbox.put_message(message=gym_msg, sender=self._agent_address) + last_msg = self.active_dialogue.last_message + assert last_msg is not None, "Cannot retrieve last message." + gym_msg = GymMessage( + dialogue_reference=self.active_dialogue.dialogue_label.dialogue_reference, + performative=GymMessage.Performative.CLOSE, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, + ) + gym_msg.counterparty = self.gym_address + assert self.active_dialogue.update(gym_msg) + self._agent.outbox.put_message(message=gym_msg) self._disconnect() @@ -160,16 +215,23 @@ def _encode_and_send_action(self, action: Action, step_id: int) -> None: :param step_id: the step id :return: an envelope """ + last_msg = self.active_dialogue.last_message + assert last_msg is not None, "Cannot retrieve last message." gym_msg = GymMessage( + dialogue_reference=self.active_dialogue.dialogue_label.dialogue_reference, performative=GymMessage.Performative.ACT, action=GymMessage.AnyObject(action), step_id=step_id, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, ) - gym_msg.counterparty = DEFAULT_GYM + gym_msg.counterparty = self.gym_address + assert self.active_dialogue.update(gym_msg) # Send the message via the proxy agent and to the environment - self._agent.outbox.put_message(message=gym_msg, sender=self._agent_address) + self._agent.outbox.put_message(message=gym_msg) + + def _decode_percept(self, envelope: Envelope, expected_step_id: int) -> GymMessage: - def _decode_percept(self, envelope: Envelope, expected_step_id: int) -> Message: """ Receive the response from the gym environment in the form of an envelope and decode it. @@ -179,8 +241,13 @@ def _decode_percept(self, envelope: Envelope, expected_step_id: int) -> Message: :return: a message received as a response to the action performed in apply_action. """ if envelope is not None: - if envelope.protocol_id == PublicId.from_str("fetchai/gym:0.3.0"): - gym_msg = envelope.message + if envelope.protocol_id == GymMessage.protocol_id: + orig_gym_msg = cast(GymMessage, envelope.message) + gym_msg = copy.copy(orig_gym_msg) + gym_msg.counterparty = orig_gym_msg.sender + gym_msg.is_incoming = True + if not self.active_dialogue.update(gym_msg): + raise ValueError("Could not udpate dialogue.") if ( gym_msg.performative == GymMessage.Performative.PERCEPT and gym_msg.step_id == expected_step_id @@ -197,7 +264,42 @@ def _decode_percept(self, envelope: Envelope, expected_step_id: int) -> Message: else: raise ValueError("Missing envelope.") - def _message_to_percept(self, message: Message) -> Feedback: + def _decode_status(self, envelope: Envelope) -> None: + + """ + Receive the response from the gym environment in the form of an envelope and decode it. + + The response is a STATUS message. + + :return: a message received as a response to the action performed in apply_action. + """ + if envelope is not None: + if envelope.protocol_id == GymMessage.protocol_id: + orig_gym_msg = cast(GymMessage, envelope.message) + gym_msg = copy.copy(orig_gym_msg) + gym_msg.counterparty = orig_gym_msg.sender + gym_msg.is_incoming = True + if not self.active_dialogue.update(gym_msg): + raise ValueError("Could not udpate dialogue.") + if ( + gym_msg.performative == GymMessage.Performative.STATUS + and gym_msg.content.get("reset", "failure") == "success" + ): + + return None + else: + raise ValueError( + "Unexpected performative or no step_id: {}".format( + gym_msg.performative + ) + ) + else: + raise ValueError("Unknown protocol_id: {}".format(envelope.protocol_id)) + else: + raise ValueError("Missing envelope.") + + @staticmethod + def _message_to_percept(message: GymMessage) -> Feedback: """ Transform the message received from the gym environment into observation, reward, done, info. diff --git a/examples/gym_ex/rl/agent.py b/examples/gym_ex/rl/agent.py index 2b2b108b1c..6517b1cc7e 100644 --- a/examples/gym_ex/rl/agent.py +++ b/examples/gym_ex/rl/agent.py @@ -38,7 +38,7 @@ Feedback = Tuple[Observation, Reward, Done, Info] -class PriceBandit(object): +class PriceBandit: """A class for a multi-armed bandit model of price.""" def __init__(self, price: float, beta_a: float = 1.0, beta_b: float = 1.0): @@ -73,7 +73,7 @@ def update(self, outcome: float) -> None: self.beta_b += 1 - outcome -class GoodPriceModel(object): +class GoodPriceModel: """A class for a price model of a good.""" def __init__(self, bound: int = 100): diff --git a/mkdocs.yml b/mkdocs.yml index dbedffa360..e15721d305 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,7 +82,10 @@ nav: - Ledger & Crypto APIs: 'ledger-integration.md' - Message routing: 'message-routing.md' - Configurations: 'config.md' + - Agent Communication: + - Agent Communication Network: 'acn.md' - Search & Discovery: + - Simple OEF: 'simple-oef.md' - Defining Data Models: 'defining-data-models.md' - The Query Language: 'query-language.md' - SOEF Connection: 'simple-oef-usage.md' @@ -182,7 +185,6 @@ nav: - Task: 'api/skills/tasks.md' - Test Tools: 'api/test_tools/generic.md' - Q&A: 'questions-and-answers.md' - - Simple OEF: 'simple-oef.md' plugins: - markdownmermaid diff --git a/packages/fetchai/agents/aries_alice/aea-config.yaml b/packages/fetchai/agents/aries_alice/aea-config.yaml index 10467e50f7..18f1ff8a97 100644 --- a/packages/fetchai/agents/aries_alice/aea-config.yaml +++ b/packages/fetchai/agents/aries_alice/aea-config.yaml @@ -1,29 +1,32 @@ agent_name: aries_alice author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA representing Alice in the Aries demo. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/http_client:0.5.0 -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 -- fetchai/webhook:0.4.0 +- fetchai/http_client:0.6.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 +- fetchai/webhook:0.5.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/http:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/http:0.4.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/aries_alice:0.3.0 -- fetchai/error:0.3.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/aries_alice:0.4.0 +- fetchai/error:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} registry_path: ../packages +default_routing: + fetchai/http:0.4.0: fetchai/http_client:0.6.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/aries_faber/aea-config.yaml b/packages/fetchai/agents/aries_faber/aea-config.yaml index 757e5cb0ae..cca32532ed 100644 --- a/packages/fetchai/agents/aries_faber/aea-config.yaml +++ b/packages/fetchai/agents/aries_faber/aea-config.yaml @@ -1,29 +1,32 @@ agent_name: aries_faber author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA representing Faber in the Aries demo. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/http_client:0.5.0 -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 -- fetchai/webhook:0.4.0 +- fetchai/http_client:0.6.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 +- fetchai/webhook:0.5.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/http:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/http:0.4.0 +- fetchai/oef_search:0.4.0 skills: - fetchai/aries_faber:0.3.0 -- fetchai/error:0.3.0 -default_connection: fetchai/http_client:0.5.0 +- fetchai/error:0.4.0 +default_connection: fetchai/http_client:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} registry_path: ../packages +default_routing: + fetchai/http:0.4.0: fetchai/http_client:0.6.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/car_data_buyer/aea-config.yaml b/packages/fetchai/agents/car_data_buyer/aea-config.yaml index 2a5ca6263e..47f59754cf 100644 --- a/packages/fetchai/agents/car_data_buyer/aea-config.yaml +++ b/packages/fetchai/agents/car_data_buyer/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: car_data_buyer author: fetchai -version: 0.8.0 +version: 0.9.0 description: An agent which searches for an instance of a `car_detector` agent and attempts to purchase car park data from it. license: Apache-2.0 @@ -8,21 +8,21 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/carpark_client:0.7.0 -- fetchai/error:0.3.0 -- fetchai/generic_buyer:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/carpark_client:0.8.0 +- fetchai/error:0.4.0 +- fetchai/generic_buyer:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -30,5 +30,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/car_detector/aea-config.yaml b/packages/fetchai/agents/car_detector/aea-config.yaml index dd1fd72304..e7e4dbd759 100644 --- a/packages/fetchai/agents/car_detector/aea-config.yaml +++ b/packages/fetchai/agents/car_detector/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: car_detector author: fetchai -version: 0.8.0 +version: 0.9.0 description: An agent which sells car park data to instances of `car_data_buyer` agents. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/carpark_detection:0.7.0 -- fetchai/error:0.3.0 -- fetchai/generic_seller:0.8.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/carpark_detection:0.8.0 +- fetchai/error:0.4.0 +- fetchai/generic_seller:0.9.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/erc1155_client/aea-config.yaml b/packages/fetchai/agents/erc1155_client/aea-config.yaml index 6b18e04889..c4e7551bcd 100644 --- a/packages/fetchai/agents/erc1155_client/aea-config.yaml +++ b/packages/fetchai/agents/erc1155_client/aea-config.yaml @@ -1,28 +1,29 @@ agent_name: erc1155_client author: fetchai -version: 0.9.0 +version: 0.10.0 description: An AEA to interact with the ERC1155 deployer AEA license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/contract_api:0.1.0 -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 -- fetchai/signing:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 +- fetchai/signing:0.2.0 skills: -- fetchai/erc1155_client:0.8.0 -- fetchai/error:0.3.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/erc1155_client:0.9.0 +- fetchai/error:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -30,5 +31,6 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/erc1155_deployer/aea-config.yaml b/packages/fetchai/agents/erc1155_deployer/aea-config.yaml index 2afcc6a916..884037bef2 100644 --- a/packages/fetchai/agents/erc1155_deployer/aea-config.yaml +++ b/packages/fetchai/agents/erc1155_deployer/aea-config.yaml @@ -1,28 +1,29 @@ agent_name: erc1155_deployer author: fetchai -version: 0.9.0 +version: 0.10.0 description: An AEA to deploy and interact with an ERC1155 license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/contract_api:0.1.0 -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 -- fetchai/signing:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 +- fetchai/signing:0.2.0 skills: -- fetchai/erc1155_deploy:0.9.0 -- fetchai/error:0.3.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/erc1155_deploy:0.10.0 +- fetchai/error:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -30,5 +31,6 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/generic_buyer/aea-config.yaml b/packages/fetchai/agents/generic_buyer/aea-config.yaml index d111db3c97..5412b48ae3 100644 --- a/packages/fetchai/agents/generic_buyer/aea-config.yaml +++ b/packages/fetchai/agents/generic_buyer/aea-config.yaml @@ -1,26 +1,26 @@ agent_name: generic_buyer author: fetchai -version: 0.5.0 +version: 0.6.0 description: The buyer AEA purchases the services offered by the seller AEA. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_buyer:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_buyer:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -28,5 +28,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/generic_seller/aea-config.yaml b/packages/fetchai/agents/generic_seller/aea-config.yaml index ab4e721b72..c2d29dd0a8 100644 --- a/packages/fetchai/agents/generic_seller/aea-config.yaml +++ b/packages/fetchai/agents/generic_seller/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: generic_seller author: fetchai -version: 0.5.0 +version: 0.6.0 description: The seller AEA sells the services specified in the `skill.yaml` file and delivers them upon payment to the buyer. license: Apache-2.0 @@ -8,20 +8,20 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_seller:0.8.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_seller:0.9.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/gym_aea/aea-config.yaml b/packages/fetchai/agents/gym_aea/aea-config.yaml index 551dc0861a..41f52f5da6 100644 --- a/packages/fetchai/agents/gym_aea/aea-config.yaml +++ b/packages/fetchai/agents/gym_aea/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: gym_aea author: fetchai -version: 0.6.0 +version: 0.7.0 description: The gym aea demos the interaction between a skill containing a RL agent and a gym connection. license: Apache-2.0 @@ -8,16 +8,16 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/gym:0.4.0 -- fetchai/stub:0.6.0 +- fetchai/gym:0.5.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/gym:0.3.0 -skills: -- fetchai/error:0.3.0 +- fetchai/default:0.4.0 - fetchai/gym:0.4.0 -default_connection: fetchai/gym:0.4.0 +skills: +- fetchai/error:0.4.0 +- fetchai/gym:0.5.0 +default_connection: fetchai/gym:0.5.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/packages/fetchai/agents/ml_data_provider/aea-config.yaml b/packages/fetchai/agents/ml_data_provider/aea-config.yaml index 13d383aca2..7e860c017c 100644 --- a/packages/fetchai/agents/ml_data_provider/aea-config.yaml +++ b/packages/fetchai/agents/ml_data_provider/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: ml_data_provider author: fetchai -version: 0.8.0 +version: 0.9.0 description: An agent that sells data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/ledger_api:0.1.0 -- fetchai/ml_trade:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/ledger_api:0.2.0 +- fetchai/ml_trade:0.4.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_seller:0.8.0 -- fetchai/ml_data_provider:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_seller:0.9.0 +- fetchai/ml_data_provider:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/ml_model_trainer/aea-config.yaml b/packages/fetchai/agents/ml_model_trainer/aea-config.yaml index b7fe653164..7ceb0bab21 100644 --- a/packages/fetchai/agents/ml_model_trainer/aea-config.yaml +++ b/packages/fetchai/agents/ml_model_trainer/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: ml_model_trainer author: fetchai -version: 0.8.0 +version: 0.9.0 description: An agent buying data and training a model from it. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/ledger_api:0.1.0 -- fetchai/ml_trade:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/ledger_api:0.2.0 +- fetchai/ml_trade:0.4.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_buyer:0.7.0 -- fetchai/ml_train:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_buyer:0.8.0 +- fetchai/ml_train:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/my_first_aea/aea-config.yaml b/packages/fetchai/agents/my_first_aea/aea-config.yaml index 9075fed729..4d48224cdf 100644 --- a/packages/fetchai/agents/my_first_aea/aea-config.yaml +++ b/packages/fetchai/agents/my_first_aea/aea-config.yaml @@ -1,20 +1,20 @@ agent_name: my_first_aea author: fetchai -version: 0.7.0 +version: 0.8.0 description: A simple agent to demo the echo skill. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: -- fetchai/echo:0.3.0 -- fetchai/error:0.3.0 -default_connection: fetchai/stub:0.6.0 +- fetchai/echo:0.4.0 +- fetchai/error:0.4.0 +default_connection: fetchai/stub:0.7.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/packages/fetchai/agents/simple_service_registration/aea-config.yaml b/packages/fetchai/agents/simple_service_registration/aea-config.yaml index 5026b42289..54d0278168 100644 --- a/packages/fetchai/agents/simple_service_registration/aea-config.yaml +++ b/packages/fetchai/agents/simple_service_registration/aea-config.yaml @@ -1,24 +1,24 @@ agent_name: simple_service_registration author: fetchai -version: 0.8.0 +version: 0.9.0 description: A simple example of service registration. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: '' fingerprint_ignore_patterns: [] connections: -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/simple_service_registration:0.5.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/simple_service_registration:0.6.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -26,4 +26,4 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/tac_controller/aea-config.yaml b/packages/fetchai/agents/tac_controller/aea-config.yaml index c0b381693d..2e6569f460 100644 --- a/packages/fetchai/agents/tac_controller/aea-config.yaml +++ b/packages/fetchai/agents/tac_controller/aea-config.yaml @@ -1,27 +1,30 @@ agent_name: tac_controller author: fetchai -version: 0.5.0 +version: 0.6.0 description: An AEA to manage an instance of the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/tac_control:0.3.0 -default_connection: fetchai/oef:0.6.0 -default_ledger: ethereum +- fetchai/error:0.4.0 +- fetchai/tac_control:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 +default_ledger: cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} registry_path: ../packages +default_routing: + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/tac_controller_contract/aea-config.yaml b/packages/fetchai/agents/tac_controller_contract/aea-config.yaml index 144b1d03fb..ab27813aff 100644 --- a/packages/fetchai/agents/tac_controller_contract/aea-config.yaml +++ b/packages/fetchai/agents/tac_controller_contract/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: tac_controller_contract author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA to manage an instance of the TAC (trading agent competition) using an ERC1155 smart contract. license: Apache-2.0 @@ -8,19 +8,19 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 +- fetchai/oef:0.7.0 +- fetchai/stub:0.7.0 contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/tac_control_contract:0.4.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/error:0.4.0 +- fetchai/tac_control_contract:0.5.0 +default_connection: fetchai/oef:0.7.0 default_ledger: ethereum logging_config: disable_existing_loggers: false diff --git a/packages/fetchai/agents/tac_participant/aea-config.yaml b/packages/fetchai/agents/tac_participant/aea-config.yaml index fafa55c706..177546824c 100644 --- a/packages/fetchai/agents/tac_participant/aea-config.yaml +++ b/packages/fetchai/agents/tac_participant/aea-config.yaml @@ -1,29 +1,32 @@ agent_name: tac_participant author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA to participate in the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.6.0 -- fetchai/stub:0.6.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/tac_negotiation:0.5.0 -- fetchai/tac_participation:0.4.0 -default_connection: fetchai/oef:0.6.0 -default_ledger: ethereum +- fetchai/error:0.4.0 +- fetchai/tac_negotiation:0.6.0 +- fetchai/tac_participation:0.5.0 +default_connection: fetchai/p2p_libp2p:0.6.0 +default_ledger: cosmos logging_config: disable_existing_loggers: false version: 1 private_key_paths: {} registry_path: ../packages +default_routing: + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/thermometer_aea/aea-config.yaml b/packages/fetchai/agents/thermometer_aea/aea-config.yaml index ce61010df5..d7a6c5131a 100644 --- a/packages/fetchai/agents/thermometer_aea/aea-config.yaml +++ b/packages/fetchai/agents/thermometer_aea/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: thermometer_aea author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA to represent a thermometer and sell temperature data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_seller:0.8.0 -- fetchai/thermometer:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_seller:0.9.0 +- fetchai/thermometer:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/thermometer_client/aea-config.yaml b/packages/fetchai/agents/thermometer_client/aea-config.yaml index 8a8d0cfb83..57b5f1ada1 100644 --- a/packages/fetchai/agents/thermometer_client/aea-config.yaml +++ b/packages/fetchai/agents/thermometer_client/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: thermometer_client author: fetchai -version: 0.6.0 +version: 0.7.0 description: An AEA that purchases thermometer data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_buyer:0.7.0 -- fetchai/thermometer_client:0.6.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_buyer:0.8.0 +- fetchai/thermometer_client:0.7.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/weather_client/aea-config.yaml b/packages/fetchai/agents/weather_client/aea-config.yaml index 0852aca3d2..f449ddf9a5 100644 --- a/packages/fetchai/agents/weather_client/aea-config.yaml +++ b/packages/fetchai/agents/weather_client/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: weather_client author: fetchai -version: 0.8.0 +version: 0.9.0 description: This AEA purchases weather data from the weather station. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_buyer:0.7.0 -- fetchai/weather_client:0.6.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_buyer:0.8.0 +- fetchai/weather_client:0.7.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/agents/weather_station/aea-config.yaml b/packages/fetchai/agents/weather_station/aea-config.yaml index ff76b4e17c..653db8f23b 100644 --- a/packages/fetchai/agents/weather_station/aea-config.yaml +++ b/packages/fetchai/agents/weather_station/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: weather_station author: fetchai -version: 0.8.0 +version: 0.9.0 description: This AEA represents a weather station selling weather data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.2.0 -- fetchai/p2p_libp2p:0.5.0 -- fetchai/soef:0.5.0 -- fetchai/stub:0.6.0 +- fetchai/ledger:0.3.0 +- fetchai/p2p_libp2p:0.6.0 +- fetchai/soef:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/error:0.3.0 -- fetchai/generic_seller:0.8.0 -- fetchai/weather_station:0.7.0 -default_connection: fetchai/p2p_libp2p:0.5.0 +- fetchai/error:0.4.0 +- fetchai/generic_seller:0.9.0 +- fetchai/weather_station:0.8.0 +default_connection: fetchai/p2p_libp2p:0.6.0 default_ledger: cosmos logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 diff --git a/packages/fetchai/connections/gym/README.md b/packages/fetchai/connections/gym/README.md new file mode 100644 index 0000000000..3b00a37e79 --- /dev/null +++ b/packages/fetchai/connections/gym/README.md @@ -0,0 +1,9 @@ +# Gym connection + +Connection providing access to the gym interface (https://github.com/openai/gym) for training reinforcement learning systems. + +The connection wraps a gym and allows the AEA to interact with the gym interface via the `gym` protocol. + +## Usage + +First, add the connection to your AEA project (`aea add connection fetchai/gym:0.5.0`). Then, update the `config` in `connection.yaml` by providing a dotted path to the gym module in the `env` field. diff --git a/packages/fetchai/connections/gym/connection.py b/packages/fetchai/connections/gym/connection.py index 3b53544934..21cbd5f5ae 100644 --- a/packages/fetchai/connections/gym/connection.py +++ b/packages/fetchai/connections/gym/connection.py @@ -20,27 +20,27 @@ """Gym connector and gym channel.""" import asyncio +import copy import logging from asyncio import CancelledError from asyncio.events import AbstractEventLoop from concurrent.futures.thread import ThreadPoolExecutor -from typing import Optional, Union, cast +from typing import Optional, Tuple, Union, cast import gym from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.helpers.base import locate from aea.mail.base import Address, Envelope +from packages.fetchai.protocols.gym.dialogues import GymDialogue, GymDialogues from packages.fetchai.protocols.gym.message import GymMessage -logger = logging.getLogger("aea.packages.fetchai.connections.gym") +logger = logging.getLogger("aea.packages.fetchai.connections.gym") -"""default 'to' field for Gym envelopes.""" -DEFAULT_GYM = "gym" -PUBLIC_ID = PublicId.from_str("fetchai/gym:0.4.0") +PUBLIC_ID = PublicId.from_str("fetchai/gym:0.5.0") class GymChannel: @@ -58,6 +58,28 @@ def __init__(self, address: Address, gym_env: gym.Env): self.THREAD_POOL_SIZE ) self.logger: Union[logging.Logger, logging.LoggerAdapter] = logger + self._dialogues = GymDialogues(str(PUBLIC_ID)) + + def _get_message_and_dialogue( + self, envelope: Envelope + ) -> Tuple[GymMessage, Optional[GymDialogue]]: + """ + Get a message copy and dialogue related to this message. + + :param envelope: incoming envelope + + :return: Tuple[MEssage, Optional[Dialogue]] + """ + orig_message = cast(GymMessage, envelope.message) + message = copy.copy( + orig_message + ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + message.is_incoming = True # TODO: fix; should be done by framework + message.counterparty = ( + orig_message.sender + ) # TODO: fix; should be done by framework + dialogue = cast(GymDialogue, self._dialogues.update(message)) + return message, dialogue @property def queue(self) -> asyncio.Queue: @@ -102,7 +124,14 @@ async def handle_gym_message(self, envelope: Envelope) -> None: assert isinstance( envelope.message, GymMessage ), "Message not of type GymMessage" - gym_message = cast(GymMessage, envelope.message) + gym_message, dialogue = self._get_message_and_dialogue(envelope) + + if dialogue is None: + self.logger.warning( + "Could not create dialogue from message={}".format(gym_message) + ) + return + if gym_message.performative == GymMessage.Performative.ACT: action = gym_message.action.any step_id = gym_message.step_id @@ -110,7 +139,6 @@ async def handle_gym_message(self, envelope: Envelope) -> None: observation, reward, done, info = await self._run_in_executor( self.gym_env.step, action ) - msg = GymMessage( performative=GymMessage.Performative.PERCEPT, observation=GymMessage.AnyObject(observation), @@ -118,18 +146,31 @@ async def handle_gym_message(self, envelope: Envelope) -> None: done=done, info=GymMessage.AnyObject(info), step_id=step_id, + target=gym_message.message_id, + message_id=gym_message.message_id + 1, + dialogue_reference=dialogue.dialogue_label.dialogue_reference, ) - envelope = Envelope( - to=envelope.sender, - sender=DEFAULT_GYM, - protocol_id=GymMessage.protocol_id, - message=msg, - ) - await self._send(envelope) elif gym_message.performative == GymMessage.Performative.RESET: await self._run_in_executor(self.gym_env.reset) + msg = GymMessage( + performative=GymMessage.Performative.STATUS, + content={"reset": "success"}, + target=gym_message.message_id, + message_id=gym_message.message_id + 1, + dialogue_reference=dialogue.dialogue_label.dialogue_reference, + ) elif gym_message.performative == GymMessage.Performative.CLOSE: await self._run_in_executor(self.gym_env.close) + return + msg.counterparty = gym_message.counterparty + assert dialogue.update(msg), "Error during dialogue update." + envelope = Envelope( + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, + message=msg, + ) + await self._send(envelope) async def _send(self, envelope: Envelope) -> None: """Send a message. @@ -137,7 +178,6 @@ async def _send(self, envelope: Envelope) -> None: :param envelope: the envelope :return: None """ - assert envelope.to == self.address, "Invalid destination address" await self.queue.put(envelope) async def disconnect(self) -> None: @@ -182,10 +222,13 @@ async def connect(self) -> None: :return: None """ - if not self.connection_status.is_connected: - self.connection_status.is_connected = True - self.channel.logger = self.logger - await self.channel.connect() + if self.is_connected: # pragma: nocover + return + + self._state.set(ConnectionStates.connecting) + self.channel.logger = self.logger + await self.channel.connect() + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """ @@ -193,9 +236,12 @@ async def disconnect(self) -> None: :return: None """ - if self.connection_status.is_connected: - self.connection_status.is_connected = False - await self.channel.disconnect() + if self.is_disconnected: # pragma: nocover + return + + self._state.set(ConnectionStates.disconnecting) + await self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: Envelope) -> None: """ @@ -204,7 +250,7 @@ async def send(self, envelope: Envelope) -> None: :param envelope: the envelop :return: None """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) @@ -212,7 +258,7 @@ async def send(self, envelope: Envelope) -> None: async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """Receive an envelope.""" - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) diff --git a/packages/fetchai/connections/gym/connection.yaml b/packages/fetchai/connections/gym/connection.yaml index 7515f475b5..bb1569ae8f 100644 --- a/packages/fetchai/connections/gym/connection.yaml +++ b/packages/fetchai/connections/gym/connection.yaml @@ -1,20 +1,21 @@ name: gym author: fetchai -version: 0.4.0 +version: 0.5.0 description: The gym connection wraps an OpenAI gym. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmPBurf9eeV1J7rrfmeudJXUU7KDVFNJArPrV8nNwjizfx __init__.py: QmWwxj1hGGZNteCvRtZxwtY9PuEKsrWsEmMWCKwiYCdvRR - connection.py: QmZHUedJDmV2X1kXcjjyZHwWbwV3553QEKSUYcK6NTtr4F + connection.py: QmQFTgN95u1mqPobekFJXvCD9TWS4AsFFq3exeRUGQnyMz fingerprint_ignore_patterns: [] protocols: -- fetchai/gym:0.3.0 +- fetchai/gym:0.4.0 class_name: GymConnection config: env: '' excluded_protocols: [] restricted_to_protocols: -- fetchai/gym:0.3.0 +- fetchai/gym:0.4.0 dependencies: gym: {} diff --git a/packages/fetchai/connections/http_client/README.md b/packages/fetchai/connections/http_client/README.md new file mode 100644 index 0000000000..db3b7a7e6d --- /dev/null +++ b/packages/fetchai/connections/http_client/README.md @@ -0,0 +1,7 @@ +# HTTP client connection + +This connection wraps an HTTP client. It consumes messages from the AEA, translates them into HTTP requests, then sends the HTTP response as a message back to the AEA. + +## Usage + +First, add the connection to your AEA project (`aea add connection fetchai/http_client:0.6.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. diff --git a/packages/fetchai/connections/http_client/connection.py b/packages/fetchai/connections/http_client/connection.py index ce7f7f20ee..a8710c77a2 100644 --- a/packages/fetchai/connections/http_client/connection.py +++ b/packages/fetchai/connections/http_client/connection.py @@ -16,31 +16,35 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """HTTP client connection and channel.""" import asyncio +import copy import json import logging from asyncio import CancelledError from asyncio.events import AbstractEventLoop from asyncio.tasks import Task from traceback import format_exc -from typing import Any, Optional, Set, Union, cast +from typing import Any, Optional, Set, Tuple, Union, cast import aiohttp from aiohttp.client_reqrep import ClientResponse from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Address, Envelope, EnvelopeContext +from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage + SUCCESS = 200 NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 -PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.5.0") +PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.6.0") logger = logging.getLogger("aea.packages.fetchai.connections.http_client") @@ -78,6 +82,7 @@ def __init__( self.port = port self.connection_id = connection_id self.restricted_to_protocols = restricted_to_protocols + self._dialogues = HttpDialogues(str(HTTPClientConnection.connection_id)) self._in_queue = None # type: Optional[asyncio.Queue] # pragma: no cover self._loop = ( @@ -102,7 +107,28 @@ async def connect(self, loop: AbstractEventLoop) -> None: self._in_queue = asyncio.Queue() self.is_stopped = False - async def _http_request_task(self, request_http_message: HttpMessage) -> None: + def _get_message_and_dialogue( + self, envelope: Envelope + ) -> Tuple[HttpMessage, Optional[HttpDialogue]]: + """ + Get a message copy and dialogue related to this message. + + :param envelope: incoming envelope + + :return: Tuple[MEssage, Optional[Dialogue]] + """ + orig_message = cast(HttpMessage, envelope.message) + message = copy.copy( + orig_message + ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + message.is_incoming = True # TODO: fix; should be done by framework + message.counterparty = ( + orig_message.sender + ) # TODO: fix; should be done by framework + dialogue = cast(HttpDialogue, self._dialogues.update(message)) + return message, dialogue + + async def _http_request_task(self, request_envelope: Envelope) -> None: """ Perform http request and send back response. @@ -113,6 +139,16 @@ async def _http_request_task(self, request_http_message: HttpMessage) -> None: if not self._loop: # pragma: nocover raise ValueError("Channel is not connected") + request_http_message, dialogue = self._get_message_and_dialogue( + request_envelope + ) + + if not dialogue: + self.logger.warning( + "Could not create dialogue for message={}".format(request_http_message) + ) + return + try: resp = await asyncio.wait_for( self._perform_http_request(request_http_message), @@ -127,6 +163,7 @@ async def _http_request_task(self, request_http_message: HttpMessage) -> None: bodyy=resp._body # pylint: disable=protected-access if resp._body is not None # pylint: disable=protected-access else b"", + dialogue=dialogue, ) except Exception: # pragma: nocover # pylint: disable=broad-except envelope = self.to_envelope( @@ -136,6 +173,7 @@ async def _http_request_task(self, request_http_message: HttpMessage) -> None: headers={}, status_text="HTTPConnection request error.", bodyy=format_exc().encode("utf-8"), + dialogue=dialogue, ) if self._in_queue is not None: @@ -209,7 +247,7 @@ def send(self, request_envelope: Envelope) -> None: ) return - task = self._loop.create_task(self._http_request_task(request_http_message)) + task = self._loop.create_task(self._http_request_task(request_envelope)) task.add_done_callback(self._task_done_callback) self._tasks.add(task) @@ -248,6 +286,7 @@ def to_envelope( headers: dict, status_text: Optional[Any], bodyy: bytes, + dialogue: HttpDialogue, ) -> Envelope: """ Convert an HTTP response object (from the 'requests' library) into an Envelope containing an HttpMessage (from the 'http' Protocol). @@ -263,20 +302,22 @@ def to_envelope( """ context = EnvelopeContext(connection_id=connection_id) http_message = HttpMessage( - dialogue_reference=http_request_message.dialogue_reference, - target=http_request_message.target, - message_id=http_request_message.message_id, performative=HttpMessage.Performative.RESPONSE, status_code=status_code, headers=json.dumps(dict(headers.items())), status_text=status_text, bodyy=bodyy, version="", + dialogue_reference=dialogue.dialogue_label.dialogue_reference, + target=http_request_message.message_id, + message_id=http_request_message.message_id + 1, ) + http_message.counterparty = http_request_message.counterparty + assert dialogue.update(http_message) envelope = Envelope( to=self.agent_address, sender="HTTP Server", - protocol_id=PublicId.from_str("fetchai/http:0.3.0"), + protocol_id=PublicId.from_str("fetchai/http:0.4.0"), context=context, message=http_message, ) @@ -331,10 +372,12 @@ async def connect(self) -> None: :return: None """ - if not self.connection_status.is_connected: - self.connection_status.is_connected = True - self.channel.logger = self.logger - await self.channel.connect(self._loop) + if self.is_connected: + return + self._state.set(ConnectionStates.connecting) + self.channel.logger = self.logger + await self.channel.connect(self._loop) + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """ @@ -342,9 +385,11 @@ async def disconnect(self) -> None: :return: None """ - if self.connection_status.is_connected: - self.connection_status.is_connected = False - await self.channel.disconnect() + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) + await self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope") -> None: """ @@ -353,7 +398,7 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelop :return: None """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover @@ -365,7 +410,7 @@ async def receive(self, *args, **kwargs) -> Optional[Union["Envelope", None]]: :return: the envelope received, or None. """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover diff --git a/packages/fetchai/connections/http_client/connection.yaml b/packages/fetchai/connections/http_client/connection.yaml index 5c05c1312c..b3378752d2 100644 --- a/packages/fetchai/connections/http_client/connection.yaml +++ b/packages/fetchai/connections/http_client/connection.yaml @@ -1,23 +1,24 @@ name: http_client author: fetchai -version: 0.5.0 +version: 0.6.0 description: The HTTP_client connection that wraps a web-based client connecting to a RESTful API specification. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmQmmM5CU1jh5abqbQqKEW7f51jWAzo1eKMiWyfTZYedGX __init__.py: QmPdKAks8A6XKAgZiopJzPZYXJumTeUqChd8UorqmLQQPU - connection.py: QmVYurcnjuRTK6CnuEc6qNbSykmZEzRMkjyGhknJKzKRQt + connection.py: QmTcFjUu7HvGo4Ayk2CzoyabdfCvD2dzQvF56cPFwQjmV5 fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 class_name: HTTPClientConnection config: host: 127.0.0.1 port: 8000 excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 dependencies: aiohttp: version: '>=3.6.2,<3.7' diff --git a/packages/fetchai/connections/http_server/README.md b/packages/fetchai/connections/http_server/README.md new file mode 100644 index 0000000000..4aa7efbf0c --- /dev/null +++ b/packages/fetchai/connections/http_server/README.md @@ -0,0 +1,7 @@ +# HTTP server connection + +This connection wraps an HTTP server. It consumes requests from clients, translates them into messages for the AEA, waits for a response message from the AEA, then serves the response to the client. + +## Usage + +First, add the connection to your AEA project (`aea add connection fetchai/http_server:0.6.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. Optionally, provide a path to an [OpenAPI spec](https://swagger.io/docs/specification/about/) for request validation. diff --git a/packages/fetchai/connections/http_server/connection.py b/packages/fetchai/connections/http_server/connection.py index 496443ed43..1594b04698 100644 --- a/packages/fetchai/connections/http_server/connection.py +++ b/packages/fetchai/connections/http_server/connection.py @@ -17,8 +17,10 @@ # # ------------------------------------------------------------------------------ + """HTTP server connection, channel, server, and handler.""" import asyncio +import copy import email import logging from abc import ABC, abstractmethod @@ -28,7 +30,6 @@ from traceback import format_exc from typing import Dict, Optional, Set, cast from urllib.parse import parse_qs, urlencode, urlparse -from uuid import uuid4 from aiohttp import web from aiohttp.web_request import BaseRequest @@ -54,9 +55,11 @@ ) from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates +from aea.helpers.dialogue.base import DialogueLabel from aea.mail.base import Address, Envelope, EnvelopeContext, URI +from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage SUCCESS = 200 @@ -64,10 +67,10 @@ REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 -logger = logging.getLogger("aea.packages.fetchai.connections.http_server") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.http_server") -RequestId = str -PUBLIC_ID = PublicId.from_str("fetchai/http_server:0.5.0") +RequestId = DialogueLabel +PUBLIC_ID = PublicId.from_str("fetchai/http_server:0.6.0") def headers_to_string(headers: Dict): @@ -87,6 +90,11 @@ def headers_to_string(headers: Dict): class Request(OpenAPIRequest): """Generic request object.""" + @property + def is_id_set(self): + """Check if id is set.""" + return self._id is not None + @property def id(self) -> RequestId: """Get the request id.""" @@ -130,20 +138,19 @@ async def create(cls, http_request: BaseRequest) -> "Request": body=body, mimetype=mimetype, ) - - request.id = uuid4().hex return request - def to_envelope(self, connection_id: PublicId, agent_address: str) -> Envelope: + def to_envelope_and_set_id( + self, connection_id: PublicId, agent_address: str, dialogues: HttpDialogues, + ) -> Envelope: """ Process incoming API request by packaging into Envelope and sending it in-queue. - The Envelope's message body contains the "performative", "path", "params", and "payload". + :param connection_id: id of the connection + :param agent_address: agent's address + :param dialogue_reference: new dialog refernece for envelope - :param http_method: the http method - :param url: the url - :param param: the parameter - :param body: the body + :return: envelope """ url = ( self.full_url_pattern @@ -153,9 +160,7 @@ def to_envelope(self, connection_id: PublicId, agent_address: str) -> Envelope: uri = URI(self.full_url_pattern) context = EnvelopeContext(connection_id=connection_id, uri=uri) http_message = HttpMessage( - dialogue_reference=("", ""), - target=0, - message_id=1, + dialogue_reference=dialogues.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method=self.method, url=url, @@ -163,10 +168,16 @@ def to_envelope(self, connection_id: PublicId, agent_address: str) -> Envelope: bodyy=self.body if self.body is not None else b"", version="", ) + http_message.counterparty = agent_address + dialogue = cast(Optional[HttpDialogue], dialogues.update(http_message)) + assert dialogue is not None, "Could not create dialogue for message={}".format( + http_message + ) + self.id = dialogue.incomplete_dialogue_label envelope = Envelope( to=agent_address, - sender=self.id, - protocol_id=PublicId.from_str("fetchai/http:0.3.0"), + sender=str(connection_id), + protocol_id=http_message.protocol_id, context=context, message=http_message, ) @@ -177,25 +188,20 @@ class Response(web.Response): """Generic response object.""" @classmethod - def from_envelope(cls, envelope: Envelope) -> "Response": + def from_message(cls, http_message: HttpMessage) -> "Response": """ Turn an envelope into a response. - :param envelope: the envelope + :param http_message: the http_message :return: the response """ - assert isinstance( - envelope.message, HttpMessage - ), "Message not of type HttpMessage" - - http_message = cast(HttpMessage, envelope.message) if http_message.performative == HttpMessage.Performative.RESPONSE: response = cls( status=http_message.status_code, reason=http_message.status_text, body=http_message.bodyy, ) - else: + else: # pragma: nocover response = cls(status=SERVER_ERROR, text="Server error") return response @@ -204,7 +210,10 @@ class APISpec: """API Spec class to verify a request against an OpenAPI/Swagger spec.""" def __init__( - self, api_spec_path: Optional[str] = None, server: Optional[str] = None + self, + api_spec_path: Optional[str] = None, + server: Optional[str] = None, + logger: logging.Logger = _default_logger, ): """ Initialize the API spec. @@ -212,6 +221,7 @@ def __init__( :param api_spec_path: Directory API path and filename of the API spec YAML source file. """ self._validator = None # type: Optional[RequestValidator] + self.logger = logger if api_spec_path is not None: try: api_spec_dict = read_yaml_file(api_spec_path) @@ -220,11 +230,11 @@ def __init__( api_spec = create_spec(api_spec_dict) self._validator = RequestValidator(api_spec) except OpenAPIValidationError as e: # pragma: nocover - logger.error( + self.logger.error( f"API specification YAML source file not correctly formatted: {str(e)}" ) except Exception: - logger.exception( + self.logger.exception( "API specification YAML source file not correctly formatted." ) raise @@ -237,13 +247,13 @@ def verify(self, request: Request) -> bool: :return: whether or not the request conforms with the API spec """ if self._validator is None: - logger.debug("Skipping API verification!") + self.logger.debug("Skipping API verification!") return True try: validate_request(self._validator, request) except Exception: # pragma: nocover # pylint: disable=broad-except - logger.exception("APISpec verify error") + self.logger.exception("APISpec verify error") return False return True @@ -325,6 +335,7 @@ def __init__( connection_id: PublicId, restricted_to_protocols: Set[PublicId], timeout_window: float = 5.0, + logger: logging.Logger = _default_logger, ): """ Initialize a channel and process the initial API specification from the file path (if given). @@ -343,11 +354,11 @@ def __init__( self.server_address = "http://{}:{}".format(self.host, self.port) self.restricted_to_protocols = restricted_to_protocols - self._api_spec = APISpec(api_spec_path, self.server_address) + self._api_spec = APISpec(api_spec_path, self.server_address, logger) self.timeout_window = timeout_window self.http_server: Optional[web.TCPSite] = None self.pending_requests: Dict[RequestId, Future] = {} - + self._dialogues = HttpDialogues(self.address) self.logger = logger @property @@ -398,26 +409,33 @@ async def _http_handler(self, http_request: BaseRequest) -> Response: return Response(status=NOT_FOUND, reason="Request Not Found") try: - self.pending_requests[request.id] = Future() # turn request into envelope - envelope = request.to_envelope(self.connection_id, self.address) + envelope = request.to_envelope_and_set_id( + self.connection_id, self.address, dialogues=self._dialogues, + ) + + self.pending_requests[request.id] = Future() + # send the envelope to the agent's inbox (via self.in_queue) await self._in_queue.put(envelope) # wait for response envelope within given timeout window (self.timeout_window) to appear in dispatch_ready_envelopes - response_envelope = await asyncio.wait_for( - self.pending_requests[request.id], timeout=self.RESPONSE_TIMEOUT + response_message = await asyncio.wait_for( + self.pending_requests[request.id], timeout=self.RESPONSE_TIMEOUT, ) - return Response.from_envelope(response_envelope) + + return Response.from_message(response_message) except asyncio.TimeoutError: return Response(status=REQUEST_TIMEOUT, reason="Request Timeout") except BaseException: # pragma: nocover # pylint: disable=broad-except + self.logger.exception("Error during handling incoming request") return Response( status=SERVER_ERROR, reason="Server Error", text=format_exc() ) finally: - self.pending_requests.pop(request.id, None) + if request.is_id_set: + self.pending_requests.pop(request.id, None) async def _start_http_server(self) -> None: """Start http server.""" @@ -444,16 +462,31 @@ def send(self, envelope: Envelope) -> None: ) raise ValueError("Cannot send message.") - future = self.pending_requests.pop(envelope.to, None) + http_message = cast(HttpMessage, envelope.message) + message = copy.copy( + http_message + ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + message.is_incoming = True # TODO: fix; should be done by framework + message.counterparty = envelope.sender # TODO: fix; should be done by framework + + dialogue = self._dialogues.update(message) + + if dialogue is None: + self.logger.warning( + "Could not create dialogue for message={}".format(message) + ) + return + + future = self.pending_requests.pop(dialogue.incomplete_dialogue_label, None) if not future: self.logger.warning( - "Dropping envelope for request id {} which has timed out.".format( - envelope.to + "Dropping message={} for incomplete_dialogue_label={} which has timed out.".format( + message, dialogue.incomplete_dialogue_label ) ) else: - future.set_result(envelope) + future.set_result(message) async def disconnect(self) -> None: """ @@ -489,6 +522,7 @@ def __init__(self, **kwargs): api_spec_path, connection_id=self.connection_id, restricted_to_protocols=self.restricted_to_protocols, + logger=self.logger, ) async def connect(self) -> None: @@ -497,10 +531,15 @@ async def connect(self) -> None: :return: None """ - if not self.connection_status.is_connected: - self.channel.logger = self.logger - await self.channel.connect(loop=self.loop) - self.connection_status.is_connected = not self.channel.is_stopped + if self.is_connected: + return + self._state.set(ConnectionStates.connecting) + self.channel.logger = self.logger + await self.channel.connect(loop=self.loop) + if self.channel.is_stopped: + self._state.set(ConnectionStates.disconnected) + else: + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """ @@ -508,9 +547,11 @@ async def disconnect(self) -> None: :return: None """ - if self.connection_status.is_connected: - self.connection_status.is_connected = False - await self.channel.disconnect() + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) + await self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope") -> None: """ @@ -519,7 +560,7 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelop :return: None """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover @@ -531,7 +572,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: :return: the envelope received, or None. """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover diff --git a/packages/fetchai/connections/http_server/connection.yaml b/packages/fetchai/connections/http_server/connection.yaml index e020af1227..f04bdc7a94 100644 --- a/packages/fetchai/connections/http_server/connection.yaml +++ b/packages/fetchai/connections/http_server/connection.yaml @@ -1,16 +1,17 @@ name: http_server author: fetchai -version: 0.5.0 +version: 0.6.0 description: The HTTP server connection that wraps http server implementing a RESTful API specification. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmXjfGyzKJ85U8gYhU24AqV8t6m1jxR7QxrdyzzonZf2JB __init__.py: Qmb6JEAkJeb5JweqrSGiGoQp1vGXqddjGgb9WMkm2phTgA - connection.py: QmTDwwg4Qah191WaiFizdhGGDs56jha26NWcjGkmDTDt5q + connection.py: QmQQfnyLBpVSnxY1xD1piA5KKAr65GTLhzFSRa4kcREtwi fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 class_name: HTTPServerConnection config: api_spec_path: '' @@ -18,7 +19,7 @@ config: port: 8000 excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 dependencies: aiohttp: version: '>=3.6.2,<3.7' diff --git a/packages/fetchai/connections/ledger/README.md b/packages/fetchai/connections/ledger/README.md new file mode 100644 index 0000000000..5120150b1c --- /dev/null +++ b/packages/fetchai/connections/ledger/README.md @@ -0,0 +1,11 @@ +# Ledger connection + +The ledger connection wraps the APIs needed to interact with multiple ledgers, including smart contracts deployed on those ledgers. + +The AEA communicates with the ledger connection via the `fetchai/ledger_api:0.2.0` and `fetchai/contract_api:0.2.0` protocols. + +The connection uses the ledger apis registered in the ledger api registry. + +## Usage + +First, add the connection to your AEA project (`aea add connection fetchai/ledger:0.3.0`). Optionally, update the `ledger_apis` in `config` of `connection.yaml`. diff --git a/packages/fetchai/connections/ledger/base.py b/packages/fetchai/connections/ledger/base.py index a585ba1103..29005b8900 100644 --- a/packages/fetchai/connections/ledger/base.py +++ b/packages/fetchai/connections/ledger/base.py @@ -16,24 +16,26 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This module contains base classes for the ledger API connection.""" import asyncio +import copy +import logging from abc import ABC, abstractmethod from asyncio import Task from concurrent.futures._base import Executor +from logging import Logger from typing import Any, Callable, Dict, Optional from aea.configurations.base import PublicId -from aea.connections.base import ConnectionStatus from aea.crypto.base import LedgerApi from aea.crypto.registries import Registry, ledger_apis_registry +from aea.helpers.async_utils import AsyncState from aea.helpers.dialogue.base import Dialogue, Dialogues from aea.mail.base import Envelope from aea.protocols.base import Message -CONNECTION_ID = PublicId.from_str("fetchai/ledger:0.2.0") +CONNECTION_ID = PublicId.from_str("fetchai/ledger:0.3.0") class RequestDispatcher(ABC): @@ -44,10 +46,11 @@ class RequestDispatcher(ABC): def __init__( self, - connection_status: ConnectionStatus, + connection_state: AsyncState, loop: Optional[asyncio.AbstractEventLoop] = None, executor: Optional[Executor] = None, api_configs: Optional[Dict[str, Dict[str, str]]] = None, + logger: Optional[Logger] = None, ): """ Initialize the request dispatcher. @@ -55,10 +58,17 @@ def __init__( :param loop: the asyncio loop. :param executor: an executor. """ - self.connection_status = connection_status + self.connection_state = connection_state self.loop = loop if loop is not None else asyncio.get_event_loop() self.executor = executor self._api_configs = api_configs + self.logger = ( + logger + if logger is not None + else logging.getLogger( + "aea.packages.fetchai.connections.ledger.contract_dispatcher" + ) + ) def api_config(self, ledger_id: str) -> Dict[str, str]: """Get api config.""" @@ -97,10 +107,12 @@ def dispatch(self, envelope: Envelope) -> Task: :return: an awaitable. """ assert isinstance(envelope.message, Message) - message = envelope.message + message_original = envelope.message + message = copy.copy(message_original) ledger_id = self.get_ledger_id(message) api = self.ledger_api_registry.make(ledger_id, **self.api_config(ledger_id)) message.is_incoming = True + message.counterparty = message_original.sender dialogue = self.dialogues.update(message) assert dialogue is not None, "No dialogue created." performative = message.performative diff --git a/packages/fetchai/connections/ledger/connection.py b/packages/fetchai/connections/ledger/connection.py index 6ffcf8c3ad..380d42bc79 100644 --- a/packages/fetchai/connections/ledger/connection.py +++ b/packages/fetchai/connections/ledger/connection.py @@ -23,7 +23,7 @@ from collections import deque from typing import Deque, Dict, List, Optional, cast -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope from aea.protocols.base import Message @@ -68,18 +68,35 @@ def event_new_receiving_task(self) -> asyncio.Event: async def connect(self) -> None: """Set up the connection.""" + + if self.is_connected: # pragma: nocover + return + + self._state.set(ConnectionStates.connecting) + self._ledger_dispatcher = LedgerApiRequestDispatcher( - self.connection_status, loop=self.loop, api_configs=self.api_configs + self._state, + loop=self.loop, + api_configs=self.api_configs, + logger=self.logger, ) self._contract_dispatcher = ContractApiRequestDispatcher( - self.connection_status, loop=self.loop, api_configs=self.api_configs + self._state, + loop=self.loop, + api_configs=self.api_configs, + logger=self.logger, ) self._event_new_receiving_task = asyncio.Event(loop=self.loop) - self.connection_status.is_connected = True + + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """Tear down the connection.""" - self.connection_status.is_connected = False + if self.is_disconnected: # pragma: nocover + return + + self._state.set(ConnectionStates.disconnecting) + for task in self.receiving_tasks: if not task.cancelled(): # pragma: nocover task.cancel() @@ -87,6 +104,8 @@ async def disconnect(self) -> None: self._contract_dispatcher = None self._event_new_receiving_task = None + self._state.set(ConnectionStates.disconnected) + async def send(self, envelope: "Envelope") -> None: """ Send an envelope. diff --git a/packages/fetchai/connections/ledger/connection.yaml b/packages/fetchai/connections/ledger/connection.yaml index dfe2b59f9a..fc748456a2 100644 --- a/packages/fetchai/connections/ledger/connection.yaml +++ b/packages/fetchai/connections/ledger/connection.yaml @@ -1,19 +1,20 @@ name: ledger author: fetchai -version: 0.2.0 +version: 0.3.0 description: A connection to interact with any ledger API and contract API. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmRJAjD29rx9W7mZfW7M9oxGaN42rXVfQP55xsvian7rb9 __init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj - base.py: QmTuJp4ch7gMrhrK9tTDtJ8wtZbaaG2TLqGJTkSojaMLm8 - connection.py: QmTPj9CGkDtPMT7bXXDQi3i8zoRvSJvPVr6fyK2giPjmW1 - contract_dispatcher.py: QmSkA75HLriYkKXd7wcFqchSkrQsP8RxHK1be5qtXTpgwz - ledger_dispatcher.py: QmaETup4DzFYVkembK2yZL6TfbNDL13fdr6i29CPubG3CN + base.py: QmTD7gWtgQiLsUwU2sc4VSmYi8gyxVebLbeGd7XcR4EgX9 + connection.py: QmXsfh1YGpdzGNCs7zCeEjRvYJHzUKt7Qihk2EgPBpmdiy + contract_dispatcher.py: QmURhoVnwcGAZgkHXZQKekXQiNfDNRdk9JW4CstVJmCQhn + ledger_dispatcher.py: QmbJMFKojEd2nmBZZcpR9JovsRLQk3swUjzHHkcd8N2qbZ fingerprint_ignore_patterns: [] protocols: -- fetchai/contract_api:0.1.0 -- fetchai/ledger_api:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/ledger_api:0.2.0 class_name: LedgerConnection config: ledger_apis: @@ -26,6 +27,6 @@ config: network: testnet excluded_protocols: [] restricted_to_protocols: -- fetchai/contract_api:0.1.0 -- fetchai/ledger_api:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/ledger_api:0.2.0 dependencies: {} diff --git a/packages/fetchai/connections/ledger/contract_dispatcher.py b/packages/fetchai/connections/ledger/contract_dispatcher.py index aebdac3b85..08604ae057 100644 --- a/packages/fetchai/connections/ledger/contract_dispatcher.py +++ b/packages/fetchai/connections/ledger/contract_dispatcher.py @@ -18,11 +18,13 @@ # ------------------------------------------------------------------------------ """This module contains the implementation of the contract API request dispatcher.""" -from typing import cast +import inspect +from typing import Callable, Optional, cast -from aea.contracts import contract_registry +from aea.contracts import Contract, contract_registry from aea.crypto.base import LedgerApi from aea.crypto.registries import Registry +from aea.exceptions import AEAException from aea.helpers.dialogue.base import ( Dialogue as BaseDialogue, DialogueLabel as BaseDialogueLabel, @@ -94,7 +96,7 @@ def dialogues(self) -> BaseDialogues: return self._contract_api_dialogues @property - def contract_registry(self) -> Registry: + def contract_registry(self) -> Registry[Contract]: """Get the contract registry.""" return contract_registry @@ -130,126 +132,227 @@ def get_error_message( dialogue.update(response) return response + def dispatch_request( + self, + ledger_api: LedgerApi, + message: ContractApiMessage, + dialogue: ContractApiDialogue, + response_builder: Callable[[bytes], ContractApiMessage], + ) -> ContractApiMessage: + """ + Dispatch a request to a user-defined contract method. + + :param ledger_api: the ledger apis. + :param message: the contract API request message. + :param dialogue: the contract API dialogue. + :param response_builder: callable that from bytes builds a contract API message. + :return: the response message. + """ + contract = self.contract_registry.make(message.contract_id) + try: + data = self._get_data(ledger_api, message, contract) + response = response_builder(data) + response.counterparty = message.counterparty + dialogue.update(response) + except AEAException as e: + self.logger.error(str(e)) + response = self.get_error_message(e, ledger_api, message, dialogue) + except Exception as e: # pylint: disable=broad-except # pragma: nocover + self.logger.error( + f"An error occurred while processing the contract api request: '{str(e)}'." + ) + response = self.get_error_message(e, ledger_api, message, dialogue) + return response + def get_state( self, - api: LedgerApi, + ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_state'. - :param api: the API object. + :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: None """ - contract = self.contract_registry.make(message.contract_id) - method_to_call = getattr(contract, message.callable) - try: - data = method_to_call(api, message.contract_address, **message.kwargs.body) - response = ContractApiMessage( + + def build_response(data: bytes) -> ContractApiMessage: + return ContractApiMessage( performative=ContractApiMessage.Performative.STATE, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, state=State(message.ledger_id, data), ) - response.counterparty = message.counterparty - dialogue.update(response) - except Exception as e: # pylint: disable=broad-except # pragma: nocover - response = self.get_error_message(e, api, message, dialogue) - return response + + return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_deploy_transaction( self, - api: LedgerApi, + ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_transaction'. - :param api: the API object. + :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: None """ - contract = self.contract_registry.make(message.contract_id) - method_to_call = getattr(contract, message.callable) - try: - tx = method_to_call(api, **message.kwargs.body) - response = ContractApiMessage( + + def build_response(tx: bytes) -> ContractApiMessage: + return ContractApiMessage( performative=ContractApiMessage.Performative.RAW_TRANSACTION, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, raw_transaction=RawTransaction(message.ledger_id, tx), ) - response.counterparty = message.counterparty - dialogue.update(response) - except Exception as e: # pylint: disable=broad-except # pragma: nocover - response = self.get_error_message(e, api, message, dialogue) - return response + + return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_raw_transaction( self, - api: LedgerApi, + ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_transaction'. - :param api: the API object. + :param ledger_api: the API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: None """ - contract = self.contract_registry.make(message.contract_id) - method_to_call = getattr(contract, message.callable) - try: - tx = method_to_call(api, message.contract_address, **message.kwargs.body) - response = ContractApiMessage( + + def build_response(tx: bytes) -> ContractApiMessage: + return ContractApiMessage( performative=ContractApiMessage.Performative.RAW_TRANSACTION, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, raw_transaction=RawTransaction(message.ledger_id, tx), ) - response.counterparty = message.counterparty - dialogue.update(response) - except Exception as e: # pylint: disable=broad-except # pragma: nocover - response = self.get_error_message(e, api, message, dialogue) - return response + + return self.dispatch_request(ledger_api, message, dialogue, build_response) def get_raw_message( self, - api: LedgerApi, + ledger_api: LedgerApi, message: ContractApiMessage, dialogue: ContractApiDialogue, ) -> ContractApiMessage: """ Send the request 'get_raw_message'. - :param api: the API object. + :param ledger_api: the ledger API object. :param message: the Ledger API message :param dialogue: the contract API dialogue :return: None """ - contract = self.contract_registry.make(message.contract_id) - method_to_call = getattr(contract, message.callable) - try: - rm = method_to_call(api, message.contract_address, **message.kwargs.body) - response = ContractApiMessage( + + def build_response(rm: bytes) -> ContractApiMessage: + return ContractApiMessage( performative=ContractApiMessage.Performative.RAW_MESSAGE, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, raw_message=RawMessage(message.ledger_id, rm), ) - response.counterparty = message.counterparty - dialogue.update(response) - except Exception as e: # pylint: disable=broad-except # pragma: nocover - response = self.get_error_message(e, api, message, dialogue) - return response + + return self.dispatch_request(ledger_api, message, dialogue, build_response) + + def _get_data( + self, api: LedgerApi, message: ContractApiMessage, contract: Contract, + ) -> bytes: + """Get the data from the contract method, either from the stub or + from the callable specified by the message.""" + # first, check if the custom handler for this type of request has been implemented. + data = self._call_stub(api, message, contract) + if data is not None: + return data + + # then, check if there is the handler for the provided callable. + data = self._validate_and_call_callable(api, message, contract) + return data + + @staticmethod + def _call_stub( + ledger_api: LedgerApi, message: ContractApiMessage, contract: Contract + ) -> Optional[bytes]: + """Try to call stub methods associated to the + contract API request performative.""" + try: + method: Callable = getattr(contract, message.performative.value) + if message.performative in [ + ContractApiMessage.Performative.GET_STATE, + ContractApiMessage.Performative.GET_RAW_MESSAGE, + ContractApiMessage.Performative.GET_RAW_TRANSACTION, + ]: + args, kwargs = ( + [ledger_api, message.contract_address], + message.kwargs.body, + ) + elif message.performative in [ # pragma: nocover + ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, + ]: + args, kwargs = [ledger_api], message.kwargs.body + else: # pragma: nocover + raise AEAException(f"Unexpected performative: {message.performative}") + data = method(*args, **kwargs) + return data + except (AttributeError, NotImplementedError): + return None + + @staticmethod + def _validate_and_call_callable( + api: LedgerApi, message: ContractApiMessage, contract: Contract + ): + """ + Validate a Contract callable, given the performative. + + In particular: + - if the performative is either 'get_state' or 'get_raw_transaction', the signature + must accept ledger api as first argument and contract address as second argument, + plus keyword arguments. + - if the performative is either 'get_deploy_transaction' or 'get_raw_message', the signature + must accept ledger api as first argument, plus keyword arguments. + + :param api: the ledger api object. + :param message: the contract api request. + :param contract: the contract instance. + :return: the data generated by the method. + """ + try: + method_to_call = getattr(contract, message.callable) + except AttributeError: + raise AEAException( + f"Cannot find {message.callable} in contract {type(contract)}" + ) + full_args_spec = inspect.getfullargspec(method_to_call) + if message.performative in [ + ContractApiMessage.Performative.GET_STATE, + ContractApiMessage.Performative.GET_RAW_MESSAGE, + ContractApiMessage.Performative.GET_RAW_TRANSACTION, + ]: + if len(full_args_spec.args) < 2: + raise AEAException( + f"Expected two or more positional arguments, got {len(full_args_spec.args)}" + ) + return method_to_call(api, message.contract_address, **message.kwargs.body) + elif message.performative in [ + ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, + ]: + if len(full_args_spec.args) < 1: + raise AEAException( + f"Expected one or more positional arguments, got {len(full_args_spec.args)}" + ) + return method_to_call(api, **message.kwargs.body) + else: # pragma: nocover + raise AEAException(f"Unexpected performative: {message.performative}") diff --git a/packages/fetchai/connections/ledger/ledger_dispatcher.py b/packages/fetchai/connections/ledger/ledger_dispatcher.py index a5b6566060..0c4832b39b 100644 --- a/packages/fetchai/connections/ledger/ledger_dispatcher.py +++ b/packages/fetchai/connections/ledger/ledger_dispatcher.py @@ -16,11 +16,11 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This module contains the implementation of the ledger API request dispatcher.""" import time from typing import cast +from aea.connections.base import ConnectionStates from aea.crypto.base import LedgerApi from aea.helpers.dialogue.base import ( Dialogue as BaseDialogue, @@ -194,7 +194,7 @@ def get_transaction_receipt( while ( not is_settled and attempts < self.MAX_ATTEMPTS - and self.connection_status.is_connected + and self.connection_state.get() == ConnectionStates.connected ): time.sleep(self.TIMEOUT) transaction_receipt = api.get_transaction_receipt( @@ -207,7 +207,7 @@ def get_transaction_receipt( while ( transaction is None and attempts < self.MAX_ATTEMPTS - and self.connection_status.is_connected + and self.connection_state.get() == ConnectionStates.connected ): time.sleep(self.TIMEOUT) transaction = api.get_transaction(message.transaction_digest.body) diff --git a/packages/fetchai/connections/local/README.md b/packages/fetchai/connections/local/README.md new file mode 100644 index 0000000000..65e59cbd02 --- /dev/null +++ b/packages/fetchai/connections/local/README.md @@ -0,0 +1,7 @@ +# Local connection + +OEF compatible local connection for testing purposes only. + +## Usage + +OEF compatible connection to be used for testing, does not interact with external nodes. Does not preserve state on restart. diff --git a/packages/fetchai/connections/local/connection.py b/packages/fetchai/connections/local/connection.py index dc82107b24..949628744a 100644 --- a/packages/fetchai/connections/local/connection.py +++ b/packages/fetchai/connections/local/connection.py @@ -16,10 +16,10 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Extension to the Local Node.""" import asyncio +import copy import logging from asyncio import AbstractEventLoop, Queue from collections import defaultdict @@ -27,28 +27,33 @@ from typing import Dict, List, Optional, Tuple, cast from aea.configurations.base import ProtocolId, PublicId -from aea.connections.base import Connection -from aea.helpers.search.models import Description, Query +from aea.connections.base import Connection, ConnectionStates +from aea.helpers.search.models import Description from aea.mail.base import AEAConnectionError, Address, Envelope from aea.protocols.default.message import DefaultMessage +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue, + OefSearchDialogues, +) from packages.fetchai.protocols.oef_search.message import OefSearchMessage -logger = logging.getLogger("aea.packages.fetchai.connections.local") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.local") TARGET = 0 MESSAGE_ID = 1 RESPONSE_TARGET = MESSAGE_ID RESPONSE_MESSAGE_ID = MESSAGE_ID + 1 STUB_DIALOGUE_ID = 0 -DEFAULT_OEF = "default_oef" -PUBLIC_ID = PublicId.from_str("fetchai/local:0.4.0") +PUBLIC_ID = PublicId.from_str("fetchai/local:0.5.0") class LocalNode: """A light-weight local implementation of a OEF Node.""" - def __init__(self, loop: AbstractEventLoop = None): + def __init__( + self, loop: AbstractEventLoop = None, logger: logging.Logger = _default_logger + ): """ Initialize a local (i.e. non-networked) implementation of an OEF Node. @@ -63,6 +68,9 @@ def __init__(self, loop: AbstractEventLoop = None): self._out_queues = {} # type: Dict[str, asyncio.Queue] self._receiving_loop_task = None # type: Optional[asyncio.Task] + self.address: Optional[Address] = None + self._dialogues: Optional[OefSearchDialogues] = None + self.logger = logger def __enter__(self): """Start the local node.""" @@ -79,10 +87,10 @@ def _run_loop(self): This method is supposed to be run only in the Multiplexer thread. """ - logger.debug("Starting threaded asyncio loop...") + self.logger.debug("Starting threaded asyncio loop...") asyncio.set_event_loop(self._loop) self._loop.run_forever() - logger.debug("Asyncio loop has been stopped.") + self.logger.debug("Asyncio loop has been stopped.") async def connect( self, address: Address, writer: asyncio.Queue @@ -101,6 +109,10 @@ async def connect( q = self._in_queue # type: asyncio.Queue self._out_queues[address] = writer + self.address = address + self._dialogues = OefSearchDialogues( + agent_address=str(OEFLocalConnection.connection_id) + ) return q def start(self): @@ -110,7 +122,7 @@ def start(self): self._receiving_loop_task = asyncio.run_coroutine_threadsafe( self.receiving_loop(), loop=self._loop ) - logger.debug("Local node has been started.") + self.logger.debug("Local node has been started.") def stop(self): """Stop the node.""" @@ -127,9 +139,9 @@ async def receiving_loop(self): while True: envelope = await self._in_queue.get() if envelope is None: - logger.debug("Receiving loop terminated.") + self.logger.debug("Receiving loop terminated.") return - logger.debug("Handling envelope: {}".format(envelope)) + self.logger.debug("Handling envelope: {}".format(envelope)) await self._handle_envelope(envelope) async def _handle_envelope(self, envelope: Envelope) -> None: @@ -138,7 +150,7 @@ async def _handle_envelope(self, envelope: Envelope) -> None: :param envelope: the envelope :return: None """ - if envelope.protocol_id == ProtocolId.from_str("fetchai/oef_search:0.3.0"): + if envelope.protocol_id == ProtocolId.from_str("fetchai/oef_search:0.4.0"): await self._handle_oef_message(envelope) else: await self._handle_agent_message(envelope) @@ -152,20 +164,24 @@ async def _handle_oef_message(self, envelope: Envelope) -> None: assert isinstance( envelope.message, OefSearchMessage ), "Message not of type OefSearchMessage" - oef_message = cast(OefSearchMessage, envelope.message) - sender = envelope.sender + oef_message, dialogue = self._get_message_and_dialogue(envelope) + + if dialogue is None: + self.logger.warning( + "Could not create dialogue for message={}".format(oef_message) + ) + return + if oef_message.performative == OefSearchMessage.Performative.REGISTER_SERVICE: - await self._register_service(sender, oef_message.service_description) + await self._register_service( + envelope.sender, oef_message.service_description + ) elif ( oef_message.performative == OefSearchMessage.Performative.UNREGISTER_SERVICE ): - await self._unregister_service( - sender, oef_message.dialogue_reference, oef_message.service_description - ) + await self._unregister_service(oef_message, dialogue) elif oef_message.performative == OefSearchMessage.Performative.SEARCH_SERVICES: - await self._search_services( - sender, oef_message.dialogue_reference, oef_message.query - ) + await self._search_services(oef_message, dialogue) else: # request not recognized pass @@ -191,7 +207,7 @@ async def _handle_agent_message(self, envelope: Envelope) -> None: ) error_envelope = Envelope( to=envelope.sender, - sender=DEFAULT_OEF, + sender=str(OEFLocalConnection.connection_id), protocol_id=DefaultMessage.protocol_id, message=msg, ) @@ -214,32 +230,32 @@ async def _register_service( self.services[address].append(service_description) async def _unregister_service( - self, - address: Address, - dialogue_reference: Tuple[str, str], - service_description: Description, + self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue, ) -> None: """ Unregister a service agent. - :param address: the address of the service agent to be unregistered. - :param dialogue_reference: the dialogue_reference. - :param service_description: the description of the service agent to be unregistered. + :param oef_search_msg: the incoming message. + :param dialogue: the dialogue. :return: None """ + service_description = oef_search_msg.service_description + address = oef_search_msg.sender async with self._lock: if address not in self.services: msg = OefSearchMessage( performative=OefSearchMessage.Performative.OEF_ERROR, - dialogue_reference=(dialogue_reference[0], dialogue_reference[0]), - target=RESPONSE_TARGET, - message_id=RESPONSE_MESSAGE_ID, + target=oef_search_msg.message_id, + message_id=oef_search_msg.message_id + 1, oef_error_operation=OefSearchMessage.OefErrorOperation.UNREGISTER_SERVICE, + dialogue_reference=dialogue.dialogue_label.dialogue_reference, ) + msg.counterparty = oef_search_msg.sender + assert dialogue.update(msg) envelope = Envelope( - to=address, - sender=DEFAULT_OEF, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) await self._send(envelope) @@ -249,7 +265,7 @@ async def _unregister_service( self.services.pop(address) async def _search_services( - self, address: Address, dialogue_reference: Tuple[str, str], query: Query + self, oef_search_msg: OefSearchMessage, dialogue: OefSearchDialogue, ) -> None: """ Search the agents in the local Service Directory, and send back the result. @@ -257,11 +273,11 @@ async def _search_services( This is actually a dummy search, it will return all the registered agents with the specified data model. If the data model is not specified, it will return all the agents. - :param address: the source of the search request. - :param dialogue_reference: the dialogue_reference. - :param query: the query that constitutes the search. + :param oef_search_msg: the message. + :param dialogue: the dialogue. :return: None """ + query = oef_search_msg.query result = [] # type: List[str] if query.model is None: result = list(set(self.services.keys())) @@ -273,25 +289,50 @@ async def _search_services( msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_RESULT, - dialogue_reference=(dialogue_reference[0], dialogue_reference[0]), - target=RESPONSE_TARGET, - message_id=RESPONSE_MESSAGE_ID, + target=oef_search_msg.message_id, + dialogue_reference=dialogue.dialogue_label.dialogue_reference, + message_id=oef_search_msg.message_id + 1, agents=tuple(sorted(set(result))), ) + msg.counterparty = oef_search_msg.sender + assert dialogue.update(msg) + envelope = Envelope( - to=address, - sender=DEFAULT_OEF, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) await self._send(envelope) + def _get_message_and_dialogue( + self, envelope: Envelope + ) -> Tuple[OefSearchMessage, Optional[OefSearchDialogue]]: + """ + Get a message copy and dialogue related to this message. + + :param envelope: incoming envelope + + :return: Tuple[MEssage, Optional[Dialogue]] + """ + assert self._dialogues is not None, "Call connect before!" + message_orig = cast(OefSearchMessage, envelope.message) + message = copy.copy( + message_orig + ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + message.is_incoming = True # TODO: fix; should be done by framework + message.counterparty = ( + message_orig.sender + ) # TODO: fix; should be done by framework + dialogue = cast(OefSearchDialogue, self._dialogues.update(message)) + return message, dialogue + async def _send(self, envelope: Envelope): """Send a message.""" destination = envelope.to destination_queue = self._out_queues[destination] destination_queue._loop.call_soon_threadsafe(destination_queue.put_nowait, envelope) # type: ignore # pylint: disable=protected-access - logger.debug("Send envelope {}".format(envelope)) + self.logger.debug("Send envelope {}".format(envelope)) async def disconnect(self, address: Address) -> None: """ @@ -331,24 +372,28 @@ def __init__(self, local_node: Optional[LocalNode] = None, **kwargs): async def connect(self) -> None: """Connect to the local OEF Node.""" assert self._local_node is not None, "No local node set!" - if not self.connection_status.is_connected: - self._reader = Queue() - self._writer = await self._local_node.connect(self.address, self._reader) - self.connection_status.is_connected = True + if self.is_connected: + return + self._state.set(ConnectionStates.connecting) + self._reader = Queue() + self._writer = await self._local_node.connect(self.address, self._reader) + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """Disconnect from the local OEF Node.""" assert self._local_node is not None, "No local node set!" - if self.connection_status.is_connected: - assert self._reader is not None - await self._local_node.disconnect(self.address) - await self._reader.put(None) - self._reader, self._writer = None, None - self.connection_status.is_connected = False + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) + assert self._reader is not None + await self._local_node.disconnect(self.address) + await self._reader.put(None) + self._reader, self._writer = None, None + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: Envelope): """Send a message.""" - if not self.connection_status.is_connected: + if not self.is_connected: raise AEAConnectionError( "Connection not established yet. Please use 'connect()'." ) @@ -360,7 +405,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: :return: the envelope received, or None. """ - if not self.connection_status.is_connected: + if not self.is_connected: raise AEAConnectionError( "Connection not established yet. Please use 'connect()'." ) diff --git a/packages/fetchai/connections/local/connection.yaml b/packages/fetchai/connections/local/connection.yaml index 57c124dd66..10b9dc44ee 100644 --- a/packages/fetchai/connections/local/connection.yaml +++ b/packages/fetchai/connections/local/connection.yaml @@ -1,15 +1,16 @@ name: local author: fetchai -version: 0.4.0 +version: 0.5.0 description: The local connection provides a stub for an OEF node. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmbK7MtyAVqh2LmSh9TY6yBZqfWaAXURP4rQGATyP2hTKC __init__.py: QmeeoX5E38Ecrb1rLdeFyyxReHLrcJoETnBcPbcNWVbiKG - connection.py: QmTNcjJSBWRrB5srBTEpjRfbvDuxJtsFcdhYJ1UYsLGqKT + connection.py: QmYoRmLSYYS88HdzmT3sQxMDkk5CwCiWudAvfRyArCoQAj fingerprint_ignore_patterns: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 class_name: OEFLocalConnection config: {} excluded_protocols: [] diff --git a/packages/fetchai/connections/oef/README.md b/packages/fetchai/connections/oef/README.md new file mode 100644 index 0000000000..b8faac57fb --- /dev/null +++ b/packages/fetchai/connections/oef/README.md @@ -0,0 +1,7 @@ +# OEF connection + +Connection to interact with an OEF node (https://fetchai.github.io/oef-sdk-python/user/introduction.html). + +## Usage + +Register/unregister services, perform searches using `fetchai/oef_search:0.4.0` protocol and send messages of any protocol to other agents connected to the same node. diff --git a/packages/fetchai/connections/oef/connection.py b/packages/fetchai/connections/oef/connection.py index ecba75c4e5..3c44fe0488 100644 --- a/packages/fetchai/connections/oef/connection.py +++ b/packages/fetchai/connections/oef/connection.py @@ -19,6 +19,7 @@ """Extension to the OEF Python SDK.""" import asyncio +import copy import logging from asyncio import AbstractEventLoop, CancelledError from concurrent.futures.thread import ThreadPoolExecutor @@ -31,7 +32,7 @@ from oef.messages import CFP_TYPES, PROPOSE_TYPES from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.helpers.dialogue.base import Dialogue as BaseDialogue from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.mail.base import Address, Envelope @@ -55,7 +56,7 @@ STUB_MESSAGE_ID = 0 STUB_DIALOGUE_ID = 0 DEFAULT_OEF = "oef" -PUBLIC_ID = PublicId.from_str("fetchai/oef:0.6.0") +PUBLIC_ID = PublicId.from_str("fetchai/oef:0.7.0") class OefSearchDialogues(BaseOefSearchDialogues): @@ -387,7 +388,7 @@ def send(self, envelope: Envelope) -> None: ) raise ValueError("Cannot send message.") - if envelope.protocol_id == PublicId.from_str("fetchai/oef_search:0.3.0"): + if envelope.protocol_id == PublicId.from_str("fetchai/oef_search:0.4.0"): self.send_oef_message(envelope) else: self.send_default_message(envelope) @@ -408,7 +409,9 @@ def send_oef_message(self, envelope: Envelope) -> None: assert isinstance( envelope.message, OefSearchMessage ), "Message not of type OefSearchMessage" - oef_message = cast(OefSearchMessage, envelope.message) + oef_message_original = cast(OefSearchMessage, envelope.message) + oef_message = copy.copy(oef_message_original) + oef_message.counterparty = oef_message_original.sender oef_message.is_incoming = True # TODO: fix oef_search_dialogue = cast( OefSearchDialogue, self.oef_search_dialogues.update(oef_message) @@ -439,7 +442,7 @@ def send_oef_message(self, envelope: Envelope) -> None: oef_query = OEFObjectTranslator.to_oef_query(query) self.search_services(self.oef_msg_id, oef_query) else: - raise ValueError("OEF request not recognized.") + raise ValueError("OEF request not recognized.") # pragma: nocover def handle_failure( # pylint: disable=no-self-use self, exception: Exception, conn @@ -539,20 +542,19 @@ async def connect(self) -> None: :return: None :raises Exception if the connection to the OEF fails. """ - if self.connection_status.is_connected: + if self.is_connected: return + self._state.set(ConnectionStates.connecting) try: self.channel.aea_logger = self.logger - self.connection_status.is_connecting = True self._loop = asyncio.get_event_loop() await self.channel.connect() - self.connection_status.is_connecting = False - self.connection_status.is_connected = True self._connection_check_task = self._loop.create_task( self._connection_check() ) + self._state.set(ConnectionStates.connected) except (CancelledError, Exception) as e: # pragma: no cover - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) raise e async def _connection_check(self) -> None: @@ -563,16 +565,15 @@ async def _connection_check(self) -> None: :return: None """ - while self.connection_status.is_connected: + while self.is_connected: await asyncio.sleep(2.0) if not self.channel.get_state() == "connected": # pragma: no cover - self.connection_status.is_connected = False - self.connection_status.is_connecting = True + self._state.set(ConnectionStates.connecting) self.logger.warning( "Lost connection to OEFChannel. Retrying to connect soon ..." ) await self.channel.connect() - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) self.logger.warning( "Successfully re-established connection to OEFChannel." ) @@ -583,16 +584,16 @@ async def disconnect(self) -> None: :return: None """ - assert ( - self.connection_status.is_connected or self.connection_status.is_connecting - ), "Call connect before disconnect." - self.connection_status.is_connected = False - self.connection_status.is_connecting = False + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) if self._connection_check_task is not None: self._connection_check_task.cancel() self._connection_check_task = None await self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) + async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """ Receive an envelope. Blocking. @@ -620,5 +621,5 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelope to send. :return: None """ - if self.connection_status.is_connected: + if self.is_connected: self.channel.send(envelope) diff --git a/packages/fetchai/connections/oef/connection.yaml b/packages/fetchai/connections/oef/connection.yaml index 9e08dbe7cf..0699f3035d 100644 --- a/packages/fetchai/connections/oef/connection.yaml +++ b/packages/fetchai/connections/oef/connection.yaml @@ -1,18 +1,19 @@ name: oef author: fetchai -version: 0.6.0 +version: 0.7.0 description: The oef connection provides a wrapper around the OEF SDK for connection with the OEF search and communication node. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmQEMSTNugha3vQg9xbqhvqNbg4yBtzbcaC1MsUyAQvFPD __init__.py: QmUAen8tmoBHuCerjA3FSGKJRLG6JYyUS3chuWzPxKYzez - connection.py: QmXutRqmffjc9xL6F8bGQ9dBPkZUP6GRZUtxsKzmdmd8G6 + connection.py: QmYPvavTKJBL3ZFQRDS86GqdgatEVbkLbV56tvSNZMY71Y object_translator.py: QmNYd7ikc3nYZMCXjyfen2nENHpNCZws44MNEDbzAsHrGu fingerprint_ignore_patterns: [] protocols: -- fetchai/default:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/oef_search:0.4.0 class_name: OEFConnection config: addr: 127.0.0.1 diff --git a/packages/fetchai/connections/p2p_client/connection.py b/packages/fetchai/connections/p2p_client/connection.py index abfe90c6f8..4500fb8694 100644 --- a/packages/fetchai/connections/p2p_client/connection.py +++ b/packages/fetchai/connections/p2p_client/connection.py @@ -30,7 +30,7 @@ from fetch.p2p.api.http_calls import HTTPCalls from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import AEAConnectionError, Address, Envelope logger = logging.getLogger("aea.packages.fetchai.connections.p2p_client") @@ -174,12 +174,14 @@ async def connect(self) -> None: :return: None """ - if not self.connection_status.is_connected: - self.channel.logger = self.logger - self.connection_status.is_connected = True - self.channel.in_queue = asyncio.Queue() - self.channel.loop = self.loop - self.channel.connect() + if self.is_connected: + return + self._state.set(ConnectionStates.connecting) + self.channel.logger = self.logger + self.channel.in_queue = asyncio.Queue() + self.channel.loop = self.loop + self.channel.connect() + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """ @@ -187,9 +189,11 @@ async def disconnect(self) -> None: :return: None """ - if self.connection_status.is_connected: - self.connection_status.is_connected = False - self.channel.disconnect() + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) + self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope") -> None: """ @@ -198,7 +202,7 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelop :return: None """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover @@ -210,7 +214,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: :return: the envelope received, or None. """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover diff --git a/packages/fetchai/connections/p2p_client/connection.yaml b/packages/fetchai/connections/p2p_client/connection.yaml index 53a561f431..c8b98ea8f4 100644 --- a/packages/fetchai/connections/p2p_client/connection.yaml +++ b/packages/fetchai/connections/p2p_client/connection.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmdwnPo8iC2uqf9CmB4ocbh6HP2jcgCtuFdS4djuajp6Li - connection.py: Qmb9Fu43w6GJYJCnAgZm27wNT9fC6EHDZqBYR8SD1QUmV2 + connection.py: QmbKTiHPPmYXXLttjJiKAFmVtxeuK7HAUaewWnE2zv2tRb fingerprint_ignore_patterns: [] protocols: [] class_name: PeerToPeerConnection diff --git a/packages/fetchai/connections/p2p_libp2p/README.md b/packages/fetchai/connections/p2p_libp2p/README.md new file mode 100644 index 0000000000..5b62432697 --- /dev/null +++ b/packages/fetchai/connections/p2p_libp2p/README.md @@ -0,0 +1,16 @@ +# P2P Libp2p Connection + +This connection enables point-to-point secure end-to-end encrypted communication between agents in a fully decentralized way. +The connection deploys a node that collectively maintains a distributed hash table (DHT) along with other nodes in the same network. +The DHT provides proper messages delivery by mapping agents addresses to their locations. + +## Usage + +First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p:0.5.0`. + +Next, ensure that the connection is properly configured by setting: + +- `local_uri` to the local ip address and port number that the node should use, in format `${ip}:${port}` +- `public_uri` to the external ip address and port number allocated for the node, can be the same as `local_uri` if running locally +- `entry_peers` to a list of multiaddresses of already deployed nodes to join their network, should be empty for genesis node +- `delegate_uri` to the ip address and port number for the delegate service, leave empty to disable the service diff --git a/packages/fetchai/connections/p2p_libp2p/connection.py b/packages/fetchai/connections/p2p_libp2p/connection.py index f5c5f9e944..63b9dd1dbb 100644 --- a/packages/fetchai/connections/p2p_libp2p/connection.py +++ b/packages/fetchai/connections/p2p_libp2p/connection.py @@ -20,7 +20,6 @@ """This module contains the p2p libp2p connection.""" import asyncio -import distutils import errno import logging import os @@ -29,19 +28,20 @@ import subprocess # nosec import tempfile from asyncio import AbstractEventLoop, CancelledError +from pathlib import Path from random import randint from threading import Thread from typing import IO, List, Optional, Sequence, cast from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.crypto.base import Crypto from aea.crypto.registries import make_crypto from aea.exceptions import AEAException from aea.mail.base import Address, Envelope -logger = logging.getLogger("aea.packages.fetchai.connections.p2p_libp2p") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.p2p_libp2p") LIBP2P_NODE_MODULE = str(os.path.abspath(os.path.dirname(__file__))) @@ -58,7 +58,7 @@ # TOFIX(LR) not sure is needed LIBP2P = "libp2p" -PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p:0.5.0") +PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p:0.6.0") MultiAddr = str @@ -68,8 +68,8 @@ async def _golang_module_build_async( path: str, log_file_desc: IO[str], - loop: Optional[asyncio.AbstractEventLoop] = None, timeout: float = LIBP2P_NODE_DEPS_DOWNLOAD_TIMEOUT, + logger: logging.Logger = _default_logger, ) -> int: """ Builds go module located at `path`, downloads necessary dependencies @@ -102,7 +102,11 @@ async def _golang_module_build_async( def _golang_module_run( - path: str, name: str, args: Sequence[str], log_file_desc: IO[str] + path: str, + name: str, + args: Sequence[str], + log_file_desc: IO[str], + logger: logging.Logger = _default_logger, ) -> subprocess.Popen: """ Runs a built module located at `path` @@ -222,19 +226,22 @@ def __init__( entry_peers: Optional[Sequence[MultiAddr]] = None, log_file: Optional[str] = None, env_file: Optional[str] = None, + logger: logging.Logger = _default_logger, ): """ Initialize a p2p libp2p node. + :param agent_addr: the agent address. :param key: secp256k1 curve private key. - :param source: the source path + :param module_path: the module path. :param clargs: the command line arguments for the libp2p node :param uri: libp2p node ip address and port number in format ipaddress:port. :param public_uri: libp2p node public ip address and port number in format ipaddress:port. - :param delegation_uri: libp2p node delegate service ip address and port number in format ipaddress:port. + :param delegate_uri: libp2p node delegate service ip address and port number in format ipaddress:port. :param entry_peers: libp2p entry peers multiaddresses. :param log_file: the logfile path for the libp2p node :param env_file: the env file path for the exchange of environment variables + :param logger: the logger. """ self.address = agent_addr @@ -308,8 +315,10 @@ async def start(self) -> None: # build the node # TOFIX(LR) fix async version - logger.info("Downloading golang dependencies. This may take a while...") - returncode = await _golang_module_build_async(self.source, self._log_file_desc) + self.logger.info("Downloading golang dependencies. This may take a while...") + returncode = await _golang_module_build_async( + self.source, self._log_file_desc, logger=self.logger + ) with open(self.log_file, "r") as f: self.logger.debug(f.read()) node_log = "" @@ -383,8 +392,9 @@ async def _connect(self) -> None: """ if self._connection_attempts == 1: with open(self.log_file, "r") as f: - self.logger.debug("Couldn't connect to libp2p p2p process, logs:") - self.logger.debug(f.read()) + self.logger.error("Couldn't connect to libp2p p2p process, logs:") + self.logger.error(f.read()) + self.stop() raise Exception("Couldn't connect to libp2p p2p process") # TOFIX(LR) use proper exception self._connection_attempts -= 1 @@ -405,7 +415,7 @@ async def _connect(self) -> None: ) except OSError as e: if e.errno == errno.ENXIO: - logger.debug("Sleeping for {}...".format(self._connection_timeout)) + self.logger.debug("Sleeping for {}...".format(self._connection_timeout)) await asyncio.sleep(self._connection_timeout) await self._connect() return @@ -429,8 +439,7 @@ async def _connect(self) -> None: self.multiaddrs = self.get_libp2p_node_multiaddrs() self.logger.info("My libp2p addresses: {}".format(self.multiaddrs)) - @asyncio.coroutine - def write(self, data: bytes) -> None: + async def write(self, data: bytes) -> None: """ Write to the writer stream. @@ -587,7 +596,7 @@ def __init__(self, **kwargs): "At least one Entry Peer should be provided when node can not be publically reachable" ) if delegate_uri is not None: # pragma: no cover - logger.warning( + self.logger.warning( "Ignoring Delegate Uri configuration as node can not be publically reachable" ) else: @@ -599,9 +608,10 @@ def __init__(self, **kwargs): ) # libp2p local node - logger.debug("Public key used by libp2p node: {}".format(key.public_key)) - self.libp2p_workdir = tempfile.mkdtemp() - distutils.dir_util.copy_tree(LIBP2P_NODE_MODULE, self.libp2p_workdir) + self.logger.debug("Public key used by libp2p node: {}".format(key.public_key)) + temp_dir = tempfile.mkdtemp() + self.libp2p_workdir = os.path.join(temp_dir, "libp2p_workdir") + shutil.copytree(LIBP2P_NODE_MODULE, self.libp2p_workdir) self.node = Libp2pNode( self.address, @@ -614,6 +624,7 @@ def __init__(self, **kwargs): entry_peers, log_file, env_file, + self.logger, ) self._in_queue = None # type: Optional[asyncio.Queue] @@ -635,23 +646,22 @@ async def connect(self) -> None: :return: None """ - if self.connection_status.is_connected: # pragma: no cover + if self.is_connected: return + self._state.set(ConnectionStates.connecting) try: # start libp2p node - self.connection_status.is_connecting = True + self._state.set(ConnectionStates.connecting) self.node.logger = self.logger await self.node.start() - self.connection_status.is_connecting = False - self.connection_status.is_connected = True - # starting receiving msgs self._in_queue = asyncio.Queue() self._receive_from_node_task = asyncio.ensure_future( self._receive_from_node(), loop=self._loop ) + self._state.set(ConnectionStates.connected) except (CancelledError, Exception) as e: - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) raise e async def disconnect(self) -> None: @@ -660,21 +670,20 @@ async def disconnect(self) -> None: :return: None """ - assert ( - self.connection_status.is_connected or self.connection_status.is_connecting - ), "Call connect before disconnect." - self.connection_status.is_connected = False - self.connection_status.is_connecting = False + if self.is_disconnected: + return + self._state.set(ConnectionStates.disconnecting) if self._receive_from_node_task is not None: self._receive_from_node_task.cancel() self._receive_from_node_task = None self.node.stop() if self.libp2p_workdir is not None: - distutils.dir_util.remove_tree(self.libp2p_workdir) + shutil.rmtree(Path(self.libp2p_workdir).parent) if self._in_queue is not None: self._in_queue.put_nowait(None) else: self.logger.debug("Called disconnect when input queue not initialized.") + self._state.set(ConnectionStates.disconnected) async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """ @@ -688,7 +697,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: if data is None: self.logger.debug("Received None.") self.node.stop() - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) return None # TOFIX(LR) attempt restarting the node? self.logger.debug("Received data: {}".format(data)) diff --git a/packages/fetchai/connections/p2p_libp2p/connection.yaml b/packages/fetchai/connections/p2p_libp2p/connection.yaml index e627ed343e..930cb63bd9 100644 --- a/packages/fetchai/connections/p2p_libp2p/connection.yaml +++ b/packages/fetchai/connections/p2p_libp2p/connection.yaml @@ -1,18 +1,19 @@ name: p2p_libp2p author: fetchai -version: 0.5.0 +version: 0.6.0 description: The p2p libp2p connection implements an interface to standalone golang go-libp2p node that can exchange aea envelopes with other agents connected to the same DHT. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmPvrvhRUQEt2hruwoY73xwwdNdQyssBuxK9XNACSjvQRS __init__.py: QmYQuLNyQ8WTjgRYAoKAzoJEb7ocKXvM2hTyK4hsGch5D6 aea/api.go: QmW5fUpVZmV3pxgoakm3RvsvCGC6FwT2XprcqXHM8rBXP5 aea/envelope.pb.go: QmRfUNGpCeVJfsW3H1MzCN4pwDWgumfyWufVFp6xvUjjug aea/envelope.proto: QmSC8EGCKiNFR2vf5bSWymSzYDFMipQW9aQVMwPzQoKb4n - connection.py: QmPMUYiH5PocuLfUt3zTz4cfn17KfxZTLMpNtBRtDdx4bp - dht/dhtclient/dhtclient.go: QmNnU1pVCUtj8zJ1Pz5eMk9sznsjPFSJ9qDkzbrNwzEecV + connection.py: QmRDSgAPjY9D2CZVnGth3xKsncAtaBH8kob42Teu6zbnd4 + dht/dhtclient/dhtclient.go: QmasA3GrgswTnUJoffBzeeqxeT3GjLu6foN6PHJhWNpMMa dht/dhtclient/dhtclient_test.go: QmPfnHSHXtbaW5VYuq1QsKQWey64pUEvLEaKKkT9eAcmws dht/dhtclient/options.go: QmPorj38wNrxGrzsbFe5wwLmiHzxbTJ2VsgvSd8tLDYS8s dht/dhtnode/dhtnode.go: QmbyhgbCSAbQ1QsDw7FM7Nt5sZcvhbupA1jv5faxutbV7N @@ -24,7 +25,7 @@ fingerprint: go.mod: QmacqAAxC3dkydmfbEyVWVkMDmZECTWKZcBoPyRSnheQzD go.sum: Qmbu57aSPSqanJ1xHNmMHAqLL8nvCV61URknizsKJDvenG libp2p_node.go: QmZQoa9RGdVkcE8Hu9kVAdSh3jRUveScDhG84UkSY6N3vz - utils/utils.go: QmUsNceCQKYfaLqJN8YhTkPoB7aD2ahn6gvFG1iHKeimax + utils/utils.go: QmSdLqVNwtupDKxvE2qbCEEoACCaRG2ccNPq6p34YtrDqA fingerprint_ignore_patterns: [] protocols: [] class_name: P2PLibp2pConnection diff --git a/packages/fetchai/connections/p2p_libp2p/dht/dhtclient/dhtclient.go b/packages/fetchai/connections/p2p_libp2p/dht/dhtclient/dhtclient.go index e0a23e872c..b9c69c0d39 100644 --- a/packages/fetchai/connections/p2p_libp2p/dht/dhtclient/dhtclient.go +++ b/packages/fetchai/connections/p2p_libp2p/dht/dhtclient/dhtclient.go @@ -53,7 +53,8 @@ func ignore(err error) { } const ( - newStreamTimeout = 5 * time.Second + newStreamTimeout = 5 * 60 * time.Second // includes peer restart + sleepTimeIncreaseFactor = 2 // multiplicative increase ) // DHTClient A restricted libp2p node for the Agents Communication Network @@ -275,6 +276,22 @@ func (dhtClient *DHTClient) RouteEnvelope(envel *aea.Envelope) error { ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeout) defer cancel() stream, err := dhtClient.routedHost.NewStream(ctx, dhtClient.relayPeer, dhtnode.AeaAddressStream) + sleepTime := 200 * time.Millisecond + for err != nil { + lerror(err). + Str("op", "route"). + Str("target", target). + Msgf("couldn't open stream to relay, retrying in %s", sleepTime) + select { + default: + time.Sleep(sleepTime) + sleepTime = sleepTime * sleepTimeIncreaseFactor + stream, err = dhtClient.routedHost.NewStream(ctx, dhtClient.relayPeer, dhtnode.AeaAddressStream) + case <-ctx.Done(): + break + } + } + err = dhtClient.registerAgentAddress() if err != nil { lerror(err). Str("op", "route"). @@ -355,6 +372,22 @@ func (dhtClient *DHTClient) RouteEnvelope(envel *aea.Envelope) error { ctx, cancel = context.WithTimeout(context.Background(), newStreamTimeout) defer cancel() stream, err = dhtClient.routedHost.NewStream(ctx, peerID, dhtnode.AeaEnvelopeStream) + sleepTime = 200 * time.Millisecond + for err != nil { + lerror(err). + Str("op", "route"). + Str("target", target). + Msgf("timeout, couldn't open stream to target %s, retrying in %s", peerID, sleepTime) + select { + default: + time.Sleep(sleepTime) + sleepTime = sleepTime * sleepTimeIncreaseFactor + stream, err = dhtClient.routedHost.NewStream(ctx, peerID, dhtnode.AeaEnvelopeStream) + case <-ctx.Done(): + break + } + } + err = dhtClient.registerAgentAddress() if err != nil { lerror(err). Str("op", "route"). @@ -453,6 +486,21 @@ func (dhtClient *DHTClient) registerAgentAddress() error { ctx, cancel := context.WithTimeout(context.Background(), newStreamTimeout) defer cancel() stream, err := dhtClient.routedHost.NewStream(ctx, dhtClient.relayPeer, dhtnode.AeaRegisterRelayStream) + sleepTime := 200 * time.Millisecond + for err != nil { + lerror(err). + Str("op", "register"). + Str("addr", dhtClient.myAgentAddress). + Msgf("timeout, couldn't open stream to relay peer, retrying in %s", sleepTime) + select { + default: + time.Sleep(sleepTime) + sleepTime = sleepTime * sleepTimeIncreaseFactor + stream, err = dhtClient.routedHost.NewStream(ctx, dhtClient.relayPeer, dhtnode.AeaRegisterRelayStream) + case <-ctx.Done(): + break + } + } if err != nil { lerror(err). Str("op", "register"). diff --git a/packages/fetchai/connections/p2p_libp2p/utils/utils.go b/packages/fetchai/connections/p2p_libp2p/utils/utils.go index cbd2c6ab49..250ed82fa3 100644 --- a/packages/fetchai/connections/p2p_libp2p/utils/utils.go +++ b/packages/fetchai/connections/p2p_libp2p/utils/utils.go @@ -233,13 +233,10 @@ func IDFromFetchAIPublicKey(public_key string) (peer.ID, error) { // WriteBytesConn send bytes to `conn` func WriteBytesConn(conn net.Conn, data []byte) error { size := uint32(len(data)) - buf := make([]byte, 4) + buf := make([]byte, 4, 4+size) binary.BigEndian.PutUint32(buf, size) + buf = append(buf, data...) _, err := conn.Write(buf) - if err != nil { - return err - } - _, err = conn.Write(data) return err } diff --git a/packages/fetchai/connections/p2p_libp2p_client/README.md b/packages/fetchai/connections/p2p_libp2p_client/README.md new file mode 100644 index 0000000000..489b5d1b22 --- /dev/null +++ b/packages/fetchai/connections/p2p_libp2p_client/README.md @@ -0,0 +1,15 @@ +# P2P Libp2p Client Connection + +A lightweight tcp connection to a libp2p DHT node. + +It allows for using the DHT without having to deploy a node by delegating its communication traffic to an already running DHT node with delegate service enabled. + + +## Usage + +First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p_client:0.5.0`. + +Next, ensure that the connection is properly configured by setting: + +- `nodes` to a list of `uri`s, connection will choose the delegate randomly +- `uri` to the public ip address and port number of the delegate service of a running DHT node, in format `${ip|dns}:${port}` diff --git a/packages/fetchai/connections/p2p_libp2p_client/connection.py b/packages/fetchai/connections/p2p_libp2p_client/connection.py index 6d5404c601..2e4c562b18 100644 --- a/packages/fetchai/connections/p2p_libp2p_client/connection.py +++ b/packages/fetchai/connections/p2p_libp2p_client/connection.py @@ -29,13 +29,13 @@ from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.crypto.registries import make_crypto from aea.mail.base import Envelope logger = logging.getLogger("aea.packages.fetchai.connections.p2p_libp2p_client") -PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_client:0.4.0") +PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_client:0.5.0") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] @@ -156,13 +156,15 @@ async def connect(self) -> None: :return: None """ - if self.connection_status.is_connected: # pragma: no cover + if self.is_connected: # pragma: nocover return + + self._state.set(ConnectionStates.connecting) + if self._loop is None: self._loop = asyncio.get_event_loop() try: # connect libp2p client - self.connection_status.is_connecting = True # connect the tcp socket self._reader, self._writer = await asyncio.open_connection( @@ -174,9 +176,6 @@ async def connect(self) -> None: # send agent address to node await self._setup_connection() - self.connection_status.is_connecting = False - self.connection_status.is_connected = True - self.logger.info( "Successfully connected to libp2p node {}".format(str(self.node_uri)) ) @@ -186,8 +185,9 @@ async def connect(self) -> None: self._process_messages_task = asyncio.ensure_future( self._process_messages(), loop=self._loop ) + self._state.set(ConnectionStates.connected) except (CancelledError, Exception) as e: - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) raise e async def _setup_connection(self): @@ -200,15 +200,11 @@ async def disconnect(self) -> None: :return: None """ - assert ( - self.connection_status.is_connected or self.connection_status.is_connecting - ), "Call connect before disconnect." - self.connection_status.is_connected = False - self.connection_status.is_connecting = False - + if self.is_disconnected: # pragma: nocover + return assert self._process_messages_task is not None assert self._writer is not None - + self._state.set(ConnectionStates.disconnecting) if self._process_messages_task is not None: self._process_messages_task.cancel() # TOFIX(LR) mypy issue https://github.com/python/mypy/issues/8546 @@ -225,6 +221,7 @@ async def disconnect(self) -> None: self._in_queue.put_nowait(None) else: # pragma: no cover self.logger.debug("Called disconnect when input queue not initialized.") + self._state.set(ConnectionStates.disconnected) async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """ @@ -237,10 +234,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: data = await self._in_queue.get() if data is None: self.logger.debug("Received None.") - if ( - self._connection_status.is_connected - or self._connection_status.is_connecting - ): + if not self.is_disconnected: await self.disconnect() return None # TOFIX(LR) attempt restarting the node? diff --git a/packages/fetchai/connections/p2p_libp2p_client/connection.yaml b/packages/fetchai/connections/p2p_libp2p_client/connection.yaml index 382d2404d2..457a44f82a 100644 --- a/packages/fetchai/connections/p2p_libp2p_client/connection.yaml +++ b/packages/fetchai/connections/p2p_libp2p_client/connection.yaml @@ -1,14 +1,15 @@ name: p2p_libp2p_client author: fetchai -version: 0.4.0 +version: 0.5.0 description: The libp2p client connection implements a tcp connection to a running libp2p node as a traffic delegate to send/receive envelopes to/from agents in the DHT. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: Qmc8eDQRX15bcJZr8J9ty9EZmXMZN8VUtufkkCm35LWU55 __init__.py: QmT1FEHkPGMHV5oiVEfQHHr25N2qdZxydSNRJabJvYiTgf - connection.py: QmSWqusLpeET9U6zQyVGYppsoLY7MSDkxocAdUNc7HYDtu + connection.py: QmPY3yzUkioLkPATZR6K44NwyxCxRXiGf7WHi5j9x5Zg9j fingerprint_ignore_patterns: [] protocols: [] class_name: P2PLibp2pClientConnection diff --git a/packages/fetchai/connections/p2p_stub/README.md b/packages/fetchai/connections/p2p_stub/README.md new file mode 100644 index 0000000000..911f0f5feb --- /dev/null +++ b/packages/fetchai/connections/p2p_stub/README.md @@ -0,0 +1,9 @@ +# p2p stub connection + +Simple file based connection to perform interaction between multiple local agents. + +## Usage + +First, add the connection to your AEA project: `aea add connection fetchai/p2p_stub:0.5.0`. + +Optionally, in the `connection.yaml` file under `config` set the `namespace_dir` to the desired file path. The `p2p_stub` connection reads encoded envelopes from its input file and writes encoded envelopes to its output file. Multiple agents can be pointed to the same `namespace_dir` and are then able to exchange envelopes via the file system. diff --git a/packages/fetchai/connections/p2p_stub/connection.py b/packages/fetchai/connections/p2p_stub/connection.py index 0b51fd1500..067d2724b1 100644 --- a/packages/fetchai/connections/p2p_stub/connection.py +++ b/packages/fetchai/connections/p2p_stub/connection.py @@ -28,7 +28,7 @@ from aea.identity.base import Identity from aea.mail.base import Envelope -PUBLIC_ID = PublicId.from_str("fetchai/p2p_stub:0.4.0") +PUBLIC_ID = PublicId.from_str("fetchai/p2p_stub:0.5.0") class P2PStubConnection(StubConnection): diff --git a/packages/fetchai/connections/p2p_stub/connection.yaml b/packages/fetchai/connections/p2p_stub/connection.yaml index eb5ec4d729..a4921edd5b 100644 --- a/packages/fetchai/connections/p2p_stub/connection.yaml +++ b/packages/fetchai/connections/p2p_stub/connection.yaml @@ -1,13 +1,14 @@ name: p2p_stub author: fetchai -version: 0.4.0 +version: 0.5.0 description: The stub p2p connection implements a local p2p connection allowing agents to communicate with each other through files created in the namespace directory. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmPqvbK3yBn5w25RGf8X5cZQ6KDBXMYoVUDufGgURe3VKd __init__.py: QmW9XFKGsea4u3fupkFMcQutgsjqusCMBMyTcTmLLmQ4tR - connection.py: QmbGLdt5T3aV69HDch74DXv7an5N3nJJnxWQqgfVuHpXif + connection.py: QmXoKRBMddf5GzfP4nKER17R76pfbeqgJjU5NjAEydHLum fingerprint_ignore_patterns: [] protocols: [] class_name: P2PStubConnection diff --git a/packages/fetchai/connections/soef/README.md b/packages/fetchai/connections/soef/README.md new file mode 100644 index 0000000000..298fe4b478 --- /dev/null +++ b/packages/fetchai/connections/soef/README.md @@ -0,0 +1,9 @@ +# SOEF connection + +The SOEF connection is used to connect to an SOEF node. The SOEF provides OEF services of register/unregister and search. + +## Usage + +First, add the connection to your AEA project: `aea add connection fetchai/soef:0.5.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, make sure `chain_identifier` matches your `default_ledger`. + +To register/unregister services and perform searches use the `fetchai/oef_search:0.4.0` protocol diff --git a/packages/fetchai/connections/soef/connection.py b/packages/fetchai/connections/soef/connection.py index c790958c09..76d524ffe7 100644 --- a/packages/fetchai/connections/soef/connection.py +++ b/packages/fetchai/connections/soef/connection.py @@ -22,6 +22,7 @@ import copy import logging from asyncio import CancelledError +from concurrent.futures._base import CancelledError as ConcurrentCancelledError from concurrent.futures.thread import ThreadPoolExecutor from contextlib import suppress from typing import Callable, Dict, List, Optional, Set, Union, cast @@ -33,7 +34,7 @@ import requests from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.helpers.dialogue.base import Dialogue as BaseDialogue from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.helpers.search.models import ( @@ -53,9 +54,9 @@ ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage -logger = logging.getLogger("aea.packages.fetchai.connections.oef") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.oef") -PUBLIC_ID = PublicId.from_str("fetchai/soef:0.5.0") +PUBLIC_ID = PublicId.from_str("fetchai/soef:0.6.0") NOT_SPECIFIED = object() @@ -83,28 +84,36 @@ class ModelNames: class SOEFException(Exception): - """Soef chanlle expected exception.""" + """SOEF channel expected exception.""" @classmethod - def warning(cls, msg: str) -> "SOEFException": # pragma: no cover + def warning( + cls, msg: str, logger: logging.Logger = _default_logger + ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.warning(msg) return cls(msg) @classmethod - def debug(cls, msg: str) -> "SOEFException": # pragma: no cover + def debug( + cls, msg: str, logger: logging.Logger = _default_logger + ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.debug(msg) return cls(msg) @classmethod - def error(cls, msg: str) -> "SOEFException": # pragma: no cover + def error( + cls, msg: str, logger: logging.Logger = _default_logger + ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.error(msg) return cls(msg) @classmethod - def exception(cls, msg: str) -> "SOEFException": # pragma: no cover + def exception( + cls, msg: str, logger: logging.Logger = _default_logger + ) -> "SOEFException": # pragma: no cover """Construct exception and write log.""" logger.exception(msg) return cls(msg) @@ -164,6 +173,7 @@ class SOEFChannel: DEFAULT_PERSONALITY_PIECES = ["architecture,agentframework"] PING_PERIOD = 30 * 60 # 30 minutes + FIND_AROUND_ME_REQUEST_DELAY = 2 # seconds def __init__( self, @@ -174,6 +184,7 @@ def __init__( excluded_protocols: Set[PublicId], restricted_to_protocols: Set[PublicId], chain_identifier: Optional[str] = None, + logger: logging.Logger = _default_logger, ): """ Initialize. @@ -202,8 +213,6 @@ def __init__( self.excluded_protocols = excluded_protocols self.restricted_to_protocols = restricted_to_protocols self.oef_search_dialogues = OefSearchDialogues() - self.oef_msg_id = 0 - self.oef_msg_id_to_dialogue = {} # type: Dict[int, OefSearchDialogue] self.declared_name = uuid4().hex self.unique_page_address = None # type: Optional[str] @@ -213,6 +222,33 @@ def __init__( self.chain_identifier: str = chain_identifier or self.DEFAULT_CHAIN_IDENTIFIER self._loop = None # type: Optional[asyncio.AbstractEventLoop] self._ping_periodic_task: Optional[asyncio.Task] = None + self._find_around_me_queue: Optional[asyncio.Queue] = None + self._find_around_me_processor_task: Optional[asyncio.Task] = None + self.logger = logger + + async def _find_around_me_processor(self) -> None: + """Process find me around requests in background task.""" + while self._find_around_me_queue is not None: + try: + task = await self._find_around_me_queue.get() + oef_message, oef_search_dialogue, radius, params = task + await self._find_around_me_handle_requet( + oef_message, oef_search_dialogue, radius, params + ) + await asyncio.sleep(self.FIND_AROUND_ME_REQUEST_DELAY) + except asyncio.CancelledError: # pylint: disable=try-except-raise + return + except Exception: # pylint: disable=broad-except # pragma: nocover + self.logger.exception( + "Exception occoured in _find_around_me_processor" + ) + await self._send_error_response( + oef_message, + oef_search_dialogue, + oef_error_operation=OefSearchMessage.OefErrorOperation.OTHER, + ) + finally: + self.logger.debug("_find_around_me_processor exited") @property def loop(self) -> asyncio.AbstractEventLoop: @@ -300,7 +336,7 @@ def _check_protocol_valid(self, envelope: Envelope) -> None: ) if is_in_excluded or not is_in_restricted: - logger.error( + self.logger.error( "This envelope cannot be sent with the soef connection: protocol_id={}".format( envelope.protocol_id ) @@ -337,13 +373,13 @@ async def process_envelope(self, envelope: Envelope) -> None: assert isinstance(envelope.message, OefSearchMessage), ValueError( "Message not of type OefSearchMessage" ) - oef_message = cast(OefSearchMessage, envelope.message) - oef_message = copy.deepcopy( - oef_message + oef_message_orig = cast(OefSearchMessage, envelope.message) + oef_message = copy.copy( + oef_message_orig ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" oef_message.is_incoming = True # TODO: fix; should be done by framework oef_message.counterparty = ( - envelope.sender + oef_message_orig.sender ) # TODO: fix; should be done by framework oef_search_dialogue = cast( OefSearchDialogue, self.oef_search_dialogues.update(oef_message) @@ -387,8 +423,10 @@ async def process_envelope(self, envelope: Envelope) -> None: oef_search_dialogue, oef_error_operation=oef_error_operation, ) + except (asyncio.CancelledError, ConcurrentCancelledError): # pragma: nocover + pass except Exception: # pylint: disable=broad-except # pragma: nocover - logger.exception("Exception during envelope processing") + self.logger.exception("Exception during envelope processing") await self._send_error_response( oef_message, oef_search_dialogue, @@ -454,7 +492,7 @@ async def _ping_periodic(self, period: float = 30 * 60) -> None: except asyncio.CancelledError: # pylint: disable=try-except-raise raise except Exception: # pylint: disable=broad-except - logger.exception("Error on periodic ping command!") + self.logger.exception("Error on periodic ping command!") await asyncio.sleep(period) async def _set_service_key_handler(self, service_description: Description) -> None: @@ -484,7 +522,7 @@ async def _generic_oef_command( :return: response text """ params = params or {} - logger.debug(f"Perform `{command}` with {params}") + self.logger.debug(f"Perform `{command}` with {params}") url = parse.urljoin( self.base_url, unique_page_address or self.unique_page_address ) @@ -498,7 +536,7 @@ async def _generic_oef_command( el = root.find("./success") assert el is not None, "No success element" assert str(el.text).strip() == "1", "Success is not 1" - logger.debug(f"`{command}` SUCCESS!") + self.logger.debug(f"`{command}` SUCCESS!") return response_text except Exception as e: raise SOEFException.error(f"`{command}` error: {response_text}: {[e]}") @@ -633,7 +671,7 @@ async def _register_agent(self) -> None: :return: None """ - logger.debug("Applying to SOEF lobby with address={}".format(self.address)) + self.logger.debug("Applying to SOEF lobby with address={}".format(self.address)) url = parse.urljoin(self.base_url, "register") params = { "api_key": self.api_key, @@ -643,11 +681,11 @@ async def _register_agent(self) -> None: } response_text = await self._request_text("get", url=url, params=params) root = ET.fromstring(response_text) - logger.debug("Root tag: {}".format(root.tag)) + self.logger.debug("Root tag: {}".format(root.tag)) unique_page_address = "" unique_token = "" # nosec for child in root: - logger.debug( + self.logger.debug( "Child tag={}, child attrib={}, child text={}".format( child.tag, child.attrib, child.text ) @@ -660,7 +698,7 @@ async def _register_agent(self) -> None: raise SOEFException.error( "Agent registration error - page address or token not received" ) - logger.debug("Registering agent") + self.logger.debug("Registering agent") params = {"token": unique_token} await self._generic_oef_command( "acknowledge", params, unique_page_address=unique_page_address @@ -697,10 +735,7 @@ async def _send_error_response( message_id=oef_search_message.message_id + 1, ) message.counterparty = oef_search_message.counterparty - oef_search_dialogue.update(message) - message = copy.deepcopy( - message - ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + assert oef_search_dialogue.update(message) envelope = Envelope( to=message.counterparty, sender=SOEFConnection.connection_id.latest, @@ -727,7 +762,7 @@ async def unregister_service( } # type: Dict[str, Callable] data_model_name = service_description.data_model.name - if data_model_name not in data_model_handlers: + if data_model_name not in data_model_handlers: # pragma: nocover raise SOEFException.error( f'Data model name: {data_model_name} is not supported. Valid models are: {", ".join(data_model_handlers.keys())}' ) @@ -746,7 +781,7 @@ async def _unregister_agent(self) -> None: """ await self._stop_periodic_ping_task() if self.unique_page_address is None: # pragma: nocover - logger.debug( + self.logger.debug( "The service is not registered to the simple OEF. Cannot unregister." ) return @@ -768,7 +803,11 @@ async def connect(self) -> None: """Connect channel set queues and executor pool.""" self._loop = asyncio.get_event_loop() self.in_queue = asyncio.Queue() + self._find_around_me_queue = asyncio.Queue() self._executor_pool = ThreadPoolExecutor(max_workers=10) + self._find_around_me_processor_task = self._loop.create_task( + self._find_around_me_processor() + ) async def disconnect(self) -> None: """ @@ -780,7 +819,14 @@ async def disconnect(self) -> None: assert self.in_queue, ValueError("Queue is not set, use connect first!") await self._unregister_agent() + + if self._find_around_me_processor_task: + if not self._find_around_me_processor_task.done(): + self._find_around_me_processor_task.cancel() + await self._find_around_me_processor_task + await self.in_queue.put(None) + self._find_around_me_queue = None async def search_services( self, oef_message: OefSearchMessage, oef_search_dialogue: OefSearchDialogue @@ -820,6 +866,7 @@ async def search_services( if self.agent_location is None or self.agent_location != service_location: # we update the location to match the query. await self._set_location(service_location) # pragma: nocover + await self._find_around_me(oef_message, oef_search_dialogue, radius, params) async def _find_around_me( @@ -828,6 +875,28 @@ async def _find_around_me( oef_search_dialogue: OefSearchDialogue, radius: float, params: Dict[str, List[str]], + ) -> None: + """ + Add find agent task to queue to process in dedictated loop respectful to timeouts. + + :param oef_message: OefSearchMessage + :param oef_search_dialogue: OefSearchDialogue + :param radius: the radius in which to search + :param params: the parameters for the query + :return: None + """ + if not self._find_around_me_queue: + raise ValueError("SOEFChannel not started.") # pragma: nocover + await self._find_around_me_queue.put( + (oef_message, oef_search_dialogue, radius, params) + ) + + async def _find_around_me_handle_requet( + self, + oef_message: OefSearchMessage, + oef_search_dialogue: OefSearchDialogue, + radius: float, + params: Dict[str, List[str]], ) -> None: """ Find agents around me. @@ -839,7 +908,7 @@ async def _find_around_me( :return: None """ assert self.in_queue is not None, "Inqueue not set!" - logger.debug("Searching in radius={} of myself".format(radius)) + self.logger.debug("Searching in radius={} of myself".format(radius)) response_text = await self._generic_oef_command( "find_around_me", {"range_in_km": [str(radius)], **params} @@ -873,10 +942,7 @@ async def _find_around_me( message_id=oef_message.message_id + 1, ) message.counterparty = oef_message.counterparty - oef_search_dialogue.update(message) - message = copy.deepcopy( - message - ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + assert oef_search_dialogue.update(message) envelope = Envelope( to=message.counterparty, sender=SOEFConnection.connection_id.latest, @@ -896,7 +962,7 @@ def __init__(self, **kwargs): if kwargs.get("configuration") is None: # pragma: nocover kwargs["excluded_protocols"] = kwargs.get("excluded_protocols") or [] kwargs["restricted_to_protocols"] = kwargs.get("excluded_protocols") or [ - PublicId.from_str("fetchai/oef_search:0.3.0") + PublicId.from_str("fetchai/oef_search:0.4.0") ] super().__init__(**kwargs) @@ -928,15 +994,15 @@ async def connect(self) -> None: :return: None :raises Exception if the connection to the OEF fails. """ - if self.connection_status.is_connected: # pragma: no cover + if self.is_connected: # pragma: nocover return + + self._state.set(ConnectionStates.connecting) try: - self.connection_status.is_connecting = True await self.channel.connect() - self.connection_status.is_connecting = False - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) except (CancelledError, Exception) as e: # pragma: no cover - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) raise e @property @@ -950,13 +1016,12 @@ async def disconnect(self) -> None: :return: None """ - assert ( - self.connection_status.is_connected or self.connection_status.is_connecting - ), "Call connect before disconnect." + if self.is_disconnected: # pragma: nocover + return assert self.in_queue is not None + self._state.set(ConnectionStates.disconnecting) await self.channel.disconnect() - self.connection_status.is_connected = False - self.connection_status.is_connecting = False + self._state.set(ConnectionStates.disconnected) async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """ @@ -986,5 +1051,5 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelope to send. :return: None """ - if self.connection_status.is_connected: + if self.is_connected: await self.channel.send(envelope) diff --git a/packages/fetchai/connections/soef/connection.yaml b/packages/fetchai/connections/soef/connection.yaml index c8efbd8c45..c5eef8186f 100644 --- a/packages/fetchai/connections/soef/connection.yaml +++ b/packages/fetchai/connections/soef/connection.yaml @@ -1,15 +1,16 @@ name: soef author: fetchai -version: 0.5.0 +version: 0.6.0 description: The soef connection provides a connection api to the simple OEF. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmUaLTefPhGVDn3SZ5oK461JASpUALPBj4x9HmyJV39iqG __init__.py: Qmd5VBGFJHXFe1H45XoUh5mMSYBwvLSViJuGFeMgbPdQts - connection.py: QmdwV3H3zaaZTNcEfr5YBFuaUdjwK5vyNQHAtFPRLWmuH9 + connection.py: QmXJZTroYKyQEcruGGWhDesjDhn8WVDmEMgrtH6vEQSa38 fingerprint_ignore_patterns: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 class_name: SOEFConnection config: api_key: TwiCIriSl0mLahw17pyqoA @@ -18,6 +19,6 @@ config: soef_port: 9002 excluded_protocols: [] restricted_to_protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 dependencies: defusedxml: {} diff --git a/packages/fetchai/connections/tcp/README.md b/packages/fetchai/connections/tcp/README.md new file mode 100644 index 0000000000..431d3459f6 --- /dev/null +++ b/packages/fetchai/connections/tcp/README.md @@ -0,0 +1,7 @@ +# TCP client connection + +A simple TCP client/server connection to use the TCP protocol for sending and receiving envelopes. + +## Usage + +Add the connection to your AEA project: `aea add connection fetchai/tcp:0.6.0`. diff --git a/packages/fetchai/connections/tcp/base.py b/packages/fetchai/connections/tcp/base.py index 0866411ae4..ca364fb84f 100644 --- a/packages/fetchai/connections/tcp/base.py +++ b/packages/fetchai/connections/tcp/base.py @@ -25,12 +25,12 @@ from typing import Optional from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Envelope logger = logging.getLogger("aea.packages.fetchai.connections.tcp") -PUBLIC_ID = PublicId.from_str("fetchai/tcp:0.5.0") +PUBLIC_ID = PublicId.from_str("fetchai/tcp:0.6.0") class TCPConnection(Connection, ABC): @@ -75,16 +75,17 @@ async def connect(self): :return: A queue or None. :raises ConnectionError: if a problem occurred during the connection. """ - if self.connection_status.is_connected: + if self.is_connected: # pragma: nocover self.logger.warning("Connection already set up.") return + self._state.set(ConnectionStates.connecting) try: await self.setup() - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) except Exception as e: # pragma: nocover # pylint: disable=broad-except self.logger.error(str(e)) - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) async def disconnect(self) -> None: """ @@ -92,17 +93,19 @@ async def disconnect(self) -> None: :return: None. """ - if not self.connection_status.is_connected: + + if self.is_disconnected: # pragma: nocover self.logger.warning("Connection already disconnected.") return + self._state.set(ConnectionStates.disconnecting) await self.teardown() - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) async def _recv(self, reader: StreamReader) -> Optional[bytes]: """Receive bytes.""" data = await reader.read(len(struct.pack("I", 0))) - if not self.connection_status.is_connected: + if not self.is_connected: return None nbytes = struct.unpack("I", data)[0] nbytes_read = 0 diff --git a/packages/fetchai/connections/tcp/connection.yaml b/packages/fetchai/connections/tcp/connection.yaml index d81a997b57..119f4a92d4 100644 --- a/packages/fetchai/connections/tcp/connection.yaml +++ b/packages/fetchai/connections/tcp/connection.yaml @@ -1,12 +1,13 @@ name: tcp author: fetchai -version: 0.5.0 +version: 0.6.0 description: The tcp connection implements a tcp server and client. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: Qma4uDSzQ57JWfiUShXMXYzmfMyjXYVEdqrpfMnwX6EaV7 __init__.py: QmTxAtQ9ffraStxxLAkvmWxyGhoV3jE16Sw6SJ9xzTthLb - base.py: QmNoodDEsFfPUSmayxqqUSdAaxbXQ1gof7jTsLvMdEoAek + base.py: QmRwaGZS51WTDWh7TvfNEw4MtuzNH5yqT22HRVmGMrzwZ8 connection.py: QmTFkiw3JLmhEM6CKRpKjv9Y32nuCQevZ2gVKoQ4gExeW9 tcp_client.py: QmTXs6z3rvxB59FmGuu46CeY1eHRPBNQ4CPZm1y7hRpusp tcp_server.py: QmPLTPEzeWPGU2Bt4kCaTXXKTqNNffHX5dr3LG75YQ249z diff --git a/packages/fetchai/connections/webhook/README.md b/packages/fetchai/connections/webhook/README.md new file mode 100644 index 0000000000..6ab7ae7a9b --- /dev/null +++ b/packages/fetchai/connections/webhook/README.md @@ -0,0 +1,7 @@ +# Webhook connection + +An HTTP webhook connection which registers a webhook and waits for incoming requests. It generates messages based on webhook requests received and forwards them to the agent. + +## Usage + +First, add the connection to your AEA project: `aea add connection fetchai/webhook:0.5.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, set `webhook_address`, `webhook_port` and `webhook_url_path` appropriately. diff --git a/packages/fetchai/connections/webhook/connection.py b/packages/fetchai/connections/webhook/connection.py index e646187c68..3b3d8f5224 100644 --- a/packages/fetchai/connections/webhook/connection.py +++ b/packages/fetchai/connections/webhook/connection.py @@ -16,7 +16,6 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Webhook connection and channel.""" import asyncio @@ -28,18 +27,19 @@ from aiohttp import web # type: ignore from aea.configurations.base import PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.mail.base import Address, Envelope, EnvelopeContext, URI +from packages.fetchai.protocols.http.dialogues import HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage SUCCESS = 200 NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 -PUBLIC_ID = PublicId.from_str("fetchai/webhook:0.4.0") +PUBLIC_ID = PublicId.from_str("fetchai/webhook:0.5.0") -logger = logging.getLogger("aea.packages.fetchai.connections.webhook") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.webhook") RequestId = str @@ -54,6 +54,7 @@ def __init__( webhook_port: int, webhook_url_path: str, connection_id: PublicId, + logger: logging.Logger = _default_logger, ): """ Initialize a webhook channel. @@ -80,6 +81,7 @@ def __init__( self.in_queue = None # type: Optional[asyncio.Queue] # pragma: no cover self.logger = logger self.logger.info("Initialised a webhook channel") + self._dialogues = HttpDialogues(str(WebhookConnection.connection_id)) async def connect(self) -> None: """ @@ -121,7 +123,7 @@ async def disconnect(self) -> None: await self.runner.cleanup() await self.app.shutdown() await self.app.cleanup() - logger.info("Webhook app is shutdown.") + self.logger.info("Webhook app is shutdown.") self.is_stopped = True async def _receive_webhook(self, request: web.Request) -> web.Response: @@ -145,7 +147,7 @@ async def send(self, envelope: Envelope) -> None: :param envelope: the envelope """ - logger.warning( + self.logger.warning( "Dropping envelope={} as sending via the webhook is not possible!".format( envelope ) @@ -169,11 +171,15 @@ async def to_envelope(self, request: web.Request) -> Envelope: version=version, headers=json.dumps(dict(request.headers)), bodyy=payload_bytes if payload_bytes is not None else b"", + dialogue_reference=self._dialogues.new_self_initiated_dialogue_reference(), ) + http_message.counterparty = self.agent_address + http_dialogue = self._dialogues.update(http_message) + assert http_dialogue is not None, "Could not create dialogue." envelope = Envelope( - to=self.agent_address, - sender=request.remote, - protocol_id=PublicId.from_str("fetchai/http:0.3.0"), + to=http_message.counterparty, + sender=http_message.sender, + protocol_id=http_message.protocol_id, context=context, message=http_message, ) @@ -202,6 +208,7 @@ def __init__(self, **kwargs): webhook_port=webhook_port, webhook_url_path=webhook_url_path, connection_id=self.connection_id, + logger=self.logger, ) async def connect(self) -> None: @@ -210,11 +217,14 @@ async def connect(self) -> None: :return: None """ - if not self.connection_status.is_connected: - self.connection_status.is_connected = True - self.channel.logger = self.logger - self.channel.in_queue = asyncio.Queue() - await self.channel.connect() + if self.is_connected: # pragma: nocover + return + + self._state.set(ConnectionStates.connecting) + self.channel.logger = self.logger + self.channel.in_queue = asyncio.Queue() + await self.channel.connect() + self._state.set(ConnectionStates.connected) async def disconnect(self) -> None: """ @@ -222,9 +232,12 @@ async def disconnect(self) -> None: :return: None """ - if self.connection_status.is_connected: - self.connection_status.is_connected = False - await self.channel.disconnect() + if self.is_disconnected: # pragma: nocover + return + + self._state.set(ConnectionStates.disconnecting) + await self.channel.disconnect() + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope") -> None: """ @@ -233,7 +246,7 @@ async def send(self, envelope: "Envelope") -> None: :param envelope: the envelop :return: None """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover @@ -246,7 +259,7 @@ async def receive(self, *args, **kwargs) -> Optional[Union["Envelope", None]]: :return: the envelope received, or None. """ - if not self.connection_status.is_connected: + if not self.is_connected: raise ConnectionError( "Connection not established yet. Please use 'connect()'." ) # pragma: no cover diff --git a/packages/fetchai/connections/webhook/connection.yaml b/packages/fetchai/connections/webhook/connection.yaml index 62245d007a..81340ceeab 100644 --- a/packages/fetchai/connections/webhook/connection.yaml +++ b/packages/fetchai/connections/webhook/connection.yaml @@ -1,15 +1,16 @@ name: webhook author: fetchai -version: 0.4.0 +version: 0.5.0 description: The webhook connection that wraps a webhook functionality. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmU79DgcrbrZkZzxV14HrYXwrsGuqPGnDBYPxeZFM9EwhF __init__.py: QmWUKSmXaBgGMvKgdmzKmMjCx43BnrfW6og2n3afNoAALq - connection.py: QmeGqgig7Ab95znNf2kBHukAjbsaofFX24SYRaDreEwn9V + connection.py: QmVgBgXsAz82disfpUMZQueNEezi12w3sWxWWwpgERNf3X fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 class_name: WebhookConnection config: webhook_address: 127.0.0.1 @@ -17,7 +18,7 @@ config: webhook_url_path: /some/url/path excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 dependencies: aiohttp: version: ==3.6.2 diff --git a/packages/fetchai/contracts/erc1155/contract.py b/packages/fetchai/contracts/erc1155/contract.py index 897ace4f28..43f35a5de3 100644 --- a/packages/fetchai/contracts/erc1155/contract.py +++ b/packages/fetchai/contracts/erc1155/contract.py @@ -68,7 +68,7 @@ def _generate_id(index: int, token_type: int) -> int: return token_id @classmethod - def get_deploy_transaction( + def get_deploy_transaction( # type: ignore cls, ledger_api: LedgerApi, deployer_address: Address, diff --git a/packages/fetchai/contracts/erc1155/contract.yaml b/packages/fetchai/contracts/erc1155/contract.yaml index 34cba4e2ae..c594050bcd 100644 --- a/packages/fetchai/contracts/erc1155/contract.yaml +++ b/packages/fetchai/contracts/erc1155/contract.yaml @@ -1,6 +1,6 @@ name: erc1155 author: fetchai -version: 0.6.0 +version: 0.7.0 description: The erc1155 contract implements an ERC1155 contract package. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -8,7 +8,7 @@ fingerprint: __init__.py: QmVadErLF2u6xuTP4tnTGcMCvhh34V9VDZm53r7Z4Uts9Z build/Migrations.json: QmfFYYWoq1L1Ni6YPBWWoRPvCZKBLZ7qzN3UDX537mCeuE build/erc1155.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw - contract.py: QmTLSEcNMGXK3H5hjYYxTPADzLtErgXi8znzm7a3Mfim4M + contract.py: QmVp2X1dLHpkR9uvZZxjvPNgs9sQSLLiCJRobEyqTnMnNV contracts/Migrations.sol: QmbW34mYrj3uLteyHf3S46pnp9bnwovtCXHbdBHfzMkSZx contracts/erc1155.vy: QmXwob8G1uX7fDvtuuKW139LALWtQmGw2vvaTRBVAWRxTx migrations/1_initial_migration.js: QmcxaWKQ2yPkQBmnpXmcuxPZQUMuUudmPmX3We8Z9vtAf7 diff --git a/examples/protocol_specification_ex/contract_api.yaml b/packages/fetchai/protocols/contract_api/README.md similarity index 82% rename from examples/protocol_specification_ex/contract_api.yaml rename to packages/fetchai/protocols/contract_api/README.md index 0b02f144ef..4001a89852 100644 --- a/examples/protocol_specification_ex/contract_api.yaml +++ b/packages/fetchai/protocols/contract_api/README.md @@ -1,7 +1,26 @@ +# Contract API Protocol + +**Name:** contract_api + +**Author**: fetchai + +**Version**: 0.2.0 + +**Short Description**: A protocol for contract APIs' requests and responses. + +**License**: Apache-2.0 + +## Description + +This is a protocol for contract APIs' requests and responses. + +## Specification + +```yaml --- name: contract_api author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -65,3 +84,6 @@ termination: [state, raw_transaction, raw_message] roles: {agent, ledger} end_states: [successful, failed] ... +``` + +## Links diff --git a/packages/fetchai/protocols/contract_api/custom_types.py b/packages/fetchai/protocols/contract_api/custom_types.py index 241f7a281d..b8a682e2a5 100644 --- a/packages/fetchai/protocols/contract_api/custom_types.py +++ b/packages/fetchai/protocols/contract_api/custom_types.py @@ -44,9 +44,9 @@ def __init__( def _check_consistency(self) -> None: """Check consistency of the object.""" assert self._body is not None, "body must not be None" - assert isinstance(self._body, dict) and [ - isinstance(key, str) for key in self._body.keys() - ] + assert isinstance(self._body, dict) and all( + [isinstance(key, str) for key in self._body.keys()] + ) @property def body(self) -> Dict[str, Any]: @@ -58,7 +58,7 @@ def encode(kwargs_protobuf_object, kwargs_object: "Kwargs") -> None: """ Encode an instance of this class into the protocol buffer object. - The protocol buffer object in the kwargs_protobuf_object argument must be matched with the instance of this class in the 'kwargs_object' argument. + The protocol buffer object in the kwargs_protobuf_object argument is matched with the instance of this class in the 'kwargs_object' argument. :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. :param kwargs_object: an instance of this class to be encoded in the protocol buffer object. @@ -72,10 +72,10 @@ def decode(cls, kwargs_protobuf_object) -> "Kwargs": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. - A new instance of this class must be created that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. + A new instance of this class is created that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. - :param raw_transaction_protobuf_object: the protocol buffer object whose type corresponds with this class. - :return: A new instance of this class that matches the protocol buffer object in the 'raw_transaction_protobuf_object' argument. + :param kwargs_protobuf_object: the protocol buffer object whose type corresponds with this class. + :return: A new instance of this class that matches the protocol buffer object in the 'kwargs_protobuf_object' argument. """ kwargs = pickle.loads(kwargs_protobuf_object.kwargs_bytes) # nosec return kwargs diff --git a/packages/fetchai/protocols/contract_api/dialogues.py b/packages/fetchai/protocols/contract_api/dialogues.py index 6a4cc668ae..87230d4b57 100644 --- a/packages/fetchai/protocols/contract_api/dialogues.py +++ b/packages/fetchai/protocols/contract_api/dialogues.py @@ -161,7 +161,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> ContractApiDialogue: """ - Create an instance of fipa dialogue. + Create an instance of contract_api dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/contract_api/message.py b/packages/fetchai/protocols/contract_api/message.py index c561e3fa70..af799232ae 100644 --- a/packages/fetchai/protocols/contract_api/message.py +++ b/packages/fetchai/protocols/contract_api/message.py @@ -43,7 +43,7 @@ class ContractApiMessage(Message): """A protocol for contract APIs requests and responses.""" - protocol_id = ProtocolId("fetchai", "contract_api", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/contract_api:0.2.0") Kwargs = CustomKwargs diff --git a/packages/fetchai/protocols/contract_api/protocol.yaml b/packages/fetchai/protocols/contract_api/protocol.yaml index dc4a21921c..836741f16a 100644 --- a/packages/fetchai/protocols/contract_api/protocol.yaml +++ b/packages/fetchai/protocols/contract_api/protocol.yaml @@ -1,16 +1,17 @@ name: contract_api author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmYgqZHPmrEL78eNbryWGSKowpLGv6EbyPcG49jU4pomni __init__.py: QmZodYjNqoMgGAGKfkCU4zU9t1Cx9MAownqSy4wyVdwaHF contract_api.proto: QmNwngtcYFSuqL8yeTGVXmrHjfebCybdUa9BnTDKXn8odk contract_api_pb2.py: QmVT6Fv53KyFhshNFEo38seHypd7Y62psBaF8NszV8iRHK - custom_types.py: QmRVz9wCrLeTaF8iJsG1NdLuDGXzUEy6UXJ6opP71wrd7e - dialogues.py: QmYnc1GDhQ9p79LwzvKo49Xx4RiVtVwekskNniG5Rw9zoa - message.py: QmTgkpQYgZHqBdJaBdS5hrcZ5B8D1JPCyAcNiPFkVydznN + custom_types.py: QmZsFfRJNQ9grP5FpNeY8683uDGNTAL97Wfcx7VaLR1cSe + dialogues.py: Qmf6dpmyEbHrvzMhEzDZ6SnbxHRMNNkgPinjB8Ptsz1k1U + message.py: QmZNEBb21xwomsn6bovewjm1WvxdGm9x9dc4LcL6S2BrX3 serialization.py: QmdJZ6GBrURgzJCfYSZzLhWirfm5bDJxumz7ieAELC9juw fingerprint_ignore_patterns: [] dependencies: diff --git a/examples/protocol_specification_ex/fipa.yaml b/packages/fetchai/protocols/fipa/README.md similarity index 74% rename from examples/protocol_specification_ex/fipa.yaml rename to packages/fetchai/protocols/fipa/README.md index a774647101..ebe80c17e8 100644 --- a/examples/protocol_specification_ex/fipa.yaml +++ b/packages/fetchai/protocols/fipa/README.md @@ -1,7 +1,26 @@ +# Fipa Protocol + +**Name:** fipa + +**Author**: fetchai + +**Version**: 0.5.0 + +**Short Description**: A protocol for FIPA ACL. + +**License**: Apache-2.0 + +## Description + +This is a protocol for two agents to negotiate over a fixed set of resources. + +## Specification + +```yaml --- name: fipa author: fetchai -version: 0.4.0 +version: 0.5.0 description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -46,4 +65,9 @@ reply: termination: [decline, match_accept, match_accept_w_inform, inform] roles: {seller, buyer} end_states: [successful, declined_cfp, declined_propose, declined_accept] -... \ No newline at end of file +... +``` + +## Links + +* FIPA Foundation \ No newline at end of file diff --git a/packages/fetchai/protocols/fipa/message.py b/packages/fetchai/protocols/fipa/message.py index 237481fcb7..4c637a072e 100644 --- a/packages/fetchai/protocols/fipa/message.py +++ b/packages/fetchai/protocols/fipa/message.py @@ -39,7 +39,7 @@ class FipaMessage(Message): """A protocol for FIPA ACL.""" - protocol_id = ProtocolId("fetchai", "fipa", "0.4.0") + protocol_id = ProtocolId.from_str("fetchai/fipa:0.5.0") Description = CustomDescription diff --git a/packages/fetchai/protocols/fipa/protocol.yaml b/packages/fetchai/protocols/fipa/protocol.yaml index 32fa579a7f..cfa31e43a2 100644 --- a/packages/fetchai/protocols/fipa/protocol.yaml +++ b/packages/fetchai/protocols/fipa/protocol.yaml @@ -1,16 +1,17 @@ name: fipa author: fetchai -version: 0.4.0 +version: 0.5.0 description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmQknY2SSJqEHU6K4Wzm8NQovtCCkJ78DuNLg2wWNq18hB __init__.py: QmZuv8RGegxunYaJ7sHLwj2oLLCFCAGF139b8DxEY68MRT custom_types.py: Qmb7bzEUAW74ZeSFqL7sTccNCjudStV63K4CFNZtibKUHB dialogues.py: QmYcgipy556vUs74sC9CsckBbPCYSMsiR36Z8TCPVkEkpq fipa.proto: QmP7JqnuQSQ9BDcKkscrTydKEX4wFBoyFaY1bkzGkamcit fipa_pb2.py: QmZMkefJLrb3zJKoimb6a9tdpxDBhc8rR2ghimqg7gZ471 - message.py: QmeQiZadU2g6T4hw4mXkNLLBirVdPmJUQjTwA6wVv9hrbn + message.py: QmZYMvPcDogeZPkhx7YPjAEJRX8VNGSF1jetdXcQmYnT6R serialization.py: QmU6Xj55eaRxCYAeyR1difC769NHLB8kciorajvkLZCwDR fingerprint_ignore_patterns: [] dependencies: diff --git a/examples/protocol_specification_ex/gym.yaml b/packages/fetchai/protocols/gym/README.md similarity index 64% rename from examples/protocol_specification_ex/gym.yaml rename to packages/fetchai/protocols/gym/README.md index 6235bcf067..cced15fa91 100644 --- a/examples/protocol_specification_ex/gym.yaml +++ b/packages/fetchai/protocols/gym/README.md @@ -1,7 +1,26 @@ +# Gym Protocol + +**Name:** gym + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: A protocol for interacting with a gym connection. + +**License**: Apache-2.0 + +## Description + +This is a protocol for interacting with a gym connection. + +## Specification + +```yaml --- name: gym author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -36,3 +55,8 @@ termination: [close] roles: {agent, environment} end_states: [successful] ... +``` + +## Links + +* OpenAI Gym \ No newline at end of file diff --git a/packages/fetchai/protocols/gym/dialogues.py b/packages/fetchai/protocols/gym/dialogues.py index f21f745b90..6268d341f5 100644 --- a/packages/fetchai/protocols/gym/dialogues.py +++ b/packages/fetchai/protocols/gym/dialogues.py @@ -134,7 +134,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> GymDialogue: """ - Create an instance of fipa dialogue. + Create an instance of gym dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/gym/message.py b/packages/fetchai/protocols/gym/message.py index 906971a0e7..07d9776ba3 100644 --- a/packages/fetchai/protocols/gym/message.py +++ b/packages/fetchai/protocols/gym/message.py @@ -36,7 +36,7 @@ class GymMessage(Message): """A protocol for interacting with a gym connection.""" - protocol_id = ProtocolId("fetchai", "gym", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/gym:0.4.0") AnyObject = CustomAnyObject diff --git a/packages/fetchai/protocols/gym/protocol.yaml b/packages/fetchai/protocols/gym/protocol.yaml index bb5aa7f9a3..ace1311bc5 100644 --- a/packages/fetchai/protocols/gym/protocol.yaml +++ b/packages/fetchai/protocols/gym/protocol.yaml @@ -1,16 +1,17 @@ name: gym author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmPcnBEUW1NfTBRwMvpPQJ8NTP6MG3CueUjjHQJ1sf77uQ __init__.py: QmWBvruqGuU2BVCq8cuP1S3mgvuC78yrG4TdtSvKhCT8qX custom_types.py: QmfDaswopanUqsETQXMatKfwwDSSo7q2Edz9MXGimT5jbf - dialogues.py: QmWJv1gRNvqkFGyx9FGkhhorymD5javXuBA8HwQ6z9BLPw + dialogues.py: Qmd7n1c7wWacCkTx1kxsuo73QhxN6e4uufPYmF775cYqVx gym.proto: QmQGF9Xz4Z93wmhdKoztzxjo5pS4SsAWe2TQdvZCLuzdGC gym_pb2.py: QmSTz7xrL8ryqzR1Sgu1NpR6PmW7GUhBGnN2qYc8m8NCcN - message.py: QmaiYJbphafhurv7cYCLfJLY4hHCTzyqWz2r8YRJngkpq4 + message.py: QmYr1qpXRTh8xMWmKBQoBfdTsms4YExioQxD3RyxY9JZVm serialization.py: QmaZd7YMHrHZvbeMMb1JfnkUZRHk7zKy45M7kDvG5wbY9C fingerprint_ignore_patterns: [] dependencies: diff --git a/examples/protocol_specification_ex/http.yaml b/packages/fetchai/protocols/http/README.md similarity index 55% rename from examples/protocol_specification_ex/http.yaml rename to packages/fetchai/protocols/http/README.md index 19ee91c45a..0ea3d8648f 100644 --- a/examples/protocol_specification_ex/http.yaml +++ b/packages/fetchai/protocols/http/README.md @@ -1,7 +1,26 @@ +# HTTP Protocol + +**Name:** http + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: A protocol for HTTP requests and responses. + +**License**: Apache-2.0 + +## Description + +This is a protocol for interacting with a client/server via HTTP requests and responses. + +## Specification + +```yaml --- name: http author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -28,3 +47,8 @@ termination: [response] roles: {client, server} end_states: [successful] ... +``` + +## Links + +* HTTP Specification \ No newline at end of file diff --git a/packages/fetchai/protocols/http/dialogues.py b/packages/fetchai/protocols/http/dialogues.py index a6d3ae9f6c..abbd0d05b4 100644 --- a/packages/fetchai/protocols/http/dialogues.py +++ b/packages/fetchai/protocols/http/dialogues.py @@ -121,7 +121,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> HttpDialogue: """ - Create an instance of fipa dialogue. + Create an instance of http dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/http/message.py b/packages/fetchai/protocols/http/message.py index 250ceba044..699e9422df 100644 --- a/packages/fetchai/protocols/http/message.py +++ b/packages/fetchai/protocols/http/message.py @@ -34,7 +34,7 @@ class HttpMessage(Message): """A protocol for HTTP requests and responses.""" - protocol_id = ProtocolId("fetchai", "http", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/http:0.4.0") class Performative(Enum): """Performatives for the http protocol.""" diff --git a/packages/fetchai/protocols/http/protocol.yaml b/packages/fetchai/protocols/http/protocol.yaml index caaca49edf..32585f1a6b 100644 --- a/packages/fetchai/protocols/http/protocol.yaml +++ b/packages/fetchai/protocols/http/protocol.yaml @@ -1,15 +1,16 @@ name: http author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmP8ATR4x159fe1363orvdFj7JTJJHWmSdFEVVW62USGyC __init__.py: QmRWie4QPiFJE8nK4fFJ6prqoG3u36cPo7st5JUZAGpVWv - dialogues.py: QmYXrUN76rptudYbvdZwzf4DRPN2HkuG67mkxvzznLBvao + dialogues.py: QmbhfvfdniejPAUT9dZD8AGv6vZNkVRRy9spi8aCU1kJb5 http.proto: QmdTUTvvxGxMxSTB67AXjMUSDLdsxBYiSuJNVxHuLKB1jS http_pb2.py: QmYYKqdwiueq54EveL9WXn216FXLSQ6XGJJHoiJxwJjzHC - message.py: QmX1rFsvggjpHcujLhB3AZRJpUWpEsf9gG6M2A2qdg6FVY + message.py: QmTMEru7pjE4RQXPbTcMm6fNSwyCu9xdpZvQapjqd22ypG serialization.py: QmUgo5BtLYDyy7syHBd6brd8zAXivNR2UEiBckryCwg6hk fingerprint_ignore_patterns: [] dependencies: diff --git a/examples/protocol_specification_ex/ledger_api.yaml b/packages/fetchai/protocols/ledger_api/README.md similarity index 81% rename from examples/protocol_specification_ex/ledger_api.yaml rename to packages/fetchai/protocols/ledger_api/README.md index eb39a555fe..692a7ae6d1 100644 --- a/examples/protocol_specification_ex/ledger_api.yaml +++ b/packages/fetchai/protocols/ledger_api/README.md @@ -1,7 +1,26 @@ +# Ledger API Protocol + +**Name:** ledger_api + +**Author**: fetchai + +**Version**: 0.2.0 + +**Short Description**: A protocol for ledger APIs' requests and responses. + +**License**: Apache-2.0 + +## Description + +This is a protocol for interacting with ledger APIs. + +## Specification + +```yaml --- name: ledger_api author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -42,7 +61,7 @@ ct:TransactionReceipt: | bytes transaction_receipt = 1; ... --- -initiation: [get_balance, get_raw_transaction, send_signed_transaction] +initiation: [get_balance, get_raw_transaction, send_signed_transaction, get_transaction_receipt] reply: get_balance: [balance] balance: [] @@ -56,4 +75,7 @@ reply: termination: [balance, transaction_receipt] roles: {agent, ledger} end_states: [successful] -... \ No newline at end of file +... +``` + +## Links diff --git a/packages/fetchai/protocols/ledger_api/dialogues.py b/packages/fetchai/protocols/ledger_api/dialogues.py index d9ba38ddac..11ce182a86 100644 --- a/packages/fetchai/protocols/ledger_api/dialogues.py +++ b/packages/fetchai/protocols/ledger_api/dialogues.py @@ -42,6 +42,7 @@ class LedgerApiDialogue(Dialogue): LedgerApiMessage.Performative.GET_BALANCE, LedgerApiMessage.Performative.GET_RAW_TRANSACTION, LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, + LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, } ) TERMINAL_PERFORMATIVES = frozenset( @@ -158,7 +159,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> LedgerApiDialogue: """ - Create an instance of fipa dialogue. + Create an instance of ledger_api dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/ledger_api/message.py b/packages/fetchai/protocols/ledger_api/message.py index a18d83b613..54d418f28f 100644 --- a/packages/fetchai/protocols/ledger_api/message.py +++ b/packages/fetchai/protocols/ledger_api/message.py @@ -48,7 +48,7 @@ class LedgerApiMessage(Message): """A protocol for ledger APIs requests and responses.""" - protocol_id = ProtocolId("fetchai", "ledger_api", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/ledger_api:0.2.0") RawTransaction = CustomRawTransaction diff --git a/packages/fetchai/protocols/ledger_api/protocol.yaml b/packages/fetchai/protocols/ledger_api/protocol.yaml index 489723c3ea..06b32a9639 100644 --- a/packages/fetchai/protocols/ledger_api/protocol.yaml +++ b/packages/fetchai/protocols/ledger_api/protocol.yaml @@ -1,16 +1,17 @@ name: ledger_api author: fetchai -version: 0.1.0 +version: 0.2.0 description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmdSwpA6Z5p93mwxJdA6go8Fs3H7pHVx8yBa2Qm6QDoKXa __init__.py: Qmct8jVx6ndWwaa5HXJAJgMraVuZ8kMeyx6rnEeHAYHwDJ custom_types.py: QmWRrvFStMhVJy8P2WD6qjDgk14ZnxErN7XymxUtof7HQo - dialogues.py: QmdXcqQQAMZQWscKkgi61JtzMAsucFKjSimnephhxyWaPp + dialogues.py: QmW93kSNv6sETs2zZTcPVqoVnEADSHotu9vLP9QFQV7zrP ledger_api.proto: QmfLcv7jJcGJ1gAdCMqsyxJcRud7RaTWteSXHL5NvGuViP ledger_api_pb2.py: QmQhM848REJTDKDoiqxkTniChW8bNNm66EtwMRkvVdbMry - message.py: QmNPKh6Pdb9Eryc2mFxkzeiZZt1wESrvKBGriqeszUAGSj + message.py: QmTvJcttBx5Z2hNbJVHNFPyL9vXQYRkvmriCRDEeJnVVqq serialization.py: QmUvysZKkt5xLKLVHAyaZQ3jsRDkPn5bJURdsTDHgkE3HS fingerprint_ignore_patterns: [] dependencies: diff --git a/examples/protocol_specification_ex/ml_trade.yaml b/packages/fetchai/protocols/ml_trade/README.md similarity index 66% rename from examples/protocol_specification_ex/ml_trade.yaml rename to packages/fetchai/protocols/ml_trade/README.md index 44098d5bf6..45e7ee2637 100644 --- a/examples/protocol_specification_ex/ml_trade.yaml +++ b/packages/fetchai/protocols/ml_trade/README.md @@ -1,7 +1,26 @@ +# ML Trade Protocol + +**Name:** ml_trade + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: A protocol for trading data for training and prediction purposes. + +**License**: Apache-2.0 + +## Description + +This is a protocol for trading data for training and prediction purposes. + +## Specification + +```yaml --- name: ml_trade author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -39,4 +58,7 @@ reply: termination: [data] roles: {seller, buyer} end_states: [successful] -... \ No newline at end of file +... +``` + +## Links diff --git a/packages/fetchai/protocols/ml_trade/dialogues.py b/packages/fetchai/protocols/ml_trade/dialogues.py index 263794b3c9..6c18244170 100644 --- a/packages/fetchai/protocols/ml_trade/dialogues.py +++ b/packages/fetchai/protocols/ml_trade/dialogues.py @@ -125,7 +125,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> MlTradeDialogue: """ - Create an instance of fipa dialogue. + Create an instance of ml_trade dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/ml_trade/message.py b/packages/fetchai/protocols/ml_trade/message.py index 72cedcd1d0..7161f68c3b 100644 --- a/packages/fetchai/protocols/ml_trade/message.py +++ b/packages/fetchai/protocols/ml_trade/message.py @@ -39,7 +39,7 @@ class MlTradeMessage(Message): """A protocol for trading data for training and prediction purposes.""" - protocol_id = ProtocolId("fetchai", "ml_trade", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/ml_trade:0.4.0") Description = CustomDescription diff --git a/packages/fetchai/protocols/ml_trade/protocol.yaml b/packages/fetchai/protocols/ml_trade/protocol.yaml index ab17b5299b..f44b46a106 100644 --- a/packages/fetchai/protocols/ml_trade/protocol.yaml +++ b/packages/fetchai/protocols/ml_trade/protocol.yaml @@ -1,14 +1,15 @@ name: ml_trade author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmPtJwd9ApR8N6pYdC5ddetPF7tEn6EPmnqiU1NZNmz5EU __init__.py: QmXZMVdsBXUJxLZvwwhWBx58xfxMSyoGxdYp5Aeqmzqhzt custom_types.py: QmPa6mxbN8WShsniQxJACfzAPRjGzYLbUFGoVU4N9DewUw - dialogues.py: QmZFztFu4LxHdsJZpSHizELFStHtz2ZGfQBx9cnP7gHHWf - message.py: QmdCpkebeDrFZk4R7S2mrX2KMCDgo8JV78Hj6jb6sA5EL4 + dialogues.py: QmUV1KiKgdAak1UHe3me7kRUNonhZY1FC57SLnpSCgYpvX + message.py: QmNaZ6tFMcux7K374dDCGeKx6FMVQSrog2ruSxnprPnFEv ml_trade.proto: QmeB21MQduEGQCrtiYZQzPpRqHL4CWEkvvcaKZ9GsfE8f6 ml_trade_pb2.py: QmZVvugPysR1og6kWCJkvo3af2s9pQRHfuj4BptE7gU1EU serialization.py: QmSHywy12uQkzakU1RHnnkaPuTzaFTALsKisyYF8dPc8ns diff --git a/examples/protocol_specification_ex/oef_search.yaml b/packages/fetchai/protocols/oef_search/README.md similarity index 62% rename from examples/protocol_specification_ex/oef_search.yaml rename to packages/fetchai/protocols/oef_search/README.md index 2ec88ba6ea..c3e11c95c6 100644 --- a/examples/protocol_specification_ex/oef_search.yaml +++ b/packages/fetchai/protocols/oef_search/README.md @@ -1,3 +1,23 @@ +# OEF Search Protocol + +**Name:** oef_search + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: A protocol for interacting with an OEF search service. + +**License**: Apache-2.0 + +## Description + +This is a protocol for interacting with an OEF search service. +It allows for registering of agents and services, and searching of agents and services using a query language. + +## Specification + +```yaml --- name: oef_search author: fetchai @@ -14,8 +34,7 @@ speech_acts: query: ct:Query search_result: agents: pt:list[pt:str] - success: {} - error: + oef_error: oef_error_operation: ct:OefErrorOperation ... --- @@ -41,13 +60,16 @@ ct:OefErrorOperation: | --- initiation: [register_service, unregister_service, search_services] reply: - register_service: [success, error] - unregister_service: [success, error] - search_services: [search_result, error] + register_service: [oef_error] + unregister_service: [oef_error] + search_services: [search_result, oef_error] success: [] search_result: [] - error: [] -termination: [success, error, search_result] + oef_error: [] +termination: [oef_error, search_result] roles: {agent, oef_node} end_states: [successful, failed] -... \ No newline at end of file +... +``` + +## Links diff --git a/packages/fetchai/protocols/oef_search/dialogues.py b/packages/fetchai/protocols/oef_search/dialogues.py index 90daad5cef..d69507a40a 100644 --- a/packages/fetchai/protocols/oef_search/dialogues.py +++ b/packages/fetchai/protocols/oef_search/dialogues.py @@ -145,7 +145,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> OefSearchDialogue: """ - Create an instance of fipa dialogue. + Create an instance of oef_search dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/oef_search/message.py b/packages/fetchai/protocols/oef_search/message.py index 44162b200a..b1dbadac49 100644 --- a/packages/fetchai/protocols/oef_search/message.py +++ b/packages/fetchai/protocols/oef_search/message.py @@ -42,7 +42,7 @@ class OefSearchMessage(Message): """A protocol for interacting with an OEF search service.""" - protocol_id = ProtocolId("fetchai", "oef_search", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/oef_search:0.4.0") Description = CustomDescription diff --git a/packages/fetchai/protocols/oef_search/protocol.yaml b/packages/fetchai/protocols/oef_search/protocol.yaml index dfa8095446..1efc074c8d 100644 --- a/packages/fetchai/protocols/oef_search/protocol.yaml +++ b/packages/fetchai/protocols/oef_search/protocol.yaml @@ -1,14 +1,15 @@ name: oef_search author: fetchai -version: 0.3.0 +version: 0.4.0 description: A protocol for interacting with an OEF search service. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmWKpDy8H5V6vXkejyH8XQuQCA4aScTNvnDtM1gpLgFiC6 __init__.py: QmRvTtynKcd7shmzgf8aZdcA5witjNL5cL2a7WPgscp7wq custom_types.py: QmR4TS6KhXpRtGqq78B8mXMiiFXcFe7JEkxB7jHvqPVkgD - dialogues.py: QmQyUVWzX8uMq48sWU6pUBazk7UiTMhydLDVLWQs9djY6v - message.py: QmY5qSJawsgmcKZ3dDBij9s4hN41BpnhbzTtVkRaQdT6QU + dialogues.py: QmUYiDwidkEiwGh7xqTuBcZ44CTtu5wdCCUZqpEmQkDWha + message.py: QmV8AFX5pQjC4u3ZDfkyy2DzJsTHd9zE5b6GNteKuenAs6 oef_search.proto: QmRg28H6bNo1PcyJiKLYjHe6FCwtE6nJ43DeJ4RFTcHm68 oef_search_pb2.py: Qmd6S94v2GuZ2ffDupTa5ESBx4exF9dgoV8KcYtJVL6KhN serialization.py: QmfXX9HJsQvNfeffGxPeUBw7cMznSjojDYe6TZ6jHpphQ4 diff --git a/examples/protocol_specification_ex/tac.yaml b/packages/fetchai/protocols/tac/README.md similarity index 69% rename from examples/protocol_specification_ex/tac.yaml rename to packages/fetchai/protocols/tac/README.md index 0aa87c1ec7..fbcfa8bb8c 100644 --- a/examples/protocol_specification_ex/tac.yaml +++ b/packages/fetchai/protocols/tac/README.md @@ -1,7 +1,26 @@ +# TAC Protocol + +**Name:** tac + +**Author**: fetchai + +**Version**: 0.4.0 + +**Short Description**: The tac protocol implements the messages an AEA needs to participate in a Trading Agent Competition (TAC). + +**License**: Apache-2.0 + +## Description + +This is a protocol for participating in a Trading Agent Competition (TAC). + +## Specification + +```yaml --- name: tac author: fetchai -version: 0.3.0 +version: 0.4.0 description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 @@ -11,30 +30,30 @@ speech_acts: agent_name: pt:str unregister: {} transaction: - tx_id: pt:str - tx_sender_addr: pt:str - tx_counterparty_addr: pt:str + transaction_id: pt:str + ledger_id: pt:str + sender_address: pt:str + counterparty_address: pt:str amount_by_currency_id: pt:dict[pt:str, pt:int] - tx_sender_fee: pt:int - tx_counterparty_fee: pt:int + fee_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] - tx_nonce: pt:int - tx_sender_signature: pt:str - tx_counterparty_signature: pt:str + nonce: pt:str + sender_signature: pt:str + counterparty_signature: pt:str cancelled: {} game_data: amount_by_currency_id: pt:dict[pt:str, pt:int] exchange_params_by_currency_id: pt:dict[pt:str, pt:float] quantities_by_good_id: pt:dict[pt:str, pt:int] utility_params_by_good_id: pt:dict[pt:str, pt:float] - tx_fee: pt:int + fee_by_currency_id: pt:dict[pt:str, pt:int] agent_addr_to_name: pt:dict[pt:str, pt:str] currency_id_to_name: pt:dict[pt:str, pt:str] good_id_to_name: pt:dict[pt:str, pt:str] version_id: pt:str info: pt:optional[pt:dict[pt:str, pt:str]] transaction_confirmation: - tx_id: pt:str + transaction_id: pt:str amount_by_currency_id: pt:dict[pt:str, pt:int] quantities_by_good_id: pt:dict[pt:str, pt:int] tac_error: @@ -71,3 +90,8 @@ termination: [cancelled, tac_error] roles: {participant, controller} end_states: [successful, failed] ... +``` + +## Links + +* TAC skill in the AEA framework \ No newline at end of file diff --git a/packages/fetchai/protocols/tac/dialogues.py b/packages/fetchai/protocols/tac/dialogues.py index e26f2853ae..37c63d6d36 100644 --- a/packages/fetchai/protocols/tac/dialogues.py +++ b/packages/fetchai/protocols/tac/dialogues.py @@ -146,7 +146,7 @@ def create_dialogue( self, dialogue_label: DialogueLabel, role: Dialogue.Role, ) -> TacDialogue: """ - Create an instance of fipa dialogue. + Create an instance of tac dialogue. :param dialogue_label: the identifier of the dialogue :param role: the role of the agent this dialogue is maintained for diff --git a/packages/fetchai/protocols/tac/message.py b/packages/fetchai/protocols/tac/message.py index 257dc705b2..6d032cb2b4 100644 --- a/packages/fetchai/protocols/tac/message.py +++ b/packages/fetchai/protocols/tac/message.py @@ -36,7 +36,7 @@ class TacMessage(Message): """The tac protocol implements the messages an AEA needs to participate in the TAC.""" - protocol_id = ProtocolId("fetchai", "tac", "0.3.0") + protocol_id = ProtocolId.from_str("fetchai/tac:0.4.0") ErrorCode = CustomErrorCode @@ -139,6 +139,22 @@ def amount_by_currency_id(self) -> Dict[str, int]: ), "'amount_by_currency_id' content is not set." return cast(Dict[str, int], self.get("amount_by_currency_id")) + @property + def counterparty_address(self) -> str: + """Get the 'counterparty_address' content from the message.""" + assert self.is_set( + "counterparty_address" + ), "'counterparty_address' content is not set." + return cast(str, self.get("counterparty_address")) + + @property + def counterparty_signature(self) -> str: + """Get the 'counterparty_signature' content from the message.""" + assert self.is_set( + "counterparty_signature" + ), "'counterparty_signature' content is not set." + return cast(str, self.get("counterparty_signature")) + @property def currency_id_to_name(self) -> Dict[str, str]: """Get the 'currency_id_to_name' content from the message.""" @@ -161,6 +177,14 @@ def exchange_params_by_currency_id(self) -> Dict[str, float]: ), "'exchange_params_by_currency_id' content is not set." return cast(Dict[str, float], self.get("exchange_params_by_currency_id")) + @property + def fee_by_currency_id(self) -> Dict[str, int]: + """Get the 'fee_by_currency_id' content from the message.""" + assert self.is_set( + "fee_by_currency_id" + ), "'fee_by_currency_id' content is not set." + return cast(Dict[str, int], self.get("fee_by_currency_id")) + @property def good_id_to_name(self) -> Dict[str, str]: """Get the 'good_id_to_name' content from the message.""" @@ -172,6 +196,18 @@ def info(self) -> Optional[Dict[str, str]]: """Get the 'info' content from the message.""" return cast(Optional[Dict[str, str]], self.get("info")) + @property + def ledger_id(self) -> str: + """Get the 'ledger_id' content from the message.""" + assert self.is_set("ledger_id"), "'ledger_id' content is not set." + return cast(str, self.get("ledger_id")) + + @property + def nonce(self) -> str: + """Get the 'nonce' content from the message.""" + assert self.is_set("nonce"), "'nonce' content is not set." + return cast(str, self.get("nonce")) + @property def quantities_by_good_id(self) -> Dict[str, int]: """Get the 'quantities_by_good_id' content from the message.""" @@ -181,66 +217,22 @@ def quantities_by_good_id(self) -> Dict[str, int]: return cast(Dict[str, int], self.get("quantities_by_good_id")) @property - def tx_counterparty_addr(self) -> str: - """Get the 'tx_counterparty_addr' content from the message.""" - assert self.is_set( - "tx_counterparty_addr" - ), "'tx_counterparty_addr' content is not set." - return cast(str, self.get("tx_counterparty_addr")) - - @property - def tx_counterparty_fee(self) -> int: - """Get the 'tx_counterparty_fee' content from the message.""" - assert self.is_set( - "tx_counterparty_fee" - ), "'tx_counterparty_fee' content is not set." - return cast(int, self.get("tx_counterparty_fee")) - - @property - def tx_counterparty_signature(self) -> str: - """Get the 'tx_counterparty_signature' content from the message.""" - assert self.is_set( - "tx_counterparty_signature" - ), "'tx_counterparty_signature' content is not set." - return cast(str, self.get("tx_counterparty_signature")) - - @property - def tx_fee(self) -> int: - """Get the 'tx_fee' content from the message.""" - assert self.is_set("tx_fee"), "'tx_fee' content is not set." - return cast(int, self.get("tx_fee")) + def sender_address(self) -> str: + """Get the 'sender_address' content from the message.""" + assert self.is_set("sender_address"), "'sender_address' content is not set." + return cast(str, self.get("sender_address")) @property - def tx_id(self) -> str: - """Get the 'tx_id' content from the message.""" - assert self.is_set("tx_id"), "'tx_id' content is not set." - return cast(str, self.get("tx_id")) + def sender_signature(self) -> str: + """Get the 'sender_signature' content from the message.""" + assert self.is_set("sender_signature"), "'sender_signature' content is not set." + return cast(str, self.get("sender_signature")) @property - def tx_nonce(self) -> int: - """Get the 'tx_nonce' content from the message.""" - assert self.is_set("tx_nonce"), "'tx_nonce' content is not set." - return cast(int, self.get("tx_nonce")) - - @property - def tx_sender_addr(self) -> str: - """Get the 'tx_sender_addr' content from the message.""" - assert self.is_set("tx_sender_addr"), "'tx_sender_addr' content is not set." - return cast(str, self.get("tx_sender_addr")) - - @property - def tx_sender_fee(self) -> int: - """Get the 'tx_sender_fee' content from the message.""" - assert self.is_set("tx_sender_fee"), "'tx_sender_fee' content is not set." - return cast(int, self.get("tx_sender_fee")) - - @property - def tx_sender_signature(self) -> str: - """Get the 'tx_sender_signature' content from the message.""" - assert self.is_set( - "tx_sender_signature" - ), "'tx_sender_signature' content is not set." - return cast(str, self.get("tx_sender_signature")) + def transaction_id(self) -> str: + """Get the 'transaction_id' content from the message.""" + assert self.is_set("transaction_id"), "'transaction_id' content is not set." + return cast(str, self.get("transaction_id")) @property def utility_params_by_good_id(self) -> Dict[str, float]: @@ -308,19 +300,24 @@ def _is_consistent(self) -> bool: elif self.performative == TacMessage.Performative.TRANSACTION: expected_nb_of_contents = 10 assert ( - type(self.tx_id) == str - ), "Invalid type for content 'tx_id'. Expected 'str'. Found '{}'.".format( - type(self.tx_id) + type(self.transaction_id) == str + ), "Invalid type for content 'transaction_id'. Expected 'str'. Found '{}'.".format( + type(self.transaction_id) + ) + assert ( + type(self.ledger_id) == str + ), "Invalid type for content 'ledger_id'. Expected 'str'. Found '{}'.".format( + type(self.ledger_id) ) assert ( - type(self.tx_sender_addr) == str - ), "Invalid type for content 'tx_sender_addr'. Expected 'str'. Found '{}'.".format( - type(self.tx_sender_addr) + type(self.sender_address) == str + ), "Invalid type for content 'sender_address'. Expected 'str'. Found '{}'.".format( + type(self.sender_address) ) assert ( - type(self.tx_counterparty_addr) == str - ), "Invalid type for content 'tx_counterparty_addr'. Expected 'str'. Found '{}'.".format( - type(self.tx_counterparty_addr) + type(self.counterparty_address) == str + ), "Invalid type for content 'counterparty_address'. Expected 'str'. Found '{}'.".format( + type(self.counterparty_address) ) assert ( type(self.amount_by_currency_id) == dict @@ -342,15 +339,24 @@ def _is_consistent(self) -> bool: type(value_of_amount_by_currency_id) ) assert ( - type(self.tx_sender_fee) == int - ), "Invalid type for content 'tx_sender_fee'. Expected 'int'. Found '{}'.".format( - type(self.tx_sender_fee) - ) - assert ( - type(self.tx_counterparty_fee) == int - ), "Invalid type for content 'tx_counterparty_fee'. Expected 'int'. Found '{}'.".format( - type(self.tx_counterparty_fee) + type(self.fee_by_currency_id) == dict + ), "Invalid type for content 'fee_by_currency_id'. Expected 'dict'. Found '{}'.".format( + type(self.fee_by_currency_id) ) + for ( + key_of_fee_by_currency_id, + value_of_fee_by_currency_id, + ) in self.fee_by_currency_id.items(): + assert ( + type(key_of_fee_by_currency_id) == str + ), "Invalid type for dictionary keys in content 'fee_by_currency_id'. Expected 'str'. Found '{}'.".format( + type(key_of_fee_by_currency_id) + ) + assert ( + type(value_of_fee_by_currency_id) == int + ), "Invalid type for dictionary values in content 'fee_by_currency_id'. Expected 'int'. Found '{}'.".format( + type(value_of_fee_by_currency_id) + ) assert ( type(self.quantities_by_good_id) == dict ), "Invalid type for content 'quantities_by_good_id'. Expected 'dict'. Found '{}'.".format( @@ -371,19 +377,19 @@ def _is_consistent(self) -> bool: type(value_of_quantities_by_good_id) ) assert ( - type(self.tx_nonce) == int - ), "Invalid type for content 'tx_nonce'. Expected 'int'. Found '{}'.".format( - type(self.tx_nonce) + type(self.nonce) == str + ), "Invalid type for content 'nonce'. Expected 'str'. Found '{}'.".format( + type(self.nonce) ) assert ( - type(self.tx_sender_signature) == str - ), "Invalid type for content 'tx_sender_signature'. Expected 'str'. Found '{}'.".format( - type(self.tx_sender_signature) + type(self.sender_signature) == str + ), "Invalid type for content 'sender_signature'. Expected 'str'. Found '{}'.".format( + type(self.sender_signature) ) assert ( - type(self.tx_counterparty_signature) == str - ), "Invalid type for content 'tx_counterparty_signature'. Expected 'str'. Found '{}'.".format( - type(self.tx_counterparty_signature) + type(self.counterparty_signature) == str + ), "Invalid type for content 'counterparty_signature'. Expected 'str'. Found '{}'.".format( + type(self.counterparty_signature) ) elif self.performative == TacMessage.Performative.CANCELLED: expected_nb_of_contents = 0 @@ -466,10 +472,24 @@ def _is_consistent(self) -> bool: type(value_of_utility_params_by_good_id) ) assert ( - type(self.tx_fee) == int - ), "Invalid type for content 'tx_fee'. Expected 'int'. Found '{}'.".format( - type(self.tx_fee) + type(self.fee_by_currency_id) == dict + ), "Invalid type for content 'fee_by_currency_id'. Expected 'dict'. Found '{}'.".format( + type(self.fee_by_currency_id) ) + for ( + key_of_fee_by_currency_id, + value_of_fee_by_currency_id, + ) in self.fee_by_currency_id.items(): + assert ( + type(key_of_fee_by_currency_id) == str + ), "Invalid type for dictionary keys in content 'fee_by_currency_id'. Expected 'str'. Found '{}'.".format( + type(key_of_fee_by_currency_id) + ) + assert ( + type(value_of_fee_by_currency_id) == int + ), "Invalid type for dictionary values in content 'fee_by_currency_id'. Expected 'int'. Found '{}'.".format( + type(value_of_fee_by_currency_id) + ) assert ( type(self.agent_addr_to_name) == dict ), "Invalid type for content 'agent_addr_to_name'. Expected 'dict'. Found '{}'.".format( @@ -554,9 +574,9 @@ def _is_consistent(self) -> bool: elif self.performative == TacMessage.Performative.TRANSACTION_CONFIRMATION: expected_nb_of_contents = 3 assert ( - type(self.tx_id) == str - ), "Invalid type for content 'tx_id'. Expected 'str'. Found '{}'.".format( - type(self.tx_id) + type(self.transaction_id) == str + ), "Invalid type for content 'transaction_id'. Expected 'str'. Found '{}'.".format( + type(self.transaction_id) ) assert ( type(self.amount_by_currency_id) == dict diff --git a/packages/fetchai/protocols/tac/protocol.yaml b/packages/fetchai/protocols/tac/protocol.yaml index 2fafda3710..4c89e6ec3b 100644 --- a/packages/fetchai/protocols/tac/protocol.yaml +++ b/packages/fetchai/protocols/tac/protocol.yaml @@ -1,18 +1,19 @@ name: tac author: fetchai -version: 0.3.0 +version: 0.4.0 description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: + README.md: QmTbC6jKYdPHMRp8KfFo5m7gvk7tDUVts8PGHaPSkK5cTw __init__.py: QmZYdAjm3o44drRiY3MT4RtG2fFLxtaL8h898DmjoJwJzV custom_types.py: QmXQATfnvuCpt4FicF4QcqCcLj9PQNsSHjCBvVQknWpyaN - dialogues.py: QmPgpHYgGMvhs11j1mwfMLyBwY8njfMkFNa11JVvyUnb8V - message.py: QmSwTV913SRq1AcJP6NTwkBRx6JS6Jt89LNJFwHB7dpo6m - serialization.py: QmYfsDQXv8j3CyQgQqv77CYLfu9WeNFSGgfhhVzLcPbJpj - tac.proto: QmedPvKHu387gAsdxTDLWgGcCucYXEfCaTiLJbTJPRqDkR - tac_pb2.py: QmbjMx3iSHq1FY2kGQR4tJfnS1HQiRCQRrnyv7dFUxEi2V + dialogues.py: QmTEK2XKfH3wQ728K7ex6zW15vjzmxT93eN147HeFoBPeV + message.py: QmW6AYgm62sRfrVXYeduc7BYRm7wWvUUfn3JJWr2awTVZc + serialization.py: QmfZMesx1EFVYx1pj5SBn3eF7A2fz5a8cnBKzhBmVha31U + tac.proto: QmdpPZNhUW593qVNVoSTWZgd9R69bmBbw6Y9xjzYpvuDvV + tac_pb2.py: QmUwW3kixKwD2o1RRdq4NoNoihPb5BXKKRngWXztq32fea fingerprint_ignore_patterns: [] dependencies: protobuf: {} diff --git a/packages/fetchai/protocols/tac/serialization.py b/packages/fetchai/protocols/tac/serialization.py index 694acb3d5b..d709b5cee0 100644 --- a/packages/fetchai/protocols/tac/serialization.py +++ b/packages/fetchai/protocols/tac/serialization.py @@ -59,26 +59,26 @@ def encode(msg: Message) -> bytes: tac_msg.unregister.CopyFrom(performative) elif performative_id == TacMessage.Performative.TRANSACTION: performative = tac_pb2.TacMessage.Transaction_Performative() # type: ignore - tx_id = msg.tx_id - performative.tx_id = tx_id - tx_sender_addr = msg.tx_sender_addr - performative.tx_sender_addr = tx_sender_addr - tx_counterparty_addr = msg.tx_counterparty_addr - performative.tx_counterparty_addr = tx_counterparty_addr + transaction_id = msg.transaction_id + performative.transaction_id = transaction_id + ledger_id = msg.ledger_id + performative.ledger_id = ledger_id + sender_address = msg.sender_address + performative.sender_address = sender_address + counterparty_address = msg.counterparty_address + performative.counterparty_address = counterparty_address amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) - tx_sender_fee = msg.tx_sender_fee - performative.tx_sender_fee = tx_sender_fee - tx_counterparty_fee = msg.tx_counterparty_fee - performative.tx_counterparty_fee = tx_counterparty_fee + fee_by_currency_id = msg.fee_by_currency_id + performative.fee_by_currency_id.update(fee_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id performative.quantities_by_good_id.update(quantities_by_good_id) - tx_nonce = msg.tx_nonce - performative.tx_nonce = tx_nonce - tx_sender_signature = msg.tx_sender_signature - performative.tx_sender_signature = tx_sender_signature - tx_counterparty_signature = msg.tx_counterparty_signature - performative.tx_counterparty_signature = tx_counterparty_signature + nonce = msg.nonce + performative.nonce = nonce + sender_signature = msg.sender_signature + performative.sender_signature = sender_signature + counterparty_signature = msg.counterparty_signature + performative.counterparty_signature = counterparty_signature tac_msg.transaction.CopyFrom(performative) elif performative_id == TacMessage.Performative.CANCELLED: performative = tac_pb2.TacMessage.Cancelled_Performative() # type: ignore @@ -95,8 +95,8 @@ def encode(msg: Message) -> bytes: performative.quantities_by_good_id.update(quantities_by_good_id) utility_params_by_good_id = msg.utility_params_by_good_id performative.utility_params_by_good_id.update(utility_params_by_good_id) - tx_fee = msg.tx_fee - performative.tx_fee = tx_fee + fee_by_currency_id = msg.fee_by_currency_id + performative.fee_by_currency_id.update(fee_by_currency_id) agent_addr_to_name = msg.agent_addr_to_name performative.agent_addr_to_name.update(agent_addr_to_name) currency_id_to_name = msg.currency_id_to_name @@ -112,8 +112,8 @@ def encode(msg: Message) -> bytes: tac_msg.game_data.CopyFrom(performative) elif performative_id == TacMessage.Performative.TRANSACTION_CONFIRMATION: performative = tac_pb2.TacMessage.Transaction_Confirmation_Performative() # type: ignore - tx_id = msg.tx_id - performative.tx_id = tx_id + transaction_id = msg.transaction_id + performative.transaction_id = transaction_id amount_by_currency_id = msg.amount_by_currency_id performative.amount_by_currency_id.update(amount_by_currency_id) quantities_by_good_id = msg.quantities_by_good_id @@ -160,30 +160,29 @@ def decode(obj: bytes) -> Message: elif performative_id == TacMessage.Performative.UNREGISTER: pass elif performative_id == TacMessage.Performative.TRANSACTION: - tx_id = tac_pb.transaction.tx_id - performative_content["tx_id"] = tx_id - tx_sender_addr = tac_pb.transaction.tx_sender_addr - performative_content["tx_sender_addr"] = tx_sender_addr - tx_counterparty_addr = tac_pb.transaction.tx_counterparty_addr - performative_content["tx_counterparty_addr"] = tx_counterparty_addr + transaction_id = tac_pb.transaction.transaction_id + performative_content["transaction_id"] = transaction_id + ledger_id = tac_pb.transaction.ledger_id + performative_content["ledger_id"] = ledger_id + sender_address = tac_pb.transaction.sender_address + performative_content["sender_address"] = sender_address + counterparty_address = tac_pb.transaction.counterparty_address + performative_content["counterparty_address"] = counterparty_address amount_by_currency_id = tac_pb.transaction.amount_by_currency_id amount_by_currency_id_dict = dict(amount_by_currency_id) performative_content["amount_by_currency_id"] = amount_by_currency_id_dict - tx_sender_fee = tac_pb.transaction.tx_sender_fee - performative_content["tx_sender_fee"] = tx_sender_fee - tx_counterparty_fee = tac_pb.transaction.tx_counterparty_fee - performative_content["tx_counterparty_fee"] = tx_counterparty_fee + fee_by_currency_id = tac_pb.transaction.fee_by_currency_id + fee_by_currency_id_dict = dict(fee_by_currency_id) + performative_content["fee_by_currency_id"] = fee_by_currency_id_dict quantities_by_good_id = tac_pb.transaction.quantities_by_good_id quantities_by_good_id_dict = dict(quantities_by_good_id) performative_content["quantities_by_good_id"] = quantities_by_good_id_dict - tx_nonce = tac_pb.transaction.tx_nonce - performative_content["tx_nonce"] = tx_nonce - tx_sender_signature = tac_pb.transaction.tx_sender_signature - performative_content["tx_sender_signature"] = tx_sender_signature - tx_counterparty_signature = tac_pb.transaction.tx_counterparty_signature - performative_content[ - "tx_counterparty_signature" - ] = tx_counterparty_signature + nonce = tac_pb.transaction.nonce + performative_content["nonce"] = nonce + sender_signature = tac_pb.transaction.sender_signature + performative_content["sender_signature"] = sender_signature + counterparty_signature = tac_pb.transaction.counterparty_signature + performative_content["counterparty_signature"] = counterparty_signature elif performative_id == TacMessage.Performative.CANCELLED: pass elif performative_id == TacMessage.Performative.GAME_DATA: @@ -205,8 +204,9 @@ def decode(obj: bytes) -> Message: performative_content[ "utility_params_by_good_id" ] = utility_params_by_good_id_dict - tx_fee = tac_pb.game_data.tx_fee - performative_content["tx_fee"] = tx_fee + fee_by_currency_id = tac_pb.game_data.fee_by_currency_id + fee_by_currency_id_dict = dict(fee_by_currency_id) + performative_content["fee_by_currency_id"] = fee_by_currency_id_dict agent_addr_to_name = tac_pb.game_data.agent_addr_to_name agent_addr_to_name_dict = dict(agent_addr_to_name) performative_content["agent_addr_to_name"] = agent_addr_to_name_dict @@ -223,8 +223,8 @@ def decode(obj: bytes) -> Message: info_dict = dict(info) performative_content["info"] = info_dict elif performative_id == TacMessage.Performative.TRANSACTION_CONFIRMATION: - tx_id = tac_pb.transaction_confirmation.tx_id - performative_content["tx_id"] = tx_id + transaction_id = tac_pb.transaction_confirmation.transaction_id + performative_content["transaction_id"] = transaction_id amount_by_currency_id = ( tac_pb.transaction_confirmation.amount_by_currency_id ) diff --git a/packages/fetchai/protocols/tac/tac.proto b/packages/fetchai/protocols/tac/tac.proto index f81404142c..ce16512417 100644 --- a/packages/fetchai/protocols/tac/tac.proto +++ b/packages/fetchai/protocols/tac/tac.proto @@ -30,16 +30,16 @@ message TacMessage{ message Unregister_Performative{} message Transaction_Performative{ - string tx_id = 1; - string tx_sender_addr = 2; - string tx_counterparty_addr = 3; - map amount_by_currency_id = 4; - int32 tx_sender_fee = 5; - int32 tx_counterparty_fee = 6; + string transaction_id = 1; + string ledger_id = 2; + string sender_address = 3; + string counterparty_address = 4; + map amount_by_currency_id = 5; + map fee_by_currency_id = 6; map quantities_by_good_id = 7; - int32 tx_nonce = 8; - string tx_sender_signature = 9; - string tx_counterparty_signature = 10; + string nonce = 8; + string sender_signature = 9; + string counterparty_signature = 10; } message Cancelled_Performative{} @@ -49,7 +49,7 @@ message TacMessage{ map exchange_params_by_currency_id = 2; map quantities_by_good_id = 3; map utility_params_by_good_id = 4; - int32 tx_fee = 5; + map fee_by_currency_id = 5; map agent_addr_to_name = 6; map currency_id_to_name = 7; map good_id_to_name = 8; @@ -59,7 +59,7 @@ message TacMessage{ } message Transaction_Confirmation_Performative{ - string tx_id = 1; + string transaction_id = 1; map amount_by_currency_id = 2; map quantities_by_good_id = 3; } diff --git a/packages/fetchai/protocols/tac/tac_pb2.py b/packages/fetchai/protocols/tac/tac_pb2.py index 3e55f37fa7..cedae73bc1 100644 --- a/packages/fetchai/protocols/tac/tac_pb2.py +++ b/packages/fetchai/protocols/tac/tac_pb2.py @@ -17,7 +17,7 @@ package="fetch.aea.Tac", syntax="proto3", serialized_options=None, - serialized_pb=b'\n\ttac.proto\x12\rfetch.aea.Tac"\xfe\x1c\n\nTacMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12\x45\n\tcancelled\x18\x05 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Cancelled_PerformativeH\x00\x12\x45\n\tgame_data\x18\x06 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Game_Data_PerformativeH\x00\x12\x43\n\x08register\x18\x07 \x01(\x0b\x32/.fetch.aea.Tac.TacMessage.Register_PerformativeH\x00\x12\x45\n\ttac_error\x18\x08 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Tac_Error_PerformativeH\x00\x12I\n\x0btransaction\x18\t \x01(\x0b\x32\x32.fetch.aea.Tac.TacMessage.Transaction_PerformativeH\x00\x12\x63\n\x18transaction_confirmation\x18\n \x01(\x0b\x32?.fetch.aea.Tac.TacMessage.Transaction_Confirmation_PerformativeH\x00\x12G\n\nunregister\x18\x0b \x01(\x0b\x32\x31.fetch.aea.Tac.TacMessage.Unregister_PerformativeH\x00\x1a\x80\x03\n\tErrorCode\x12\x45\n\nerror_code\x18\x01 \x01(\x0e\x32\x31.fetch.aea.Tac.TacMessage.ErrorCode.ErrorCodeEnum"\xab\x02\n\rErrorCodeEnum\x12\x11\n\rGENERIC_ERROR\x10\x00\x12\x15\n\x11REQUEST_NOT_VALID\x10\x01\x12!\n\x1d\x41GENT_ADDR_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\x12\x19\n\x15\x44IALOGUE_INCONSISTENT\x10\t\x1a+\n\x15Register_Performative\x12\x12\n\nagent_name\x18\x01 \x01(\t\x1a\x19\n\x17Unregister_Performative\x1a\xb1\x04\n\x18Transaction_Performative\x12\r\n\x05tx_id\x18\x01 \x01(\t\x12\x16\n\x0etx_sender_addr\x18\x02 \x01(\t\x12\x1c\n\x14tx_counterparty_addr\x18\x03 \x01(\t\x12i\n\x15\x61mount_by_currency_id\x18\x04 \x03(\x0b\x32J.fetch.aea.Tac.TacMessage.Transaction_Performative.AmountByCurrencyIdEntry\x12\x15\n\rtx_sender_fee\x18\x05 \x01(\x05\x12\x1b\n\x13tx_counterparty_fee\x18\x06 \x01(\x05\x12i\n\x15quantities_by_good_id\x18\x07 \x03(\x0b\x32J.fetch.aea.Tac.TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry\x12\x10\n\x08tx_nonce\x18\x08 \x01(\x05\x12\x1b\n\x13tx_sender_signature\x18\t \x01(\t\x12!\n\x19tx_counterparty_signature\x18\n \x01(\t\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x18\n\x16\x43\x61ncelled_Performative\x1a\xc6\n\n\x16Game_Data_Performative\x12g\n\x15\x61mount_by_currency_id\x18\x01 \x03(\x0b\x32H.fetch.aea.Tac.TacMessage.Game_Data_Performative.AmountByCurrencyIdEntry\x12x\n\x1e\x65xchange_params_by_currency_id\x18\x02 \x03(\x0b\x32P.fetch.aea.Tac.TacMessage.Game_Data_Performative.ExchangeParamsByCurrencyIdEntry\x12g\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32H.fetch.aea.Tac.TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry\x12n\n\x19utility_params_by_good_id\x18\x04 \x03(\x0b\x32K.fetch.aea.Tac.TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry\x12\x0e\n\x06tx_fee\x18\x05 \x01(\x05\x12\x61\n\x12\x61gent_addr_to_name\x18\x06 \x03(\x0b\x32\x45.fetch.aea.Tac.TacMessage.Game_Data_Performative.AgentAddrToNameEntry\x12\x63\n\x13\x63urrency_id_to_name\x18\x07 \x03(\x0b\x32\x46.fetch.aea.Tac.TacMessage.Game_Data_Performative.CurrencyIdToNameEntry\x12[\n\x0fgood_id_to_name\x18\x08 \x03(\x0b\x32\x42.fetch.aea.Tac.TacMessage.Game_Data_Performative.GoodIdToNameEntry\x12\x12\n\nversion_id\x18\t \x01(\t\x12H\n\x04info\x18\n \x03(\x0b\x32:.fetch.aea.Tac.TacMessage.Game_Data_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x0b \x01(\x08\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x41\n\x1f\x45xchangeParamsByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a<\n\x1aUtilityParamsByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x36\n\x14\x41gentAddrToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x37\n\x15\x43urrencyIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11GoodIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x9c\x03\n%Transaction_Confirmation_Performative\x12\r\n\x05tx_id\x18\x01 \x01(\t\x12v\n\x15\x61mount_by_currency_id\x18\x02 \x03(\x0b\x32W.fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.AmountByCurrencyIdEntry\x12v\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32W.fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.QuantitiesByGoodIdEntry\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\xdd\x01\n\x16Tac_Error_Performative\x12\x37\n\nerror_code\x18\x01 \x01(\x0b\x32#.fetch.aea.Tac.TacMessage.ErrorCode\x12H\n\x04info\x18\x02 \x03(\x0b\x32:.fetch.aea.Tac.TacMessage.Tac_Error_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x03 \x01(\x08\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0cperformativeb\x06proto3', + serialized_pb=b'\n\ttac.proto\x12\rfetch.aea.Tac"\x8e\x1f\n\nTacMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12\x45\n\tcancelled\x18\x05 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Cancelled_PerformativeH\x00\x12\x45\n\tgame_data\x18\x06 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Game_Data_PerformativeH\x00\x12\x43\n\x08register\x18\x07 \x01(\x0b\x32/.fetch.aea.Tac.TacMessage.Register_PerformativeH\x00\x12\x45\n\ttac_error\x18\x08 \x01(\x0b\x32\x30.fetch.aea.Tac.TacMessage.Tac_Error_PerformativeH\x00\x12I\n\x0btransaction\x18\t \x01(\x0b\x32\x32.fetch.aea.Tac.TacMessage.Transaction_PerformativeH\x00\x12\x63\n\x18transaction_confirmation\x18\n \x01(\x0b\x32?.fetch.aea.Tac.TacMessage.Transaction_Confirmation_PerformativeH\x00\x12G\n\nunregister\x18\x0b \x01(\x0b\x32\x31.fetch.aea.Tac.TacMessage.Unregister_PerformativeH\x00\x1a\x80\x03\n\tErrorCode\x12\x45\n\nerror_code\x18\x01 \x01(\x0e\x32\x31.fetch.aea.Tac.TacMessage.ErrorCode.ErrorCodeEnum"\xab\x02\n\rErrorCodeEnum\x12\x11\n\rGENERIC_ERROR\x10\x00\x12\x15\n\x11REQUEST_NOT_VALID\x10\x01\x12!\n\x1d\x41GENT_ADDR_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\x12\x19\n\x15\x44IALOGUE_INCONSISTENT\x10\t\x1a+\n\x15Register_Performative\x12\x12\n\nagent_name\x18\x01 \x01(\t\x1a\x19\n\x17Unregister_Performative\x1a\xad\x05\n\x18Transaction_Performative\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12\x11\n\tledger_id\x18\x02 \x01(\t\x12\x16\n\x0esender_address\x18\x03 \x01(\t\x12\x1c\n\x14\x63ounterparty_address\x18\x04 \x01(\t\x12i\n\x15\x61mount_by_currency_id\x18\x05 \x03(\x0b\x32J.fetch.aea.Tac.TacMessage.Transaction_Performative.AmountByCurrencyIdEntry\x12\x63\n\x12\x66\x65\x65_by_currency_id\x18\x06 \x03(\x0b\x32G.fetch.aea.Tac.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry\x12i\n\x15quantities_by_good_id\x18\x07 \x03(\x0b\x32J.fetch.aea.Tac.TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry\x12\r\n\x05nonce\x18\x08 \x01(\t\x12\x18\n\x10sender_signature\x18\t \x01(\t\x12\x1e\n\x16\x63ounterparty_signature\x18\n \x01(\t\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x36\n\x14\x46\x65\x65\x42yCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x18\n\x16\x43\x61ncelled_Performative\x1a\xd1\x0b\n\x16Game_Data_Performative\x12g\n\x15\x61mount_by_currency_id\x18\x01 \x03(\x0b\x32H.fetch.aea.Tac.TacMessage.Game_Data_Performative.AmountByCurrencyIdEntry\x12x\n\x1e\x65xchange_params_by_currency_id\x18\x02 \x03(\x0b\x32P.fetch.aea.Tac.TacMessage.Game_Data_Performative.ExchangeParamsByCurrencyIdEntry\x12g\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32H.fetch.aea.Tac.TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry\x12n\n\x19utility_params_by_good_id\x18\x04 \x03(\x0b\x32K.fetch.aea.Tac.TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry\x12\x61\n\x12\x66\x65\x65_by_currency_id\x18\x05 \x03(\x0b\x32\x45.fetch.aea.Tac.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry\x12\x61\n\x12\x61gent_addr_to_name\x18\x06 \x03(\x0b\x32\x45.fetch.aea.Tac.TacMessage.Game_Data_Performative.AgentAddrToNameEntry\x12\x63\n\x13\x63urrency_id_to_name\x18\x07 \x03(\x0b\x32\x46.fetch.aea.Tac.TacMessage.Game_Data_Performative.CurrencyIdToNameEntry\x12[\n\x0fgood_id_to_name\x18\x08 \x03(\x0b\x32\x42.fetch.aea.Tac.TacMessage.Game_Data_Performative.GoodIdToNameEntry\x12\x12\n\nversion_id\x18\t \x01(\t\x12H\n\x04info\x18\n \x03(\x0b\x32:.fetch.aea.Tac.TacMessage.Game_Data_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x0b \x01(\x08\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x41\n\x1f\x45xchangeParamsByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a<\n\x1aUtilityParamsByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x36\n\x14\x46\x65\x65\x42yCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x36\n\x14\x41gentAddrToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x37\n\x15\x43urrencyIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11GoodIdToNameEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xa5\x03\n%Transaction_Confirmation_Performative\x12\x16\n\x0etransaction_id\x18\x01 \x01(\t\x12v\n\x15\x61mount_by_currency_id\x18\x02 \x03(\x0b\x32W.fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.AmountByCurrencyIdEntry\x12v\n\x15quantities_by_good_id\x18\x03 \x03(\x0b\x32W.fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.QuantitiesByGoodIdEntry\x1a\x39\n\x17\x41mountByCurrencyIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x39\n\x17QuantitiesByGoodIdEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\xdd\x01\n\x16Tac_Error_Performative\x12\x37\n\nerror_code\x18\x01 \x01(\x0b\x32#.fetch.aea.Tac.TacMessage.ErrorCode\x12H\n\x04info\x18\x02 \x03(\x0b\x32:.fetch.aea.Tac.TacMessage.Tac_Error_Performative.InfoEntry\x12\x13\n\x0binfo_is_set\x18\x03 \x01(\x08\x1a+\n\tInfoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0cperformativeb\x06proto3', ) @@ -249,8 +249,64 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1589, - serialized_end=1646, + serialized_start=1657, + serialized_end=1714, +) + +_TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY = _descriptor.Descriptor( + name="FeeByCurrencyIdEntry", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1716, + serialized_end=1770, ) _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = _descriptor.Descriptor( @@ -305,8 +361,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1648, - serialized_end=1705, + serialized_start=1772, + serialized_end=1829, ) _TACMESSAGE_TRANSACTION_PERFORMATIVE = _descriptor.Descriptor( @@ -317,8 +373,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="tx_id", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_id", + name="transaction_id", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.transaction_id", index=0, number=1, type=9, @@ -335,8 +391,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_sender_addr", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_sender_addr", + name="ledger_id", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.ledger_id", index=1, number=2, type=9, @@ -353,8 +409,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_counterparty_addr", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_counterparty_addr", + name="sender_address", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.sender_address", index=2, number=3, type=9, @@ -371,15 +427,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="amount_by_currency_id", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.amount_by_currency_id", + name="counterparty_address", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.counterparty_address", index=3, number=4, - type=11, - cpp_type=10, - label=3, + type=9, + cpp_type=9, + label=1, has_default_value=False, - default_value=[], + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -389,15 +445,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_sender_fee", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_sender_fee", + name="amount_by_currency_id", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.amount_by_currency_id", index=4, number=5, - type=5, - cpp_type=1, - label=1, + type=11, + cpp_type=10, + label=3, has_default_value=False, - default_value=0, + default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -407,15 +463,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_counterparty_fee", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_counterparty_fee", + name="fee_by_currency_id", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.fee_by_currency_id", index=5, number=6, - type=5, - cpp_type=1, - label=1, + type=11, + cpp_type=10, + label=3, has_default_value=False, - default_value=0, + default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -443,15 +499,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_nonce", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_nonce", + name="nonce", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.nonce", index=7, number=8, - type=5, - cpp_type=1, + type=9, + cpp_type=9, label=1, has_default_value=False, - default_value=0, + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -461,8 +517,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_sender_signature", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_sender_signature", + name="sender_signature", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.sender_signature", index=8, number=9, type=9, @@ -479,8 +535,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_counterparty_signature", - full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.tx_counterparty_signature", + name="counterparty_signature", + full_name="fetch.aea.Tac.TacMessage.Transaction_Performative.counterparty_signature", index=9, number=10, type=9, @@ -500,6 +556,7 @@ extensions=[], nested_types=[ _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY, + _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY, _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, ], enum_types=[], @@ -509,7 +566,7 @@ extension_ranges=[], oneofs=[], serialized_start=1144, - serialized_end=1705, + serialized_end=1829, ) _TACMESSAGE_CANCELLED_PERFORMATIVE = _descriptor.Descriptor( @@ -527,8 +584,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1707, - serialized_end=1731, + serialized_start=1831, + serialized_end=1855, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = _descriptor.Descriptor( @@ -583,8 +640,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1589, - serialized_end=1646, + serialized_start=1657, + serialized_end=1714, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY = _descriptor.Descriptor( @@ -639,8 +696,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2687, - serialized_end=2752, + serialized_start=2894, + serialized_end=2959, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = _descriptor.Descriptor( @@ -695,8 +752,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1648, - serialized_end=1705, + serialized_start=1772, + serialized_end=1829, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY = _descriptor.Descriptor( @@ -751,8 +808,64 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2813, - serialized_end=2873, + serialized_start=3020, + serialized_end=3080, +) + +_TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY = _descriptor.Descriptor( + name="FeeByCurrencyIdEntry", + full_name="fetch.aea.Tac.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.Tac.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.Tac.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1716, + serialized_end=1770, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY = _descriptor.Descriptor( @@ -807,8 +920,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2875, - serialized_end=2929, + serialized_start=3138, + serialized_end=3192, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY = _descriptor.Descriptor( @@ -863,8 +976,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2931, - serialized_end=2986, + serialized_start=3194, + serialized_end=3249, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY = _descriptor.Descriptor( @@ -919,8 +1032,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2988, - serialized_end=3039, + serialized_start=3251, + serialized_end=3302, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE_INFOENTRY = _descriptor.Descriptor( @@ -975,8 +1088,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3041, - serialized_end=3084, + serialized_start=3304, + serialized_end=3347, ) _TACMESSAGE_GAME_DATA_PERFORMATIVE = _descriptor.Descriptor( @@ -1059,15 +1172,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="tx_fee", - full_name="fetch.aea.Tac.TacMessage.Game_Data_Performative.tx_fee", + name="fee_by_currency_id", + full_name="fetch.aea.Tac.TacMessage.Game_Data_Performative.fee_by_currency_id", index=4, number=5, - type=5, - cpp_type=1, - label=1, + type=11, + cpp_type=10, + label=3, has_default_value=False, - default_value=0, + default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -1191,6 +1304,7 @@ _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY, _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY, _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY, + _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY, _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY, _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY, _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY, @@ -1202,8 +1316,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1734, - serialized_end=3084, + serialized_start=1858, + serialized_end=3347, ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY = _descriptor.Descriptor( @@ -1258,8 +1372,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1589, - serialized_end=1646, + serialized_start=1657, + serialized_end=1714, ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY = _descriptor.Descriptor( @@ -1314,8 +1428,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1648, - serialized_end=1705, + serialized_start=1772, + serialized_end=1829, ) _TACMESSAGE_TRANSACTION_CONFIRMATION_PERFORMATIVE = _descriptor.Descriptor( @@ -1326,8 +1440,8 @@ containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="tx_id", - full_name="fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.tx_id", + name="transaction_id", + full_name="fetch.aea.Tac.TacMessage.Transaction_Confirmation_Performative.transaction_id", index=0, number=1, type=9, @@ -1391,8 +1505,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3087, - serialized_end=3499, + serialized_start=3350, + serialized_end=3771, ) _TACMESSAGE_TAC_ERROR_PERFORMATIVE_INFOENTRY = _descriptor.Descriptor( @@ -1447,8 +1561,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3041, - serialized_end=3084, + serialized_start=3304, + serialized_end=3347, ) _TACMESSAGE_TAC_ERROR_PERFORMATIVE = _descriptor.Descriptor( @@ -1521,8 +1635,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3502, - serialized_end=3723, + serialized_start=3774, + serialized_end=3995, ) _TACMESSAGE = _descriptor.Descriptor( @@ -1757,7 +1871,7 @@ ), ], serialized_start=29, - serialized_end=3739, + serialized_end=4011, ) _TACMESSAGE_ERRORCODE.fields_by_name[ @@ -1770,12 +1884,18 @@ _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY.containing_type = ( _TACMESSAGE_TRANSACTION_PERFORMATIVE ) +_TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY.containing_type = ( + _TACMESSAGE_TRANSACTION_PERFORMATIVE +) _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY.containing_type = ( _TACMESSAGE_TRANSACTION_PERFORMATIVE ) _TACMESSAGE_TRANSACTION_PERFORMATIVE.fields_by_name[ "amount_by_currency_id" ].message_type = _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY +_TACMESSAGE_TRANSACTION_PERFORMATIVE.fields_by_name[ + "fee_by_currency_id" +].message_type = _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY _TACMESSAGE_TRANSACTION_PERFORMATIVE.fields_by_name[ "quantities_by_good_id" ].message_type = _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY @@ -1793,6 +1913,9 @@ _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY.containing_type = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE ) +_TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY.containing_type = ( + _TACMESSAGE_GAME_DATA_PERFORMATIVE +) _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY.containing_type = ( _TACMESSAGE_GAME_DATA_PERFORMATIVE ) @@ -1817,6 +1940,9 @@ _TACMESSAGE_GAME_DATA_PERFORMATIVE.fields_by_name[ "utility_params_by_good_id" ].message_type = _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY +_TACMESSAGE_GAME_DATA_PERFORMATIVE.fields_by_name[ + "fee_by_currency_id" +].message_type = _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY _TACMESSAGE_GAME_DATA_PERFORMATIVE.fields_by_name[ "agent_addr_to_name" ].message_type = _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY @@ -1965,6 +2091,15 @@ # @@protoc_insertion_point(class_scope:fetch.aea.Tac.TacMessage.Transaction_Performative.AmountByCurrencyIdEntry) }, ), + "FeeByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( + "FeeByCurrencyIdEntry", + (_message.Message,), + { + "DESCRIPTOR": _TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY, + "__module__": "tac_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.Tac.TacMessage.Transaction_Performative.FeeByCurrencyIdEntry) + }, + ), "QuantitiesByGoodIdEntry": _reflection.GeneratedProtocolMessageType( "QuantitiesByGoodIdEntry", (_message.Message,), @@ -2028,6 +2163,15 @@ # @@protoc_insertion_point(class_scope:fetch.aea.Tac.TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry) }, ), + "FeeByCurrencyIdEntry": _reflection.GeneratedProtocolMessageType( + "FeeByCurrencyIdEntry", + (_message.Message,), + { + "DESCRIPTOR": _TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY, + "__module__": "tac_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.Tac.TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry) + }, + ), "AgentAddrToNameEntry": _reflection.GeneratedProtocolMessageType( "AgentAddrToNameEntry", (_message.Message,), @@ -2125,6 +2269,7 @@ _sym_db.RegisterMessage(TacMessage.Unregister_Performative) _sym_db.RegisterMessage(TacMessage.Transaction_Performative) _sym_db.RegisterMessage(TacMessage.Transaction_Performative.AmountByCurrencyIdEntry) +_sym_db.RegisterMessage(TacMessage.Transaction_Performative.FeeByCurrencyIdEntry) _sym_db.RegisterMessage(TacMessage.Transaction_Performative.QuantitiesByGoodIdEntry) _sym_db.RegisterMessage(TacMessage.Cancelled_Performative) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative) @@ -2134,6 +2279,7 @@ ) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.QuantitiesByGoodIdEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.UtilityParamsByGoodIdEntry) +_sym_db.RegisterMessage(TacMessage.Game_Data_Performative.FeeByCurrencyIdEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.AgentAddrToNameEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.CurrencyIdToNameEntry) _sym_db.RegisterMessage(TacMessage.Game_Data_Performative.GoodIdToNameEntry) @@ -2150,11 +2296,13 @@ _TACMESSAGE_TRANSACTION_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None +_TACMESSAGE_TRANSACTION_PERFORMATIVE_FEEBYCURRENCYIDENTRY._options = None _TACMESSAGE_TRANSACTION_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_AMOUNTBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_EXCHANGEPARAMSBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_QUANTITIESBYGOODIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_UTILITYPARAMSBYGOODIDENTRY._options = None +_TACMESSAGE_GAME_DATA_PERFORMATIVE_FEEBYCURRENCYIDENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_AGENTADDRTONAMEENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_CURRENCYIDTONAMEENTRY._options = None _TACMESSAGE_GAME_DATA_PERFORMATIVE_GOODIDTONAMEENTRY._options = None diff --git a/packages/fetchai/skills/aries_alice/behaviours.py b/packages/fetchai/skills/aries_alice/behaviours.py new file mode 100644 index 0000000000..20d7c19e89 --- /dev/null +++ b/packages/fetchai/skills/aries_alice/behaviours.py @@ -0,0 +1,185 @@ +# -*- 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 package contains the behaviour of a generic seller AEA.""" + +from typing import cast + +from aea.skills.behaviours import TickerBehaviour + +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.aries_alice.dialogues import OefSearchDialogues +from packages.fetchai.skills.aries_alice.strategy import AliceStrategy + +DEFAULT_ADMIN_HOST = "127.0.0.1" +DEFAULT_ADMIN_PORT = 8031 + +DEFAULT_SERVICES_INTERVAL = 60.0 + + +class AliceBehaviour(TickerBehaviour): + """This class implements a behaviour.""" + + def __init__(self, **kwargs): + """Initialise the behaviour.""" + self._admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) + self._admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) + self._admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) + + services_interval = kwargs.pop( + "services_interval", DEFAULT_SERVICES_INTERVAL + ) # type: int + super().__init__(tick_interval=services_interval, **kwargs) + + @property + def admin_host(self) -> str: + """Get the admin host.""" + return self._admin_host + + @property + def admin_port(self) -> str: + """Get the admin port.""" + return self._admin_port + + @property + def admin_url(self) -> str: + """Get the admin URL.""" + return self._admin_url + + def setup(self) -> None: + """ + Implement the setup. + + :return: None + """ + self.context.logger.info("My address is: " + self.context.agent_address) + self._register_agent() + self._register_service() + + def act(self) -> None: + """ + Implement the act. + + :return: None + """ + + def teardown(self) -> None: + """ + Implement the task teardown. + + :return: None + """ + self._unregister_service() + self._unregister_agent() + + def _register_agent(self) -> None: + """ + Register the agent's location. + + :return: None + """ + strategy = cast(AliceStrategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_search_msg) + assert ( + oef_dialogue is not None + ), "alice -> behaviour -> _register_agent(): something went wrong when registering Alice on SOEF." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering Alice on SOEF.") + + def _register_service(self) -> None: + """ + Register the agent's service. + + :return: None + """ + strategy = cast(AliceStrategy, self.context.strategy) + description = strategy.get_register_service_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_search_msg) + assert ( + oef_dialogue is not None + ), "alice -> behaviour -> _register_service(): something went wrong when registering Alice service on SOEF." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering Alice service on SOEF.") + + def _unregister_service(self) -> None: + """ + Unregister service from the SOEF. + + :return: None + """ + strategy = cast(AliceStrategy, self.context.strategy) + description = strategy.get_unregister_service_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_search_msg) + assert ( + oef_dialogue is not None + ), "alice -> behaviour -> _unregister_service(): something went wrong when unregistering Alice service on SOEF." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("unregistering service from SOEF.") + + def _unregister_agent(self) -> None: + """ + Unregister agent from the SOEF. + + :return: None + """ + strategy = cast(AliceStrategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_search_msg) + assert ( + oef_dialogue is not None + ), "alice -> behaviour -> _unregister_agent(): something went wrong when unregistering Alice on SOEF." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("unregistering agent from SOEF.") diff --git a/packages/fetchai/skills/aries_alice/dialogues.py b/packages/fetchai/skills/aries_alice/dialogues.py new file mode 100644 index 0000000000..205d233abc --- /dev/null +++ b/packages/fetchai/skills/aries_alice/dialogues.py @@ -0,0 +1,165 @@ +# -*- 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. + +- Dialogue: The dialogue class maintains state of a dialogue and manages it. +- Dialogues: The dialogues class keeps track of all dialogues. +""" + +from aea.helpers.dialogue.base import Dialogue as BaseDialogue +from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel +from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.skills.base import Model + +from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue +from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue as BaseOefSearchDialogue, +) +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogues as BaseOefSearchDialogues, +) + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseDefaultDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +HttpDialogue = BaseHttpDialogue + + +class HttpDialogues(Model, BaseHttpDialogues): + """This class keeps track of all http dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseHttpDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseHttpDialogue.Role.CLIENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> HttpDialogue: + """ + Create an instance of http dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = HttpDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +OefSearchDialogue = BaseOefSearchDialogue + + +class OefSearchDialogues(Model, BaseOefSearchDialogues): + """This class keeps track of all oef_search dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseOefSearchDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseOefSearchDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> OefSearchDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = OefSearchDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/packages/fetchai/skills/aries_alice/handlers.py b/packages/fetchai/skills/aries_alice/handlers.py index bb18596001..46cf0a3728 100644 --- a/packages/fetchai/skills/aries_alice/handlers.py +++ b/packages/fetchai/skills/aries_alice/handlers.py @@ -32,32 +32,41 @@ PUBLIC_ID as HTTP_CLIENT_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage - -DEFAULT_ADMIN_HOST = "127.0.0.1" -DEFAULT_ADMIN_PORT = 8031 +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.aries_alice.dialogues import ( + DefaultDialogue, + DefaultDialogues, + HttpDialogue, + HttpDialogues, + OefSearchDialogue, + OefSearchDialogues, +) ADMIN_COMMAND_RECEIVE_INVITE = "/connections/receive-invitation" +HTTP_COUNTERPARTY = "HTTP Server" -class DefaultHandler(Handler): +class AliceDefaultHandler(Handler): """This class represents alice's handler for default messages.""" SUPPORTED_PROTOCOL = DefaultMessage.protocol_id # type: Optional[ProtocolId] def __init__(self, **kwargs): """Initialize the handler.""" - self.admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) - self.admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) - super().__init__(**kwargs) - self.admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) - self.handled_message = None + @property + def admin_url(self) -> str: + """Get the admin URL.""" + return self.context.behaviours.alice.admin_url + def _admin_post(self, path: str, content: Dict = None): # Request message & envelope + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) request_http_message = HttpMessage( + dialogue_reference=http_dialogues.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="POST", url=self.admin_url + path, @@ -65,7 +74,11 @@ def _admin_post(self, path: str, content: Dict = None): version="", bodyy=b"" if content is None else json.dumps(content).encode("utf-8"), ) - request_http_message.counterparty = self.admin_url + request_http_message.counterparty = HTTP_COUNTERPARTY + http_dialogue = http_dialogues.update(request_http_message) + assert ( + http_dialogue is not None + ), "alice -> default_handler -> _admin_post(): something went wrong when sending a HTTP message." self.context.outbox.put_message( message=request_http_message, context=EnvelopeContext(connection_id=HTTP_CLIENT_CONNECTION_PUBLIC_ID), @@ -87,8 +100,18 @@ def handle(self, message: Message) -> None: :return: None """ message = cast(DefaultMessage, message) + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) + self.handled_message = message if message.performative == DefaultMessage.Performative.BYTES: + http_dialogue = cast( + Optional[DefaultDialogue], default_dialogues.update(message) + ) + if http_dialogue is None: + self.context.logger.exception( + "alice -> default_handler -> handle(): something went wrong when adding the incoming HTTP response message to the dialogue." + ) + return content_bytes = message.content content = json.loads(content_bytes) self.context.logger.info("Received message content:" + str(content)) @@ -104,19 +127,15 @@ def teardown(self) -> None: pass -class HttpHandler(Handler): +class AliceHttpHandler(Handler): """This class represents alice's handler for HTTP messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id # type: Optional[ProtocolId] def __init__(self, **kwargs): """Initialize the handler.""" - self.admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) - self.admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) - super().__init__(**kwargs) - self.admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) self.connection_id = None # type: Optional[str] self.is_connected_to_Faber = False @@ -128,7 +147,7 @@ def setup(self) -> None: :return: None """ - self.context.logger.info("My address is: " + self.context.agent_address) + pass def handle(self, message: Message) -> None: """ @@ -138,8 +157,16 @@ def handle(self, message: Message) -> None: :return: None """ message = cast(HttpMessage, message) + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) + self.handled_message = message if message.performative == HttpMessage.Performative.REQUEST: # webhook + http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) + if http_dialogue is None: + self.context.logger.exception( + "alice -> http_handler -> handle() -> REQUEST: something went wrong when adding the incoming HTTP webhook request message to the dialogue." + ) + return content_bytes = message.bodyy content = json.loads(content_bytes) self.context.logger.info("Received webhook message content:" + str(content)) @@ -151,6 +178,12 @@ def handle(self, message: Message) -> None: elif ( message.performative == HttpMessage.Performative.RESPONSE ): # response to http_client request + http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) + if http_dialogue is None: + self.context.logger.exception( + "alice -> http_handler -> handle() -> RESPONSE: something went wrong when adding the incoming HTTP response message to the dialogue." + ) + return content_bytes = message.bodyy content = content_bytes.decode("utf-8") if "Error" in content: @@ -176,3 +209,91 @@ def teardown(self) -> None: :return: None """ pass + + +class AliceOefSearchHandler(Handler): + """This class implements an OEF search handler.""" + + SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[ProtocolId] + + def setup(self) -> None: + """Call to setup the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + oef_search_msg = cast(OefSearchMessage, message) + + # recover dialogue + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) + ) + if oef_search_dialogue is None: + self._handle_unidentified_dialogue(oef_search_msg) + return + + # handle message + if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: + self._handle_error(oef_search_msg, oef_search_dialogue) + else: + self._handle_invalid(oef_search_msg, oef_search_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: + """ + Handle an unidentified dialogue. + + :param oef_search_msg: the oef search message + """ + self.context.logger.info( + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg + ) + ) + + def _handle_error( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.info( + "received oef_search error message={} in dialogue={}.".format( + oef_search_msg, oef_search_dialogue + ) + ) + + def _handle_invalid( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, + ) + ) diff --git a/packages/fetchai/skills/aries_alice/skill.yaml b/packages/fetchai/skills/aries_alice/skill.yaml index 6913515f95..c3598ac6f5 100644 --- a/packages/fetchai/skills/aries_alice/skill.yaml +++ b/packages/fetchai/skills/aries_alice/skill.yaml @@ -1,30 +1,56 @@ name: aries_alice author: fetchai -version: 0.3.0 +version: 0.4.0 description: The aries_alice skill implements the alice player in the aries cloud agent demo license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: Qma8qSTU34ADKWskBwQKQLGNpe3xDKNgjNQ6Q4MxUnKa3Q - handlers.py: Qmf27rceAx3bwYjm1UXTXHnXratBPz9JwmLb5emqpruqyi + behaviours.py: QmZ1hadywhjNUxw56R3r5ZoiZwvoK6EyzGPVHRVtRYXaQ4 + dialogues.py: QmdPR5nNYj4JJdR9Stfis97M8XfP2pi2KqsVsApUzhD9hH + handlers.py: QmVVjSsPPSLTH6gTDSYKqtFn13YdVfhnL9fJnqKEJ6j92M + strategy.py: QmPXzDbERHva7wu3yL787JBVWVPxb1RR4VHR16S8GEaJg5 fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/http:0.3.0 +- fetchai/default:0.4.0 +- fetchai/http:0.4.0 skills: [] -behaviours: {} -handlers: - aries_demo_default: +behaviours: + alice: args: admin_host: 127.0.0.1 admin_port: 8031 - class_name: DefaultHandler - aries_demo_http: + services_interval: 20 + class_name: AliceBehaviour +handlers: + default: + args: {} + class_name: AliceDefaultHandler + http: + args: {} + class_name: AliceHttpHandler + oef_search: + args: {} + class_name: AliceOefSearchHandler +models: + default_dialogues: + args: {} + class_name: DefaultDialogues + http_dialogues: + args: {} + class_name: HttpDialogues + oef_search_dialogues: + args: {} + class_name: OefSearchDialogues + strategy: args: - admin_host: 127.0.0.1 - admin_port: 8031 - class_name: HttpHandler -models: {} + location: + latitude: 0.127 + longitude: 51.5194 + service_data: + key: intro_service + value: intro_alice + class_name: AliceStrategy dependencies: {} diff --git a/packages/fetchai/skills/aries_alice/strategy.py b/packages/fetchai/skills/aries_alice/strategy.py new file mode 100644 index 0000000000..1d3c373ef2 --- /dev/null +++ b/packages/fetchai/skills/aries_alice/strategy.py @@ -0,0 +1,91 @@ +# -*- 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 strategy class.""" + +from aea.helpers.search.generic import ( + AGENT_LOCATION_MODEL, + AGENT_REMOVE_SERVICE_MODEL, + AGENT_SET_SERVICE_MODEL, +) +from aea.helpers.search.models import Description, Location +from aea.skills.base import Model + +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SERVICE_DATA = {"key": "intro_service", "value": "intro_alice"} + + +class AliceStrategy(Model): + """This class defines a strategy for the agent.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize the strategy of the agent. + + :param register_as: determines whether the agent registers as seller, buyer or both + :param search_for: determines whether the agent searches for sellers, buyers or both + + :return: None + """ + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = { + "location": Location(location["longitude"], location["latitude"]) + } + self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) + assert ( + len(self._set_service_data) == 2 + and "key" in self._set_service_data + and "value" in self._set_service_data + ), "service_data must contain keys `key` and `value`" + self._remove_service_data = {"key": self._set_service_data["key"]} + + super().__init__(**kwargs) + + def get_location_description(self) -> Description: + """ + Get the location description. + + :return: a description of the agent's location + """ + description = Description( + self._agent_location, data_model=AGENT_LOCATION_MODEL, + ) + return description + + def get_register_service_description(self) -> Description: + """ + Get the register service description. + + :return: a description of the offered services + """ + description = Description( + self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, + ) + return description + + def get_unregister_service_description(self) -> Description: + """ + Get the unregister service description. + + :return: a description of the to be removed service + """ + description = Description( + self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, + ) + return description diff --git a/packages/fetchai/skills/aries_faber/__init__.py b/packages/fetchai/skills/aries_faber/__init__.py index 6e2baadb91..9113cb0cf5 100644 --- a/packages/fetchai/skills/aries_faber/__init__.py +++ b/packages/fetchai/skills/aries_faber/__init__.py @@ -17,4 +17,4 @@ # # ------------------------------------------------------------------------------ -"""This module contains the implementation of the aries_alice skill.""" +"""This module contains the implementation of the aries_faber skill.""" diff --git a/packages/fetchai/skills/aries_faber/behaviours.py b/packages/fetchai/skills/aries_faber/behaviours.py index 28d347f719..bf26ba3299 100644 --- a/packages/fetchai/skills/aries_faber/behaviours.py +++ b/packages/fetchai/skills/aries_faber/behaviours.py @@ -20,31 +20,66 @@ """This package contains the behaviour for the aries_faber skill.""" import json -from typing import Dict +from typing import Dict, cast -from aea.skills.behaviours import OneShotBehaviour +from aea.mail.base import Address +from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.http.message import HttpMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.aries_faber.dialogues import ( + HttpDialogues, + OefSearchDialogues, +) +from packages.fetchai.skills.aries_faber.strategy import FaberStrategy DEFAULT_ADMIN_HOST = "127.0.0.1" DEFAULT_ADMIN_PORT = 8021 +HTTP_COUNTERPARTY = "HTTP Server" -ADMIN_COMMAND_STATUS = "/status" +DEFAULT_SEARCH_INTERVAL = 5.0 -class FaberBehaviour(OneShotBehaviour): +class FaberBehaviour(TickerBehaviour): """This class represents the behaviour of faber.""" def __init__(self, **kwargs): """Initialize the handler.""" - self.admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) - self.admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) + self._admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) + self._admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) + self._admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) + self._alice_address = "" - super().__init__(**kwargs) + search_interval = cast( + float, kwargs.pop("search_interval", DEFAULT_SEARCH_INTERVAL) + ) + super().__init__(tick_interval=search_interval, **kwargs) + + @property + def admin_host(self) -> str: + """Get the admin host.""" + return self._admin_host + + @property + def admin_port(self) -> str: + """Get the admin port.""" + return self._admin_port + + @property + def admin_url(self) -> str: + """Get the admin URL.""" + return self._admin_url - self.admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) + @property + def alice_address(self) -> Address: + """Get Alice's address.""" + return self._alice_address - def _admin_get(self, path: str, content: Dict = None) -> None: + @alice_address.setter + def alice_address(self, address: Address) -> None: + self._alice_address = address + + def admin_get(self, path: str, content: Dict = None) -> None: """ Get from admin. @@ -53,7 +88,10 @@ def _admin_get(self, path: str, content: Dict = None) -> None: :return: None """ # Request message & envelope + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) + request_http_message = HttpMessage( + dialogue_reference=http_dialogues.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="GET", url=self.admin_url + path, @@ -61,7 +99,11 @@ def _admin_get(self, path: str, content: Dict = None) -> None: version="", bodyy=b"" if content is None else json.dumps(content).encode("utf-8"), ) - request_http_message.counterparty = self.admin_url + request_http_message.counterparty = HTTP_COUNTERPARTY + http_dialogue = http_dialogues.update(request_http_message) + assert ( + http_dialogue is not None + ), "faber -> behaviour -> admin_get(): something went wrong when sending a HTTP message." self.context.outbox.put_message(message=request_http_message) def setup(self) -> None: @@ -70,7 +112,8 @@ def setup(self) -> None: :return: None """ - pass + strategy = cast(FaberStrategy, self.context.strategy) + strategy.is_searching = True def act(self) -> None: """ @@ -78,7 +121,24 @@ def act(self) -> None: :return: None """ - self._admin_get(ADMIN_COMMAND_STATUS) + strategy = cast(FaberStrategy, self.context.strategy) + if strategy.is_searching: + query = strategy.get_location_and_service_query() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + query=query, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_search_msg) + assert ( + oef_dialogue is not None + ), "faber -> behaviour -> act(): something went wrong when searching for Alice on SOEF." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("Searching for Alice on SOEF...") def teardown(self) -> None: """ diff --git a/packages/fetchai/skills/aries_faber/dialogues.py b/packages/fetchai/skills/aries_faber/dialogues.py new file mode 100644 index 0000000000..9cf94a3b3a --- /dev/null +++ b/packages/fetchai/skills/aries_faber/dialogues.py @@ -0,0 +1,165 @@ +# -*- 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. + +- OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef search and manages it. +- OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef search. +""" + +from aea.helpers.dialogue.base import Dialogue as BaseDialogue +from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel +from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.skills.base import Model + +from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue +from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue as BaseOefSearchDialogue, +) +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogues as BaseOefSearchDialogues, +) + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseDefaultDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +HttpDialogue = BaseHttpDialogue + + +class HttpDialogues(Model, BaseHttpDialogues): + """This class keeps track of all http dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseHttpDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseHttpDialogue.Role.CLIENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> HttpDialogue: + """ + Create an instance of http dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = HttpDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +OefSearchDialogue = BaseOefSearchDialogue + + +class OefSearchDialogues(Model, BaseOefSearchDialogues): + """This class keeps track of all oef_search dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseOefSearchDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseOefSearchDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> OefSearchDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = OefSearchDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/packages/fetchai/skills/aries_faber/handlers.py b/packages/fetchai/skills/aries_faber/handlers.py index 4317b1dd67..261346f4b6 100644 --- a/packages/fetchai/skills/aries_faber/handlers.py +++ b/packages/fetchai/skills/aries_faber/handlers.py @@ -23,45 +23,61 @@ from typing import Dict, Optional, cast from aea.configurations.base import ProtocolId -from aea.mail.base import EnvelopeContext +from aea.mail.base import Address, EnvelopeContext from aea.protocols.base import Message from aea.protocols.default.message import DefaultMessage from aea.skills.base import Handler -from packages.fetchai.connections.oef.connection import ( - PUBLIC_ID as OEF_CONNECTION_PUBLIC_ID, +from packages.fetchai.connections.p2p_libp2p.connection import ( + PUBLIC_ID as P2P_CONNECTION_PUBLIC_ID, ) from packages.fetchai.protocols.http.message import HttpMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.aries_faber.dialogues import ( + DefaultDialogues, + HttpDialogue, + HttpDialogues, + OefSearchDialogue, + OefSearchDialogues, +) +from packages.fetchai.skills.aries_faber.strategy import FaberStrategy -DEFAULT_ADMIN_HOST = "127.0.0.1" -DEFAULT_ADMIN_PORT = 8021 SUPPORT_REVOCATION = False ADMIN_COMMAND_CREATE_INVITATION = "/connections/create-invitation" +ADMIN_COMMAND_STATUS = "/status" +HTTP_COUNTERPARTY = "HTTP Server" -class HTTPHandler(Handler): +class FaberHTTPHandler(Handler): """This class represents faber's handler for default messages.""" SUPPORTED_PROTOCOL = HttpMessage.protocol_id # type: Optional[ProtocolId] def __init__(self, **kwargs): """Initialize the handler.""" - self.admin_host = kwargs.pop("admin_host", DEFAULT_ADMIN_HOST) - self.admin_port = kwargs.pop("admin_port", DEFAULT_ADMIN_PORT) - self.alice_id = kwargs.pop("alice_id") - super().__init__(**kwargs) - self.admin_url = "http://{}:{}".format(self.admin_host, self.admin_port) self.connection_id = None # type: Optional[str] self.is_connected_to_Alice = False self.handled_message = None + @property + def admin_url(self) -> str: + """Get the admin URL.""" + return self.context.behaviours.faber.admin_url + + @property + def alice_address(self) -> Address: + """Get Alice's address.""" + return self.context.behaviours.faber.alice_address + def _admin_post(self, path: str, content: Dict = None) -> None: # Request message & envelope + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) request_http_message = HttpMessage( + dialogue_reference=http_dialogues.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="POST", url=self.admin_url + path, @@ -69,17 +85,27 @@ def _admin_post(self, path: str, content: Dict = None) -> None: version="", bodyy=b"" if content is None else json.dumps(content).encode("utf-8"), ) - request_http_message.counterparty = self.admin_url + request_http_message.counterparty = HTTP_COUNTERPARTY + http_dialogue = http_dialogues.update(request_http_message) + assert ( + http_dialogue is not None + ), "faber -> http_handler -> _admin_post(): something went wrong when sending a HTTP message." self.context.outbox.put_message(message=request_http_message) def _send_message(self, content: Dict) -> None: # message & envelope + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) message = DefaultMessage( + dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), performative=DefaultMessage.Performative.BYTES, content=json.dumps(content).encode("utf-8"), ) - message.counterparty = self.alice_id - context = EnvelopeContext(connection_id=OEF_CONNECTION_PUBLIC_ID) + message.counterparty = self.alice_address + context = EnvelopeContext(connection_id=P2P_CONNECTION_PUBLIC_ID) + default_dialogue = default_dialogues.update(message) + assert ( + default_dialogue is not None + ), "faber -> http_handler -> _send_message(): something went wrong when sending a default message." self.context.outbox.put_message(message=message, context=context) def setup(self) -> None: @@ -98,11 +124,21 @@ def handle(self, message: Message) -> None: :return: None """ message = cast(HttpMessage, message) + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) + self.handled_message = message if ( message.performative == HttpMessage.Performative.RESPONSE and message.status_code == 200 ): # response to http request + http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) + if http_dialogue is None: + self.context.logger.exception( + "faber -> http_handler -> handle() -> RESPONSE: " + "something went wrong when adding the incoming HTTP response message to the dialogue." + ) + return + content_bytes = message.bodyy # type: ignore content = json.loads(content_bytes) self.context.logger.info("Received message: " + str(content)) @@ -122,6 +158,13 @@ def handle(self, message: Message) -> None: elif ( message.performative == HttpMessage.Performative.REQUEST ): # webhook request + http_dialogue = cast(Optional[HttpDialogue], http_dialogues.update(message)) + if http_dialogue is None: + self.context.logger.exception( + "faber -> http_handler -> handle() -> REQUEST: " + "something went wrong when adding the incoming HTTP webhook request message to the dialogue." + ) + return content_bytes = message.bodyy content = json.loads(content_bytes) self.context.logger.info("Received webhook message content:" + str(content)) @@ -138,3 +181,124 @@ def teardown(self) -> None: :return: None """ pass + + +class FaberOefSearchHandler(Handler): + """This class implements an OEF search handler.""" + + SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[ProtocolId] + + def setup(self) -> None: + """Call to setup the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + self.context.logger.info("Handling SOEF message...") + oef_search_msg = cast(OefSearchMessage, message) + + # recover dialogue + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) + ) + if oef_search_dialogue is None: + self._handle_unidentified_dialogue(oef_search_msg) + return + + # handle message + if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: + self._handle_error(oef_search_msg, oef_search_dialogue) + elif oef_search_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: + self._handle_search(oef_search_msg) + else: + self._handle_invalid(oef_search_msg, oef_search_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: + """ + Handle an unidentified dialogue. + + :param oef_search_msg: the oef search message to be handled + :return: None + """ + self.context.logger.info( + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg + ) + ) + + def _handle_error( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message to be handled + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.info( + "received oef_search error message={} in dialogue={}.".format( + oef_search_msg, oef_search_dialogue + ) + ) + + def _handle_search(self, oef_search_msg: OefSearchMessage) -> None: + """ + Handle the search response. + + :param oef_search_msg: the oef search message to be handled + :return: None + """ + if len(oef_search_msg.agents) != 1: + self.context.logger.info( + "did not find Alice. found {} agents. continue searching.".format( + len(oef_search_msg.agents) + ) + ) + return + + self.context.logger.info( + "found Alice with address {}, stopping search.".format( + oef_search_msg.agents[0] + ) + ) + strategy = cast(FaberStrategy, self.context.strategy) + strategy.is_searching = False # stopping search + + # set alice address + self.context.behaviours.faber.alice_address = oef_search_msg.agents[0] + + # check ACA is running + self.context.behaviours.faber.admin_get(ADMIN_COMMAND_STATUS) + + def _handle_invalid( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, + ) + ) diff --git a/packages/fetchai/skills/aries_faber/skill.yaml b/packages/fetchai/skills/aries_faber/skill.yaml index a88c0a9da5..8b01e5a2f5 100644 --- a/packages/fetchai/skills/aries_faber/skill.yaml +++ b/packages/fetchai/skills/aries_faber/skill.yaml @@ -6,25 +6,48 @@ description: The aries_faber skill implements the faber player in the aries clou license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: - __init__.py: Qma8qSTU34ADKWskBwQKQLGNpe3xDKNgjNQ6Q4MxUnKa3Q - behaviours.py: QmUErSz1FXfsX7VyQU9YcxteS3j7CpDBAELz4yGEdzdEw1 - handlers.py: Qma2xG1pf5o19uumuQuThEEuWutBpMUbKgdPCb1VVxQAvu + __init__.py: QmNPVQ6UajvJodqTLWbLvQZkKCfrNn1nYPrQXai3xdj6F7 + behaviours.py: QmRh5cVZHXzatgDNC16L3eB1L4jr4Qtmb9E3m23Y9CLXWt + dialogues.py: QmP9VtJL3tJWcKU5VTPN7ZcUiPw6oDpCq8La7ZtewcwrUE + handlers.py: QmU77Fef51oJP7bsDyTPY1sbmhY8Dk6McZAsHgCv3Fgm3C + strategy.py: QmWySg1wmunzCjCGkR6qSUFN9RKYX7ejFX8GxyKmNBvDQH fingerprint_ignore_patterns: [] contracts: [] protocols: [] skills: [] behaviours: - aries_demo_faber: + faber: args: admin_host: 127.0.0.1 admin_port: 8021 + services_interval: 20 class_name: FaberBehaviour handlers: - aries_demo_http: + http: + args: {} + class_name: FaberHTTPHandler + oef_search: + args: {} + class_name: FaberOefSearchHandler +models: + default_dialogues: + args: {} + class_name: DefaultDialogues + http_dialogues: + args: {} + class_name: HttpDialogues + oef_search_dialogues: + args: {} + class_name: OefSearchDialogues + strategy: args: - admin_host: 127.0.0.1 - admin_port: 8021 - alice_id: alice_identity_address - class_name: HTTPHandler -models: {} + location: + latitude: 0.127 + longitude: 51.5194 + search_query: + constraint_type: == + search_key: intro_service + search_value: intro_alice + search_radius: 5.0 + class_name: FaberStrategy dependencies: {} diff --git a/packages/fetchai/skills/aries_faber/strategy.py b/packages/fetchai/skills/aries_faber/strategy.py new file mode 100644 index 0000000000..72e77f99c7 --- /dev/null +++ b/packages/fetchai/skills/aries_faber/strategy.py @@ -0,0 +1,84 @@ +# -*- 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 strategy class.""" + +from aea.helpers.search.models import ( + Constraint, + ConstraintType, + Location, + Query, +) +from aea.skills.base import Model + +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SEARCH_QUERY = { + "search_key": "intro_service", + "search_value": "intro_alice", + "constraint_type": "==", +} +DEFAULT_SEARCH_RADIUS = 5.0 + + +class FaberStrategy(Model): + """This class defines a strategy for the agent.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize the strategy of the agent. + + :return: None + """ + self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = Location(location["longitude"], location["latitude"]) + self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) + + super().__init__(**kwargs) + self._is_searching = False + + @property + def is_searching(self) -> bool: + """Check if the agent is searching.""" + return self._is_searching + + @is_searching.setter + def is_searching(self, is_searching: bool) -> None: + """Check if the agent is searching.""" + assert isinstance(is_searching, bool), "Can only set bool on is_searching!" + self._is_searching = is_searching + + def get_location_and_service_query(self) -> Query: + """ + Get the location and service query of the agent. + + :return: the query + """ + close_to_my_service = Constraint( + "location", ConstraintType("distance", (self._agent_location, self._radius)) + ) + service_key_filter = Constraint( + self._search_query["search_key"], + ConstraintType( + self._search_query["constraint_type"], + self._search_query["search_value"], + ), + ) + query = Query([close_to_my_service, service_key_filter],) + return query diff --git a/packages/fetchai/skills/carpark_client/skill.yaml b/packages/fetchai/skills/carpark_client/skill.yaml index 16169fc7f2..434ad3baac 100644 --- a/packages/fetchai/skills/carpark_client/skill.yaml +++ b/packages/fetchai/skills/carpark_client/skill.yaml @@ -1,6 +1,6 @@ name: carpark_client author: fetchai -version: 0.7.0 +version: 0.8.0 description: The carpark client skill implements the functionality to run a client for carpark data. license: Apache-2.0 @@ -14,12 +14,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_buyer:0.7.0 +- fetchai/generic_buyer:0.8.0 behaviours: search: args: diff --git a/packages/fetchai/skills/carpark_detection/database.py b/packages/fetchai/skills/carpark_detection/database.py index 2667c6b056..51702f9af2 100644 --- a/packages/fetchai/skills/carpark_detection/database.py +++ b/packages/fetchai/skills/carpark_detection/database.py @@ -23,10 +23,11 @@ import shutil import sqlite3 import time +from typing import Optional import skimage # type: ignore -logger = logging.getLogger( +_logger = logging.getLogger( "aea.packages.fetchai.skills.carpark_detection.detection_database" ) @@ -34,7 +35,12 @@ class DetectionDatabase: """Communicate between the database and the python objects.""" - def __init__(self, temp_dir, create_if_not_present=True): + def __init__( + self, + temp_dir, + create_if_not_present=True, + logger: Optional[logging.Logger] = None, + ): """Initialise the Detection Database Communication class.""" self.this_dir = os.path.dirname(__file__) self.temp_dir = temp_dir @@ -54,6 +60,8 @@ def __init__(self, temp_dir, create_if_not_present=True): if create_if_not_present: self.initialise_backend() + self.logger = logger if logger is not None else _logger + def is_db_exits(self): """Return true if database exixts and is set up.""" if not os.path.isfile(self.database_path): @@ -65,7 +73,7 @@ def is_db_exits(self): def reset_database(self): """Reset the database and remove all data.""" # If we need to reset the database, then remove the table and any stored images - logger.info("Database being reset.") + self.logger.info("Database being reset.") # Remove the actual database file if os.path.isfile(self.database_path): @@ -76,14 +84,14 @@ def reset_database(self): shutil.rmtree(self.processed_image_dir) # Recreate them - logger.info("Initialising backend ...") + self.logger.info("Initialising backend ...") self.initialise_backend() - logger.info("Finished initialising backend!") + self.logger.info("Finished initialising backend!") def reset_mask(self): """Just reset the detection mask.""" # If we need to reset the database, then remove the table and any stored images - logger.info("Mask being reset.") + self.logger.info("Mask being reset.") # Remove the actual database file if os.path.isfile(self.mask_image_path): @@ -383,7 +391,7 @@ def execute_single_sql(self, command, variables=(), print_exceptions=True): conn.commit() except Exception as e: # pragma: nocover # pylint: disable=broad-except if print_exceptions: - logger.warning("Exception in database: {}".format(e)) + self.logger.warning("Exception in database: {}".format(e)) finally: if conn is not None: conn.close() diff --git a/packages/fetchai/skills/carpark_detection/skill.yaml b/packages/fetchai/skills/carpark_detection/skill.yaml index 74e9f6a7bb..574f97a032 100644 --- a/packages/fetchai/skills/carpark_detection/skill.yaml +++ b/packages/fetchai/skills/carpark_detection/skill.yaml @@ -1,6 +1,6 @@ name: carpark_detection author: fetchai -version: 0.7.0 +version: 0.8.0 description: The carpark detection skill implements the detection and trading functionality for a carpark agent. license: Apache-2.0 @@ -8,20 +8,20 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmQoECB7dpCDCG3xCnBsoMy6oqgSdu69CzRcAcuZuyapnQ behaviours.py: QmTNboU3YH8DehWnpZmoiDUCncpNmqoSVt1Yp4j7NsgY2S - database.py: QmVUoN2cuAE54UPvSBRFArdGmVzoSuEjrJXiVkGcfwHrvb + database.py: QmZqLyAQiZUaNEhf5t34BGwoLzYVcszwwR69HeDDwqR9uV dialogues.py: QmPXfUWDxnHDaHQqsgtVhJ2v9dEgGWLtvEHKFvvFcDXGms handlers.py: QmbkmEP9K4Qu2MsRtnkdx3PGNbSW46qi48bCHVCUJHpcQF - strategy.py: QmUJsWA9GYHxn5cmuXUQTkc9oCLJNJtWbRDJdRy2Yp3pQk + strategy.py: Qmc4m4GuTZvW1oTLJZDLeUErgtMCf9eQhMp1gcvihWKZQD fingerprint_ignore_patterns: - temp_files_placeholder/* contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_seller:0.8.0 +- fetchai/generic_seller:0.9.0 behaviours: service_registration: args: diff --git a/packages/fetchai/skills/carpark_detection/strategy.py b/packages/fetchai/skills/carpark_detection/strategy.py index e7417cf334..81f2443875 100644 --- a/packages/fetchai/skills/carpark_detection/strategy.py +++ b/packages/fetchai/skills/carpark_detection/strategy.py @@ -52,8 +52,8 @@ def __init__(self, **kwargs) -> None: if not os.path.isdir(db_dir): raise ValueError("Database directory does not exist!") - self.db = DetectionDatabase(db_dir, False) super().__init__(**kwargs) + self.db = DetectionDatabase(db_dir, False, logger=self.context.logger) self._update_service_data() def collect_from_data_source(self) -> Dict[str, str]: diff --git a/packages/fetchai/skills/echo/handlers.py b/packages/fetchai/skills/echo/handlers.py index e599276437..5b74f4836e 100644 --- a/packages/fetchai/skills/echo/handlers.py +++ b/packages/fetchai/skills/echo/handlers.py @@ -43,7 +43,8 @@ def handle(self, message: Message) -> None: self.context.logger.info( "Echo Handler: message={}, sender={}".format(message, message.counterparty) ) - self.context.outbox.put_message(sender=self.context.agent_name, message=message) + message.sender = self.context.agent_name + self.context.outbox.put_message(message=message) def teardown(self) -> None: """ diff --git a/packages/fetchai/skills/echo/skill.yaml b/packages/fetchai/skills/echo/skill.yaml index 801d377120..6e4d408c9b 100644 --- a/packages/fetchai/skills/echo/skill.yaml +++ b/packages/fetchai/skills/echo/skill.yaml @@ -1,17 +1,17 @@ name: echo author: fetchai -version: 0.3.0 +version: 0.4.0 description: The echo skill implements simple echo functionality. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmTf1GCgHxu7qq4HvUNYiBwuGEL1DcsHQuWH7N7TB5TtoC behaviours.py: QmXARXRvJkpzuqnYNhJhv42Sk6J4KzRW2AKvC6FJWLU9JL - handlers.py: Qmez6kjFaP3BfeD474gDZCt71KL3sEipoh67osf4urzRFM + handlers.py: QmQboz43ehKuVWFbMmxHBqi7EwKuPH4CBuVaAi5D8ABg7m fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: [] behaviours: echo: diff --git a/packages/fetchai/skills/erc1155_client/behaviours.py b/packages/fetchai/skills/erc1155_client/behaviours.py index a2f652abb6..b821e04471 100644 --- a/packages/fetchai/skills/erc1155_client/behaviours.py +++ b/packages/fetchai/skills/erc1155_client/behaviours.py @@ -32,7 +32,7 @@ from packages.fetchai.skills.erc1155_client.strategy import Strategy DEFAULT_SEARCH_INTERVAL = 5.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class SearchBehaviour(TickerBehaviour): @@ -69,7 +69,7 @@ def act(self) -> None: """ strategy = cast(Strategy, self.context.strategy) if strategy.is_searching: - query = strategy.get_service_query() + query = strategy.get_location_and_service_query() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) diff --git a/packages/fetchai/skills/erc1155_client/handlers.py b/packages/fetchai/skills/erc1155_client/handlers.py index 8b5ae4c736..eaa46851e1 100644 --- a/packages/fetchai/skills/erc1155_client/handlers.py +++ b/packages/fetchai/skills/erc1155_client/handlers.py @@ -47,7 +47,7 @@ ) from packages.fetchai.skills.erc1155_client.strategy import Strategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class FipaHandler(Handler): @@ -103,7 +103,7 @@ def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: :return: None """ self.context.logger.info( - "[{}]: unidentified dialogue.".format(self.context.agent_name) + "unidentified dialogue for message={}.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg = DefaultMessage( @@ -143,10 +143,8 @@ def _handle_propose( ): # accept any proposal with the correct keys self.context.logger.info( - "[{}]: received valid PROPOSE from sender={}: proposal={}".format( - self.context.agent_name, - fipa_msg.counterparty[-5:], - fipa_msg.proposal.values, + "received valid PROPOSE from sender={}: proposal={}".format( + fipa_msg.counterparty[-5:], fipa_msg.proposal.values, ) ) strategy = cast(Strategy, self.context.strategy) @@ -157,7 +155,7 @@ def _handle_propose( performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=fipa_msg.proposal.values["contract_address"], callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( @@ -198,16 +196,12 @@ def _handle_propose( contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.outbox.put_message(message=contract_api_msg) self.context.logger.info( - "[{}]: requesting single hash message from contract api...".format( - self.context.agent_name - ) + "requesting single hash message from contract api..." ) else: self.context.logger.info( - "[{}]: received invalid PROPOSE from sender={}: proposal={}".format( - self.context.agent_name, - fipa_msg.counterparty[-5:], - fipa_msg.proposal.values, + "received invalid PROPOSE from sender={}: proposal={}".format( + fipa_msg.counterparty[-5:], fipa_msg.proposal.values, ) ) @@ -222,8 +216,8 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle fipa message of performative={} in dialogue={}.".format( - self.context.agent_name, fipa_msg.performative, fipa_dialogue + "cannot handle fipa message of performative={} in dialogue={}.".format( + fipa_msg.performative, fipa_dialogue ) ) @@ -280,8 +274,8 @@ def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> Non :param msg: the message """ self.context.logger.info( - "[{}]: received invalid oef_search message={}, unidentified dialogue.".format( - self.context.agent_name, oef_search_msg + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg ) ) @@ -296,8 +290,8 @@ def _handle_error( :return: None """ self.context.logger.info( - "[{}]: received oef_search error message={} in dialogue={}.".format( - self.context.agent_name, oef_search_msg, oef_search_dialogue + "received oef_search error message={} in dialogue={}.".format( + oef_search_msg, oef_search_dialogue ) ) @@ -311,16 +305,11 @@ def _handle_search( :return: None """ if len(oef_search_msg.agents) == 0: - self.context.logger.info( - "[{}]: found no agents, continue searching.".format( - self.context.agent_name - ) - ) + self.context.logger.info("found no agents, continue searching.") return self.context.logger.info( - "[{}]: found agents={}, stopping search.".format( - self.context.agent_name, + "found agents={}, stopping search.".format( list(map(lambda x: x[-5:], oef_search_msg.agents)), ) ) @@ -337,9 +326,7 @@ def _handle_search( cfp_msg.counterparty = opponent_address fipa_dialogues.update(cfp_msg) self.context.logger.info( - "[{}]: sending CFP to agent={}".format( - self.context.agent_name, opponent_address[-5:] - ) + "sending CFP to agent={}".format(opponent_address[-5:]) ) self.context.outbox.put_message(message=cfp_msg) @@ -354,10 +341,8 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle oef_search message of performative={} in dialogue={}.".format( - self.context.agent_name, - oef_search_msg.performative, - oef_search_dialogue, + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, ) ) @@ -417,8 +402,8 @@ def _handle_unidentified_dialogue( :param msg: the message """ self.context.logger.info( - "[{}]: received invalid contract_api message={}, unidentified dialogue.".format( - self.context.agent_name, contract_api_msg + "received invalid contract_api message={}, unidentified dialogue.".format( + contract_api_msg ) ) @@ -433,11 +418,7 @@ def _handle_raw_message( :param contract_api_message: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ - self.context.logger.info( - "[{}]: received raw message={}".format( - self.context.agent_name, contract_api_msg - ) - ) + self.context.logger.info("received raw message={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, @@ -459,9 +440,7 @@ def _handle_raw_message( signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( - "[{}]: proposing the transaction to the decision maker. Waiting for confirmation ...".format( - self.context.agent_name - ) + "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( @@ -476,8 +455,8 @@ def _handle_error( :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: received ledger_api error message={} in dialogue={}.".format( - self.context.agent_name, contract_api_msg, contract_api_dialogue + "received ledger_api error message={} in dialogue={}.".format( + contract_api_msg, contract_api_dialogue ) ) @@ -493,10 +472,8 @@ def _handle_invalid( :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( - "[{}]: cannot handle contract_api message of performative={} in dialogue={}.".format( - self.context.agent_name, - contract_api_msg.performative, - contract_api_dialogue, + "cannot handle contract_api message of performative={} in dialogue={}.".format( + contract_api_msg.performative, contract_api_dialogue, ) ) @@ -551,8 +528,8 @@ def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: :param msg: the message """ self.context.logger.info( - "[{}]: received invalid signing message={}, unidentified dialogue.".format( - self.context.agent_name, signing_msg + "received invalid signing message={}, unidentified dialogue.".format( + signing_msg ) ) @@ -580,10 +557,8 @@ def _handle_signed_message( ) inform_msg.counterparty = last_fipa_msg.counterparty self.context.logger.info( - "[{}]: sending ACCEPT_W_INFORM to agent={}: tx_signature={}".format( - self.context.agent_name, - last_fipa_msg.counterparty[-5:], - signing_msg.signed_message, + "sending ACCEPT_W_INFORM to agent={}: tx_signature={}".format( + last_fipa_msg.counterparty[-5:], signing_msg.signed_message, ) ) self.context.outbox.put_message(message=inform_msg) @@ -599,8 +574,8 @@ def _handle_error( :return: None """ self.context.logger.info( - "[{}]: transaction signing was not successful. Error_code={} in dialogue={}".format( - self.context.agent_name, signing_msg.error_code, signing_dialogue + "transaction signing was not successful. Error_code={} in dialogue={}".format( + signing_msg.error_code, signing_dialogue ) ) @@ -615,8 +590,8 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle signing message of performative={} in dialogue={}.".format( - self.context.agent_name, signing_msg.performative, signing_dialogue + "cannot handle signing message of performative={} in dialogue={}.".format( + signing_msg.performative, signing_dialogue ) ) @@ -673,8 +648,8 @@ def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> Non :param msg: the message """ self.context.logger.info( - "[{}]: received invalid ledger_api message={}, unidentified dialogue.".format( - self.context.agent_name, ledger_api_msg + "received invalid ledger_api message={}, unidentified dialogue.".format( + ledger_api_msg ) ) @@ -688,10 +663,8 @@ def _handle_balance( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: starting balance on {} ledger={}.".format( - self.context.agent_name, - ledger_api_msg.ledger_id, - ledger_api_msg.balance, + "starting balance on {} ledger={}.".format( + ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) @@ -705,8 +678,8 @@ def _handle_error( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: received ledger_api error message={} in dialogue={}.".format( - self.context.agent_name, ledger_api_msg, ledger_api_dialogue + "received ledger_api error message={} in dialogue={}.".format( + ledger_api_msg, ledger_api_dialogue ) ) @@ -720,9 +693,7 @@ def _handle_invalid( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( - "[{}]: cannot handle ledger_api message of performative={} in dialogue={}.".format( - self.context.agent_name, - ledger_api_msg.performative, - ledger_api_dialogue, + "cannot handle ledger_api message of performative={} in dialogue={}.".format( + ledger_api_msg.performative, ledger_api_dialogue, ) ) diff --git a/packages/fetchai/skills/erc1155_client/skill.yaml b/packages/fetchai/skills/erc1155_client/skill.yaml index be87217fc7..80f912d298 100644 --- a/packages/fetchai/skills/erc1155_client/skill.yaml +++ b/packages/fetchai/skills/erc1155_client/skill.yaml @@ -1,26 +1,26 @@ name: erc1155_client author: fetchai -version: 0.8.0 +version: 0.9.0 description: The erc1155 client interacts with the erc1155 deployer to conduct an atomic swap. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmRXXJsv5bfvb7qsyxQtVzXwn6PMLJKkbm6kg4DNkT1NtW - behaviours.py: QmNkEycwsuyBQ2Ay7s3zzYJHCQTSGwmcSfS2YQ3km6mg2X + behaviours.py: QmToJBBbG2z8FGwWEtxL7tZkXfWuSUDbesxiAsmxRQxmdj dialogues.py: QmXd6KC9se6qZWaAsoqJpRYNF6BvVPBd5KJBxSKq9xhLLh - handlers.py: QmcDbeow6ebn5Q9JbxyanVb8MH5hs4imqLGb9b2hvEhuvF - strategy.py: QmPr8aXdXnAwJ2NKXcV9TULgu1UuxUH29W8YiMc8LMvLj3 + handlers.py: QmZeKbEaYpStT2p5p9FzpQ3Yd3JZj1DrzfVL9tpTnG3BV4 + strategy.py: QmNg87LgfLPoPyokFrmvrNghQD7JkWehRNAdRNyB3YogeN fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/contract_api:0.1.0 -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 -- fetchai/signing:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 +- fetchai/signing:0.2.0 skills: [] behaviours: search: @@ -65,9 +65,13 @@ models: strategy: args: ledger_id: ethereum + location: + latitude: 0.127 + longitude: 51.5194 search_query: constraint_type: == - search_term: has_erc1155_contract - search_value: true + search_key: seller_service + search_value: erc1155_contract + search_radius: 5.0 class_name: Strategy dependencies: {} diff --git a/packages/fetchai/skills/erc1155_client/strategy.py b/packages/fetchai/skills/erc1155_client/strategy.py index 9d82b8afd3..d528a4bb95 100644 --- a/packages/fetchai/skills/erc1155_client/strategy.py +++ b/packages/fetchai/skills/erc1155_client/strategy.py @@ -20,14 +20,18 @@ """This module contains the strategy class.""" from aea.configurations.constants import DEFAULT_LEDGER -from aea.helpers.search.models import Constraint, ConstraintType, Query +from aea.helpers.search.generic import SIMPLE_SERVICE_MODEL +from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.skills.base import Model +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} DEFAULT_SEARCH_QUERY = { - "search_term": "has_erc1155_contract", - "search_value": True, + "search_key": "seller_service", + "search_value": "erc1155_contract", "constraint_type": "==", } +DEFAULT_SEARCH_RADIUS = 5.0 + DEFAULT_LEDGER_ID = DEFAULT_LEDGER @@ -41,12 +45,10 @@ def __init__(self, **kwargs) -> None: :return: None """ self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) - assert all( - [ - key in self._search_query - for key in ["search_term", "constraint_type", "search_value"] - ] - ), "Invalid search query data." + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = Location(location["longitude"], location["latitude"]) + self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) + self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) super().__init__(**kwargs) self.is_searching = True @@ -56,22 +58,37 @@ def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id + def get_location_and_service_query(self) -> Query: + """ + Get the location and service query of the agent. + + :return: the query + """ + close_to_my_service = Constraint( + "location", ConstraintType("distance", (self._agent_location, self._radius)) + ) + service_key_filter = Constraint( + self._search_query["search_key"], + ConstraintType( + self._search_query["constraint_type"], + self._search_query["search_value"], + ), + ) + query = Query([close_to_my_service, service_key_filter],) + return query + def get_service_query(self) -> Query: """ Get the service query of the agent. :return: the query """ - query = Query( - [ - Constraint( - self._search_query["search_term"], - ConstraintType( - self._search_query["constraint_type"], - self._search_query["search_value"], - ), - ) - ], - model=None, + service_key_filter = Constraint( + self._search_query["search_key"], + ConstraintType( + self._search_query["constraint_type"], + self._search_query["search_value"], + ), ) + query = Query([service_key_filter], model=SIMPLE_SERVICE_MODEL) return query diff --git a/packages/fetchai/skills/erc1155_deploy/behaviours.py b/packages/fetchai/skills/erc1155_deploy/behaviours.py index 44e009b0ef..adace5ec53 100644 --- a/packages/fetchai/skills/erc1155_deploy/behaviours.py +++ b/packages/fetchai/skills/erc1155_deploy/behaviours.py @@ -21,7 +21,6 @@ from typing import Optional, cast -from aea.helpers.search.models import Description from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.contract_api.message import ContractApiMessage @@ -36,7 +35,7 @@ from packages.fetchai.skills.erc1155_deploy.strategy import Strategy DEFAULT_SERVICES_INTERVAL = 30.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class ServiceRegistrationBehaviour(TickerBehaviour): @@ -48,7 +47,7 @@ def __init__(self, **kwargs): "services_interval", DEFAULT_SERVICES_INTERVAL ) # type: int super().__init__(tick_interval=services_interval, **kwargs) - self._registered_service_description = None # type: Optional[Description] + self.is_service_registered = False def setup(self) -> None: """ @@ -83,9 +82,11 @@ def act(self) -> None: strategy.is_contract_deployed and strategy.is_tokens_created and strategy.is_tokens_minted + and not self.is_service_registered ): - self._unregister_service() + self._register_agent() self._register_service() + self.is_service_registered = True def teardown(self) -> None: """ @@ -94,6 +95,7 @@ def teardown(self) -> None: :return: None """ self._unregister_service() + self._unregister_agent() def _request_balance(self) -> None: """ @@ -130,7 +132,7 @@ def _request_contract_deploy_transaction(self) -> None: performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address} @@ -144,11 +146,7 @@ def _request_contract_deploy_transaction(self) -> None: assert contract_api_dialogue is not None, "ContractApiDialogue not generated" contract_api_dialogue.terms = strategy.get_deploy_terms() self.context.outbox.put_message(message=contract_api_msg) - self.context.logger.info( - "[{}]: Requesting contract deployment transaction...".format( - self.context.agent_name - ) - ) + self.context.logger.info("requesting contract deployment transaction...") def _request_token_create_transaction(self) -> None: """ @@ -165,7 +163,7 @@ def _request_token_create_transaction(self) -> None: performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -183,11 +181,7 @@ def _request_token_create_transaction(self) -> None: assert contract_api_dialogue is not None, "ContractApiDialogue not generated" contract_api_dialogue.terms = strategy.get_create_token_terms() self.context.outbox.put_message(message=contract_api_msg) - self.context.logger.info( - "[{}]: Requesting create batch transaction...".format( - self.context.agent_name - ) - ) + self.context.logger.info("requesting create batch transaction...") def _request_token_mint_transaction(self) -> None: """ @@ -204,7 +198,7 @@ def _request_token_mint_transaction(self) -> None: performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=strategy.contract_address, callable="get_mint_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -224,19 +218,37 @@ def _request_token_mint_transaction(self) -> None: assert contract_api_dialogue is not None, "ContractApiDialogue not generated" contract_api_dialogue.terms = strategy.get_mint_token_terms() self.context.outbox.put_message(message=contract_api_msg) - self.context.logger.info( - "[{}]: Requesting mint batch transaction...".format(self.context.agent_name) + self.context.logger.info("requesting mint batch transaction...") + + def _register_agent(self) -> None: + """ + Register the agent's location. + + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering agent on SOEF.") def _register_service(self) -> None: """ - Register to the OEF Service Directory. + Register the agent's service. :return: None """ strategy = cast(Strategy, self.context.strategy) - description = strategy.get_service_description() - self._registered_service_description = description + description = strategy.get_register_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) @@ -248,34 +260,46 @@ def _register_service(self) -> None: oef_search_msg.counterparty = self.context.search_service_address oef_search_dialogues.update(oef_search_msg) self.context.outbox.put_message(message=oef_search_msg) - self.context.logger.info( - "[{}]: updating erc1155 service on OEF search node.".format( - self.context.agent_name - ) - ) + self.context.logger.info("registering service on SOEF.") def _unregister_service(self) -> None: """ - Unregister service from OEF Service Directory. + Unregister service from the SOEF. :return: None """ - if self._registered_service_description is None: - return + strategy = cast(Strategy, self.context.strategy) + description = strategy.get_unregister_service_description() oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) oef_search_msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), - service_description=self._registered_service_description, + service_description=description, ) oef_search_msg.counterparty = self.context.search_service_address oef_search_dialogues.update(oef_search_msg) self.context.outbox.put_message(message=oef_search_msg) - self.context.logger.info( - "[{}]: unregistering erc1155 service from OEF search node.".format( - self.context.agent_name - ) + self.context.logger.info("unregistering service from SOEF.") + + def _unregister_agent(self) -> None: + """ + Unregister agent from the SOEF. + + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues ) - self._registered_service_description = None + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("unregistering agent from SOEF.") diff --git a/packages/fetchai/skills/erc1155_deploy/handlers.py b/packages/fetchai/skills/erc1155_deploy/handlers.py index 36ccb83620..6da272c1e8 100644 --- a/packages/fetchai/skills/erc1155_deploy/handlers.py +++ b/packages/fetchai/skills/erc1155_deploy/handlers.py @@ -31,6 +31,7 @@ from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.erc1155_deploy.dialogues import ( ContractApiDialogue, ContractApiDialogues, @@ -39,13 +40,15 @@ FipaDialogues, LedgerApiDialogue, LedgerApiDialogues, + OefSearchDialogue, + OefSearchDialogues, SigningDialogue, SigningDialogues, ) from packages.fetchai.skills.erc1155_deploy.strategy import Strategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class FipaHandler(Handler): @@ -98,7 +101,7 @@ def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: :return: None """ self.context.logger.info( - "[{}]: unidentified dialogue.".format(self.context.agent_name) + "unidentified dialogue for message={}.".format(fipa_msg) ) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg = DefaultMessage( @@ -124,9 +127,7 @@ def _handle_cfp(self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue) -> Non """ strategy = cast(Strategy, self.context.strategy) self.context.logger.info( - "[{}]: received CFP from sender={}".format( - self.context.agent_name, fipa_msg.counterparty[-5:] - ) + "received CFP from sender={}".format(fipa_msg.counterparty[-5:]) ) if not strategy.is_tokens_minted: self.context.logger.info("Contract items not minted yet. Try again later.") @@ -144,10 +145,8 @@ def _handle_cfp(self, fipa_msg: FipaMessage, fipa_dialogue: FipaDialogue) -> Non proposal_msg.counterparty = fipa_msg.counterparty fipa_dialogue.update(proposal_msg) self.context.logger.info( - "[{}]: Sending PROPOSE to agent={}: proposal={}".format( - self.context.agent_name, - fipa_msg.counterparty[-5:], - fipa_dialogue.proposal.values, + "sending PROPOSE to agent={}: proposal={}".format( + fipa_msg.counterparty[-5:], fipa_dialogue.proposal.values, ) ) self.context.outbox.put_message(message=proposal_msg) @@ -167,8 +166,8 @@ def _handle_accept_w_inform( tx_signature = fipa_msg.info.get("tx_signature", None) if tx_signature is not None: self.context.logger.info( - "[{}]: received ACCEPT_W_INFORM from sender={}: tx_signature={}".format( - self.context.agent_name, fipa_msg.counterparty[-5:], tx_signature + "received ACCEPT_W_INFORM from sender={}: tx_signature={}".format( + fipa_msg.counterparty[-5:], tx_signature ) ) strategy = cast(Strategy, self.context.strategy) @@ -179,7 +178,7 @@ def _handle_accept_w_inform( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=strategy.contract_address, callable="get_atomic_swap_single_transaction", kwargs=ContractApiMessage.Kwargs( @@ -211,15 +210,11 @@ def _handle_accept_w_inform( fipa_dialogue.proposal, fipa_msg.counterparty ) self.context.outbox.put_message(message=contract_api_msg) - self.context.logger.info( - "[{}]: Requesting single atomic swap transaction...".format( - self.context.agent_name - ) - ) + self.context.logger.info("requesting single atomic swap transaction...") else: self.context.logger.info( - "[{}]: received ACCEPT_W_INFORM from sender={} with no signature.".format( - self.context.agent_name, fipa_msg.counterparty[-5:] + "received ACCEPT_W_INFORM from sender={} with no signature.".format( + fipa_msg.counterparty[-5:] ) ) @@ -234,8 +229,8 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle fipa message of performative={} in dialogue={}.".format( - self.context.agent_name, fipa_msg.performative, fipa_dialogue + "cannot handle fipa message of performative={} in dialogue={}.".format( + fipa_msg.performative, fipa_dialogue ) ) @@ -302,8 +297,8 @@ def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> Non :param msg: the message """ self.context.logger.info( - "[{}]: received invalid ledger_api message={}, unidentified dialogue.".format( - self.context.agent_name, ledger_api_msg + "received invalid ledger_api message={}, unidentified dialogue.".format( + ledger_api_msg ) ) @@ -317,10 +312,8 @@ def _handle_balance( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: starting balance on {} ledger={}.".format( - self.context.agent_name, - ledger_api_msg.ledger_id, - ledger_api_msg.balance, + "starting balance on {} ledger={}.".format( + ledger_api_msg.ledger_id, ledger_api_msg.balance, ) ) @@ -334,8 +327,8 @@ def _handle_transaction_digest( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: transaction was successfully submitted. Transaction digest={}".format( - self.context.agent_name, ledger_api_msg.transaction_digest + "transaction was successfully submitted. Transaction digest={}".format( + ledger_api_msg.transaction_digest ) ) msg = LedgerApiMessage( @@ -348,9 +341,7 @@ def _handle_transaction_digest( msg.counterparty = ledger_api_msg.counterparty ledger_api_dialogue.update(msg) self.context.outbox.put_message(message=msg) - self.context.logger.info( - "[{}]: requesting transaction receipt.".format(self.context.agent_name) - ) + self.context.logger.info("requesting transaction receipt.") def _handle_transaction_receipt( self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue @@ -366,8 +357,8 @@ def _handle_transaction_receipt( ) if is_transaction_successful: self.context.logger.info( - "[{}]: transaction was successfully settled. Transaction receipt={}".format( - self.context.agent_name, ledger_api_msg.transaction_receipt + "transaction was successfully settled. Transaction receipt={}".format( + ledger_api_msg.transaction_receipt ) ) strategy = cast(Strategy, self.context.strategy) @@ -386,19 +377,13 @@ def _handle_transaction_receipt( strategy.is_behaviour_active = is_transaction_successful elif strategy.is_tokens_minted: self.context.is_active = False - self.context.logger.info( - "[{}]: Demo finished!".format(self.context.agent_name) - ) + self.context.logger.info("demo finished!") else: - self.context.logger.error( - "[{}]: Unexpected transaction receipt!".format( - self.context.agent_name - ) - ) + self.context.logger.error("unexpected transaction receipt!") else: self.context.logger.error( - "[{}]: transaction failed. Transaction receipt={}".format( - self.context.agent_name, ledger_api_msg.transaction_receipt + "transaction failed. Transaction receipt={}".format( + ledger_api_msg.transaction_receipt ) ) @@ -412,8 +397,8 @@ def _handle_error( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: received ledger_api error message={} in dialogue={}.".format( - self.context.agent_name, ledger_api_msg, ledger_api_dialogue + "received ledger_api error message={} in dialogue={}.".format( + ledger_api_msg, ledger_api_dialogue ) ) @@ -427,10 +412,8 @@ def _handle_invalid( :param ledger_api_dialogue: the ledger api dialogue """ self.context.logger.warning( - "[{}]: cannot handle ledger_api message of performative={} in dialogue={}.".format( - self.context.agent_name, - ledger_api_msg.performative, - ledger_api_dialogue, + "cannot handle ledger_api message of performative={} in dialogue={}.".format( + ledger_api_msg.performative, ledger_api_dialogue, ) ) @@ -493,8 +476,8 @@ def _handle_unidentified_dialogue( :param msg: the message """ self.context.logger.info( - "[{}]: received invalid contract_api message={}, unidentified dialogue.".format( - self.context.agent_name, contract_api_msg + "received invalid contract_api message={}, unidentified dialogue.".format( + contract_api_msg ) ) @@ -509,11 +492,7 @@ def _handle_raw_transaction( :param contract_api_message: the ledger api message :param contract_api_dialogue: the ledger api dialogue """ - self.context.logger.info( - "[{}]: received raw transaction={}".format( - self.context.agent_name, contract_api_msg - ) - ) + self.context.logger.info("received raw transaction={}".format(contract_api_msg)) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, @@ -531,9 +510,7 @@ def _handle_raw_transaction( signing_dialogue.associated_contract_api_dialogue = contract_api_dialogue self.context.decision_maker_message_queue.put_nowait(signing_msg) self.context.logger.info( - "[{}]: proposing the transaction to the decision maker. Waiting for confirmation ...".format( - self.context.agent_name - ) + "proposing the transaction to the decision maker. Waiting for confirmation ..." ) def _handle_error( @@ -548,8 +525,8 @@ def _handle_error( :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.info( - "[{}]: received ledger_api error message={} in dialogue={}.".format( - self.context.agent_name, contract_api_msg, contract_api_dialogue + "received ledger_api error message={} in dialogue={}.".format( + contract_api_msg, contract_api_dialogue ) ) @@ -565,10 +542,8 @@ def _handle_invalid( :param contract_api_dialogue: the ledger api dialogue """ self.context.logger.warning( - "[{}]: cannot handle contract_api message of performative={} in dialogue={}.".format( - self.context.agent_name, - contract_api_msg.performative, - contract_api_dialogue, + "cannot handle contract_api message of performative={} in dialogue={}.".format( + contract_api_msg.performative, contract_api_dialogue, ) ) @@ -623,8 +598,8 @@ def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: :param msg: the message """ self.context.logger.info( - "[{}]: received invalid signing message={}, unidentified dialogue.".format( - self.context.agent_name, signing_msg + "received invalid signing message={}, unidentified dialogue.".format( + signing_msg ) ) @@ -638,9 +613,7 @@ def _handle_signed_transaction( :param signing_dialogue: the dialogue :return: None """ - self.context.logger.info( - "[{}]: transaction signing was successful.".format(self.context.agent_name) - ) + self.context.logger.info("transaction signing was successful.") ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) @@ -656,9 +629,7 @@ def _handle_signed_transaction( assert ledger_api_dialogue is not None, "Error when creating signing dialogue." ledger_api_dialogue.associated_signing_dialogue = signing_dialogue self.context.outbox.put_message(message=ledger_api_msg) - self.context.logger.info( - "[{}]: sending transaction to ledger.".format(self.context.agent_name) - ) + self.context.logger.info("sending transaction to ledger.") def _handle_error( self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue @@ -671,8 +642,8 @@ def _handle_error( :return: None """ self.context.logger.info( - "[{}]: transaction signing was not successful. Error_code={} in dialogue={}".format( - self.context.agent_name, signing_msg.error_code, signing_dialogue + "transaction signing was not successful. Error_code={} in dialogue={}".format( + signing_msg.error_code, signing_dialogue ) ) @@ -687,7 +658,95 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle signing message of performative={} in dialogue={}.".format( - self.context.agent_name, signing_msg.performative, signing_dialogue + "cannot handle signing message of performative={} in dialogue={}.".format( + signing_msg.performative, signing_dialogue + ) + ) + + +class OefSearchHandler(Handler): + """This class implements an OEF search handler.""" + + SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[ProtocolId] + + def setup(self) -> None: + """Call to setup the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + oef_search_msg = cast(OefSearchMessage, message) + + # recover dialogue + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) + ) + if oef_search_dialogue is None: + self._handle_unidentified_dialogue(oef_search_msg) + return + + # handle message + if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: + self._handle_error(oef_search_msg, oef_search_dialogue) + else: + self._handle_invalid(oef_search_msg, oef_search_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: + """ + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.info( + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg + ) + ) + + def _handle_error( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.info( + "received oef_search error message={} in dialogue={}.".format( + oef_search_msg, oef_search_dialogue + ) + ) + + def _handle_invalid( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, ) ) diff --git a/packages/fetchai/skills/erc1155_deploy/skill.yaml b/packages/fetchai/skills/erc1155_deploy/skill.yaml index f2a116698e..268d5c780f 100644 --- a/packages/fetchai/skills/erc1155_deploy/skill.yaml +++ b/packages/fetchai/skills/erc1155_deploy/skill.yaml @@ -1,26 +1,26 @@ name: erc1155_deploy author: fetchai -version: 0.9.0 +version: 0.10.0 description: The ERC1155 deploy skill has the ability to deploy and interact with the smart contract. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: Qmbm3ZtGpfdvvzqykfRqbaReAK9a16mcyK7qweSfeN5pq1 - behaviours.py: Qmejkpw5Ug9nW8Ju4y8Mg3wTgtJTDFGGcQLXYQKCDjbpVP + behaviours.py: QmNo9goXkGc5FYEDqWKheLLhq5aNAgAVGhEijbUBB4XsxQ dialogues.py: QmR6qb8PdmUozHANKMuLaKfLGKxgnx2zFzbkmcgqXq8wgg - handlers.py: Qmd6U3zTZqapH5EyaLp2rGCABWVRfkx2arHLVHQgdLWvCf - strategy.py: QmNLnx4zKMgwe18ou5unotaEJj5jMWTKoSL2UT7PtZZjg3 + handlers.py: QmaK97BKAbBzwKu8nxwQch4PaCdV4ttSEGAY2hzEXtTMZe + strategy.py: QmYvu2yH9fh2reqPXuTLFcX82fjeFA5Wm5K9DrTrDduJ49 fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/contract_api:0.1.0 -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 -- fetchai/signing:0.1.0 +- fetchai/contract_api:0.2.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 +- fetchai/signing:0.2.0 skills: [] behaviours: service_registration: @@ -37,6 +37,9 @@ handlers: ledger_api: args: {} class_name: LedgerApiHandler + oef_search: + args: {} + class_name: OefSearchHandler signing: args: {} class_name: SigningHandler @@ -61,14 +64,11 @@ models: class_name: SigningDialogues strategy: args: - data_model: - attribute_one: - is_required: true - name: has_erc1155_contract - type: bool - data_model_name: erc1155_deploy from_supply: 10 ledger_id: ethereum + location: + latitude: 0.127 + longitude: 51.5194 mint_quantities: - 100 - 100 @@ -82,7 +82,8 @@ models: - 100 nb_tokens: 10 service_data: - has_erc1155_contract: true + key: seller_service + value: erc1155_contract to_supply: 0 token_type: 2 value: 0 diff --git a/packages/fetchai/skills/erc1155_deploy/strategy.py b/packages/fetchai/skills/erc1155_deploy/strategy.py index c0326855eb..1ba558902b 100644 --- a/packages/fetchai/skills/erc1155_deploy/strategy.py +++ b/packages/fetchai/skills/erc1155_deploy/strategy.py @@ -20,11 +20,16 @@ """This module contains the strategy class.""" import random # nosec -from typing import Any, Dict, List, Optional +from typing import List from aea.configurations.constants import DEFAULT_LEDGER -from aea.helpers.search.generic import GenericDataModel -from aea.helpers.search.models import Description +from aea.helpers.search.generic import ( + AGENT_LOCATION_MODEL, + AGENT_REMOVE_SERVICE_MODEL, + AGENT_SET_SERVICE_MODEL, + SIMPLE_SERVICE_MODEL, +) +from aea.helpers.search.models import Description, Location from aea.helpers.transaction.base import Terms from aea.skills.base import Model @@ -39,15 +44,8 @@ DEFAULT_FROM_SUPPLY = 10 DEFAULT_TO_SUPPLY = 0 DEFAULT_VALUE = 0 -DEFAULT_DATA_MODEL_NAME = "erc1155_deploy" -DEFAULT_DATA_MODEL = { - "attribute_one": { - "name": "has_erc1155_contract", - "type": "bool", - "is_required": "True", - }, -} # type: Optional[Dict[str, Any]] -DEFAULT_SERVICE_DATA = {"has_erc1155_contract": True} +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SERVICE_DATA = {"key": "seller_service", "value": "erc1155_contract"} DEFAULT_LEDGER_ID = DEFAULT_LEDGER @@ -78,9 +76,20 @@ def __init__(self, **kwargs) -> None: self.to_supply = kwargs.pop("to_supply", DEFAULT_TO_SUPPLY) self.value = kwargs.pop("value", DEFAULT_VALUE) - self._service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) - self._data_model = kwargs.pop("data_model", DEFAULT_DATA_MODEL) - self._data_model_name = kwargs.pop("data_model_name", DEFAULT_DATA_MODEL_NAME) + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = { + "location": Location(location["longitude"], location["latitude"]) + } + self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) + assert ( + len(self._set_service_data) == 2 + and "key" in self._set_service_data + and "value" in self._set_service_data + ), "service_data must contain keys `key` and `value`" + self._remove_service_data = {"key": self._set_service_data["key"]} + self._simple_service_data = { + self._set_service_data["key"]: self._set_service_data["value"] + } super().__init__(**kwargs) @@ -160,15 +169,47 @@ def is_tokens_minted(self, is_tokens_minted: bool) -> None: ), "Only allowed to switch to true." self._is_tokens_minted = is_tokens_minted + def get_location_description(self) -> Description: + """ + Get the location description. + + :return: a description of the agent's location + """ + description = Description( + self._agent_location, data_model=AGENT_LOCATION_MODEL, + ) + return description + + def get_register_service_description(self) -> Description: + """ + Get the register service description. + + :return: a description of the offered services + """ + description = Description( + self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, + ) + return description + def get_service_description(self) -> Description: """ - Get the service description. + Get the simple service description. :return: a description of the offered services """ description = Description( - self._service_data, - data_model=GenericDataModel(self._data_model_name, self._data_model), + self._simple_service_data, data_model=SIMPLE_SERVICE_MODEL, + ) + return description + + def get_unregister_service_description(self) -> Description: + """ + Get the unregister service description. + + :return: a description of the to be removed service + """ + description = Description( + self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, ) return description @@ -179,14 +220,12 @@ def get_deploy_terms(self) -> Terms: :return: terms """ terms = Terms( - self.ledger_id, - self.context.agent_address, - self.context.agent_address, - {}, - {}, - True, - "", - {}, + ledger_id=self.ledger_id, + sender_address=self.context.agent_address, + counterparty_address=self.context.agent_address, + amount_by_currency_id={}, + quantities_by_good_id={}, + nonce="", ) return terms @@ -197,14 +236,12 @@ def get_create_token_terms(self) -> Terms: :return: terms """ terms = Terms( - self.ledger_id, - self.context.agent_address, - self.context.agent_address, - {}, - {}, - True, - "", - {}, + ledger_id=self.ledger_id, + sender_address=self.context.agent_address, + counterparty_address=self.context.agent_address, + amount_by_currency_id={}, + quantities_by_good_id={}, + nonce="", ) return terms @@ -215,14 +252,12 @@ def get_mint_token_terms(self) -> Terms: :return: terms """ terms = Terms( - self.ledger_id, - self.context.agent_address, - self.context.agent_address, - {}, - {}, - True, - "", - {}, + ledger_id=self.ledger_id, + sender_address=self.context.agent_address, + counterparty_address=self.context.agent_address, + amount_by_currency_id={}, + quantities_by_good_id={}, + nonce="", ) return terms diff --git a/packages/fetchai/skills/generic_buyer/behaviours.py b/packages/fetchai/skills/generic_buyer/behaviours.py index 5448efab29..f7789991bf 100644 --- a/packages/fetchai/skills/generic_buyer/behaviours.py +++ b/packages/fetchai/skills/generic_buyer/behaviours.py @@ -32,7 +32,7 @@ from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy DEFAULT_SEARCH_INTERVAL = 5.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class GenericSearchBehaviour(TickerBehaviour): diff --git a/packages/fetchai/skills/generic_buyer/handlers.py b/packages/fetchai/skills/generic_buyer/handlers.py index dbf2d7d0d6..478ffed7ab 100644 --- a/packages/fetchai/skills/generic_buyer/handlers.py +++ b/packages/fetchai/skills/generic_buyer/handlers.py @@ -44,7 +44,7 @@ ) from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class GenericFipaHandler(Handler): diff --git a/packages/fetchai/skills/generic_buyer/skill.yaml b/packages/fetchai/skills/generic_buyer/skill.yaml index 27d759f819..44d80c643f 100644 --- a/packages/fetchai/skills/generic_buyer/skill.yaml +++ b/packages/fetchai/skills/generic_buyer/skill.yaml @@ -1,22 +1,22 @@ name: generic_buyer author: fetchai -version: 0.7.0 +version: 0.8.0 description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmaEDrNJBeHCJpbdFckRUhLSBqCXQ6umdipTMpYhqSKxSG - behaviours.py: QmUHgMCuvWYyAU382c7hUikNi6R6rfmH1toKUB1K2rcbXQ + behaviours.py: QmQjGXY2qPdmSAkPsEkCntE5GiZwERKV35TXoW54P3jn1J dialogues.py: QmYMR28TDqE56GdUxP9LwerktaJrD9SBkGoeJsoLSMHpx6 - handlers.py: QmYevHGuYJ8bsQUT22ZJcSx2aotUveNTLbdyEMMzCEMw7U + handlers.py: QmTFSy4MCPhtrrPCAoFZJJ7WdHPC4EZRd7qey5hwM7ucJF strategy.py: QmU2gH921MoxvVCCQhnEvcFNbDsgBojeHeTXDEY3ZBMC2A fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: search: diff --git a/packages/fetchai/skills/generic_seller/behaviours.py b/packages/fetchai/skills/generic_seller/behaviours.py index f8c8277914..790ca0da03 100644 --- a/packages/fetchai/skills/generic_seller/behaviours.py +++ b/packages/fetchai/skills/generic_seller/behaviours.py @@ -33,7 +33,7 @@ DEFAULT_SERVICES_INTERVAL = 60.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class GenericServiceRegistrationBehaviour(TickerBehaviour): diff --git a/packages/fetchai/skills/generic_seller/handlers.py b/packages/fetchai/skills/generic_seller/handlers.py index 85303f9279..2d91d793e5 100644 --- a/packages/fetchai/skills/generic_seller/handlers.py +++ b/packages/fetchai/skills/generic_seller/handlers.py @@ -42,7 +42,7 @@ ) from packages.fetchai.skills.generic_seller.strategy import GenericStrategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class GenericFipaHandler(Handler): diff --git a/packages/fetchai/skills/generic_seller/skill.yaml b/packages/fetchai/skills/generic_seller/skill.yaml index 27e72fbbea..ecb9807e9a 100644 --- a/packages/fetchai/skills/generic_seller/skill.yaml +++ b/packages/fetchai/skills/generic_seller/skill.yaml @@ -1,23 +1,23 @@ name: generic_seller author: fetchai -version: 0.8.0 +version: 0.9.0 description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmbfkeFnZVKppLEHpBrTXUXBwg2dpPABJWSLND8Lf1cmpG - behaviours.py: QmZuzEpqCZjW1rAYT1PZXoYRPjCXxKNQ2ZEkL32WQhxtwf + behaviours.py: QmPKPmNmmjXHxk2kVmREwNiAoVQjwdBqCHmpyaZ5n8Exap dialogues.py: QmNf96REY7PiRdStRJrn97fuCRgqTAeQti5uf4sPzgMNau - handlers.py: QmfFY8HGULapXzCHHLuwWhgADXvBw8NJvfX155pY3qWS1h + handlers.py: QmbpmWTst15PTYMoPf2GBnTJvCMyHaToCV2qRtESyQhvWB strategy.py: QmRVkBtcCUKXf68RAqnHAi6UWqcygesppUNzSm9oceYNHH fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: service_registration: diff --git a/packages/fetchai/skills/gym/dialogues.py b/packages/fetchai/skills/gym/dialogues.py new file mode 100644 index 0000000000..01a6c49e5d --- /dev/null +++ b/packages/fetchai/skills/gym/dialogues.py @@ -0,0 +1,119 @@ +# -*- 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. + +- DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. +- DefaultDialogues: The dialogues class keeps track of all dialogues of type default. +- GymDialogue: The dialogue class maintains state of a dialogue of type gym and manages it. +- GymDialogues: The dialogues class keeps track of all dialogues of type gym. +""" + +from aea.helpers.dialogue.base import Dialogue as BaseDialogue +from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel +from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.skills.base import Model + + +from packages.fetchai.protocols.gym.dialogues import GymDialogue as BaseGymDialogue +from packages.fetchai.protocols.gym.dialogues import GymDialogues as BaseGymDialogues + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseDefaultDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of default dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +GymDialogue = BaseGymDialogue + + +class GymDialogues(Model, BaseGymDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseGymDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseGymDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> GymDialogue: + """ + Create an instance of gym dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = GymDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/packages/fetchai/skills/gym/handlers.py b/packages/fetchai/skills/gym/handlers.py index 1a0e654005..06c668e965 100644 --- a/packages/fetchai/skills/gym/handlers.py +++ b/packages/fetchai/skills/gym/handlers.py @@ -22,9 +22,15 @@ from typing import cast from aea.protocols.base import Message +from aea.protocols.default.message import DefaultMessage from aea.skills.base import Handler from packages.fetchai.protocols.gym.message import GymMessage +from packages.fetchai.skills.gym.dialogues import ( + DefaultDialogues, + GymDialogue, + GymDialogues, +) from packages.fetchai.skills.gym.rl_agent import DEFAULT_NB_STEPS from packages.fetchai.skills.gym.tasks import GymTask @@ -49,18 +55,89 @@ def setup(self) -> None: def handle(self, message: Message) -> None: """ - Handle messages. + Implement messages. :param message: the message :return: None """ gym_msg = cast(GymMessage, message) + + # recover dialogue + gym_dialogues = cast(GymDialogues, self.context.gym_dialogues) + gym_dialogue = cast(GymDialogue, gym_dialogues.update(gym_msg)) + if gym_dialogue is None: + self._handle_unidentified_dialogue(gym_msg) + return + + # handle message if gym_msg.performative == GymMessage.Performative.PERCEPT: + self._handle_percept(gym_msg, gym_dialogue) + elif gym_msg.performative == GymMessage.Performative.STATUS: + self._handle_status(gym_msg, gym_dialogue) + else: + self._handle_invalid(gym_msg, gym_dialogue) + + def _handle_unidentified_dialogue(self, gym_msg: GymMessage) -> None: + """ + Handle an unidentified dialogue. + + :param gym_msg: the message + """ + self.context.logger.info( + "received invalid gym message={}, unidentified dialogue.".format(gym_msg) + ) + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) + default_msg = DefaultMessage( + performative=DefaultMessage.Performative.ERROR, + dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), + error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, + error_msg="Invalid dialogue.", + error_data={"gym_message": gym_msg.encode()}, + ) + default_msg.counterparty = gym_msg.counterparty + default_dialogues.update(default_msg) + self.context.outbox.put_message(message=default_msg) + + def _handle_percept(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: + """ + Handle messages. + + :param message: the message + :return: None + """ + if self.task.proxy_env.active_gym_dialogue == gym_dialogue: self.task.proxy_env_queue.put(gym_msg) else: - raise ValueError( - "Unexpected performative or no step_id: {}".format(gym_msg.performative) + self.context.logger.warning("gym dialogue not active dialogue.") + + def _handle_status(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: + """ + Handle messages. + + :param message: the message + :return: None + """ + if ( + self.task.proxy_env.active_gym_dialogue == gym_dialogue + and gym_msg.content.get("reset", "failure") == "success" + ): + self.task.proxy_env_queue.put(gym_msg) + else: + self.context.logger.warning("gym dialogue not active dialogue.") + + def _handle_invalid(self, gym_msg: GymMessage, gym_dialogue: GymDialogue) -> None: + """ + Handle an invalid http message. + + :param gym_msg: the gym message + :param gym_dialogue: the gym dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle gym message of performative={} in dialogue={}.".format( + gym_msg.performative, gym_dialogue ) + ) def teardown(self) -> None: """ diff --git a/packages/fetchai/skills/gym/helpers.py b/packages/fetchai/skills/gym/helpers.py index cac620aac1..d5f501d1dd 100644 --- a/packages/fetchai/skills/gym/helpers.py +++ b/packages/fetchai/skills/gym/helpers.py @@ -17,11 +17,12 @@ # # ------------------------------------------------------------------------------ + """This module contains the helpers for the 'gym' skill.""" from abc import ABC, abstractmethod from queue import Queue -from typing import Any, Tuple, cast +from typing import Any, Optional, Tuple, cast import gym @@ -29,6 +30,7 @@ from aea.skills.base import SkillContext from packages.fetchai.protocols.gym.message import GymMessage +from packages.fetchai.skills.gym.dialogues import GymDialogue, GymDialogues Action = Any Observation = Any @@ -56,6 +58,19 @@ def __init__(self, skill_context: SkillContext) -> None: self._queue = Queue() # type: Queue self._is_rl_agent_trained = False self._step_count = 0 + self._active_dialogue = None # type: Optional[GymDialogue] + self.gym_address = "fetchai/gym:0.5.0" + + @property + def gym_dialogues(self) -> GymDialogues: + """Get the gym dialogues.""" + return cast(GymDialogues, self._skill_context.gym_dialogues) + + @property + def active_gym_dialogue(self) -> GymDialogue: + """Get the active gym dialogue.""" + assert self._active_dialogue is not None, "GymDialogue not set yet." + return self._active_dialogue @property def queue(self) -> Queue: @@ -90,6 +105,13 @@ def step(self, action: Action) -> Feedback: # Wait (blocking!) for the response envelope from the environment gym_msg = self._queue.get(block=True, timeout=None) # type: GymMessage + if gym_msg.performative != GymMessage.Performative.PERCEPT: + raise ValueError( + "Unexpected performative. Expected={} got={}".format( + GymMessage.Performative.PERCEPT, gym_msg.performative + ) + ) + if gym_msg.step_id == step_id: observation, reward, done, info = self._message_to_percept(gym_msg) else: @@ -117,10 +139,26 @@ def reset(self) -> None: """ self._step_count = 0 self._is_rl_agent_trained = False - gym_msg = GymMessage(performative=GymMessage.Performative.RESET) - gym_msg.counterparty = DEFAULT_GYM + gym_msg = GymMessage( + dialogue_reference=self.gym_dialogues.new_self_initiated_dialogue_reference(), + performative=GymMessage.Performative.RESET, + ) + gym_msg.counterparty = self.gym_address + gym_dialogue = cast(Optional[GymDialogue], self.gym_dialogues.update(gym_msg)) + assert gym_dialogue is not None + self._active_dialogue = gym_dialogue self._skill_context.outbox.put_message(message=gym_msg) + # Wait (blocking!) for the response envelope from the environment + response_msg = self._queue.get(block=True, timeout=None) # type: GymMessage + + if response_msg.performative != GymMessage.Performative.STATUS: + raise ValueError( + "Unexpected performative. Expected={} got={}".format( + GymMessage.Performative.PERCEPT, response_msg.performative + ) + ) + def close(self) -> None: """ Close the environment. @@ -128,8 +166,16 @@ def close(self) -> None: :return: None """ self._is_rl_agent_trained = True - gym_msg = GymMessage(performative=GymMessage.Performative.CLOSE) - gym_msg.counterparty = DEFAULT_GYM + last_msg = self.active_gym_dialogue.last_message + assert last_msg is not None, "Cannot retrieve last message." + gym_msg = GymMessage( + dialogue_reference=self.active_gym_dialogue.dialogue_label.dialogue_reference, + performative=GymMessage.Performative.CLOSE, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, + ) + gym_msg.counterparty = self.gym_address + assert self.active_gym_dialogue.update(gym_msg) self._skill_context.outbox.put_message(message=gym_msg) def _encode_and_send_action(self, action: Action, step_id: int) -> None: @@ -140,12 +186,18 @@ def _encode_and_send_action(self, action: Action, step_id: int) -> None: :param step_id: the step id :return: an envelope """ + last_msg = self.active_gym_dialogue.last_message + assert last_msg is not None, "Cannot retrieve last message." gym_msg = GymMessage( + dialogue_reference=self.active_gym_dialogue.dialogue_label.dialogue_reference, performative=GymMessage.Performative.ACT, action=GymMessage.AnyObject(action), step_id=step_id, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, ) - gym_msg.counterparty = DEFAULT_GYM + gym_msg.counterparty = self.gym_address + assert self.active_gym_dialogue.update(gym_msg) # Send the message via the proxy agent and to the environment self._skill_context.outbox.put_message(message=gym_msg) diff --git a/packages/fetchai/skills/gym/rl_agent.py b/packages/fetchai/skills/gym/rl_agent.py index 73aa0317de..9e8b3ccca5 100644 --- a/packages/fetchai/skills/gym/rl_agent.py +++ b/packages/fetchai/skills/gym/rl_agent.py @@ -30,7 +30,7 @@ DEFAULT_NB_STEPS = 4000 NB_GOODS = 10 -logger = logging.getLogger("aea.packages.fetchai.skills.gym.rl_agent") +_default_logger = logging.getLogger("aea.packages.fetchai.skills.gym.rl_agent") class PriceBandit: @@ -108,16 +108,18 @@ def get_price_expectation(self) -> int: class MyRLAgent(RLAgent): """This class is a reinforcement learning agent that interacts with the agent framework.""" - def __init__(self, nb_goods: int) -> None: + def __init__(self, nb_goods: int, logger: logging.Logger = _default_logger) -> None: """ Instantiate the RL agent. :param nb_goods: number of goods + :param logger: the logger. :return: None """ self.good_price_models = dict( (good_id, GoodPriceModel()) for good_id in range(nb_goods) ) # type: Dict[int, GoodPriceModel] + self.logger = logger def _pick_an_action(self) -> Any: """ @@ -178,7 +180,7 @@ def fit(self, proxy_env: ProxyEnv, nb_steps: int) -> None: self._update_model(obs, reward, done, info, action) action_counter += 1 if action_counter % 10 == 0: - logger.info( + self.logger.info( "Action: step_id='{}' action='{}' reward='{}'".format( action_counter, action, reward ) diff --git a/packages/fetchai/skills/gym/skill.yaml b/packages/fetchai/skills/gym/skill.yaml index 9b3796c592..dbd1d9c4c9 100644 --- a/packages/fetchai/skills/gym/skill.yaml +++ b/packages/fetchai/skills/gym/skill.yaml @@ -1,19 +1,20 @@ name: gym author: fetchai -version: 0.4.0 +version: 0.5.0 description: The gym skill wraps an RL agent. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmTf1GCgHxu7qq4HvUNYiBwuGEL1DcsHQuWH7N7TB5TtoC - handlers.py: QmaYf2XGHhGDYQpyud9BDrP7jfENpjRKARr6Y1H2vKM5cQ - helpers.py: QmQDHWAnBC6kkXWTcizhJFoJy9pNBPNMPp2Xam8s92CRyK - rl_agent.py: QmVQHRWY4w8Ch8hhCxuzS1qZqG7ZJENiTEWHCGH484FPMP - tasks.py: QmURSaDncmKj9Ri6JM4eBwWkEg2JEJrMdxMygKiBNiD2cf + dialogues.py: Qmek8aEAEFsM8tjofQcst7hckAUdGUpAML9D8aPjLUbq9L + handlers.py: QmWqN7TrqZ9Hfh4yVU7JmR7655TsdLPASSkLsVHbTkDEjV + helpers.py: QmX9La8CJH3oQDHZKExTABg8omszpSRnXFFKnv568m6Nh1 + rl_agent.py: QmZQBXQS1nS64irym5y3eKkGzTdE652aQoq8V54ph17D8U + tasks.py: QmVf9K7zCkKYduTnS7nS3d8FvUbEVy7s7a26yaCC8Q3rgd fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/gym:0.3.0 +- fetchai/gym:0.4.0 skills: [] behaviours: {} handlers: @@ -21,6 +22,12 @@ handlers: args: nb_steps: 4000 class_name: GymHandler -models: {} +models: + default_dialogues: + args: {} + class_name: DefaultDialogues + gym_dialogues: + args: {} + class_name: GymDialogues dependencies: gym: {} diff --git a/packages/fetchai/skills/gym/tasks.py b/packages/fetchai/skills/gym/tasks.py index 5aa42a2ca9..0972210c19 100644 --- a/packages/fetchai/skills/gym/tasks.py +++ b/packages/fetchai/skills/gym/tasks.py @@ -19,7 +19,6 @@ """This module contains the tasks for the 'gym' skill.""" -import logging from queue import Queue from threading import Thread @@ -29,17 +28,15 @@ from packages.fetchai.skills.gym.helpers import ProxyEnv from packages.fetchai.skills.gym.rl_agent import DEFAULT_NB_STEPS, MyRLAgent, NB_GOODS -logger = logging.getLogger("aea.packages.fetchai.skills.gym.tasks") - class GymTask(Task): """Gym task.""" def __init__(self, skill_context: SkillContext, nb_steps: int = DEFAULT_NB_STEPS): """Initialize the task.""" - logger.debug("GymTask.__init__: arguments: nb_steps={}".format(nb_steps)) - super().__init__() - self._rl_agent = MyRLAgent(NB_GOODS) + super().__init__(logger=skill_context.logger) + self.logger.debug("GymTask.__init__: arguments: nb_steps={}".format(nb_steps)) + self._rl_agent = MyRLAgent(NB_GOODS, self.logger) self._proxy_env = ProxyEnv(skill_context) self.nb_steps = nb_steps self._rl_agent_training_thread = Thread( @@ -50,7 +47,12 @@ def __init__(self, skill_context: SkillContext, nb_steps: int = DEFAULT_NB_STEPS def _fit(self, proxy_env: ProxyEnv, nb_steps: int): """Fit the RL agent.""" self._rl_agent.fit(proxy_env, nb_steps) - logger.info("Training finished. You can exit now via CTRL+C.") + self.logger.info("Training finished. You can exit now via CTRL+C.") + + @property + def proxy_env(self) -> ProxyEnv: + """Get the queue.""" + return self._proxy_env @property def proxy_env_queue(self) -> Queue: @@ -59,7 +61,7 @@ def proxy_env_queue(self) -> Queue: def setup(self) -> None: """Set up the task.""" - logger.info("Gym task: setup method called.") + self.logger.info("Gym task: setup method called.") def execute(self, *args, **kwargs) -> None: """Execute the task.""" @@ -70,13 +72,13 @@ def execute(self, *args, **kwargs) -> None: def teardown(self) -> None: """Teardown the task.""" - logger.info("Gym Task: teardown method called.") + self.logger.info("Gym Task: teardown method called.") if self.is_rl_agent_training: self._stop_training() def _start_training(self) -> None: """Start training the RL agent.""" - logger.info("Training starting ...") + self.logger.info("Training starting ...") self.is_rl_agent_training = True self._rl_agent_training_thread.start() diff --git a/packages/fetchai/skills/http_echo/dialogues.py b/packages/fetchai/skills/http_echo/dialogues.py new file mode 100644 index 0000000000..6b33e9f779 --- /dev/null +++ b/packages/fetchai/skills/http_echo/dialogues.py @@ -0,0 +1,119 @@ +# -*- 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. + +- DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. +- DefaultDialogues: The dialogues class keeps track of all dialogues of type default. +- HttpDialogue: The dialogue class maintains state of a dialogue of type http and manages it. +- HttpDialogues: The dialogues class keeps track of all dialogues of type http. +""" + +from aea.helpers.dialogue.base import Dialogue as BaseDialogue +from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel +from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.skills.base import Model + + +from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue +from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseDefaultDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of default dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +HttpDialogue = BaseHttpDialogue + + +class HttpDialogues(Model, BaseHttpDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseHttpDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> BaseDialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseHttpDialogue.Role.SERVER + + def create_dialogue( + self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, + ) -> HttpDialogue: + """ + Create an instance of http dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = HttpDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/packages/fetchai/skills/http_echo/handlers.py b/packages/fetchai/skills/http_echo/handlers.py index 444e0818e1..7ed1c1d0eb 100644 --- a/packages/fetchai/skills/http_echo/handlers.py +++ b/packages/fetchai/skills/http_echo/handlers.py @@ -23,9 +23,15 @@ from typing import cast from aea.protocols.base import Message +from aea.protocols.default import DefaultMessage from aea.skills.base import Handler from packages.fetchai.protocols.http.message import HttpMessage +from packages.fetchai.skills.http_echo.dialogues import ( + DefaultDialogues, + HttpDialogue, + HttpDialogues, +) class HttpHandler(Handler): @@ -49,35 +55,71 @@ def handle(self, message: Message) -> None: :return: None """ http_msg = cast(HttpMessage, message) + + # recover dialogue + http_dialogues = cast(HttpDialogues, self.context.http_dialogues) + http_dialogue = cast(HttpDialogue, http_dialogues.update(http_msg)) + if http_dialogue is None: + self._handle_unidentified_dialogue(http_msg) + return + + # handle message if http_msg.performative == HttpMessage.Performative.REQUEST: - self.context.logger.info( - "[{}] received http request with method={}, url={} and body={!r}".format( - self.context.agent_name, - http_msg.method, - http_msg.url, - http_msg.bodyy, - ) - ) - if http_msg.method == "get": - self._handle_get(http_msg) - elif http_msg.method == "post": - self._handle_post(http_msg) + self._handle_request(http_msg, http_dialogue) else: - self.context.logger.info( - "[{}] received response ({}) unexpectedly!".format( - self.context.agent_name, http_msg - ) + self._handle_invalid(http_msg, http_dialogue) + + def _handle_unidentified_dialogue(self, http_msg: HttpMessage) -> None: + """ + Handle an unidentified dialogue. + + :param http_msg: the message + """ + self.context.logger.info( + "received invalid http message={}, unidentified dialogue.".format(http_msg) + ) + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) + default_msg = DefaultMessage( + performative=DefaultMessage.Performative.ERROR, + dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), + error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, + error_msg="Invalid dialogue.", + error_data={"http_message": http_msg.encode()}, + ) + default_msg.counterparty = http_msg.counterparty + default_dialogues.update(default_msg) + self.context.outbox.put_message(message=default_msg) + + def _handle_request( + self, http_msg: HttpMessage, http_dialogue: HttpDialogue + ) -> None: + """ + Handle a Http request. + + :param http_msg: the http message + :param http_dialogue: the http dialogue + :return: None + """ + self.context.logger.info( + "received http request with method={}, url={} and body={!r}".format( + http_msg.method, http_msg.url, http_msg.bodyy, ) + ) + if http_msg.method == "get": + self._handle_get(http_msg, http_dialogue) + elif http_msg.method == "post": + self._handle_post(http_msg, http_dialogue) - def _handle_get(self, http_msg: HttpMessage) -> None: + def _handle_get(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb GET. :param http_msg: the http message + :param http_dialogue: the http dialogue :return: None """ http_response = HttpMessage( - dialogue_reference=http_msg.dialogue_reference, + dialogue_reference=http_dialogue.dialogue_label.dialogue_reference, target=http_msg.message_id, message_id=http_msg.message_id + 1, performative=HttpMessage.Performative.RESPONSE, @@ -87,21 +129,21 @@ def _handle_get(self, http_msg: HttpMessage) -> None: headers=http_msg.headers, bodyy=json.dumps({"tom": {"type": "cat", "age": 10}}).encode("utf-8"), ) - self.context.logger.info( - "[{}] responding with: {}".format(self.context.agent_name, http_response) - ) + self.context.logger.info("responding with: {}".format(http_response)) http_response.counterparty = http_msg.counterparty + assert http_dialogue.update(http_response) self.context.outbox.put_message(message=http_response) - def _handle_post(self, http_msg: HttpMessage) -> None: + def _handle_post(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb POST. :param http_msg: the http message + :param http_dialogue: the http dialogue :return: None """ http_response = HttpMessage( - dialogue_reference=http_msg.dialogue_reference, + dialogue_reference=http_dialogue.dialogue_label.dialogue_reference, target=http_msg.message_id, message_id=http_msg.message_id + 1, performative=HttpMessage.Performative.RESPONSE, @@ -111,12 +153,27 @@ def _handle_post(self, http_msg: HttpMessage) -> None: headers=http_msg.headers, bodyy=b"", ) - self.context.logger.info( - "[{}] responding with: {}".format(self.context.agent_name, http_response) - ) + self.context.logger.info("responding with: {}".format(http_response)) http_response.counterparty = http_msg.counterparty + assert http_dialogue.update(http_response) self.context.outbox.put_message(message=http_response) + def _handle_invalid( + self, http_msg: HttpMessage, http_dialogue: HttpDialogue + ) -> None: + """ + Handle an invalid http message. + + :param http_msg: the http message + :param http_dialogue: the http dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle http message of performative={} in dialogue={}.".format( + http_msg.performative, http_dialogue + ) + ) + def teardown(self) -> None: """ Implement the handler teardown. diff --git a/packages/fetchai/skills/http_echo/skill.yaml b/packages/fetchai/skills/http_echo/skill.yaml index 49a7a1e182..69b796c40f 100644 --- a/packages/fetchai/skills/http_echo/skill.yaml +++ b/packages/fetchai/skills/http_echo/skill.yaml @@ -1,22 +1,29 @@ name: http_echo author: fetchai -version: 0.3.0 +version: 0.4.0 description: The http echo skill prints out the content of received http messages and responds with success. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmaKik9dXg6cajBPG9RTDr6BhVdWk8aoR8QDNfPQgiy1kv - handlers.py: QmUZsmWggTTWiGj3qWkD6Hv3tin1BtqUaKmQD1a2e3z6J5 + dialogues.py: QmXWBBSj9U7ZYH3anv9fr3WenKqdD9NqnG9jeoWmxnf5ds + handlers.py: QmUzf5DeEgN7B8ZTopSF18A7qLV1dGmvJ1bmYzq5a9ahXr fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/http:0.3.0 +- fetchai/http:0.4.0 skills: [] behaviours: {} handlers: http_handler: args: {} class_name: HttpHandler -models: {} +models: + default_dialogues: + args: {} + class_name: DefaultDialogues + http_dialogues: + args: {} + class_name: HttpDialogues dependencies: {} diff --git a/packages/fetchai/skills/ml_data_provider/skill.yaml b/packages/fetchai/skills/ml_data_provider/skill.yaml index 4940cad544..08da1dd16d 100644 --- a/packages/fetchai/skills/ml_data_provider/skill.yaml +++ b/packages/fetchai/skills/ml_data_provider/skill.yaml @@ -1,6 +1,6 @@ name: ml_data_provider author: fetchai -version: 0.7.0 +version: 0.8.0 description: The ml data provider skill implements a provider for Machine Learning datasets in order to monetize data. license: Apache-2.0 @@ -14,12 +14,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/ledger_api:0.1.0 -- fetchai/ml_trade:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/ledger_api:0.2.0 +- fetchai/ml_trade:0.4.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_seller:0.8.0 +- fetchai/generic_seller:0.9.0 behaviours: service_registration: args: diff --git a/packages/fetchai/skills/ml_train/handlers.py b/packages/fetchai/skills/ml_train/handlers.py index 3aa535e435..774791eca1 100644 --- a/packages/fetchai/skills/ml_train/handlers.py +++ b/packages/fetchai/skills/ml_train/handlers.py @@ -48,7 +48,7 @@ DUMMY_DIGEST = "dummy_digest" -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class MlTradeHandler(Handler): diff --git a/packages/fetchai/skills/ml_train/skill.yaml b/packages/fetchai/skills/ml_train/skill.yaml index 07ccf0f020..d9953c6828 100644 --- a/packages/fetchai/skills/ml_train/skill.yaml +++ b/packages/fetchai/skills/ml_train/skill.yaml @@ -1,6 +1,6 @@ name: ml_train author: fetchai -version: 0.7.0 +version: 0.8.0 description: The ml train and predict skill implements a simple skill which buys training data, trains a model and sells predictions. license: Apache-2.0 @@ -9,20 +9,20 @@ fingerprint: __init__.py: QmbQigh7SV7dD2hLTGv3k9tnvpYWN1otG5yjiM7F3bbGEQ behaviours.py: QmQiBzKV5rEFpMQbSjfjzAJ7SqwwGmso6TozWkjdytucLR dialogues.py: QmYnVHVF2EMt3Rfvqpi7T7R6XTEcxaSXhDdim4kjt9a4dL - handlers.py: QmXRnQXtSSX4KZNm7hPfD2UNJUaPnF6quB73RRnvqLYk2q + handlers.py: QmSEsHjwNoF4tpPW377fqXAai5EqNN45kLnnTgj5KdkUBZ ml_model.py: QmZiJGCarjpczcHKQ4EFYSx1e4mEehfaApnHp2W4VQs1od model.json: QmdV2tGrRY6VQ5VLgUa4yqAhPDG6X8tYsWecypq8nox9Td strategy.py: QmPVHfq6okd7Yq9RqA2697L5pnXzBjcJfad97sAXfDUQq9 - tasks.py: QmS5pGbxvMXSh1Vmuvq26e5APnheQJJ3r3BK6GEyUBUpAf + tasks.py: QmTb7kCt2UheQ8kwPewkzgfX8m2DF4KtnYCugWdmERJnTU fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/ledger_api:0.1.0 -- fetchai/ml_trade:0.3.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/ledger_api:0.2.0 +- fetchai/ml_trade:0.4.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_buyer:0.7.0 +- fetchai/generic_buyer:0.8.0 behaviours: search: args: diff --git a/packages/fetchai/skills/ml_train/tasks.py b/packages/fetchai/skills/ml_train/tasks.py index 641e1b417d..72468c0f39 100644 --- a/packages/fetchai/skills/ml_train/tasks.py +++ b/packages/fetchai/skills/ml_train/tasks.py @@ -19,30 +19,29 @@ """This module contains the tasks for the 'ml_train' skill.""" -import logging from typing import Tuple import numpy as np from tensorflow import keras +from aea.skills.base import SkillContext from aea.skills.tasks import Task -logger = logging.getLogger("aea.packages.fetchai.skills.ml_train.tasks") - class MLTrainTask(Task): """ML train task.""" def __init__( self, + skill_context: SkillContext, train_data: Tuple[np.ndarray, np.ndarray], model: keras.Model, epochs_per_batch: int = 10, batch_size: int = 32, ): """Initialize the task.""" - super().__init__() + super().__init__(logger=skill_context.logger) self.train_x, self.train_y = train_data self.model = model @@ -51,16 +50,16 @@ def __init__( def setup(self) -> None: """Set up the task.""" - logger.info("ML Train task: setup method called.") + self.logger.info("ML Train task: setup method called.") def execute(self, *args, **kwargs) -> keras.Model: """Execute the task.""" - logger.info("Start training with {} rows".format(self.train_x.shape[0])) + self.logger.info("Start training with {} rows".format(self.train_x.shape[0])) self.model.fit(self.train_x, self.train_y, epochs=self.epochs_per_batch) loss, acc = self.model.evaluate(self.train_x, self.train_y, verbose=2) - logger.info("Loss: {}, Acc: {}".format(loss, acc)) + self.logger.info("Loss: {}, Acc: {}".format(loss, acc)) return self.model def teardown(self) -> None: """Teardown the task.""" - logger.info("ML Train task: teardown method called.") + self.logger.info("ML Train task: teardown method called.") diff --git a/packages/fetchai/skills/simple_service_registration/handlers.py b/packages/fetchai/skills/simple_service_registration/handlers.py index ef787f4679..1b19883fb8 100644 --- a/packages/fetchai/skills/simple_service_registration/handlers.py +++ b/packages/fetchai/skills/simple_service_registration/handlers.py @@ -31,7 +31,7 @@ OefSearchDialogues, ) -LEDGER_API_ADDRESS = "fetchai/ledger:0.2.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.3.0" class OefSearchHandler(Handler): diff --git a/packages/fetchai/skills/simple_service_registration/skill.yaml b/packages/fetchai/skills/simple_service_registration/skill.yaml index 6e355becbf..9edbd7c606 100644 --- a/packages/fetchai/skills/simple_service_registration/skill.yaml +++ b/packages/fetchai/skills/simple_service_registration/skill.yaml @@ -1,6 +1,6 @@ name: simple_service_registration author: fetchai -version: 0.5.0 +version: 0.6.0 description: The simple service registration skills is a skill to register a service. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -8,12 +8,12 @@ fingerprint: __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta behaviours.py: QmRr1oe3zWKyPcktzKP4BiKqjCqmKjEDdLUQhn1JzNm4nD dialogues.py: QmayFh6ytPefJng5ENTUg46zsd6guHCZSsG3Cc2sy3xz6y - handlers.py: QmViyyV5KvR3kkLEMpvDfqH5QtHowTbnpDxRYnKABpVvpC + handlers.py: QmVahH8Ck5ukgR2kThaNR58DQYizC9CPp4aUzoe5Q6MHVy strategy.py: Qmdp6LCPZSnnyfM4EdRDTGZPqwxiJ3A1jsc3oF2Hv4m5Mv fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: service: diff --git a/packages/fetchai/skills/tac_control/behaviours.py b/packages/fetchai/skills/tac_control/behaviours.py index 789ba0b725..d6e2828f75 100644 --- a/packages/fetchai/skills/tac_control/behaviours.py +++ b/packages/fetchai/skills/tac_control/behaviours.py @@ -22,28 +22,26 @@ import datetime from typing import Optional, cast -from aea.helpers.search.models import Attribute, DataModel, Description +from aea.helpers.search.models import Description from aea.skills.base import Behaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage +from packages.fetchai.skills.tac_control.dialogues import ( + OefSearchDialogues, + TacDialogues, +) from packages.fetchai.skills.tac_control.game import Game, Phase from packages.fetchai.skills.tac_control.parameters import Parameters -CONTROLLER_DATAMODEL = DataModel( - "tac", - [Attribute("version", str, True, "Version number of the TAC Controller Agent.")], -) - -class TACBehaviour(Behaviour): +class TacBehaviour(Behaviour): """This class implements the TAC control behaviour.""" def __init__(self, **kwargs): """Instantiate the behaviour.""" super().__init__(**kwargs) - self._oef_msg_id = 0 - self._registered_desc = None # type: Optional[Description] + self._registered_description = None # type: Optional[Description] def setup(self) -> None: """ @@ -51,7 +49,7 @@ def setup(self) -> None: :return: None """ - pass + self._register_agent() def act(self) -> None: """ @@ -69,25 +67,23 @@ def act(self) -> None: game.phase = Phase.GAME_REGISTRATION self._register_tac() self.context.logger.info( - "[{}]: TAC open for registration until: {}".format( - self.context.agent_name, parameters.start_time - ) + "TAC open for registration until: {}".format(parameters.start_time) ) elif ( game.phase.value == Phase.GAME_REGISTRATION.value and parameters.start_time < now < parameters.end_time ): if game.registration.nb_agents < parameters.min_nb_agents: - self._cancel_tac() + self._cancel_tac(game) game.phase = Phase.POST_GAME self._unregister_tac() else: game.phase = Phase.GAME_SETUP - self._start_tac() + self._start_tac(game) self._unregister_tac() game.phase = Phase.GAME elif game.phase.value == Phase.GAME.value and now > parameters.end_time: - self._cancel_tac() + self._cancel_tac(game) game.phase = Phase.POST_GAME def teardown(self) -> None: @@ -96,8 +92,29 @@ def teardown(self) -> None: :return: None """ - if self._registered_desc is not None: - self._unregister_tac() + self._unregister_tac() + self._unregister_agent() + + def _register_agent(self) -> None: + """ + Register the agent's location. + + :return: None + """ + game = cast(Game, self.context.game) + description = game.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering agent on SOEF.") def _register_tac(self) -> None: """ @@ -105,22 +122,20 @@ def _register_tac(self) -> None: :return: None. """ - self._oef_msg_id += 1 - desc = Description( - {"version": self.context.parameters.version_id}, - data_model=CONTROLLER_DATAMODEL, - ) - self.context.logger.info( - "[{}]: Registering TAC data model".format(self.context.agent_name) + game = cast(Game, self.context.game) + description = game.get_register_tac_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues ) - oef_msg = OefSearchMessage( + oef_search_msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(self._oef_msg_id), ""), - service_description=desc, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) - self._registered_desc = desc + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering TAC data model on SOEF.") def _unregister_tac(self) -> None: """ @@ -128,78 +143,104 @@ def _unregister_tac(self) -> None: :return: None. """ - if self._registered_desc is not None: - self._oef_msg_id += 1 - self.context.logger.info( - "[{}]: Unregistering TAC data model".format(self.context.agent_name) - ) - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(self._oef_msg_id), ""), - service_description=self._registered_desc, - ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) - self._registered_desc = None + game = cast(Game, self.context.game) + description = game.get_unregister_tac_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self._registered_description = None + self.context.logger.info("unregistering TAC data model from SOEF.") - def _start_tac(self): - """Create a game and send the game configuration to every registered agent.""" + def _unregister_agent(self) -> None: + """ + Unregister agent from the SOEF. + + :return: None + """ game = cast(Game, self.context.game) + description = game.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogues.update(oef_search_msg) + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("unregistering agent from SOEF.") + + def _start_tac(self, game: Game): + """Create a game and send the game configuration to every registered agent.""" game.create() self.context.logger.info( - "[{}]: Started competition:\n{}".format( - self.context.agent_name, game.holdings_summary - ) + "started competition:\n{}".format(game.holdings_summary) ) self.context.logger.info( - "[{}]: Computed equilibrium:\n{}".format( - self.context.agent_name, game.equilibrium_summary - ) + "computed equilibrium:\n{}".format(game.equilibrium_summary) ) + tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) for agent_address in game.conf.agent_addr_to_name.keys(): + tac_dialogue = tac_dialogues.dialogue_by_address.get(agent_address, None) + assert tac_dialogue is not None, "Error when retrieving dialogue." + last_msg = tac_dialogue.last_message + assert last_msg is not None, "Error when retrieving last message." agent_state = game.current_agent_states[agent_address] tac_msg = TacMessage( performative=TacMessage.Performative.GAME_DATA, + dialogue_reference=tac_dialogue.dialogue_label.dialogue_reference, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, amount_by_currency_id=agent_state.amount_by_currency_id, exchange_params_by_currency_id=agent_state.exchange_params_by_currency_id, quantities_by_good_id=agent_state.quantities_by_good_id, utility_params_by_good_id=agent_state.utility_params_by_good_id, - tx_fee=game.conf.tx_fee, + fee_by_currency_id=game.conf.fee_by_currency_id, currency_id_to_name=game.conf.currency_id_to_name, agent_addr_to_name=game.conf.agent_addr_to_name, good_id_to_name=game.conf.good_id_to_name, version_id=game.conf.version_id, ) - self.context.logger.debug( - "[{}]: sending game data to '{}': {}".format( - self.context.agent_name, agent_address, str(tac_msg) - ) - ) tac_msg.counterparty = agent_address + tac_dialogues.update(tac_msg) self.context.outbox.put_message(message=tac_msg) + self.context.logger.debug( + "sending game data to '{}': {}".format(agent_address, str(tac_msg)) + ) - def _cancel_tac(self): + def _cancel_tac(self, game: Game): """Notify agents that the TAC is cancelled.""" - game = cast(Game, self.context.game) - self.context.logger.info( - "[{}]: Notifying agents that TAC is cancelled.".format( - self.context.agent_name + self.context.logger.info("notifying agents that TAC is cancelled.") + tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) + for agent_address in game.registration.agent_addr_to_name.keys(): + tac_dialogue = tac_dialogues.dialogue_by_address.get(agent_address, None) + assert tac_dialogue is not None, "Error when retrieving dialogue." + last_msg = tac_dialogue.last_message + assert last_msg is not None, "Error when retrieving last message." + tac_msg = TacMessage( + performative=TacMessage.Performative.CANCELLED, + dialogue_reference=tac_dialogue.dialogue_label.dialogue_reference, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, ) - ) - for agent_addr in game.registration.agent_addr_to_name.keys(): - tac_msg = TacMessage(performative=TacMessage.Performative.CANCELLED) - tac_msg.counterparty = agent_addr + tac_msg.counterparty = agent_address + tac_dialogues.update(tac_msg) self.context.outbox.put_message(message=tac_msg) if game.phase == Phase.GAME: self.context.logger.info( - "[{}]: Finished competition:\n{}".format( - self.context.agent_name, game.holdings_summary - ) + "finished competition:\n{}".format(game.holdings_summary) ) self.context.logger.info( - "[{}]: Computed equilibrium:\n{}".format( - self.context.agent_name, game.equilibrium_summary - ) + "computed equilibrium:\n{}".format(game.equilibrium_summary) ) - self.context.is_active = False diff --git a/packages/fetchai/skills/tac_control/dialogues.py b/packages/fetchai/skills/tac_control/dialogues.py new file mode 100644 index 0000000000..646c15e498 --- /dev/null +++ b/packages/fetchai/skills/tac_control/dialogues.py @@ -0,0 +1,178 @@ +# -*- 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. + +- DefaultDialogue: The dialogue class maintains state of a dialogue of type default and manages it. +- DefaultDialogues: The dialogues class keeps track of all dialogues of type default. +- OefSearchDialogue: The dialogue class maintains state of a dialogue of type oef_search and manages it. +- OefSearchDialogues: The dialogues class keeps track of all dialogues of type oef_search. +- TacDialogue: The dialogue class maintains state of a dialogue of type tac and manages it. +- TacDialogues: The dialogues class keeps track of all dialogues of type tac. +""" + +from typing import Dict + +from aea.helpers.dialogue.base import Dialogue +from aea.helpers.dialogue.base import DialogueLabel +from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.skills.base import Model + +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue as BaseOefSearchDialogue, +) +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogues as BaseOefSearchDialogues, +) +from packages.fetchai.protocols.tac.dialogues import TacDialogue as BaseTacDialogue +from packages.fetchai.protocols.tac.dialogues import TacDialogues as BaseTacDialogues + + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseDefaultDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +OefSearchDialogue = BaseOefSearchDialogue + + +class OefSearchDialogues(Model, BaseOefSearchDialogues): + """This class keeps track of all oef_search dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseOefSearchDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseOefSearchDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> OefSearchDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = OefSearchDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +TacDialogue = BaseTacDialogue + + +class TacDialogues(Model, BaseTacDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseTacDialogues.__init__(self, self.context.agent_address) + self._dialogue_by_address = {} # type: Dict[str, Dialogue] + + @property + def dialogue_by_address(self) -> Dict[str, Dialogue]: + """Get the dialogue by address.""" + return self._dialogue_by_address + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return TacDialogue.Role.CONTROLLER + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> TacDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = TacDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + self._dialogue_by_address[dialogue_label.dialogue_opponent_addr] = dialogue + return dialogue diff --git a/packages/fetchai/skills/tac_control/game.py b/packages/fetchai/skills/tac_control/game.py index 620ca8a16c..c3b52e283d 100644 --- a/packages/fetchai/skills/tac_control/game.py +++ b/packages/fetchai/skills/tac_control/game.py @@ -25,16 +25,18 @@ from enum import Enum from typing import Dict, List, Optional, cast -from eth_account.messages import encode_defunct - -from hexbytes import HexBytes - -from web3 import Web3 - +from aea.crypto.ledger_apis import LedgerApis from aea.helpers.preference_representations.base import ( linear_utility, logarithmic_utility, ) +from aea.helpers.search.generic import ( + AGENT_LOCATION_MODEL, + AGENT_REMOVE_SERVICE_MODEL, + AGENT_SET_SERVICE_MODEL, +) +from aea.helpers.search.models import Description +from aea.helpers.transaction.base import Terms from aea.mail.base import Address from aea.skills.base import Model @@ -48,7 +50,6 @@ generate_good_endowments, generate_good_id_to_name, generate_utility_params, - tx_hash_from_values, ) from packages.fetchai.skills.tac_control.parameters import Parameters @@ -107,9 +108,9 @@ def version_id(self) -> str: return self._version_id @property - def tx_fee(self) -> int: + def fee_by_currency_id(self) -> Dict[str, int]: """Transaction fee for the TAC instance.""" - return self._tx_fee + return {next(iter(self.currency_id_to_name.keys())): self._tx_fee} @property def agent_addr_to_name(self) -> Dict[Address, str]: @@ -134,7 +135,7 @@ def _check_consistency(self): :raises: AssertionError: if some constraint is not satisfied. """ assert self.version_id is not None, "A version id must be set." - assert self.tx_fee >= 0, "Tx fee must be non-negative." + assert self._tx_fee >= 0, "Tx fee must be non-negative." assert len(self.agent_addr_to_name) >= 2, "Must have at least two agents." assert len(self.good_id_to_name) >= 2, "Must have at least two goods." assert len(self.currency_id_to_name) == 1, "Must have exactly one currency." @@ -262,88 +263,50 @@ def _check_consistency(self): ), "Dimensions for utility_params and good_endowments rows must be the same." -class Transaction: +class Transaction(Terms): """Convenience representation of a transaction.""" def __init__( self, - transaction_id: TransactionId, - sender_addr: Address, - counterparty_addr: Address, + ledger_id: str, + sender_address: Address, + counterparty_address: Address, amount_by_currency_id: Dict[str, int], - sender_fee: int, - counterparty_fee: int, quantities_by_good_id: Dict[str, int], - nonce: int, + is_sender_payable_tx_fee: bool, + nonce: str, + fee_by_currency_id: Optional[Dict[str, int]], sender_signature: str, counterparty_signature: str, ) -> None: """ - Instantiate transaction request. - - :param transaction_id: the id of the transaction. - :param sender_addr: the sender of the transaction. - :param tx_counterparty_addr: the counterparty of the transaction. - :param amount_by_currency_id: the currency used. - :param sender_fee: the transaction fee covered by the sender. - :param counterparty_fee: the transaction fee covered by the counterparty. - :param quantities_by_good_id: a map from good pbk to the quantity of that good involved in the transaction. - :param nonce: the nonce of the transaction - :param sender_signature: the signature of the transaction sender - :param counterparty_signature: the signature of the transaction counterparty - :return: None - """ - self._id = transaction_id - self._sender_addr = sender_addr - self._counterparty_addr = counterparty_addr - self._amount_by_currency_id = amount_by_currency_id - self._sender_fee = sender_fee - self._counterparty_fee = counterparty_fee - self._quantities_by_good_id = quantities_by_good_id - self._nonce = nonce + Instantiate transaction. + + This extends a terms object to be used as a transaction. + + :param ledger_id: the ledger on which the terms are to be settled. + :param sender_address: the sender address of the transaction. + :param counterparty_address: the counterparty address of the transaction. + :param amount_by_currency_id: the amount by the currency of the transaction. + :param quantities_by_good_id: a map from good id to the quantity of that good involved in the transaction. + :param is_sender_payable_tx_fee: whether the sender or counterparty pays the tx fee. + :param nonce: nonce to be included in transaction to discriminate otherwise identical transactions. + :param fee_by_currency_id: the fee associated with the transaction. + :param sender_signature: the signature of the terms by the sender. + :param counterparty_signature: the signature of the terms by the counterparty. + """ + super().__init__( + ledger_id=ledger_id, + sender_address=sender_address, + counterparty_address=counterparty_address, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + fee_by_currency_id=fee_by_currency_id, + ) self._sender_signature = sender_signature self._counterparty_signature = counterparty_signature - self._check_consistency() - - @property - def id(self) -> str: - """Get the transaction id.""" - return self._id - - @property - def sender_addr(self) -> Address: - """Get the sender address.""" - return self._sender_addr - - @property - def counterparty_addr(self) -> Address: - """Get the counterparty address.""" - return self._counterparty_addr - - @property - def amount_by_currency_id(self) -> Dict[CurrencyId, Quantity]: - """Get the amount for each currency.""" - return copy.copy(self._amount_by_currency_id) - - @property - def sender_fee(self) -> int: - """Get the sender fee.""" - return self._sender_fee - - @property - def counterparty_fee(self) -> int: - """Get the counterparty fee.""" - return self._counterparty_fee - - @property - def quantities_by_good_id(self) -> Dict[GoodId, Quantity]: - """Get the quantities by good_id.""" - return copy.copy(self._quantities_by_good_id) - - @property - def nonce(self) -> int: - """Get the nonce of the transaction.""" - return self._nonce @property def sender_signature(self) -> str: @@ -355,119 +318,31 @@ def counterparty_signature(self) -> str: """Get the counterparty signature.""" return self._counterparty_signature - @property - def is_sender_buyer(self) -> bool: - """Get the sender is buyer status.""" - return all(value <= 0 for value in self.amount_by_currency_id.values()) - - @property - def buyer_addr(self) -> Address: - """Get the buyer address.""" - return self._sender_addr if self.is_sender_buyer else self._counterparty_addr - - @property - def sender_hash(self) -> bytes: - """Get the sender hash.""" - return tx_hash_from_values( - tx_sender_addr=self.sender_addr, - tx_counterparty_addr=self.counterparty_addr, - tx_quantities_by_good_id=self.quantities_by_good_id, - tx_amount_by_currency_id=self.amount_by_currency_id, - tx_nonce=self.nonce, - ) - - @property - def counterparty_hash(self) -> bytes: - """Get the sender hash.""" - return tx_hash_from_values( - tx_sender_addr=self.counterparty_addr, - tx_counterparty_addr=self.sender_addr, - tx_quantities_by_good_id={ - good_id: -quantity - for good_id, quantity in self.quantities_by_good_id.items() - }, - tx_amount_by_currency_id={ - currency_id: -amount - for currency_id, amount in self.amount_by_currency_id.items() - }, - tx_nonce=self.nonce, - ) - - @property - def amount(self) -> int: - """Get the amount.""" - return list(self.amount_by_currency_id.values())[0] - - @property - def currency_id(self) -> str: - """Get the currency id.""" - return list(self.amount_by_currency_id.keys())[0] - - @property - def sender_amount(self) -> int: - """Get the amount the sender gets/pays.""" - return self.amount - self.sender_fee - - @property - def counterparty_amount(self) -> int: - """Get the amount the counterparty gets/pays.""" - return -self.amount - self.counterparty_fee - - 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_addr != self.counterparty_addr - assert ( - len(self.amount_by_currency_id.keys()) == 1 - ) # For now we restrict to one currency per transaction. - assert self.sender_fee >= 0 - assert self.counterparty_fee >= 0 - assert ( - self.amount >= 0 - and all(quantity <= 0 for quantity in self.quantities_by_good_id.values()) - ) or ( - self.amount <= 0 - and all(quantity >= 0 for quantity in self.quantities_by_good_id.values()) - ) - assert isinstance(self.sender_signature, str) and isinstance( - self.counterparty_signature, str - ) - if self.amount >= 0: - assert ( - self.sender_amount >= 0 - ), "Sender_amount must be positive when the sender is the payment receiver." - else: - assert ( - self.counterparty_amount >= 0 - ), "Counterparty_amount must be positive when the counterpary is the payment receiver." - def has_matching_signatures(self) -> bool: """ Check that the signatures match the terms of trade. :return: True if the transaction has been signed by both parties """ - w3 = Web3() - singable_message = encode_defunct(primitive=self.sender_hash) + + # singable_message = LedgerApis.sign_message(self.sender_hash) result = ( - w3.eth.account.recover_message( # pylint: disable=no-member - signable_message=singable_message, - signature=HexBytes(self.sender_signature), + self.sender_address + in LedgerApis.recover_message( # pylint: disable=no-member + identifier=self.ledger_id, + message=self.sender_hash.encode("utf-8"), + signature=self.sender_signature, ) - == self.sender_addr ) - counterparty_signable_message = encode_defunct(primitive=self.counterparty_hash) + # counterparty_signable_message = LedgerApis.sign_message(self.counterparty_hash) result = ( result - and w3.eth.account.recover_message( # pylint: disable=no-member - signable_message=counterparty_signable_message, - signature=HexBytes(self.counterparty_signature), + and self.counterparty_address + in LedgerApis.recover_message( # pylint: disable=no-member + identifier=self.ledger_id, + message=self.counterparty_hash.encode("utf-8"), + signature=self.counterparty_signature, ) - == self.counterparty_addr ) return result @@ -479,32 +354,31 @@ def from_message(cls, message: TacMessage) -> "Transaction": :param message: the message :return: Transaction """ - assert message.performative == TacMessage.Performative.TRANSACTION - return Transaction( - message.tx_id, - message.tx_sender_addr, - message.tx_counterparty_addr, - message.amount_by_currency_id, - message.tx_sender_fee, - message.tx_counterparty_fee, - message.quantities_by_good_id, - message.tx_nonce, - message.tx_sender_signature, - message.tx_counterparty_signature, + assert ( + message.performative == TacMessage.Performative.TRANSACTION + ), "Wrong performative" + transaction = Transaction( + ledger_id=message.ledger_id, + sender_address=message.sender_address, + counterparty_address=message.counterparty_address, + amount_by_currency_id=message.amount_by_currency_id, + fee_by_currency_id=message.fee_by_currency_id, + quantities_by_good_id=message.quantities_by_good_id, + is_sender_payable_tx_fee=True, # TODO: check + nonce=str(message.nonce), + sender_signature=message.sender_signature, + counterparty_signature=message.counterparty_signature, ) + assert ( + transaction.id == message.transaction_id + ), "Transaction content does not match hash." + return transaction def __eq__(self, other): """Compare to another object.""" return ( isinstance(other, Transaction) - and self.id == other.id - and self.sender_addr == other.sender_addr - and self.counterparty_addr == other.counterparty_addr - and self.amount_by_currency_id == other.amount_by_currency_id - and self.sender_fee == other.sender_fee - and self.counterparty_fee == other.counterparty_fee - and self.quantities_by_good_id == other.quantities_by_good_id - and self.nonce == other.nonce + and super.__eq__() and self.sender_signature == other.sender_signature and self.counterparty_signature == other.counterparty_signature ) @@ -592,42 +466,47 @@ def is_consistent_transaction(self, tx: Transaction) -> bool: or enough holdings if it is a seller. :return: True if the transaction is legal wrt the current state, False otherwise. """ - result = self.agent_address in [tx.sender_addr, tx.counterparty_addr] - if tx.amount == 0 and all( + result = self.agent_address in [tx.sender_address, tx.counterparty_address] + result = result and tx.is_single_currency + if not result: + return result + if all(amount == 0 for amount in tx.amount_by_currency_id.values()) and all( quantity == 0 for quantity in tx.quantities_by_good_id.values() ): # reject the transaction when there is no wealth exchange result = False - elif tx.amount <= 0 and all( + elif all(amount <= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity >= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is buyer, counterparty is seller - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # check this sender state has enough money result = result and ( - self.amount_by_currency_id[tx.currency_id] >= tx.sender_amount + self.amount_by_currency_id[tx.currency_id] + >= tx.sender_payable_amount ) - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= quantity for good_id, quantity in tx.quantities_by_good_id.items() ) - elif tx.amount >= 0 and all( + elif all(amount >= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity <= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is seller, counterparty is buyer # Note, on a ledger, this atomic swap would only be possible for amount == 0! - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # check this sender state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= -quantity for good_id, quantity in tx.quantities_by_good_id.items() ) - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough money result = result and ( - self.amount_by_currency_id[tx.currency_id] >= tx.counterparty_amount + self.amount_by_currency_id[tx.currency_id] + >= tx.counterparty_payable_amount ) else: result = False @@ -656,20 +535,22 @@ def update(self, tx: Transaction) -> None: assert self.is_consistent_transaction(tx), "Inconsistent transaction." new_amount_by_currency_id = self.amount_by_currency_id - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # settling the transaction for the sender - new_amount_by_currency_id[tx.currency_id] += tx.sender_amount - elif self.agent_address == tx.counterparty_addr: + for currency_id, amount in tx.amount_by_currency_id.items(): + new_amount_by_currency_id[currency_id] += amount + elif self.agent_address == tx.counterparty_address: # settling the transaction for the counterparty - new_amount_by_currency_id[tx.currency_id] += tx.counterparty_amount + for currency_id, amount in tx.amount_by_currency_id.items(): + new_amount_by_currency_id[currency_id] += amount self._amount_by_currency_id = new_amount_by_currency_id new_quantities_by_good_id = self.quantities_by_good_id for good_id, quantity in tx.quantities_by_good_id.items(): - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: new_quantities_by_good_id[good_id] += quantity - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: new_quantities_by_good_id[good_id] -= quantity self._quantities_by_good_id = new_quantities_by_good_id @@ -741,12 +622,12 @@ def add(self, transaction: Transaction) -> None: """ now = datetime.datetime.now() self._confirmed[now] = transaction - if self._confirmed_per_agent.get(transaction.sender_addr) is None: - self._confirmed_per_agent[transaction.sender_addr] = {} - self._confirmed_per_agent[transaction.sender_addr][now] = transaction - if self._confirmed_per_agent.get(transaction.counterparty_addr) is None: - self._confirmed_per_agent[transaction.counterparty_addr] = {} - self._confirmed_per_agent[transaction.counterparty_addr][now] = transaction + if self._confirmed_per_agent.get(transaction.sender_address) is None: + self._confirmed_per_agent[transaction.sender_address] = {} + self._confirmed_per_agent[transaction.sender_address][now] = transaction + if self._confirmed_per_agent.get(transaction.counterparty_address) is None: + self._confirmed_per_agent[transaction.counterparty_address] = {} + self._confirmed_per_agent[transaction.counterparty_address][now] = transaction class Registration: @@ -1020,8 +901,8 @@ def is_transaction_valid(self, tx: Transaction) -> bool: :return: True if the transaction is valid, False otherwise. :raises: AssertionError: if the data in the transaction are not allowed (e.g. negative amount). """ - sender_state = self.current_agent_states[tx.sender_addr] - counterparty_state = self.current_agent_states[tx.counterparty_addr] + sender_state = self.current_agent_states[tx.sender_address] + counterparty_state = self.current_agent_states[tx.counterparty_address] result = tx.has_matching_signatures() result = result and sender_state.is_consistent_transaction(tx) result = result and counterparty_state.is_consistent_transaction(tx) @@ -1039,14 +920,41 @@ def settle_transaction(self, tx: Transaction) -> None: self._current_agent_states is not None ), "Call create before calling current_agent_states." assert self.is_transaction_valid(tx), "Transaction is not valid." - sender_state = self.current_agent_states[tx.sender_addr] - counterparty_state = self.current_agent_states[tx.counterparty_addr] + sender_state = self.current_agent_states[tx.sender_address] + counterparty_state = self.current_agent_states[tx.counterparty_address] new_sender_state = sender_state.apply([tx]) new_counterparty_state = counterparty_state.apply([tx]) self.transactions.add(tx) - self._current_agent_states.update({tx.sender_addr: new_sender_state}) + self._current_agent_states.update({tx.sender_address: new_sender_state}) self._current_agent_states.update( - {tx.counterparty_addr: new_counterparty_state} + {tx.counterparty_address: new_counterparty_state} + ) + + def get_location_description(self) -> Description: + """ + Get the location description. + + :return: a description of the agent's location + """ + description = Description( + self.context.parameters.agent_location, data_model=AGENT_LOCATION_MODEL, + ) + return description + + def get_register_tac_description(self) -> Description: + """Get the tac description for registering.""" + description = Description( + self.context.parameters.set_service_data, + data_model=AGENT_SET_SERVICE_MODEL, + ) + return description + + def get_unregister_tac_description(self) -> Description: + """Get the tac description for unregistering.""" + description = Description( + self.context.parameters.remove_service_data, + data_model=AGENT_REMOVE_SERVICE_MODEL, ) + return description diff --git a/packages/fetchai/skills/tac_control/handlers.py b/packages/fetchai/skills/tac_control/handlers.py index 7d55057a03..64f82deb66 100644 --- a/packages/fetchai/skills/tac_control/handlers.py +++ b/packages/fetchai/skills/tac_control/handlers.py @@ -19,18 +19,26 @@ """This package contains the handlers.""" -from typing import cast +from typing import Optional, cast from aea.protocols.base import Message +from aea.protocols.default.message import DefaultMessage from aea.skills.base import Handler from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.tac.message import TacMessage +from packages.fetchai.skills.tac_control.dialogues import ( + DefaultDialogues, + OefSearchDialogue, + OefSearchDialogues, + TacDialogue, + TacDialogues, +) from packages.fetchai.skills.tac_control.game import Game, Phase, Transaction from packages.fetchai.skills.tac_control.parameters import Parameters -class TACHandler(Handler): +class TacHandler(Handler): """This class handles oef messages.""" SUPPORTED_PROTOCOL = TacMessage.protocol_id @@ -52,150 +60,188 @@ def handle(self, message: Message) -> None: :param message: the 'get agent state' TacMessage. :return: None """ - tac_message = cast(TacMessage, message) + tac_msg = cast(TacMessage, message) - game = cast(Game, self.context.game) + # recover dialogue + tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) + tac_dialogue = cast(TacDialogue, tac_dialogues.update(tac_msg)) + if tac_dialogue is None: + self._handle_unidentified_dialogue(tac_msg) + return self.context.logger.debug( - "[{}]: Handling TAC message. performative={}".format( - self.context.agent_name, tac_message.performative - ) + "handling TAC message. performative={}".format(tac_msg.performative) ) - if ( - tac_message.performative == TacMessage.Performative.REGISTER - and game.phase == Phase.GAME_REGISTRATION - ): - self._on_register(tac_message) - elif ( - tac_message.performative == TacMessage.Performative.UNREGISTER - and game.phase == Phase.GAME_REGISTRATION - ): - self._on_unregister(tac_message) - elif ( - tac_message.performative == TacMessage.Performative.TRANSACTION - and game.phase == Phase.GAME - ): - self._on_transaction(tac_message) + if tac_msg.performative == TacMessage.Performative.REGISTER: + self._on_register(tac_msg, tac_dialogue) + elif tac_msg.performative == TacMessage.Performative.UNREGISTER: + self._on_unregister(tac_msg, tac_dialogue) + elif tac_msg.performative == TacMessage.Performative.TRANSACTION: + self._on_transaction(tac_msg, tac_dialogue) else: + self._handle_invalid(tac_msg, tac_dialogue) + self.context.logger.warning( - "[{}]: TAC Message performative not recognized or not permitted.".format( - self.context.agent_name - ) + "TAC Message performative not recognized or not permitted." ) - def _on_register(self, message: TacMessage) -> None: + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, tac_msg: TacMessage) -> None: + """ + Handle an unidentified dialogue. + + :param tac_msg: the message + """ + self.context.logger.info( + "received invalid tac message={}, unidentified dialogue.".format(tac_msg) + ) + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) + default_msg = DefaultMessage( + performative=DefaultMessage.Performative.ERROR, + dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), + error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, + error_msg="Invalid dialogue.", + error_data={"tac_message": tac_msg.encode()}, + ) + default_msg.counterparty = tac_msg.counterparty + default_dialogues.update(default_msg) + self.context.outbox.put_message(message=default_msg) + + def _on_register(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a register message. If the address is not registered, answer with an error message. - :param message: the 'get agent state' TacMessage. + :param tac_msg: the tac message + :param tac_dialogue: the tac dialogue :return: None """ + game = cast(Game, self.context.game) + if not game.phase == Phase.GAME_REGISTRATION: + self.context.logger.warning( + "received registration outside of game registration phase: '{}'".format( + tac_msg + ) + ) + return + parameters = cast(Parameters, self.context.parameters) - agent_name = message.agent_name + agent_name = tac_msg.agent_name if len(parameters.whitelist) != 0 and agent_name not in parameters.whitelist: self.context.logger.warning( - "[{}]: Agent name not in whitelist: '{}'".format( - self.context.agent_name, agent_name - ) + "agent name not in whitelist: '{}'".format(agent_name) ) - tac_msg = TacMessage( + error_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.AGENT_NAME_NOT_IN_WHITELIST, ) - tac_msg.counterparty = message.counterparty - self.context.outbox.put_message(message=tac_msg) + error_msg.counterparty = tac_msg.counterparty + self.context.outbox.put_message(message=error_msg) return game = cast(Game, self.context.game) - if message.counterparty in game.registration.agent_addr_to_name: + if tac_msg.counterparty in game.registration.agent_addr_to_name: self.context.logger.warning( - "[{}]: Agent already registered: '{}'".format( - self.context.agent_name, - game.registration.agent_addr_to_name[message.counterparty], + "agent already registered: '{}'".format( + game.registration.agent_addr_to_name[tac_msg.counterparty], ) ) - tac_msg = TacMessage( + error_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.AGENT_ADDR_ALREADY_REGISTERED, ) - tac_msg.counterparty = message.counterparty - self.context.outbox.put_message(message=tac_msg) + error_msg.counterparty = tac_msg.counterparty + self.context.outbox.put_message(message=error_msg) + return if agent_name in game.registration.agent_addr_to_name.values(): self.context.logger.warning( - "[{}]: Agent with this name already registered: '{}'".format( - self.context.agent_name, agent_name - ) + "agent with this name already registered: '{}'".format(agent_name) ) - tac_msg = TacMessage( + error_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.AGENT_NAME_ALREADY_REGISTERED, ) - tac_msg.counterparty = message.counterparty - self.context.outbox.put_message(message=tac_msg) + error_msg.counterparty = tac_msg.counterparty + self.context.outbox.put_message(message=error_msg) + return - game.registration.register_agent(message.counterparty, agent_name) - self.context.logger.info( - "[{}]: Agent registered: '{}'".format(self.context.agent_name, agent_name) - ) + game.registration.register_agent(tac_msg.counterparty, agent_name) + self.context.logger.info("agent registered: '{}'".format(agent_name)) - def _on_unregister(self, message: TacMessage) -> None: + def _on_unregister(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ Handle a unregister message. If the address is not registered, answer with an error message. - :param message: the 'get agent state' TacMessage. + :param tac_msg: the tac message + :param tac_dialogue: the tac dialogue :return: None """ game = cast(Game, self.context.game) - if message.counterparty not in game.registration.agent_addr_to_name: + if not game.phase == Phase.GAME_REGISTRATION: self.context.logger.warning( - "[{}]: Agent not registered: '{}'".format( - self.context.agent_name, message.counterparty + "received unregister outside of game registration phase: '{}'".format( + tac_msg ) ) - tac_msg = TacMessage( + return + + if tac_msg.counterparty not in game.registration.agent_addr_to_name: + self.context.logger.warning( + "agent not registered: '{}'".format(tac_msg.counterparty) + ) + error_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.AGENT_NOT_REGISTERED, ) - tac_msg.counterparty = message.counterparty - self.context.outbox.put_message(message=tac_msg) + error_msg.counterparty = tac_msg.counterparty + self.context.outbox.put_message(message=error_msg) else: self.context.logger.debug( - "[{}]: Agent unregistered: '{}'".format( - self.context.agent_name, - game.conf.agent_addr_to_name[message.counterparty], + "agent unregistered: '{}'".format( + game.conf.agent_addr_to_name[tac_msg.counterparty], ) ) - game.registration.unregister_agent(message.counterparty) + game.registration.unregister_agent(tac_msg.counterparty) - def _on_transaction(self, message: TacMessage) -> None: + def _on_transaction(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> 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 tac_msg: the tac message + :param tac_dialogue: the tac dialogue :return: None """ - transaction = Transaction.from_message(message) - self.context.logger.debug( - "[{}]: Handling transaction: {}".format( - self.context.agent_name, transaction + game = cast(Game, self.context.game) + if not game.phase == Phase.GAME: + self.context.logger.warning( + "received transaction outside of game phase: '{}'".format(tac_msg) ) - ) + return + + transaction = Transaction.from_message(tac_msg) + self.context.logger.debug("handling transaction: {}".format(transaction)) game = cast(Game, self.context.game) if game.is_transaction_valid(transaction): - self._handle_valid_transaction(message, transaction) + self._handle_valid_transaction(tac_msg, transaction) else: - self._handle_invalid_transaction(message) + self._handle_invalid_transaction(tac_msg) def _handle_valid_transaction( - self, message: TacMessage, transaction: Transaction + self, tac_msg: TacMessage, transaction: Transaction ) -> None: """ Handle a valid transaction. @@ -209,9 +255,7 @@ def _handle_valid_transaction( """ game = cast(Game, self.context.game) self.context.logger.info( - "[{}]: Handling valid transaction: {}".format( - self.context.agent_name, transaction.id[-10:] - ) + "handling valid transaction: {}".format(transaction.id[-10:]) ) game.settle_transaction(transaction) @@ -229,49 +273,46 @@ def _handle_valid_transaction( amount_by_currency_id=transaction.amount_by_currency_id, quantities_by_good_id=transaction.quantities_by_good_id, ) - sender_tac_msg.counterparty = transaction.sender_addr + sender_tac_msg.counterparty = transaction.sender_address self.context.outbox.put_message(message=sender_tac_msg) - counterparty_tac_msg.counterparty = transaction.counterparty_addr + counterparty_tac_msg.counterparty = transaction.counterparty_address self.context.outbox.put_message(message=counterparty_tac_msg) # log messages self.context.logger.info( - "[{}]: Transaction '{}' settled successfully.".format( - self.context.agent_name, transaction.id[-10:] - ) - ) - self.context.logger.info( - "[{}]: Current state:\n{}".format( - self.context.agent_name, game.holdings_summary - ) + "transaction '{}' settled successfully.".format(transaction.id[-10:]) ) + self.context.logger.info("current state:\n{}".format(game.holdings_summary)) - def _handle_invalid_transaction(self, message: TacMessage) -> None: + def _handle_invalid_transaction(self, tac_msg: TacMessage) -> None: """Handle an invalid transaction.""" - tx_id = message.tx_id[-10:] self.context.logger.info( - "[{}]: Handling invalid transaction: {}".format( - self.context.agent_name, tx_id - ) + "handling invalid transaction: {}".format(tac_msg.transaction_id) ) tac_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, error_code=TacMessage.ErrorCode.TRANSACTION_NOT_VALID, - info={"transaction_id": message.tx_id}, + info={"transaction_id": tac_msg.transaction_id}, ) - tac_msg.counterparty = message.counterparty + tac_msg.counterparty = tac_msg.counterparty self.context.outbox.put_message(message=tac_msg) - def teardown(self) -> None: + def _handle_invalid(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ - Implement the handler teardown. + Handle a tac message of invalid performative. + :param tac_msg: the message + :param tac_dialogue: the fipa dialogue :return: None """ - pass + self.context.logger.warning( + "cannot handle tac message of performative={} in dialogue={}.".format( + tac_msg.performative, tac_dialogue + ) + ) -class OEFRegistrationHandler(Handler): +class OefSearchHandler(Handler): """Handle the message exchange with the OEF search node.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id @@ -291,40 +332,73 @@ def handle(self, message: Message) -> None: :param message: the message :return: None """ - oef_message = cast(OefSearchMessage, message) + oef_search_msg = cast(OefSearchMessage, message) - self.context.logger.debug( - "[{}]: Handling OEF message. performative={}".format( - self.context.agent_name, oef_message.performative - ) + # recover dialogue + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) ) - if oef_message.performative == OefSearchMessage.Performative.OEF_ERROR: - self._on_oef_error(oef_message) + if oef_search_dialogue is None: + self._handle_unidentified_dialogue(oef_search_msg) + return + + # handle message + if oef_search_msg.performative is OefSearchMessage.Performative.OEF_ERROR: + self._handle_error(oef_search_msg, oef_search_dialogue) else: - self.context.logger.warning( - "[{}]: OEF Message type not recognized.".format(self.context.agent_name) - ) + self._handle_invalid(oef_search_msg, oef_search_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass - def _on_oef_error(self, oef_error: OefSearchMessage) -> None: + def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: """ - Handle an OEF error message. + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.info( + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg + ) + ) - :param oef_error: the oef error + def _handle_error( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue :return: None """ - self.context.logger.warning( - "[{}]: Received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( - self.context.agent_name, - oef_error.dialogue_reference, - oef_error.oef_error_operation, + self.context.logger.info( + "received oef_search error message={} in dialogue={}.".format( + oef_search_msg, oef_search_dialogue ) ) - def teardown(self) -> None: + def _handle_invalid( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: """ - Implement the handler teardown. + Handle an oef search message. + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue :return: None """ - pass + self.context.logger.warning( + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, + ) + ) diff --git a/packages/fetchai/skills/tac_control/helpers.py b/packages/fetchai/skills/tac_control/helpers.py index 2fa07b57bc..8758272cf3 100644 --- a/packages/fetchai/skills/tac_control/helpers.py +++ b/packages/fetchai/skills/tac_control/helpers.py @@ -20,17 +20,12 @@ """This module contains the helpers methods for the controller agent.""" -import collections import math import random from typing import Dict, List, Tuple, cast import numpy as np -from web3 import Web3 # pylint: disable=wrong-import-order - -from aea.mail.base import Address - QUANTITY_SHIFT = 1 # Any non-negative integer is fine. DEFAULT_CURRENCY_ID_TO_NAME = {"0": "FET"} @@ -282,98 +277,3 @@ def generate_equilibrium_prices_and_holdings( ) } return eq_prices_dict, eq_good_holdings_dict, eq_currency_holdings_dict - - -def _get_hash( - tx_sender_addr: Address, - tx_counterparty_addr: Address, - good_ids: List[int], - sender_supplied_quantities: List[int], - counterparty_supplied_quantities: List[int], - tx_amount: int, - tx_nonce: int, -) -> bytes: - """ - Generate a hash from transaction information. - - :param tx_sender_addr: the sender address - :param tx_counterparty_addr: the counterparty address - :param good_ids: the list of good ids - :param sender_supplied_quantities: the quantities supplied by the sender (must all be positive) - :param counterparty_supplied_quantities: the quantities supplied by the counterparty (must all be positive) - :param tx_amount: the amount of the transaction - :param tx_nonce: the nonce of the transaction - :return: the hash - """ - aggregate_hash = Web3.keccak( - b"".join( - [ - good_ids[0].to_bytes(32, "big"), - sender_supplied_quantities[0].to_bytes(32, "big"), - counterparty_supplied_quantities[0].to_bytes(32, "big"), - ] - ) - ) - for idx, good_id in enumerate(good_ids): - if not idx == 0: - aggregate_hash = Web3.keccak( - b"".join( - [ - aggregate_hash, - good_id.to_bytes(32, "big"), - sender_supplied_quantities[idx].to_bytes(32, "big"), - counterparty_supplied_quantities[idx].to_bytes(32, "big"), - ] - ) - ) - - m_list = [] # type: List[bytes] - m_list.append(tx_sender_addr.encode("utf-8")) - m_list.append(tx_counterparty_addr.encode("utf-8")) - m_list.append(aggregate_hash) - m_list.append(tx_amount.to_bytes(32, "big")) - m_list.append(tx_nonce.to_bytes(32, "big")) - return Web3.keccak(b"".join(m_list)) - - -def tx_hash_from_values( - tx_sender_addr: str, - tx_counterparty_addr: str, - tx_quantities_by_good_id: Dict[str, int], - tx_amount_by_currency_id: Dict[str, int], - tx_nonce: int, -) -> bytes: - """ - Get the hash for a transaction based on the transaction message. - - :param tx_message: the transaction message - :return: the hash - """ - _tx_quantities_by_good_id = { - int(good_id): quantity for good_id, quantity in tx_quantities_by_good_id.items() - } # type: Dict[int, int] - ordered = collections.OrderedDict(sorted(_tx_quantities_by_good_id.items())) - good_uids = [] # type: List[int] - sender_supplied_quantities = [] # type: List[int] - counterparty_supplied_quantities = [] # type: List[int] - for good_uid, quantity in ordered.items(): - good_uids.append(good_uid) - if quantity >= 0: - sender_supplied_quantities.append(quantity) - counterparty_supplied_quantities.append(0) - else: - sender_supplied_quantities.append(0) - counterparty_supplied_quantities.append(-quantity) - assert len(tx_amount_by_currency_id) == 1 - for amount in tx_amount_by_currency_id.values(): - tx_amount = amount if amount >= 0 else 0 - tx_hash = _get_hash( - tx_sender_addr=tx_sender_addr, - tx_counterparty_addr=tx_counterparty_addr, - good_ids=good_uids, - sender_supplied_quantities=sender_supplied_quantities, - counterparty_supplied_quantities=counterparty_supplied_quantities, - tx_amount=tx_amount, - tx_nonce=tx_nonce, - ) - return tx_hash diff --git a/packages/fetchai/skills/tac_control/parameters.py b/packages/fetchai/skills/tac_control/parameters.py index ff8f8be2ad..1e646c2644 100644 --- a/packages/fetchai/skills/tac_control/parameters.py +++ b/packages/fetchai/skills/tac_control/parameters.py @@ -20,10 +20,14 @@ """This package contains a class representing the game parameters.""" import datetime -from typing import Set +from typing import Dict, Set +from aea.helpers.search.models import Location from aea.skills.base import Model +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SERVICE_DATA = {"key": "tac", "value": "v1"} + class Parameters(Model): """This class contains the parameters of the game.""" @@ -45,22 +49,38 @@ def __init__(self, **kwargs): self._competition_timeout = kwargs.pop("competition_timeout", 20) # type: int self._inactivity_timeout = kwargs.pop("inactivity_timeout", 10) # type: int self._whitelist = set(kwargs.pop("whitelist", [])) # type: Set[str] - self._version_id = kwargs.pop("version_id", "v1") # type: str + self._location = kwargs.pop("location", DEFAULT_LOCATION) + self._service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) + assert ( + len(self._service_data) == 2 + and "key" in self._service_data + and "value" in self._service_data + ), "service_data must contain keys `key` and `value`" + self._version_id = self._service_data["value"] # type: str + + self._agent_location = { + "location": Location( + self._location["longitude"], self._location["latitude"] + ) + } + self._set_service_data = self._service_data + self._remove_service_data = {"key": self._service_data["key"]} + self._simple_service_data = { + self._service_data["key"]: self._service_data["value"] + } + super().__init__(**kwargs) now = datetime.datetime.now() if now > self.registration_start_time: self.context.logger.warning( - "[{}]: TAC registration start time {} is in the past!".format( - self.context.agent_name, self.registration_start_time + "TAC registration start time {} is in the past!".format( + self.registration_start_time ) ) else: self.context.logger.info( - "[{}]: TAC registation start time: {}, and start time: {}, and end time: {}".format( - self.context.agent_name, - self.registration_start_time, - self.start_time, - self.end_time, + "TAC registation start time: {}, and start time: {}, and end time: {}".format( + self.registration_start_time, self.start_time, self.end_time, ) ) @@ -128,3 +148,23 @@ def whitelist(self) -> Set[str]: def version_id(self) -> str: """Version id.""" return self._version_id + + @property + def agent_location(self) -> Dict[str, Location]: + """Get the agent location.""" + return self._agent_location + + @property + def set_service_data(self) -> Dict[str, str]: + """Get the set service data.""" + return self._set_service_data + + @property + def remove_service_data(self) -> Dict[str, str]: + """Get the remove service data.""" + return self._remove_service_data + + @property + def simple_service_data(self) -> Dict[str, str]: + """Get the simple service data.""" + return self._simple_service_data diff --git a/packages/fetchai/skills/tac_control/skill.yaml b/packages/fetchai/skills/tac_control/skill.yaml index 554d450d2f..32c823c2f8 100644 --- a/packages/fetchai/skills/tac_control/skill.yaml +++ b/packages/fetchai/skills/tac_control/skill.yaml @@ -1,55 +1,68 @@ name: tac_control author: fetchai -version: 0.3.0 +version: 0.4.0 description: The tac control skill implements the logic for an AEA to control an instance of the TAC. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: Qme9YfgfPXymvupw1EHMJWGUSMTT6JQZxk2qaeKE76pgyN - behaviours.py: Qmcb6RPGT6x5aupA4m95nAFXJioUNjQersWfaAApL83GEA - game.py: QmRM1gtNS9aiLwHUa3WKSLVm3hbXRsnBYr93tZF4bSm4mf - handlers.py: QmRvgtFvtMsNeTUoKLSeap9efQpohySi4X6UJXDhXVv8Xx - helpers.py: QmT8vvpwxA9rUNX7Xdob4ZNXYXG8LW8nhFfyeV5dUbAFbB - parameters.py: QmSmR8PycMvfB9omUz7nzZZXqwFkSZMDTb8pBZrntfDPre + behaviours.py: QmeJhvrP7GhLPLpQVFHeyXFiaGdcTzF9Cwe8BUZS1SuRbB + dialogues.py: QmYYvm4fKUxceKc9CzkZXKZbmRegoVdBFkXqAj5YhKN8eb + game.py: QmXmGh6nBzbMSUjJqpJxbtVCm8Sn7EqQhyE9Wf6evYRekS + handlers.py: Qme9aCQDrzLT47GdnkAEj7decZsYpVNo3ZR7eg25Y6nMTz + helpers.py: QmdhGNhBwn5Zn4yacQEo3EAU74kSkhMR7icvPoj6ZVAJfV + parameters.py: QmR7EcnmmQstPKwpT7D5HjbfqWYN7cNEYsKWUE5Dvgn1LG fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: [] behaviours: tac: args: {} - class_name: TACBehaviour + class_name: TacBehaviour handlers: oef: args: {} - class_name: OEFRegistrationHandler + class_name: OefSearchHandler tac: args: {} - class_name: TACHandler + class_name: TacHandler models: + default_dialogues: + args: {} + class_name: DefaultDialogues game: args: {} class_name: Game + oef_search_dialogues: + args: {} + class_name: OefSearchDialogues parameters: args: base_good_endowment: 2 competition_timeout: 180 inactivity_timeout: 60 + location: + latitude: 0.127 + longitude: 51.5194 lower_bound_factor: 1 min_nb_agents: 2 money_endowment: 2000000 nb_goods: 10 registration_timeout: 60 + service_data: + key: tac + value: v1 start_time: 01 01 2020 00:01 tx_fee: 1 upper_bound_factor: 1 - version_id: v1 whitelist: [] class_name: Parameters + tac_dialogues: + args: {} + class_name: TacDialogues dependencies: numpy: {} - web3: - version: ==5.2.2 diff --git a/packages/fetchai/skills/tac_control_contract/behaviours.py b/packages/fetchai/skills/tac_control_contract/behaviours.py index 31552601cb..edc0b3672a 100644 --- a/packages/fetchai/skills/tac_control_contract/behaviours.py +++ b/packages/fetchai/skills/tac_control_contract/behaviours.py @@ -94,11 +94,7 @@ def _deploy_contract( """Send deploy contract tx msg to decision maker.""" game = cast(Game, self.context.game) game.phase = Phase.CONTRACT_DEPLOYMENT_PROPOSAL - self.context.logger.info( - "[{}]: Sending deploy transaction to decision maker.".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending deploy transaction to decision maker.") # request deploy tx # contract.set_instance(ledger_api) # transaction_message = contract.get_deploy_transaction_msg( @@ -131,16 +127,12 @@ def act(self) -> None: game.phase.value == Phase.GAME_REGISTRATION.value and parameters.registration_end_time < now < parameters.start_time ): - self.context.logger.info( - "[{}] Closing registration!".format(self.context.agent_name) - ) + self.context.logger.info("closing registration!") if game.registration.nb_agents < parameters.min_nb_agents: game.phase = Phase.CANCELLED_GAME self.context.logger.info( - "[{}]: Registered agents={}, minimum agents required={}".format( - self.context.agent_name, - game.registration.nb_agents, - parameters.min_nb_agents, + "registered agents={}, minimum agents required={}".format( + game.registration.nb_agents, parameters.min_nb_agents, ) ) self._end_tac(game, "cancelled") @@ -190,9 +182,7 @@ def _register_tac(self, parameters) -> None: desc = Description( {"version": parameters.version_id}, data_model=CONTROLLER_DATAMODEL, ) - self.context.logger.info( - "[{}]: Registering TAC data model".format(self.context.agent_name) - ) + self.context.logger.info("registering TAC data model") oef_msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, dialogue_reference=(str(self._oef_msg_id), ""), @@ -202,8 +192,8 @@ def _register_tac(self, parameters) -> None: self.context.outbox.put_message(message=oef_msg) self._registered_desc = desc self.context.logger.info( - "[{}]: TAC open for registration until: {}".format( - self.context.agent_name, parameters.registration_end_time + "TAC open for registration until: {}".format( + parameters.registration_end_time ) ) @@ -215,9 +205,7 @@ def _unregister_tac(self) -> None: """ if self._registered_desc is not None: self._oef_msg_id += 1 - self.context.logger.info( - "[{}]: Unregistering TAC data model".format(self.context.agent_name) - ) + self.context.logger.info("unregistering TAC data model") oef_msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, dialogue_reference=(str(self._oef_msg_id), ""), @@ -231,11 +219,7 @@ def _create_items( self, game: Game, ledger_api: LedgerApi, contract: ERC1155Contract ) -> None: """Send create items transaction to decision maker.""" - self.context.logger.info( - "[{}]: Sending create_items transaction to decision maker.".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending create_items transaction to decision maker.") tx_msg = self._get_create_items_tx_msg( # pylint: disable=assignment-from-none game.conf, ledger_api, contract ) @@ -245,11 +229,7 @@ def _mint_items( self, game: Game, ledger_api: LedgerApi, contract: ERC1155Contract ) -> None: """Send mint items transactions to decision maker.""" - self.context.logger.info( - "[{}]: Sending mint_items transactions to decision maker.".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending mint_items transactions to decision maker.") for agent_state in game.initial_agent_states.values(): tx_msg = self._get_mint_goods_and_currency_tx_msg( # pylint: disable=assignment-from-none agent_state, ledger_api, contract @@ -259,14 +239,10 @@ def _mint_items( def _start_tac(self, game: Game) -> None: """Create a game and send the game configuration to every registered agent.""" self.context.logger.info( - "[{}]: Starting competition with configuration:\n{}".format( - self.context.agent_name, game.holdings_summary - ) + "starting competition with configuration:\n{}".format(game.holdings_summary) ) self.context.logger.info( - "[{}]: Computed theoretical equilibrium:\n{}".format( - self.context.agent_name, game.equilibrium_summary - ) + "computed theoretical equilibrium:\n{}".format(game.equilibrium_summary) ) for agent_address in game.conf.agent_addr_to_name.keys(): agent_state = game.current_agent_states[agent_address] @@ -284,23 +260,15 @@ def _start_tac(self, game: Game) -> None: info={"contract_address": game.conf.contract_address}, ) self.context.logger.debug( - "[{}]: sending game data to '{}'.".format( - self.context.agent_name, agent_address - ) - ) - self.context.logger.debug( - "[{}]: game data={}".format(self.context.agent_name, str(tac_msg)) + "sending game data to '{}'.".format(agent_address) ) + self.context.logger.debug("game data={}".format(str(tac_msg))) tac_msg.counterparty = agent_address self.context.outbox.put_message(message=tac_msg) def _end_tac(self, game: Game, reason: str) -> None: """Notify agents that the TAC is cancelled.""" - self.context.logger.info( - "[{}]: Notifying agents that TAC is {}.".format( - self.context.agent_name, reason - ) - ) + self.context.logger.info("notifying agents that TAC is {}.".format(reason)) for agent_addr in game.registration.agent_addr_to_name.keys(): tac_msg = TacMessage(performative=TacMessage.Performative.CANCELLED) tac_msg.counterparty = agent_addr @@ -309,14 +277,10 @@ def _end_tac(self, game: Game, reason: str) -> None: def _game_finished_summary(self, game: Game) -> None: """Provide summary of game stats.""" self.context.logger.info( - "[{}]: Finished competition:\n{}".format( - self.context.agent_name, game.holdings_summary - ) + "finished competition:\n{}".format(game.holdings_summary) ) self.context.logger.info( - "[{}]: Computed equilibrium:\n{}".format( - self.context.agent_name, game.equilibrium_summary - ) + "computed equilibrium:\n{}".format(game.equilibrium_summary) ) def _get_create_items_tx_msg( # pylint: disable=no-self-use @@ -379,23 +343,19 @@ def act(self) -> None: ) if tx_receipt is None: self.context.logger.info( - "[{}]: Cannot verify whether contract deployment was successful. Retrying...".format( - self.context.agent_name - ) + "cannot verify whether contract deployment was successful. Retrying..." ) elif tx_receipt.status != 1: self.context.is_active = False self.context.warning( - "[{}]: The contract did not deployed successfully. Transaction hash: {}. Aborting!".format( - self.context.agent_name, tx_receipt.transactionHash.hex() + "the contract did not deployed successfully. Transaction hash: {}. Aborting!".format( + tx_receipt.transactionHash.hex() ) ) else: self.context.logger.info( - "[{}]: The contract was successfully deployed. Contract address: {}. Transaction hash: {}".format( - self.context.agent_name, - tx_receipt.contractAddress, - tx_receipt.transactionHash.hex(), + "the contract was successfully deployed. Contract address: {}. Transaction hash: {}".format( + tx_receipt.contractAddress, tx_receipt.transactionHash.hex(), ) ) configuration = Configuration(parameters.version_id, parameters.tx_fee,) @@ -414,21 +374,19 @@ def act(self) -> None: ) if tx_receipt is None: self.context.logger.info( - "[{}]: Cannot verify whether token creation was successful. Retrying...".format( - self.context.agent_name - ) + "cannot verify whether token creation was successful. Retrying..." ) elif tx_receipt.status != 1: self.context.is_active = False self.context.warning( - "[{}]: The token creation wasn't successful. Transaction hash: {}. Aborting!".format( - self.context.agent_name, tx_receipt.transactionHash.hex() + "the token creation wasn't successful. Transaction hash: {}. Aborting!".format( + tx_receipt.transactionHash.hex() ) ) else: self.context.logger.info( - "[{}]: Successfully created the tokens. Transaction hash: {}".format( - self.context.agent_name, tx_receipt.transactionHash.hex() + "successfully created the tokens. Transaction hash: {}".format( + tx_receipt.transactionHash.hex() ) ) game.phase = Phase.TOKENS_CREATED @@ -442,25 +400,21 @@ def act(self) -> None: tx_receipt = ledger_api.get_transaction_receipt(tx_digest=tx_digest) if tx_receipt is None: self.context.logger.info( - "[{}]: Cannot verify whether token minting for agent_addr={} was successful. Retrying...".format( - self.context.agent_name, agent_addr + "cannot verify whether token minting for agent_addr={} was successful. Retrying...".format( + agent_addr ) ) elif tx_receipt.status != 1: self.context.is_active = False self.context.logger.warning( - "[{}]: The token minting for agent_addr={} wasn't successful. Transaction hash: {}. Aborting!".format( - self.context.agent_name, - agent_addr, - tx_receipt.transactionHash.hex(), + "the token minting for agent_addr={} wasn't successful. Transaction hash: {}. Aborting!".format( + agent_addr, tx_receipt.transactionHash.hex(), ) ) else: self.context.logger.info( - "[{}]: Successfully minted the tokens for agent_addr={}. Transaction hash: {}".format( - self.context.agent_name, - agent_addr, - tx_receipt.transactionHash.hex(), + "successfully minted the tokens for agent_addr={}. Transaction hash: {}".format( + agent_addr, tx_receipt.transactionHash.hex(), ) ) game.contract_manager.add_confirmed_mint_tokens_agents(agent_addr) diff --git a/packages/fetchai/skills/tac_control_contract/game.py b/packages/fetchai/skills/tac_control_contract/game.py index 84b0226634..34e6c5b963 100644 --- a/packages/fetchai/skills/tac_control_contract/game.py +++ b/packages/fetchai/skills/tac_control_contract/game.py @@ -29,6 +29,7 @@ linear_utility, logarithmic_utility, ) +from aea.helpers.transaction.base import Terms from aea.mail.base import Address from aea.skills.base import Model @@ -288,88 +289,50 @@ def _check_consistency(self): ), "Dimensions for utility_params and good_endowments rows must be the same." -class Transaction: +class Transaction(Terms): """Convenience representation of a transaction.""" def __init__( self, - transaction_id: TransactionId, - sender_addr: Address, - counterparty_addr: Address, + ledger_id: str, + sender_address: Address, + counterparty_address: Address, amount_by_currency_id: Dict[str, int], - sender_fee: int, - counterparty_fee: int, quantities_by_good_id: Dict[str, int], - nonce: int, + is_sender_payable_tx_fee: bool, + nonce: str, + fee_by_currency_id: Optional[Dict[str, int]], sender_signature: str, counterparty_signature: str, ) -> None: """ - Instantiate transaction request. - - :param transaction_id: the id of the transaction. - :param sender_addr: the sender of the transaction. - :param tx_counterparty_addr: the counterparty of the transaction. - :param amount_by_currency_id: the currency used. - :param sender_fee: the transaction fee covered by the sender. - :param counterparty_fee: the transaction fee covered by the counterparty. - :param quantities_by_good_id: a map from good pbk to the quantity of that good involved in the transaction. - :param nonce: the nonce of the transaction - :param sender_signature: the signature of the transaction sender - :param counterparty_signature: the signature of the transaction counterparty - :return: None - """ - self._id = transaction_id - self._sender_addr = sender_addr - self._counterparty_addr = counterparty_addr - self._amount_by_currency_id = amount_by_currency_id - self._sender_fee = sender_fee - self._counterparty_fee = counterparty_fee - self._quantities_by_good_id = quantities_by_good_id - self._nonce = nonce + Instantiate transaction. + + This extends a terms object to be used as a transaction. + + :param ledger_id: the ledger on which the terms are to be settled. + :param sender_address: the sender address of the transaction. + :param counterparty_address: the counterparty address of the transaction. + :param amount_by_currency_id: the amount by the currency of the transaction. + :param quantities_by_good_id: a map from good id to the quantity of that good involved in the transaction. + :param is_sender_payable_tx_fee: whether the sender or counterparty pays the tx fee. + :param nonce: nonce to be included in transaction to discriminate otherwise identical transactions. + :param fee_by_currency_id: the fee associated with the transaction. + :param sender_signature: the signature of the terms by the sender. + :param counterparty_signature: the signature of the terms by the counterparty. + """ + super().__init__( + ledger_id=ledger_id, + sender_address=sender_address, + counterparty_address=counterparty_address, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + fee_by_currency_id=fee_by_currency_id, + ) self._sender_signature = sender_signature self._counterparty_signature = counterparty_signature - self._check_consistency() - - @property - def id(self) -> str: - """Get the transaction id.""" - return self._id - - @property - def sender_addr(self) -> Address: - """Get the sender address.""" - return self._sender_addr - - @property - def counterparty_addr(self) -> Address: - """Get the counterparty address.""" - return self._counterparty_addr - - @property - def amount_by_currency_id(self) -> Dict[CurrencyId, Quantity]: - """Get the amount for each currency.""" - return copy.copy(self._amount_by_currency_id) - - @property - def sender_fee(self) -> int: - """Get the sender fee.""" - return self._sender_fee - - @property - def counterparty_fee(self) -> int: - """Get the counterparty fee.""" - return self._counterparty_fee - - @property - def quantities_by_good_id(self) -> Dict[GoodId, Quantity]: - """Get the quantities by good_id.""" - return copy.copy(self._quantities_by_good_id) - - @property - def nonce(self) -> int: - """Get the nonce of the transaction.""" - return self._nonce @property def sender_signature(self) -> str: @@ -381,68 +344,6 @@ def counterparty_signature(self) -> str: """Get the counterparty signature.""" return self._counterparty_signature - @property - def is_sender_buyer(self) -> bool: - """Get the sender is buyer status.""" - return all(value <= 0 for value in self.amount_by_currency_id.values()) - - @property - def buyer_addr(self) -> Address: - """Get the buyer address.""" - return self._sender_addr if self.is_sender_buyer else self._counterparty_addr - - @property - def amount(self) -> int: - """Get the amount.""" - return list(self.amount_by_currency_id.values())[0] - - @property - def currency_id(self) -> str: - """Get the currency id.""" - return list(self.amount_by_currency_id.keys())[0] - - @property - def sender_amount(self) -> int: - """Get the amount the sender gets/pays.""" - return self.amount - self.sender_fee - - @property - def counterparty_amount(self) -> int: - """Get the amount the counterparty gets/pays.""" - return -self.amount - self.counterparty_fee - - 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_addr != self.counterparty_addr - assert ( - len(self.amount_by_currency_id.keys()) == 1 - ) # For now we restrict to one currency per transaction. - assert self.sender_fee >= 0 - assert self.counterparty_fee >= 0 - assert ( - self.amount >= 0 - and all(quantity <= 0 for quantity in self.quantities_by_good_id.values()) - ) or ( - self.amount <= 0 - and all(quantity >= 0 for quantity in self.quantities_by_good_id.values()) - ) - assert isinstance(self.sender_signature, str) and isinstance( - self.counterparty_signature, str - ) - if self.amount >= 0: - assert ( - self.sender_amount >= 0 - ), "Sender_amount must be positive when the sender is the payment receiver." - else: - assert ( - self.counterparty_amount >= 0 - ), "Counterparty_amount must be positive when the counterpary is the payment receiver." - @classmethod def from_message(cls, message: TacMessage) -> "Transaction": """ @@ -451,32 +352,31 @@ def from_message(cls, message: TacMessage) -> "Transaction": :param message: the message :return: Transaction """ - assert message.performative == TacMessage.Performative.TRANSACTION - return Transaction( - message.tx_id, - message.tx_sender_addr, - message.tx_counterparty_addr, - message.amount_by_currency_id, - message.tx_sender_fee, - message.tx_counterparty_fee, - message.quantities_by_good_id, - message.tx_nonce, - message.tx_sender_signature, - message.tx_counterparty_signature, + assert ( + message.performative == TacMessage.Performative.TRANSACTION + ), "Wrong performative" + transaction = Transaction( + ledger_id=message.ledger_id, + sender_address=message.sender_address, + counterparty_address=message.counterparty_address, + amount_by_currency_id=message.amount_by_currency_id, + fee_by_currency_id=message.fee_by_currency_id, + quantities_by_good_id=message.quantities_by_good_id, + is_sender_payable_tx_fee=True, # TODO: check + nonce=str(message.nonce), + sender_signature=message.sender_signature, + counterparty_signature=message.counterparty_signature, ) + assert ( + transaction.id == message.transaction_id + ), "Transaction content does not match hash." + return transaction def __eq__(self, other): """Compare to another object.""" return ( isinstance(other, Transaction) - and self.id == other.id - and self.sender_addr == other.sender_addr - and self.counterparty_addr == other.counterparty_addr - and self.amount_by_currency_id == other.amount_by_currency_id - and self.sender_fee == other.sender_fee - and self.counterparty_fee == other.counterparty_fee - and self.quantities_by_good_id == other.quantities_by_good_id - and self.nonce == other.nonce + and super.__eq__() and self.sender_signature == other.sender_signature and self.counterparty_signature == other.counterparty_signature ) @@ -564,42 +464,47 @@ def is_consistent_transaction(self, tx: Transaction) -> bool: or enough holdings if it is a seller. :return: True if the transaction is legal wrt the current state, False otherwise. """ - result = self.agent_address in [tx.sender_addr, tx.counterparty_addr] - if tx.amount == 0 and all( + result = self.agent_address in [tx.sender_address, tx.counterparty_address] + result = result and tx.is_single_currency + if not result: + return result + if all(amount == 0 for amount in tx.amount_by_currency_id.values()) and all( quantity == 0 for quantity in tx.quantities_by_good_id.values() ): # reject the transaction when there is no wealth exchange result = False - elif tx.amount <= 0 and all( + elif all(amount <= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity >= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is buyer, counterparty is seller - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # check this sender state has enough money result = result and ( - self.amount_by_currency_id[tx.currency_id] >= tx.sender_amount + self.amount_by_currency_id[tx.currency_id] + >= tx.sender_payable_amount ) - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= quantity for good_id, quantity in tx.quantities_by_good_id.items() ) - elif tx.amount >= 0 and all( + elif all(amount >= 0 for amount in tx.amount_by_currency_id.values()) and all( quantity <= 0 for quantity in tx.quantities_by_good_id.values() ): # sender is seller, counterparty is buyer # Note, on a ledger, this atomic swap would only be possible for amount == 0! - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # check this sender state has enough goods result = result and all( self.quantities_by_good_id[good_id] >= -quantity for good_id, quantity in tx.quantities_by_good_id.items() ) - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: # check this counterparty state has enough money result = result and ( - self.amount_by_currency_id[tx.currency_id] >= tx.counterparty_amount + self.amount_by_currency_id[tx.currency_id] + >= tx.counterparty_payable_amount ) else: result = False @@ -628,20 +533,22 @@ def update(self, tx: Transaction) -> None: assert self.is_consistent_transaction(tx), "Inconsistent transaction." new_amount_by_currency_id = self.amount_by_currency_id - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: # settling the transaction for the sender - new_amount_by_currency_id[tx.currency_id] += tx.sender_amount - elif self.agent_address == tx.counterparty_addr: + for currency_id, amount in tx.amount_by_currency_id.items(): + new_amount_by_currency_id[currency_id] += amount + elif self.agent_address == tx.counterparty_address: # settling the transaction for the counterparty - new_amount_by_currency_id[tx.currency_id] += tx.counterparty_amount + for currency_id, amount in tx.amount_by_currency_id.items(): + new_amount_by_currency_id[currency_id] += amount self._amount_by_currency_id = new_amount_by_currency_id new_quantities_by_good_id = self.quantities_by_good_id for good_id, quantity in tx.quantities_by_good_id.items(): - if self.agent_address == tx.sender_addr: + if self.agent_address == tx.sender_address: new_quantities_by_good_id[good_id] += quantity - elif self.agent_address == tx.counterparty_addr: + elif self.agent_address == tx.counterparty_address: new_quantities_by_good_id[good_id] -= quantity self._quantities_by_good_id = new_quantities_by_good_id @@ -713,12 +620,12 @@ def add(self, transaction: Transaction) -> None: """ now = datetime.datetime.now() self._confirmed[now] = transaction - if self._confirmed_per_agent.get(transaction.sender_addr) is None: - self._confirmed_per_agent[transaction.sender_addr] = {} - self._confirmed_per_agent[transaction.sender_addr][now] = transaction - if self._confirmed_per_agent.get(transaction.counterparty_addr) is None: - self._confirmed_per_agent[transaction.counterparty_addr] = {} - self._confirmed_per_agent[transaction.counterparty_addr][now] = transaction + if self._confirmed_per_agent.get(transaction.sender_address) is None: + self._confirmed_per_agent[transaction.sender_address] = {} + self._confirmed_per_agent[transaction.sender_address][now] = transaction + if self._confirmed_per_agent.get(transaction.counterparty_address) is None: + self._confirmed_per_agent[transaction.counterparty_address] = {} + self._confirmed_per_agent[transaction.counterparty_address][now] = transaction class Registration: @@ -908,9 +815,7 @@ def transactions(self) -> Transactions: def create(self): """Create a game.""" assert self.phase.value == Phase.GAME_SETUP.value, "Wrong game phase." - self.context.logger.info( - "[{}]: Setting Up the TAC game.".format(self.context.agent_name) - ) + self.context.logger.info("setting Up the TAC game.") self._generate() def _generate(self): diff --git a/packages/fetchai/skills/tac_control_contract/handlers.py b/packages/fetchai/skills/tac_control_contract/handlers.py index c0bc0da724..a9079380d8 100644 --- a/packages/fetchai/skills/tac_control_contract/handlers.py +++ b/packages/fetchai/skills/tac_control_contract/handlers.py @@ -57,9 +57,7 @@ def handle(self, message: Message) -> None: game = cast(Game, self.context.game) self.context.logger.debug( - "[{}]: Handling TAC message. type={}".format( - self.context.agent_name, tac_message.performative - ) + "handling TAC message. type={}".format(tac_message.performative) ) if ( tac_message.performative == TacMessage.Performative.REGISTER @@ -73,9 +71,7 @@ def handle(self, message: Message) -> None: self._on_unregister(tac_message) else: self.context.logger.warning( - "[{}]: TAC Message type not recognized or not permitted.".format( - self.context.agent_name - ) + "TAC Message type not recognized or not permitted." ) def _on_register(self, message: TacMessage) -> None: @@ -91,9 +87,7 @@ def _on_register(self, message: TacMessage) -> None: agent_name = message.agent_name if len(parameters.whitelist) != 0 and agent_name not in parameters.whitelist: self.context.logger.warning( - "[{}]: Agent name not in whitelist: '{}'".format( - self.context.agent_name, agent_name - ) + "agent name not in whitelist: '{}'".format(agent_name) ) tac_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, @@ -106,8 +100,7 @@ def _on_register(self, message: TacMessage) -> None: game = cast(Game, self.context.game) if message.counterparty in game.registration.agent_addr_to_name: self.context.logger.warning( - "[{}]: Agent already registered: '{}'".format( - self.context.agent_name, + "agent already registered: '{}'".format( game.registration.agent_addr_to_name[message.counterparty], ) ) @@ -120,9 +113,7 @@ def _on_register(self, message: TacMessage) -> None: if agent_name in game.registration.agent_addr_to_name.values(): self.context.logger.warning( - "[{}]: Agent with this name already registered: '{}'".format( - self.context.agent_name, agent_name - ) + "agent with this name already registered: '{}'".format(agent_name) ) tac_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, @@ -131,9 +122,7 @@ def _on_register(self, message: TacMessage) -> None: tac_msg.counterparty = message.counterparty self.context.outbox.put_message(message=tac_msg) game.registration.register_agent(message.counterparty, agent_name) - self.context.logger.info( - "[{}]: Agent registered: '{}'".format(self.context.agent_name, agent_name) - ) + self.context.logger.info("agent registered: '{}'".format(agent_name)) def _on_unregister(self, message: TacMessage) -> None: """ @@ -147,9 +136,7 @@ def _on_unregister(self, message: TacMessage) -> None: game = cast(Game, self.context.game) if message.counterparty not in game.registration.agent_addr_to_name: self.context.logger.warning( - "[{}]: Agent not registered: '{}'".format( - self.context.agent_name, message.counterparty - ) + "agent not registered: '{}'".format(message.counterparty) ) tac_msg = TacMessage( performative=TacMessage.Performative.TAC_ERROR, @@ -159,8 +146,7 @@ def _on_unregister(self, message: TacMessage) -> None: self.context.outbox.put_message(message=tac_msg) else: self.context.logger.debug( - "[{}]: Agent unregistered: '{}'".format( - self.context.agent_name, + "agent unregistered: '{}'".format( game.conf.agent_addr_to_name[message.counterparty], ) ) @@ -198,16 +184,12 @@ def handle(self, message: Message) -> None: oef_message = cast(OefSearchMessage, message) self.context.logger.debug( - "[{}]: Handling OEF message. type={}".format( - self.context.agent_name, oef_message.performative - ) + "handling OEF message. type={}".format(oef_message.performative) ) if oef_message.performative == OefSearchMessage.Performative.OEF_ERROR: self._on_oef_error(oef_message) else: - self.context.logger.warning( - "[{}]: OEF Message type not recognized.".format(self.context.agent_name) - ) + self.context.logger.warning("OEF Message type not recognized.") def _on_oef_error(self, oef_error: OefSearchMessage) -> None: """ @@ -218,10 +200,8 @@ def _on_oef_error(self, oef_error: OefSearchMessage) -> None: :return: None """ self.context.logger.warning( - "[{}]: Received OEF Search error: dialogue_reference={}, operation={}".format( - self.context.agent_name, - oef_error.dialogue_reference, - oef_error.oef_error_operation, + "received OEF Search error: dialogue_reference={}, operation={}".format( + oef_error.dialogue_reference, oef_error.oef_error_operation, ) ) @@ -256,56 +236,32 @@ def handle(self, message: Message) -> None: ledger_api = self.context.ledger_apis.get_api(parameters.ledger) if signing_msg_response.dialogue_reference[0] == "contract_deploy": game.phase = Phase.CONTRACT_DEPLOYING - self.context.logger.info( - "[{}]: Sending deployment transaction to the ledger...".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending deployment transaction to the ledger...") tx_signed = signing_msg_response.signed_transaction tx_digest = ledger_api.send_signed_transaction(tx_signed=tx_signed) if tx_digest is None: - self.context.logger.warning( - "[{}]: Sending transaction failed. Aborting!".format( - self.context.agent_name - ) - ) + self.context.logger.warning("sending transaction failed. Aborting!") self.context.is_active = False else: game.contract_manager.deploy_tx_digest = tx_digest elif signing_msg_response.dialogue_reference[0] == "contract_create_batch": game.phase = Phase.TOKENS_CREATING - self.context.logger.info( - "[{}]: Sending creation transaction to the ledger...".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending creation transaction to the ledger...") tx_signed = signing_msg_response.signed_transaction tx_digest = ledger_api.send_signed_transaction(tx_signed=tx_signed) if tx_digest is None: - self.context.logger.warning( - "[{}]: Sending transaction failed. Aborting!".format( - self.context.agent_name - ) - ) + self.context.logger.warning("sending transaction failed. Aborting!") self.context.is_active = False else: game.contract_manager.create_tokens_tx_digest = tx_digest elif signing_msg_response.dialogue_reference[0] == "contract_mint_batch": game.phase = Phase.TOKENS_MINTING - self.context.logger.info( - "[{}]: Sending minting transaction to the ledger...".format( - self.context.agent_name - ) - ) + self.context.logger.info("sending minting transaction to the ledger...") tx_signed = signing_msg_response.signed_transaction agent_addr = signing_msg_response.terms.counterparty_address tx_digest = ledger_api.send_signed_transaction(tx_signed=tx_signed) if tx_digest is None: - self.context.logger.warning( - "[{}]: Sending transaction failed. Aborting!".format( - self.context.agent_name - ) - ) + self.context.logger.warning("sending transaction failed. Aborting!") self.context.is_active = False else: game.contract_manager.set_mint_tokens_tx_digest(agent_addr, tx_digest) diff --git a/packages/fetchai/skills/tac_control_contract/parameters.py b/packages/fetchai/skills/tac_control_contract/parameters.py index e77409ef5c..7e7c1e0626 100644 --- a/packages/fetchai/skills/tac_control_contract/parameters.py +++ b/packages/fetchai/skills/tac_control_contract/parameters.py @@ -93,15 +93,14 @@ def __init__(self, **kwargs): now = datetime.datetime.now() if now > self.registration_start_time: self.context.logger.warning( - "[{}]: TAC registration start time {} is in the past! Deregistering skill.".format( - self.context.agent_name, self.registration_start_time + "TAC registration start time {} is in the past! Deregistering skill.".format( + self.registration_start_time ) ) self.context.is_active = False else: self.context.logger.info( - "[{}]: TAC registation start time: {}, and registration end time: {}, and start time: {}, and end time: {}".format( - self.context.agent_name, + "TAC registation start time: {}, and registration end time: {}, and start time: {}, and end time: {}".format( self.registration_start_time, self.registration_end_time, self.start_time, diff --git a/packages/fetchai/skills/tac_control_contract/skill.yaml b/packages/fetchai/skills/tac_control_contract/skill.yaml index fcb6df65d7..911b3abcb6 100644 --- a/packages/fetchai/skills/tac_control_contract/skill.yaml +++ b/packages/fetchai/skills/tac_control_contract/skill.yaml @@ -1,23 +1,23 @@ name: tac_control_contract author: fetchai -version: 0.4.0 +version: 0.5.0 description: The tac control skill implements the logic for an AEA to control an instance of the TAC. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmW9WBy1sNYVKpymGnpJY2pW5MEqGgVga2kBFUT9S34Yt5 - behaviours.py: QmcFmysYU23A8q1buM72R9bwkmvrHQMYyBcViRJKuFfzJ2 - game.py: QmdfWrg2y2sggm4c4so26r3g42mjaGK9o7TxHX6ADDSPRF - handlers.py: QmTsHRVTjVfPetZjkcJybwAetwePWrmPYKAkfEU9uVZXbW + behaviours.py: QmREaovF6zG3KNYHkDUgjQ677R1RvBAvgwdZbGj7HJQqXc + game.py: QmNncyVW2cndDQ7tkg3aQGuSf58EUL94Ud1hfRu8iehrmu + handlers.py: QmNhXDjNHo2QfoVFyX3HhrrLDBFJKV3p9ipKumi5KxyUth helpers.py: QmbS991iVkS7HCTHBZGoF47REXvsEfqJPi5CqGJR5BasLD - parameters.py: QmZUf8ho1bPfRZv3tvc9hqwPtwhf2wRTJnox6kSX7ZEqmU + parameters.py: QmU6afX3gsJaP8sxNyCohaFrD6eRowtWoVYXzC3ytuob4Y fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: [] behaviours: contract: diff --git a/packages/fetchai/skills/tac_negotiation/behaviours.py b/packages/fetchai/skills/tac_negotiation/behaviours.py index 931a6e2568..e3410bf40c 100644 --- a/packages/fetchai/skills/tac_negotiation/behaviours.py +++ b/packages/fetchai/skills/tac_negotiation/behaviours.py @@ -19,21 +19,32 @@ """This package contains a scaffold of a behaviour.""" -from typing import cast +from typing import Optional, cast -from aea.skills.base import Behaviour from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage -from packages.fetchai.skills.tac_negotiation.registration import Registration -from packages.fetchai.skills.tac_negotiation.search import Search +from packages.fetchai.skills.tac_negotiation.dialogues import ( + OefSearchDialogue, + OefSearchDialogues, +) from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions +DEFAULT_REGISTER_AND_SEARCH_INTERVAL = 5.0 -class GoodsRegisterAndSearchBehaviour(Behaviour): + +class GoodsRegisterAndSearchBehaviour(TickerBehaviour): """This class implements the goods register and search behaviour.""" + def __init__(self, **kwargs): + """Initialize the search behaviour.""" + search_interval = cast( + float, kwargs.pop("search_interval", DEFAULT_REGISTER_AND_SEARCH_INTERVAL) + ) + super().__init__(tick_interval=search_interval, **kwargs) + self.is_registered = False + def setup(self) -> None: """ Implement the setup. @@ -54,16 +65,16 @@ def act(self) -> None: self.context.is_active = False return - if self.context.decision_maker_handler_context.goal_pursuit_readiness.is_ready: - - registration = cast(Registration, self.context.registration) - if registration.is_time_to_update_services(): - self._unregister_service() - self._register_service() + if ( + not self.context.decision_maker_handler_context.goal_pursuit_readiness.is_ready + ): + return - search = cast(Search, self.context.search) - if search.is_time_to_search_services(): - self._search_services() + if not self.is_registered: + self._register_agent() + self._register_service() + self.is_registered = True + self._search_services() def teardown(self) -> None: """ @@ -71,35 +82,32 @@ def teardown(self) -> None: :return: None """ - self._unregister_service() + if self.is_registered: + self._unregister_service() + self._unregister_agent() + self.is_registered = False - def _unregister_service(self) -> None: + def _register_agent(self) -> None: """ - Unregister service from OEF Service Directory. + Register the agent's location. :return: None """ - registration = cast(Registration, self.context.registration) - - if registration.registered_goods_demanded_description is not None: - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(registration.get_next_id()), ""), - service_description=registration.registered_goods_demanded_description, - ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) - registration.registered_goods_demanded_description = None - - if registration.registered_goods_supplied_description is not None: - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(registration.get_next_id()), ""), - service_description=registration.registered_goods_supplied_description, - ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) - registration.registered_goods_supplied_description = None + strategy = cast(Strategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogue = oef_search_dialogues.update(oef_search_msg) + assert oef_search_dialogue is not None, "OefSearchDialogue not created." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("registering agent on SOEF.") def _register_service(self) -> None: """ @@ -112,48 +120,71 @@ def _register_service(self) -> None: :return: None """ - registration = cast(Registration, self.context.registration) strategy = cast(Strategy, self.context.strategy) + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + self.context.logger.debug( + "updating service directory as {}.".format(strategy.registering_as) + ) + description = strategy.get_register_service_description() + oef_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_msg) + assert oef_dialogue is not None, "OefSearchDialogue not created." + self.context.outbox.put_message(message=oef_msg) - if strategy.is_registering_as_seller: - self.context.logger.debug( - "[{}]: Updating service directory as seller with goods supplied.".format( - self.context.agent_name - ) - ) - goods_supplied_description = strategy.get_own_service_description( - is_supply=True, is_search_description=False, - ) - registration.registered_goods_supplied_description = ( - goods_supplied_description - ) - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(registration.get_next_id()), ""), - service_description=goods_supplied_description, - ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) + def _unregister_service(self) -> None: + """ + Unregister service from OEF Service Directory. - if strategy.is_registering_as_buyer: - self.context.logger.debug( - "[{}]: Updating service directory as buyer with goods demanded.".format( - self.context.agent_name - ) - ) - goods_demanded_description = strategy.get_own_service_description( - is_supply=False, is_search_description=False, - ) - registration.registered_goods_demanded_description = ( - goods_demanded_description - ) - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(registration.get_next_id()), ""), - service_description=goods_demanded_description, + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + self.context.logger.debug( + "unregistering from service directory as {}.".format( + strategy.registering_as ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) + ) + description = strategy.get_unregister_service_description() + oef_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_msg.counterparty = self.context.search_service_address + oef_dialogue = oef_search_dialogues.update(oef_msg) + assert oef_dialogue is not None, "OefSearchDialogue not created." + self.context.outbox.put_message(message=oef_msg) + + def _unregister_agent(self) -> None: + """ + Unregister agent from the SOEF. + + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + description = strategy.get_location_description() + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=description, + ) + oef_search_msg.counterparty = self.context.search_service_address + oef_search_dialogue = oef_search_dialogues.update(oef_search_msg) + assert oef_search_dialogue is not None, "OefSearchDialogue not created." + self.context.outbox.put_message(message=oef_search_msg) + self.context.logger.info("unregistering agent from SOEF.") def _search_services(self) -> None: """ @@ -167,59 +198,28 @@ def _search_services(self) -> None: :return: None """ strategy = cast(Strategy, self.context.strategy) - search = cast(Search, self.context.search) - - if strategy.is_searching_for_sellers: - query = strategy.get_own_services_query( - is_searching_for_sellers=True, is_search_query=True + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + query = strategy.get_location_and_service_query() + for (is_seller_search, searching_for) in strategy.searching_for_types: + oef_msg = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + query=query, ) - if query is None: - self.context.logger.warning( - "[{}]: Not searching the OEF for sellers because the agent demands no goods.".format( - self.context.agent_name - ) - ) - return None - else: - search_id = search.get_next_id(is_searching_for_sellers=True) - self.context.logger.info( - "[{}]: Searching for sellers which match the demand of the agent, search_id={}.".format( - self.context.agent_name, search_id - ) - ) - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(search_id), ""), - query=query, - ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) - - if strategy.is_searching_for_buyers: - query = strategy.get_own_services_query( - is_searching_for_sellers=False, is_search_query=True + oef_msg.counterparty = self.context.search_service_address + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_msg) ) - if query is None: - self.context.logger.warning( - "[{}]: Not searching the OEF for buyers because the agent supplies no goods.".format( - self.context.agent_name - ) - ) - return None - else: - search_id = search.get_next_id(is_searching_for_sellers=False) - self.context.logger.info( - "[{}]: Searching for buyers which match the supply of the agent, search_id={}.".format( - self.context.agent_name, search_id - ) - ) - oef_msg = OefSearchMessage( - performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(search_id), ""), - query=query, + assert oef_search_dialogue is not None, "OefSearchDialogue not created." + oef_search_dialogue.is_seller_search = is_seller_search + self.context.outbox.put_message(message=oef_msg) + self.context.logger.info( + "searching for {}, search_id={}.".format( + searching_for, oef_msg.dialogue_reference ) - oef_msg.counterparty = self.context.search_service_address - self.context.outbox.put_message(message=oef_msg) + ) class TransactionCleanUpBehaviour(TickerBehaviour): @@ -240,6 +240,7 @@ def act(self) -> None: :return: None """ transactions = cast(Transactions, self.context.transactions) + transactions.update_confirmed_transactions() transactions.cleanup_pending_transactions() def teardown(self) -> None: diff --git a/packages/fetchai/skills/tac_negotiation/dialogues.py b/packages/fetchai/skills/tac_negotiation/dialogues.py index e53143567a..1ef2ae3421 100644 --- a/packages/fetchai/skills/tac_negotiation/dialogues.py +++ b/packages/fetchai/skills/tac_negotiation/dialogues.py @@ -23,24 +23,36 @@ - Dialogues: The dialogues class keeps track of all dialogues. """ -from typing import cast +from typing import Optional, cast -from aea.helpers.dialogue.base import Dialogue as BaseDialogue +from aea.helpers.dialogue.base import Dialogue, DialogueLabel +from aea.mail.base import Address from aea.protocols.base import Message +from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue +from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues +from aea.protocols.signing.dialogues import SigningDialogue as BaseSigningDialogue +from aea.protocols.signing.dialogues import SigningDialogues as BaseSigningDialogues from aea.skills.base import Model -from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues +from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue +from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue as BaseOefSearchDialogue, +) +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogues as BaseOefSearchDialogues, +) from packages.fetchai.skills.tac_negotiation.helpers import ( DEMAND_DATAMODEL_NAME, SUPPLY_DATAMODEL_NAME, ) -Dialogue = FipaDialogue +DefaultDialogue = BaseDefaultDialogue -class Dialogues(Model, FipaDialogues): +class DefaultDialogues(Model, BaseDefaultDialogues): """The dialogues class keeps track of all dialogues.""" def __init__(self, **kwargs) -> None: @@ -50,10 +62,51 @@ def __init__(self, **kwargs) -> None: :return: None """ Model.__init__(self, **kwargs) - FipaDialogues.__init__(self, self.context.agent_address) + BaseDefaultDialogues.__init__(self, self.context.agent_address) @staticmethod - def role_from_first_message(message: Message) -> BaseDialogue.Role: + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> DefaultDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = DefaultDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +FipaDialogue = BaseFipaDialogue + + +class FipaDialogues(Model, BaseFipaDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + BaseFipaDialogues.__init__(self, self.context.agent_address) + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: """ Infer the role of the agent from an incoming or outgoing first message @@ -85,3 +138,124 @@ def role_from_first_message(message: Message) -> BaseDialogue.Role: ) # the agent is querying for demand/buyers (this agent is sending the CFP so it is the seller) role = FipaDialogue.Role.SELLER if is_seller else FipaDialogue.Role.BUYER return role + + +class OefSearchDialogue(BaseOefSearchDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__( + self, + dialogue_label: DialogueLabel, + agent_address: Address, + role: Dialogue.Role, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param agent_address: the address of the agent for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + + :return: None + """ + BaseOefSearchDialogue.__init__( + self, dialogue_label=dialogue_label, agent_address=agent_address, role=role + ) + self._is_seller_search = None # type: Optional[bool] + + @property + def is_seller_search(self) -> bool: + """Get if it is a seller search.""" + assert self._is_seller_search is not None, "is_seller_search not set!" + return self._is_seller_search + + @is_seller_search.setter + def is_seller_search(self, is_seller_search: bool) -> None: + """Set is_seller_search.""" + assert self._is_seller_search is None, "is_seller_search already set!" + self._is_seller_search = is_seller_search + + +class OefSearchDialogues(Model, BaseOefSearchDialogues): + """This class keeps track of all oef_search dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseOefSearchDialogues.__init__( + self, self.context.agent_address + "_" + str(self.context.skill_id) + ) + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseOefSearchDialogue.Role.AGENT + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> OefSearchDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = OefSearchDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue + + +SigningDialogue = BaseSigningDialogue + + +class SigningDialogues(Model, BaseSigningDialogues): + """This class keeps track of all oef_search dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Model.__init__(self, **kwargs) + BaseSigningDialogues.__init__( + self, self.context.agent_address + "_" + str(self.context.skill_id) + ) + + @staticmethod + def role_from_first_message(message: Message) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :return: The role of the agent + """ + return BaseSigningDialogue.Role.SKILL + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> SigningDialogue: + """ + Create an instance of fipa dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = SigningDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/packages/fetchai/skills/tac_negotiation/handlers.py b/packages/fetchai/skills/tac_negotiation/handlers.py index e7d4dc77db..b6375b928d 100644 --- a/packages/fetchai/skills/tac_negotiation/handlers.py +++ b/packages/fetchai/skills/tac_negotiation/handlers.py @@ -21,9 +21,9 @@ import pprint import time -from typing import Dict, Optional, Tuple, cast +from typing import Optional, Tuple, cast -from aea.configurations.base import ProtocolId, PublicId +from aea.configurations.base import ProtocolId from aea.helpers.dialogue.base import DialogueLabel from aea.helpers.search.models import Query from aea.protocols.base import Message @@ -33,13 +33,20 @@ from packages.fetchai.protocols.fipa.message import FipaMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage -from packages.fetchai.skills.tac_negotiation.dialogues import Dialogue, Dialogues -from packages.fetchai.skills.tac_negotiation.search import Search +from packages.fetchai.skills.tac_negotiation.dialogues import ( + DefaultDialogues, + FipaDialogue, + FipaDialogues, + OefSearchDialogue, + OefSearchDialogues, + SigningDialogue, + SigningDialogues, +) from packages.fetchai.skills.tac_negotiation.strategy import Strategy from packages.fetchai.skills.tac_negotiation.transactions import Transactions -class FIPANegotiationHandler(Handler): +class FipaNegotiationHandler(Handler): """This class implements the fipa negotiation handler.""" SUPPORTED_PROTOCOL = FipaMessage.protocol_id # type: Optional[ProtocolId] @@ -62,16 +69,14 @@ def handle(self, message: Message) -> None: fipa_msg = cast(FipaMessage, message) # recover dialogue - dialogues = cast(Dialogues, self.context.dialogues) - fipa_dialogue = cast(Dialogue, dialogues.update(fipa_msg)) + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + fipa_dialogue = cast(FipaDialogue, fipa_dialogues.update(fipa_msg)) if fipa_dialogue is None: self._handle_unidentified_dialogue(fipa_msg) return self.context.logger.debug( - "[{}]: Handling FipaMessage of performative={}".format( - self.context.agent_name, fipa_msg.performative - ) + "handling FipaMessage of performative={}".format(fipa_msg.performative) ) if fipa_msg.performative == FipaMessage.Performative.CFP: self._on_cfp(fipa_msg, fipa_dialogue) @@ -92,7 +97,7 @@ def teardown(self) -> None: """ pass - def _handle_unidentified_dialogue(self, msg: FipaMessage) -> None: + def _handle_unidentified_dialogue(self, fipa_msg: FipaMessage) -> None: """ Handle an unidentified dialogue. @@ -103,26 +108,28 @@ def _handle_unidentified_dialogue(self, msg: FipaMessage) -> None: :return: None """ self.context.logger.info( - "[{}]: unidentified dialogue.".format(self.context.agent_name) + "received invalid fipa message={}, unidentified dialogue.".format(fipa_msg) ) + default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) default_msg = DefaultMessage( - dialogue_reference=("", ""), - message_id=1, - target=0, performative=DefaultMessage.Performative.ERROR, + dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, error_msg="Invalid dialogue.", - error_data={"fipa_message": msg.encode()}, + error_data={"fipa_message": fipa_msg.encode()}, ) - default_msg.counterparty = msg.counterparty + default_msg.counterparty = fipa_msg.counterparty + assert ( + default_dialogues.update(default_msg) is not None + ), "DefaultDialogue not constructed." self.context.outbox.put_message(message=default_msg) - def _on_cfp(self, cfp: FipaMessage, dialogue: Dialogue) -> None: + def _on_cfp(self, cfp: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a CFP. :param cfp: the fipa message containing the CFP - :param dialogue: the dialogue + :param fipa_dialogue: the fipa_dialogue :return: None """ @@ -130,19 +137,18 @@ def _on_cfp(self, cfp: FipaMessage, dialogue: Dialogue) -> None: query = cast(Query, cfp.query) strategy = cast(Strategy, self.context.strategy) proposal_description = strategy.get_proposal_for_query( - query, cast(Dialogue.Role, dialogue.role) + query, cast(FipaDialogue.Role, fipa_dialogue.role) ) if proposal_description is None: self.context.logger.debug( - "[{}]: sending to {} a Decline{}".format( - self.context.agent_name, - dialogue.dialogue_label.dialogue_opponent_addr[-5:], + "sending to {} a Decline{}".format( + fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], pprint.pformat( { "msg_id": new_msg_id, "dialogue_reference": cfp.dialogue_reference, - "origin": dialogue.dialogue_label.dialogue_opponent_addr[ + "origin": fipa_dialogue.dialogue_label.dialogue_opponent_addr[ -5: ], "target": cfp.target, @@ -153,34 +159,33 @@ def _on_cfp(self, cfp: FipaMessage, dialogue: Dialogue) -> None: fipa_msg = FipaMessage( performative=FipaMessage.Performative.DECLINE, message_id=new_msg_id, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, target=cfp.message_id, ) - dialogues = cast(Dialogues, self.context.dialogues) - dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_CFP, dialogue.is_self_initiated + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + fipa_dialogues.dialogue_stats.add_dialogue_endstate( + FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) else: transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.generate_transaction_message( + signing_msg = transactions.generate_signing_message( SigningMessage.Performative.SIGN_MESSAGE, proposal_description, - dialogue.dialogue_label, - cast(Dialogue.Role, dialogue.role), + fipa_dialogue.dialogue_label, + cast(FipaDialogue.Role, fipa_dialogue.role), self.context.agent_address, ) transactions.add_pending_proposal( - dialogue.dialogue_label, new_msg_id, transaction_msg + fipa_dialogue.dialogue_label, new_msg_id, signing_msg ) self.context.logger.info( - "[{}]: sending to {} a Propose {}".format( - self.context.agent_name, - dialogue.dialogue_label.dialogue_opponent_addr[-5:], + "sending to {} a Propose {}".format( + fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], pprint.pformat( { "msg_id": new_msg_id, "dialogue_reference": cfp.dialogue_reference, - "origin": dialogue.dialogue_label.dialogue_opponent_addr[ + "origin": fipa_dialogue.dialogue_label.dialogue_opponent_addr[ -5: ], "target": cfp.message_id, @@ -192,153 +197,143 @@ def _on_cfp(self, cfp: FipaMessage, dialogue: Dialogue) -> None: fipa_msg = FipaMessage( performative=FipaMessage.Performative.PROPOSE, message_id=new_msg_id, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, target=cfp.message_id, proposal=proposal_description, ) fipa_msg.counterparty = cfp.counterparty - dialogue.update(fipa_msg) + fipa_dialogue.update(fipa_msg) self.context.outbox.put_message(message=fipa_msg) - def _on_propose(self, propose: FipaMessage, dialogue: Dialogue) -> None: + def _on_propose(self, propose: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a Propose. :param propose: the message containing the Propose - :param dialogue: the dialogue + :param fipa_dialogue: the fipa_dialogue :return: None """ new_msg_id = propose.message_id + 1 strategy = cast(Strategy, self.context.strategy) proposal_description = propose.proposal - self.context.logger.debug( - "[{}]: on Propose as {}.".format(self.context.agent_name, dialogue.role) - ) + self.context.logger.debug("on Propose as {}.".format(fipa_dialogue.role)) transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.generate_transaction_message( + signing_msg = transactions.generate_signing_message( SigningMessage.Performative.SIGN_MESSAGE, proposal_description, - dialogue.dialogue_label, - cast(Dialogue.Role, dialogue.role), + fipa_dialogue.dialogue_label, + cast(FipaDialogue.Role, fipa_dialogue.role), self.context.agent_address, ) if strategy.is_profitable_transaction( - transaction_msg, role=cast(Dialogue.Role, dialogue.role) + signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): self.context.logger.info( - "[{}]: Accepting propose (as {}).".format( - self.context.agent_name, dialogue.role - ) + "accepting propose (as {}).".format(fipa_dialogue.role) ) transactions.add_locked_tx( - transaction_msg, role=cast(Dialogue.Role, dialogue.role) + signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) transactions.add_pending_initial_acceptance( - dialogue.dialogue_label, new_msg_id, transaction_msg + fipa_dialogue.dialogue_label, new_msg_id, signing_msg ) fipa_msg = FipaMessage( performative=FipaMessage.Performative.ACCEPT, message_id=new_msg_id, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, target=propose.message_id, ) else: self.context.logger.info( - "[{}]: Declining propose (as {})".format( - self.context.agent_name, dialogue.role - ) + "declining propose (as {})".format(fipa_dialogue.role) ) fipa_msg = FipaMessage( performative=FipaMessage.Performative.DECLINE, message_id=new_msg_id, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, target=propose.message_id, ) - dialogues = cast(Dialogues, self.context.dialogues) - dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_PROPOSE, dialogue.is_self_initiated + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + fipa_dialogues.dialogue_stats.add_dialogue_endstate( + FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) fipa_msg.counterparty = propose.counterparty - dialogue.update(fipa_msg) + fipa_dialogue.update(fipa_msg) self.context.outbox.put_message(message=fipa_msg) - def _on_decline(self, decline: FipaMessage, dialogue: Dialogue) -> None: + def _on_decline(self, decline: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle a Decline. :param decline: the Decline message - :param dialogue: the dialogue + :param fipa_dialogue: the fipa_dialogue :return: None """ self.context.logger.debug( - "[{}]: on_decline: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - self.context.agent_name, + "on_decline: msg_id={}, dialogue_reference={}, origin={}, target={}".format( decline.message_id, decline.dialogue_reference, - dialogue.dialogue_label.dialogue_opponent_addr, + fipa_dialogue.dialogue_label.dialogue_opponent_addr, decline.target, ) ) target = decline.target - dialogues = cast(Dialogues, self.context.dialogues) + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) if target == 1: - dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_CFP, dialogue.is_self_initiated + fipa_dialogues.dialogue_stats.add_dialogue_endstate( + FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) elif target == 2: - dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_PROPOSE, dialogue.is_self_initiated + fipa_dialogues.dialogue_stats.add_dialogue_endstate( + FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.pop_pending_proposal( - dialogue.dialogue_label, target + signing_msg = transactions.pop_pending_proposal( + fipa_dialogue.dialogue_label, target ) elif target == 3: - dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_ACCEPT, dialogue.is_self_initiated + fipa_dialogues.dialogue_stats.add_dialogue_endstate( + FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.pop_pending_initial_acceptance( - dialogue.dialogue_label, target + signing_msg = transactions.pop_pending_initial_acceptance( + fipa_dialogue.dialogue_label, target ) - transactions.pop_locked_tx(transaction_msg) + transactions.pop_locked_tx(signing_msg) - def _on_accept(self, accept: FipaMessage, dialogue: Dialogue) -> None: + def _on_accept(self, accept: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ Handle an Accept. :param accept: the Accept message - :param dialogue: the dialogue + :param fipa_dialogue: the fipa_dialogue :return: None """ self.context.logger.debug( - "[{}]: on_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - self.context.agent_name, + "on_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( accept.message_id, accept.dialogue_reference, - dialogue.dialogue_label.dialogue_opponent_addr, + fipa_dialogue.dialogue_label.dialogue_opponent_addr, accept.target, ) ) new_msg_id = accept.message_id + 1 transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.pop_pending_proposal( - dialogue.dialogue_label, accept.target + signing_msg = transactions.pop_pending_proposal( + fipa_dialogue.dialogue_label, accept.target ) strategy = cast(Strategy, self.context.strategy) if strategy.is_profitable_transaction( - transaction_msg, role=cast(Dialogue.Role, dialogue.role) + signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): self.context.logger.info( - "[{}]: locking the current state (as {}).".format( - self.context.agent_name, dialogue.role - ) + "locking the current state (as {}).".format(fipa_dialogue.role) ) transactions.add_locked_tx( - transaction_msg, role=cast(Dialogue.Role, dialogue.role) + signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) if strategy.is_contract_tx: pass @@ -385,60 +380,68 @@ def _on_accept(self, accept: FipaMessage, dialogue: Dialogue) -> None: # ledger_api=self.context.ledger_apis.get_api(strategy.ledger_id), # skill_callback_id=self.context.skill_id, # skill_callback_info={ - # "dialogue_label": dialogue.dialogue_label.json + # "dialogue_label": fipa_dialogue.dialogue_label.json # }, # ) - self.context.logger.info( - "[{}]: sending tx_message={} to decison maker.".format( - self.context.agent_name, transaction_msg + else: + signing_dialogues = cast( + SigningDialogues, self.context.signing_dialogues ) - ) - self.context.decision_maker_message_queue.put(transaction_msg) + signing_dialogue = cast( + Optional[SigningDialogue], signing_dialogues.update(signing_msg) + ) + assert ( + signing_dialogue is not None + ), "Could not construct sigining dialogue." + self.context.logger.info( + "sending signing_msg={} to decison maker following ACCEPT.".format( + signing_msg + ) + ) + self.context.decision_maker_message_queue.put(signing_msg) else: self.context.logger.debug( - "[{}]: decline the Accept (as {}).".format( - self.context.agent_name, dialogue.role - ) + "decline the Accept (as {}).".format(fipa_dialogue.role) ) fipa_msg = FipaMessage( performative=FipaMessage.Performative.DECLINE, message_id=new_msg_id, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, target=accept.message_id, ) fipa_msg.counterparty = accept.counterparty - dialogue.update(fipa_msg) - dialogues = cast(Dialogues, self.context.dialogues) + fipa_dialogue.update(fipa_msg) + dialogues = cast(FipaDialogues, self.context.fipa_dialogues) dialogues.dialogue_stats.add_dialogue_endstate( - Dialogue.EndState.DECLINED_ACCEPT, dialogue.is_self_initiated + FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) self.context.outbox.put_message(message=fipa_msg) - def _on_match_accept(self, match_accept: FipaMessage, dialogue: Dialogue) -> None: + def _on_match_accept( + self, match_accept: FipaMessage, fipa_dialogue: FipaDialogue + ) -> None: """ Handle a matching Accept. :param match_accept: the MatchAccept message - :param dialogue: the dialogue + :param fipa_dialogue: the fipa_dialogue :return: None """ self.context.logger.debug( - "[{}]: on_match_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - self.context.agent_name, + "on_match_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( match_accept.message_id, match_accept.dialogue_reference, - dialogue.dialogue_label.dialogue_opponent_addr, + fipa_dialogue.dialogue_label.dialogue_opponent_addr, match_accept.target, ) ) - if (match_accept.info.get("tx_signature") is not None) and ( - match_accept.info.get("tx_id") is not None - ): + if match_accept.info.get("signature") is not None: transactions = cast(Transactions, self.context.transactions) - transaction_msg = transactions.pop_pending_initial_acceptance( - dialogue.dialogue_label, match_accept.target + signing_msg = transactions.pop_pending_initial_acceptance( + fipa_dialogue.dialogue_label, match_accept.target ) strategy = cast(Strategy, self.context.strategy) + counterparty_signature = match_accept.info.get("signature") if strategy.is_contract_tx: pass # contract = cast(ERC1155Contract, self.context.contracts.erc1155) @@ -496,34 +499,30 @@ def _on_match_accept(self, match_accept: FipaMessage, dialogue: Dialogue) -> Non # }, # ) else: - transaction_msg.set( - "skill_callback_ids", - [PublicId.from_str("fetchai/tac_participation:0.4.0")], - ) - transaction_msg.set( + signing_msg.set( "skill_callback_info", { - **transaction_msg.skill_callback_info, - **{ - "tx_counterparty_signature": match_accept.info.get( - "tx_signature" - ), - "tx_counterparty_id": match_accept.info.get("tx_id"), - }, + **signing_msg.skill_callback_info, + **{"counterparty_signature": counterparty_signature}, }, ) - self.context.logger.info( - "[{}]: sending tx_message={} to decison maker.".format( - self.context.agent_name, transaction_msg + signing_dialogues = cast( + SigningDialogues, self.context.signing_dialogues ) - ) - self.context.decision_maker_message_queue.put(transaction_msg) - else: - self.context.logger.warning( - "[{}]: match_accept did not contain tx_signature and tx_id!".format( - self.context.agent_name + signing_dialogue = cast( + Optional[SigningDialogue], signing_dialogues.update(signing_msg) ) - ) + assert ( + signing_dialogue is not None + ), "Could not construct sigining dialogue." + self.context.logger.info( + "sending signing_msg={} to decison maker following MATCH_ACCEPT.".format( + signing_msg + ) + ) + self.context.decision_maker_message_queue.put(signing_msg) + else: + self.context.logger.warning("match_accept did not contain signature!") class SigningHandler(Handler): @@ -546,139 +545,262 @@ def handle(self, message: Message) -> None: :param message: the message :return: None """ - tx_message = cast(SigningMessage, message) - if tx_message.performative == SigningMessage.Performative.SIGNED_MESSAGE: + signing_msg = cast(SigningMessage, message) + + # recover dialogue + signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) + signing_dialogue = cast( + Optional[SigningDialogue], signing_dialogues.update(signing_msg) + ) + if signing_dialogue is None: + self._handle_unidentified_dialogue(signing_msg) + return + + # handle message + if signing_msg.performative is SigningMessage.Performative.SIGNED_MESSAGE: + self._handle_signed_message(signing_msg, signing_dialogue) + elif signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: + self._handle_signed_transaction(signing_msg, signing_dialogue) + elif signing_msg.performative is SigningMessage.Performative.ERROR: + self._handle_error(signing_msg, signing_dialogue) + else: + self._handle_invalid(signing_msg, signing_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: + """ + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.info( + "received invalid signing message={}, unidentified dialogue.".format( + signing_msg + ) + ) + + def _handle_signed_message( + self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue + ) -> None: + """ + Handle an oef search message. + + :param signing_msg: the signing message + :param signing_dialogue: the dialogue + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + if strategy.is_contract_tx: + self.context.logger.warning( + "signed message handler only for non-contract case." + ) + return None + self.context.logger.info("message signed by decision maker.") + dialogue_label = DialogueLabel.from_str( + cast(str, signing_msg.skill_callback_info.get("dialogue_label")) + ) + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + fipa_dialogue = fipa_dialogues.dialogues[dialogue_label] + last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) + if ( + last_fipa_message is not None + and last_fipa_message.performative == FipaMessage.Performative.ACCEPT + ): + fipa_msg = FipaMessage( + performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + message_id=last_fipa_message.message_id + 1, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, + target=last_fipa_message.message_id, + info={"signature": signing_msg.signed_message.body}, + ) + fipa_msg.counterparty = last_fipa_message.counterparty + fipa_dialogue.update(fipa_msg) + self.context.outbox.put_message(message=fipa_msg) self.context.logger.info( - "[{}]: transaction confirmed by decision maker".format( - self.context.agent_name + "sending match accept to {}.".format( + fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], ) ) - strategy = cast(Strategy, self.context.strategy) - dialogue_label = DialogueLabel.from_json( - cast( - Dict[str, str], tx_message.skill_callback_info.get("dialogue_label") - ) + elif ( + last_fipa_message is not None + and last_fipa_message.performative + == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM + ): + counterparty_signature = cast( + str, signing_msg.skill_callback_info.get("counterparty_signature") ) - dialogues = cast(Dialogues, self.context.dialogues) - dialogue = dialogues.dialogues[dialogue_label] - last_fipa_message = cast(FipaMessage, dialogue.last_incoming_message) - if ( - last_fipa_message is not None - and last_fipa_message.performative == FipaMessage.Performative.ACCEPT - ): - self.context.logger.info( - "[{}]: sending match accept to {}.".format( - self.context.agent_name, - dialogue.dialogue_label.dialogue_opponent_addr[-5:], - ) + if counterparty_signature is not None: + last_signing_msg = cast( + Optional[SigningMessage], signing_dialogue.last_outgoing_message ) - fipa_msg = FipaMessage( - performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, - message_id=last_fipa_message.message_id + 1, - dialogue_reference=dialogue.dialogue_label.dialogue_reference, - target=last_fipa_message.message_id, - info={ - "tx_signature": tx_message.signed_transaction, - "tx_id": tx_message.dialogue_reference[0], - }, + assert ( + last_signing_msg is not None + ), "Could not recover last signing message." + tx_id = ( + last_signing_msg.terms.sender_hash + + "_" + + last_signing_msg.terms.counterparty_hash ) - fipa_msg.counterparty = dialogue.dialogue_label.dialogue_opponent_addr - dialogue.update(fipa_msg) - self.context.outbox.put_message(message=fipa_msg) - elif ( - last_fipa_message is not None - and last_fipa_message.performative - == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM - and strategy.is_contract_tx - ): - self.context.logger.info( - "[{}]: sending atomic swap tx to ledger.".format( - self.context.agent_name - ) + if "transactions" not in self.context.shared_state.keys(): + self.context.shared_state["transactions"] = {} + self.context.shared_state["transactions"][tx_id] = { + "terms": last_signing_msg.terms, + "sender_signature": signing_msg.signed_message.body, + "counterparty_signature": counterparty_signature, + } + self.context.logger.info("sending transaction to controller.") + else: + self.context.logger.warning( + "transaction has no counterparty signature!" + ) + else: + self.context.logger.warning( + "last message should be of performative accept or match accept." + ) + + def _handle_signed_transaction( + self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue + ) -> None: + """ + Handle an oef search message. + + :param signing_msg: the signing message + :param signing_dialogue: the dialogue + :return: None + """ + strategy = cast(Strategy, self.context.strategy) + if not strategy.is_contract_tx: + self.context.logger.warning( + "signed transaction handler only for contract case." + ) + return None + self.context.logger.info("transaction signed by decision maker.") + dialogue_label = DialogueLabel.from_str( + cast(str, signing_msg.skill_callback_info.get("dialogue_label")) + ) + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + fipa_dialogue = fipa_dialogues.dialogues[dialogue_label] + last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) + if ( + last_fipa_message is not None + and last_fipa_message.performative == FipaMessage.Performative.ACCEPT + ): + self.context.logger.info( + "sending match accept to {}.".format( + fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], ) - tx_signed = tx_message.signed_transaction - tx_digest = self.context.ledger_apis.get_api( + ) + fipa_msg = FipaMessage( + performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + message_id=last_fipa_message.message_id + 1, + dialogue_reference=fipa_dialogue.dialogue_label.dialogue_reference, + target=last_fipa_message.message_id, + info={ + "tx_signature": signing_msg.signed_transaction, + "tx_id": signing_msg.dialogue_reference[0], + }, + ) + fipa_msg.counterparty = fipa_dialogue.dialogue_label.dialogue_opponent_addr + fipa_dialogue.update(fipa_msg) + self.context.outbox.put_message(message=fipa_msg) + elif ( + last_fipa_message is not None + and last_fipa_message.performative + == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM + ): + self.context.logger.info("sending atomic swap tx to ledger.") + tx_signed = signing_msg.signed_transaction + tx_digest = self.context.ledger_apis.get_api( + strategy.ledger_id + ).send_signed_transaction(tx_signed=tx_signed) + # TODO; handle case when no tx_digest returned and remove loop + assert tx_digest is not None, "Error when submitting tx." + self.context.logger.info("tx_digest={}.".format(tx_digest)) + count = 0 + while ( + not self.context.ledger_apis.get_api( strategy.ledger_id - ).send_signed_transaction(tx_signed=tx_signed) - # TODO; handle case when no tx_digest returned and remove loop - assert tx_digest is not None, "Error when submitting tx." + ).is_transaction_settled(tx_digest) + and count < 20 + ): self.context.logger.info( - "[{}]: tx_digest={}.".format(self.context.agent_name, tx_digest) + "waiting for tx to confirm. Sleeping for 3 seconds ..." ) - count = 0 - while ( - not self.context.ledger_apis.get_api( - strategy.ledger_id - ).is_transaction_settled(tx_digest) - and count < 20 - ): - self.context.logger.info( - "[{}]: waiting for tx to confirm. Sleeping for 3 seconds ...".format( - self.context.agent_name - ) - ) - time.sleep(3.0) - count += 1 - tx_receipt = self.context.ledger_apis.get_api( - strategy.ledger_id - ).get_transaction_receipt(tx_digest=tx_digest) - if tx_receipt is None: - self.context.logger.info( - "[{}]: Failed to get tx receipt for atomic swap.".format( - self.context.agent_name - ) - ) - elif tx_receipt.status != 1: - self.context.logger.info( - "[{}]: Failed to conduct atomic swap.".format( - self.context.agent_name - ) - ) - else: - self.context.logger.info( - "[{}]: Successfully conducted atomic swap. Transaction digest: {}".format( - self.context.agent_name, tx_digest - ) - ) - # contract = cast(ERC1155Contract, self.context.contracts.erc1155) - # result = contract.get_balances( - # address=self.context.agent_address, - # token_ids=[ - # int(key) - # for key in tx_message.terms.quantities_by_good_id.keys() - # ] - # + [ - # int(key) - # for key in tx_message.terms.amount_by_currency_id.keys() - # ], - # ) - result = 0 - self.context.logger.info( - "[{}]: Current balances: {}".format( - self.context.agent_name, result - ) - ) + time.sleep(3.0) + count += 1 + tx_receipt = self.context.ledger_apis.get_api( + strategy.ledger_id + ).get_transaction_receipt(tx_digest=tx_digest) + if tx_receipt is None: + self.context.logger.info("failed to get tx receipt for atomic swap.") + elif tx_receipt.status != 1: + self.context.logger.info("failed to conduct atomic swap.") else: - self.context.logger.warning( - "[{}]: last message should be of performative accept or match accept.".format( - self.context.agent_name + self.context.logger.info( + "successfully conducted atomic swap. Transaction digest: {}".format( + tx_digest ) ) + # contract = cast(ERC1155Contract, self.context.contracts.erc1155) + # result = contract.get_balances( + # address=self.context.agent_address, + # token_ids=[ + # int(key) + # for key in tx_message.terms.quantities_by_good_id.keys() + # ] + # + [ + # int(key) + # for key in tx_message.terms.amount_by_currency_id.keys() + # ], + # ) + result = 0 + self.context.logger.info("current balances: {}".format(result)) else: - self.context.logger.info( - "[{}]: transaction was not successful.".format(self.context.agent_name) + self.context.logger.warning( + "last message should be of performative accept or match accept." ) - def teardown(self) -> None: + def _handle_error( + self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue + ) -> None: """ - Implement the handler teardown. + Handle an oef search message. + :param signing_msg: the signing message + :param signing_dialogue: the dialogue :return: None """ - pass + self.context.logger.info( + "transaction signing was not successful. Error_code={} in dialogue={}".format( + signing_msg.error_code, signing_dialogue + ) + ) + def _handle_invalid( + self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue + ) -> None: + """ + Handle an oef search message. -class OEFSearchHandler(Handler): + :param signing_msg: the signing message + :param signing_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle signing message of performative={} in dialogue={}.".format( + signing_msg.performative, signing_dialogue + ) + ) + + +class OefSearchHandler(Handler): """This class implements the oef search handler.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id # type: Optional[ProtocolId] @@ -698,24 +820,26 @@ def handle(self, message: Message) -> None: :param message: the message :return: None """ - # convenience representations - oef_msg = cast(OefSearchMessage, message) - - if oef_msg.performative is OefSearchMessage.Performative.SEARCH_RESULT: - agents = list(oef_msg.agents) - search_id = int(oef_msg.dialogue_reference[0]) - search = cast(Search, self.context.search) - if self.context.agent_address in agents: - agents.remove(self.context.agent_address) - agents_less_self = tuple(agents) - if search_id in search.ids_for_sellers: - self._handle_search( - agents_less_self, search_id, is_searching_for_sellers=True - ) - elif search_id in search.ids_for_buyers: - self._handle_search( - agents_less_self, search_id, is_searching_for_sellers=False - ) + oef_search_msg = cast(OefSearchMessage, message) + + # recover dialogue + oef_search_dialogues = cast( + OefSearchDialogues, self.context.oef_search_dialogues + ) + oef_search_dialogue = cast( + Optional[OefSearchDialogue], oef_search_dialogues.update(oef_search_msg) + ) + if oef_search_dialogue is None: + self._handle_unidentified_dialogue(oef_search_msg) + return + + # handle message + if oef_search_msg.performative == OefSearchMessage.Performative.SEARCH_RESULT: + self._on_search_result(oef_search_msg, oef_search_dialogue) + elif oef_search_msg.performative == OefSearchMessage.Performative.OEF_ERROR: + self._on_oef_error(oef_search_msg, oef_search_dialogue) + else: + self._handle_invalid(oef_search_msg, oef_search_dialogue) def teardown(self) -> None: """ @@ -725,6 +849,55 @@ def teardown(self) -> None: """ pass + def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> None: + """ + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.warning( + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg + ) + ) + + def _on_oef_error( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an OEF error message. + + :param oef_search_msg: the oef search msg + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( + oef_search_msg.dialogue_reference, oef_search_msg.oef_error_operation, + ) + ) + + def _on_search_result( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Split the search results from the OEF search node. + + :param oef_search_msg: the search result + :param oef_search_dialogue: the dialogue + :return: None + """ + agents = list(oef_search_msg.agents) + search_id = int(oef_search_msg.dialogue_reference[0]) + if self.context.agent_address in agents: + agents.remove(self.context.agent_address) + agents_less_self = tuple(agents) + self._handle_search( + agents_less_self, + search_id, + is_searching_for_sellers=oef_search_dialogue.is_seller_search, + ) + def _handle_search( self, agents: Tuple[str, ...], search_id: int, is_searching_for_sellers: bool ) -> None: @@ -738,38 +911,45 @@ def _handle_search( searched_for = "sellers" if is_searching_for_sellers else "buyers" if len(agents) > 0: self.context.logger.info( - "[{}]: found potential {} agents={} on search_id={}.".format( - self.context.agent_name, - searched_for, - list(map(lambda x: x[-5:], agents)), - search_id, + "found potential {} agents={} on search_id={}.".format( + searched_for, list(map(lambda x: x[-5:], agents)), search_id, ) ) strategy = cast(Strategy, self.context.strategy) - dialogues = cast(Dialogues, self.context.dialogues) - query = strategy.get_own_services_query( - is_searching_for_sellers, is_search_query=False - ) + fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) + query = strategy.get_own_services_query(is_searching_for_sellers) for opponent_addr in agents: self.context.logger.info( - "[{}]: sending CFP to agent={}".format( - self.context.agent_name, opponent_addr[-5:] - ) + "sending CFP to agent={}".format(opponent_addr[-5:]) ) fipa_msg = FipaMessage( - message_id=Dialogue.STARTING_MESSAGE_ID, - dialogue_reference=dialogues.new_self_initiated_dialogue_reference(), + dialogue_reference=fipa_dialogues.new_self_initiated_dialogue_reference(), performative=FipaMessage.Performative.CFP, - target=Dialogue.STARTING_TARGET, query=query, ) fipa_msg.counterparty = opponent_addr - dialogues.update(fipa_msg) + fipa_dialogues.update(fipa_msg) self.context.outbox.put_message(message=fipa_msg) else: self.context.logger.info( - "[{}]: found no {} agents on search_id={}, continue searching.".format( - self.context.agent_name, searched_for, search_id + "found no {} agents on search_id={}, continue searching.".format( + searched_for, search_id ) ) + + def _handle_invalid( + self, oef_search_msg: OefSearchMessage, oef_search_dialogue: OefSearchDialogue + ) -> None: + """ + Handle an oef search message. + + :param oef_search_msg: the oef search message + :param oef_search_dialogue: the dialogue + :return: None + """ + self.context.logger.warning( + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, + ) + ) diff --git a/packages/fetchai/skills/tac_negotiation/helpers.py b/packages/fetchai/skills/tac_negotiation/helpers.py index f8874798c4..ce8a30f6d2 100644 --- a/packages/fetchai/skills/tac_negotiation/helpers.py +++ b/packages/fetchai/skills/tac_negotiation/helpers.py @@ -19,12 +19,9 @@ """This class contains the helpers for FIPA negotiation.""" -import collections import copy from typing import Dict, List, Union, cast -from web3 import Web3 - from aea.helpers.search.models import ( Attribute, Constraint, @@ -35,11 +32,9 @@ Or, Query, ) -from aea.mail.base import Address SUPPLY_DATAMODEL_NAME = "supply" DEMAND_DATAMODEL_NAME = "demand" -PREFIX = "pre_" def _build_goods_datamodel(good_ids: List[str], is_supply: bool) -> DataModel: @@ -54,67 +49,55 @@ def _build_goods_datamodel(good_ids: List[str], is_supply: bool) -> DataModel: good_quantities_attributes = [ Attribute(good_id, int, True, "A good on offer.") for good_id in good_ids ] + ledger_id_attribute = Attribute( + "ledger_id", str, True, "The ledger for transacting." + ) currency_attribute = Attribute( "currency_id", str, True, "The currency for pricing and transacting the goods." ) price_attribute = Attribute( "price", int, False, "The price of the goods in the currency." ) - seller_tx_fee_attribute = Attribute( - "seller_tx_fee", - int, - False, - "The transaction fee payable by the seller in the currency.", - ) - buyer_tx_fee_attribute = Attribute( - "buyer_tx_fee", - int, - False, - "The transaction fee payable by the buyer in the currency.", + fee_attribute = Attribute( + "fee", int, False, "The transaction fee payable by the buyer in the currency.", ) - tx_nonce_attribute = Attribute( - "tx_nonce", str, False, "The nonce to distinguish identical descriptions." + nonce_attribute = Attribute( + "nonce", str, False, "The nonce to distinguish identical descriptions." ) description = SUPPLY_DATAMODEL_NAME if is_supply else DEMAND_DATAMODEL_NAME attributes = good_quantities_attributes + [ + ledger_id_attribute, currency_attribute, price_attribute, - seller_tx_fee_attribute, - buyer_tx_fee_attribute, - tx_nonce_attribute, + fee_attribute, + nonce_attribute, ] data_model = DataModel(description, attributes) return data_model def build_goods_description( - good_id_to_quantities: Dict[str, int], + quantities_by_good_id: Dict[str, int], currency_id: str, + ledger_id: str, is_supply: bool, - is_search_description: bool, ) -> Description: """ Get the service description (good quantities supplied or demanded and their price). - :param good_id_to_quantities: a dictionary mapping the ids of the goods to the quantities. + :param quantities_by_good_id: a dictionary mapping the ids of the goods to the quantities. :param currency_id: the currency used for pricing and transacting. + :param ledger_id: the ledger used for transacting. :param is_supply: True if the description is indicating supply, False if it's indicating demand. - :param is_search_description: Whether or not the description is used for search :return: the description to advertise on the Service Directory. """ - _good_id_to_quantities = copy.copy(good_id_to_quantities) - if is_search_description: - # the OEF does not accept attribute names consisting of integers only - _good_id_to_quantities = { - PREFIX + good_id: quantity - for good_id, quantity in _good_id_to_quantities.items() - } data_model = _build_goods_datamodel( - good_ids=list(_good_id_to_quantities.keys()), is_supply=is_supply + good_ids=list(quantities_by_good_id.keys()), is_supply=is_supply ) - values = cast(Dict[str, Union[int, str]], _good_id_to_quantities) + values = cast(Dict[str, Union[int, str]], copy.copy(quantities_by_good_id)) values.update({"currency_id": currency_id}) + values.update({"ledger_id": ledger_id}) desc = Description(values, data_model=data_model) return desc @@ -122,8 +105,8 @@ def build_goods_description( def build_goods_query( good_ids: List[str], currency_id: str, + ledger_id: str, is_searching_for_sellers: bool, - is_search_query: bool, ) -> Query: """ Build buyer or seller search query. @@ -141,119 +124,22 @@ def build_goods_query( :param good_ids: the list of good ids to put in the query :param currency_id: the currency used for pricing and transacting. + :param ledger_id: the ledger used for transacting. :param is_searching_for_sellers: Boolean indicating whether the query is for sellers (supply) or buyers (demand). - :param is_search_query: whether or not the query is used for search on OEF :return: the query """ - if is_search_query: - # the OEF does not accept attribute names consisting of integers only - good_ids = [PREFIX + good_id for good_id in good_ids] - data_model = _build_goods_datamodel( good_ids=good_ids, is_supply=is_searching_for_sellers ) constraints = [Constraint(good_id, ConstraintType(">=", 1)) for good_id in good_ids] constraints.append(Constraint("currency_id", ConstraintType("==", currency_id))) + constraints.append(Constraint("ledger_id", ConstraintType("==", ledger_id))) constraint_expr = cast(List[ConstraintExpr], constraints) if len(good_ids) > 1: + # TODO: fix constraint_expr = [Or(constraint_expr)] query = Query(constraint_expr, model=data_model) return query - - -def _get_hash( - tx_sender_addr: Address, - tx_counterparty_addr: Address, - good_ids: List[int], - sender_supplied_quantities: List[int], - counterparty_supplied_quantities: List[int], - tx_amount: int, - tx_nonce: int, -) -> bytes: - """ - Generate a hash from transaction information. - - :param tx_sender_addr: the sender address - :param tx_counterparty_addr: the counterparty address - :param good_ids: the list of good ids - :param sender_supplied_quantities: the quantities supplied by the sender (must all be positive) - :param counterparty_supplied_quantities: the quantities supplied by the counterparty (must all be positive) - :param tx_amount: the amount of the transaction - :param tx_nonce: the nonce of the transaction - :return: the hash - """ - aggregate_hash = Web3.keccak( - b"".join( - [ - good_ids[0].to_bytes(32, "big"), - sender_supplied_quantities[0].to_bytes(32, "big"), - counterparty_supplied_quantities[0].to_bytes(32, "big"), - ] - ) - ) - for idx, good_id in enumerate(good_ids): - if not idx == 0: - aggregate_hash = Web3.keccak( - b"".join( - [ - aggregate_hash, - good_id.to_bytes(32, "big"), - sender_supplied_quantities[idx].to_bytes(32, "big"), - counterparty_supplied_quantities[idx].to_bytes(32, "big"), - ] - ) - ) - - m_list = [] # type: List[bytes] - m_list.append(tx_sender_addr.encode("utf-8")) - m_list.append(tx_counterparty_addr.encode("utf-8")) - m_list.append(aggregate_hash) - m_list.append(tx_amount.to_bytes(32, "big")) - m_list.append(tx_nonce.to_bytes(32, "big")) - return Web3.keccak(b"".join(m_list)) - - -def tx_hash_from_values( - tx_sender_addr: str, - tx_counterparty_addr: str, - tx_quantities_by_good_id: Dict[str, int], - tx_amount_by_currency_id: Dict[str, int], - tx_nonce: int, -) -> bytes: - """ - Get the hash for a transaction based on the transaction message. - - :param tx_message: the transaction message - :return: the hash - """ - _tx_quantities_by_good_id = { - int(good_id): quantity for good_id, quantity in tx_quantities_by_good_id.items() - } # type: Dict[int, int] - ordered = collections.OrderedDict(sorted(_tx_quantities_by_good_id.items())) - good_uids = [] # type: List[int] - sender_supplied_quantities = [] # type: List[int] - counterparty_supplied_quantities = [] # type: List[int] - for good_uid, quantity in ordered.items(): - good_uids.append(good_uid) - if quantity >= 0: - sender_supplied_quantities.append(quantity) - counterparty_supplied_quantities.append(0) - else: - sender_supplied_quantities.append(0) - counterparty_supplied_quantities.append(-quantity) - assert len(tx_amount_by_currency_id) == 1 - for amount in tx_amount_by_currency_id.values(): - tx_amount = amount if amount >= 0 else 0 - tx_hash = _get_hash( - tx_sender_addr=tx_sender_addr, - tx_counterparty_addr=tx_counterparty_addr, - good_ids=good_uids, - sender_supplied_quantities=sender_supplied_quantities, - counterparty_supplied_quantities=counterparty_supplied_quantities, - tx_amount=tx_amount, - tx_nonce=tx_nonce, - ) - return tx_hash diff --git a/packages/fetchai/skills/tac_negotiation/registration.py b/packages/fetchai/skills/tac_negotiation/registration.py deleted file mode 100644 index 7b177e0ff7..0000000000 --- a/packages/fetchai/skills/tac_negotiation/registration.py +++ /dev/null @@ -1,66 +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 package contains a class representing the registration state.""" - -import datetime -from typing import Optional - -from aea.helpers.search.models import Description -from aea.skills.base import Model - - -class Registration(Model): - """This class deals with the services registration state.""" - - def __init__(self, **kwargs): - """Instantiate the search class.""" - self._update_interval = kwargs.pop("update_interval", 5) # type: int - super().__init__(**kwargs) - self._id = 0 - self.registered_goods_demanded_description = None # type: Optional[Description] - self.registered_goods_supplied_description = None # type: Optional[Description] - self._last_update_time = datetime.datetime.now() # type: datetime.datetime - - @property - def id(self) -> int: - """Get the search id.""" - return self._id - - def get_next_id(self) -> int: - """ - Generate the next search id and stores it. - - :return: a search id - """ - self._id += 1 - return self.id - - def is_time_to_update_services(self) -> bool: - """ - Check if the agent should update the service directory. - - :return: bool indicating the action - """ - now = datetime.datetime.now() - diff = now - self._last_update_time - result = diff.total_seconds() > self._update_interval - if result: - self._last_update_time = now - return result diff --git a/packages/fetchai/skills/tac_negotiation/search.py b/packages/fetchai/skills/tac_negotiation/search.py deleted file mode 100644 index d407f89c5c..0000000000 --- a/packages/fetchai/skills/tac_negotiation/search.py +++ /dev/null @@ -1,80 +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 package contains a class representing the search state.""" - -import datetime -from typing import Set - -from aea.skills.base import Model - - -class Search(Model): - """This class deals with the services search state.""" - - def __init__(self, **kwargs): - """Instantiate the search class.""" - self._search_interval = kwargs.pop("search_interval", 5) # type: int - super().__init__(**kwargs) - self._id = 0 - self._ids_for_sellers = set() # type: Set[int] - self._ids_for_buyers = set() # type: Set[int] - self._last_search_time = datetime.datetime.now() # type: datetime.datetime - - @property - def id(self) -> int: - """Get the search id.""" - return self._id - - @property - def ids_for_sellers(self) -> Set[int]: - """Get search ids for the sellers.""" - return self._ids_for_sellers - - @property - def ids_for_buyers(self) -> Set[int]: - """Get search ids for the buyers.""" - return self._ids_for_buyers - - def get_next_id(self, is_searching_for_sellers: bool) -> int: - """ - Generate the next search id and stores it. - - :param is_searching_for_sellers: whether it is a seller search - :return: a search id - """ - self._id += 1 - if is_searching_for_sellers: - self._ids_for_sellers.add(self.id) - else: - self._ids_for_buyers.add(self.id) - return self.id - - def is_time_to_search_services(self) -> bool: - """ - Check if the agent should search the service directory. - - :return: bool indicating the action - """ - now = datetime.datetime.now() - diff = now - self._last_search_time - result = diff.total_seconds() > self._search_interval - if result: - self._last_search_time = now - return result diff --git a/packages/fetchai/skills/tac_negotiation/skill.yaml b/packages/fetchai/skills/tac_negotiation/skill.yaml index 3725318581..0e10bd3a6b 100644 --- a/packages/fetchai/skills/tac_negotiation/skill.yaml +++ b/packages/fetchai/skills/tac_negotiation/skill.yaml @@ -1,63 +1,69 @@ name: tac_negotiation author: fetchai -version: 0.5.0 +version: 0.6.0 description: The tac negotiation skill implements the logic for an AEA to do fipa negotiation in the TAC. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmcgZLvHebdfocqBmbu6gJp35khs6nbdbC649jzUyS86wy - behaviours.py: QmSgtvb4rD4RZ5H2zQQqPUwBzAeoR6ZBTJ1p33YqL5XjMe - dialogues.py: QmZe9PJncaWzJ4yn9b76Mm5R93VLNxGVd5ogUWhfp8Q6km - handlers.py: QmSdEvCaP9JnfQVcEpLvnzy6c8Uva24ifbGMkr2hFy5qFZ - helpers.py: QmXa3aD15jcv3NiEAcTjqrKNHv7U1ZQfES9siknL1kLtbV - registration.py: QmexnkCCmyiFpzM9bvXNj5uQuxQ2KfBTUeMomuGN9ccP7g - search.py: QmSTtMm4sHUUhUFsQzufHjKihCEVe5CaU5MGjhzSdPUzDT - strategy.py: QmQMSPqS3TZxhQoh6SUA8u2c5BNTxYGV95DSQc4neen6Ja - transactions.py: QmTCErZmswHAx6UXfPkrredRDKVnLYhyVEBtm5ppSJpZBf + behaviours.py: QmWYDj8QSx1qYCteS7nWXKJEsh7UChNrf45HJ7rQVdQhwY + dialogues.py: QmWXhnspH2NJjV5qh7wfnNUiMfe6FGhxjeJvdTV8YoWPPT + handlers.py: QmSm9wYxLYfRbeA5NwNwDz1Az5xPeR3ACyujYRvmotztEJ + helpers.py: QmUMCBgsZ5tB24twoWjfGibb1v5uDpUBxHPtzqZbzbvyL1 + strategy.py: QmdrwXQVW49zkACzaFEbpyjX11fz7aC8riLxXamsvekR1v + transactions.py: QmZkb2GfiGHwSSQuMafEkGKF4GHiyNu6mQancZkTWS7D6F fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/fipa:0.4.0 -- fetchai/oef_search:0.3.0 -skills: [] +- fetchai/fipa:0.5.0 +- fetchai/oef_search:0.4.0 +skills: +- fetchai/tac_participation:0.5.0 behaviours: clean_up: args: tick_interval: 5.0 class_name: TransactionCleanUpBehaviour tac_negotiation: - args: {} + args: + search_interval: 5.0 class_name: GoodsRegisterAndSearchBehaviour handlers: fipa: args: {} - class_name: FIPANegotiationHandler + class_name: FipaNegotiationHandler oef: args: {} - class_name: OEFSearchHandler - transaction: + class_name: OefSearchHandler + signing: args: {} - class_name: TransactionHandler + class_name: SigningHandler models: - dialogues: + default_dialogues: args: {} - class_name: Dialogues - registration: - args: - update_interval: 5 - class_name: Registration - search: - args: - search_interval: 5 - class_name: Search + class_name: DefaultDialogues + fipa_dialogues: + args: {} + class_name: FipaDialogues + oef_search_dialogues: + args: {} + class_name: OefSearchDialogues + signing_dialogues: + args: {} + class_name: SigningDialogues strategy: args: is_contract_tx: false ledger_id: cosmos + location: + latitude: 0.127 + longitude: 51.5194 register_as: both search_for: both + search_radius: 5.0 + service_key: tac_service class_name: Strategy transactions: args: diff --git a/packages/fetchai/skills/tac_negotiation/strategy.py b/packages/fetchai/skills/tac_negotiation/strategy.py index 192d2df52a..1380ea46a3 100644 --- a/packages/fetchai/skills/tac_negotiation/strategy.py +++ b/packages/fetchai/skills/tac_negotiation/strategy.py @@ -22,13 +22,25 @@ import copy import random from enum import Enum -from typing import Dict, Optional, cast +from typing import Dict, List, Optional, Tuple, cast -from aea.helpers.search.models import Description, Query +from aea.decision_maker.default import OwnershipState, Preferences +from aea.helpers.search.generic import ( + AGENT_LOCATION_MODEL, + AGENT_REMOVE_SERVICE_MODEL, + AGENT_SET_SERVICE_MODEL, +) +from aea.helpers.search.models import ( + Constraint, + ConstraintType, + Description, + Location, + Query, +) from aea.protocols.signing.message import SigningMessage from aea.skills.base import Model -from packages.fetchai.skills.tac_negotiation.dialogues import Dialogue +from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.helpers import ( build_goods_description, build_goods_query, @@ -37,6 +49,14 @@ ROUNDING_ADJUSTMENT = 1 +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SERVICE_KEY = "tac_service" +DEFAULT_SEARCH_QUERY = { + "search_key": "tac_service", + "search_value": "generic_service", + "constraint_type": "==", +} +DEFAULT_SEARCH_RADIUS = 5.0 class Strategy(Model): @@ -69,39 +89,83 @@ def __init__(self, **kwargs) -> None: self._search_for = Strategy.SearchFor(kwargs.pop("search_for", "both")) self._is_contract_tx = kwargs.pop("is_contract_tx", False) self._ledger_id = kwargs.pop("ledger_id", "ethereum") - super().__init__(**kwargs) - @property - def is_registering_as_seller(self) -> bool: - """Check if the agent registers as a seller on the OEF service directory.""" - return ( - self._register_as == Strategy.RegisterAs.SELLER - or self._register_as == Strategy.RegisterAs.BUYER - ) + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = { + "location": Location(location["longitude"], location["latitude"]) + } + service_key = kwargs.pop("service_key", DEFAULT_SERVICE_KEY) + self._set_service_data = {"key": service_key, "value": self._register_as.value} + self._remove_service_data = {"key": service_key} + self._simple_service_data = {service_key: self._register_as.value} + self._search_query = { + "search_key": service_key, + "search_value": self._search_for.value, + "constraint_type": "==", + } + self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) + + super().__init__(**kwargs) @property - def is_searching_for_sellers(self) -> bool: - """Check if the agent searches for sellers on the OEF service directory.""" + def registering_as(self) -> str: + """Get what the agent is registering as.""" return ( - self._search_for == Strategy.SearchFor.SELLERS - or self._search_for == Strategy.SearchFor.BOTH + self._register_as.value + if self._register_as != self.RegisterAs.BOTH + else "buyer and seller" ) @property - def is_registering_as_buyer(self) -> bool: - """Check if the agent registers as a buyer on the OEF service directory.""" + def searching_for(self) -> str: + """Get what the agent is searching for.""" return ( - self._register_as == Strategy.RegisterAs.BUYER - or self._register_as == Strategy.RegisterAs.BOTH + self._search_for.value + if self._search_for != self.SearchFor.BOTH + else "buyer and seller" ) @property - def is_searching_for_buyers(self) -> bool: - """Check if the agent searches for buyers on the OEF service directory.""" - return ( - self._search_for == Strategy.SearchFor.BUYERS - or self._search_for == Strategy.SearchFor.BOTH - ) + def searching_for_types(self) -> List[Tuple[bool, str]]: + """Get the types the agent is searching for.""" + result = [] # type: List[Tuple[bool, str]] + if self._search_for in [self.SearchFor.SELLERS, self.SearchFor.BOTH]: + result.append((True, "sellers")) + if self._search_for in [self.SearchFor.BUYERS, self.SearchFor.BOTH]: + result.append((False, "buyers")) + return result + + # @property + # def is_registering_as_seller(self) -> bool: + # """Check if the agent registers as a seller on the OEF service directory.""" + # return ( + # self._register_as == Strategy.RegisterAs.SELLER + # or self._register_as == Strategy.RegisterAs.BUYER + # ) + + # @property + # def is_searching_for_sellers(self) -> bool: + # """Check if the agent searches for sellers on the OEF service directory.""" + # return ( + # self._search_for == Strategy.SearchFor.SELLERS + # or self._search_for == Strategy.SearchFor.BOTH + # ) + + # @property + # def is_registering_as_buyer(self) -> bool: + # """Check if the agent registers as a buyer on the OEF service directory.""" + # return ( + # self._register_as == Strategy.RegisterAs.BUYER + # or self._register_as == Strategy.RegisterAs.BOTH + # ) + + # @property + # def is_searching_for_buyers(self) -> bool: + # """Check if the agent searches for buyers on the OEF service directory.""" + # return ( + # self._search_for == Strategy.SearchFor.BUYERS + # or self._search_for == Strategy.SearchFor.BOTH + # ) @property def is_contract_tx(self) -> bool: @@ -113,32 +177,84 @@ def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id - def get_own_service_description( - self, is_supply: bool, is_search_description: bool - ) -> Description: + def get_location_description(self) -> Description: """ - Get the description of the supplied goods (as a seller), or the demanded goods (as a buyer). + Get the location description. - :param is_supply: Boolean indicating whether it is supply or demand. - :param is_search_description: whether or not the description is for search. + :return: a description of the agent's location + """ + description = Description( + self._agent_location, data_model=AGENT_LOCATION_MODEL, + ) + return description + def get_register_service_description(self) -> Description: + """ + Get the register service description. + + :return: a description of the offered services + """ + description = Description( + self._set_service_data, data_model=AGENT_SET_SERVICE_MODEL, + ) + return description + + def get_unregister_service_description(self) -> Description: + """ + Get the unregister service description. + + :return: a description of the to be removed service + """ + description = Description( + self._remove_service_data, data_model=AGENT_REMOVE_SERVICE_MODEL, + ) + return description + + def get_location_and_service_query(self) -> Query: + """ + Get the location and service query of the agent. + + :return: the query + """ + close_to_my_service = Constraint( + "location", + ConstraintType( + "distance", (self._agent_location["location"], self._radius) + ), + ) + service_key_filter = Constraint( + self._search_query["search_key"], + ConstraintType( + self._search_query["constraint_type"], + self._search_query["search_value"], + ), + ) + query = Query([close_to_my_service, service_key_filter],) + return query + + def get_own_service_description(self, is_supply: bool) -> Description: + """ + Get the description of the supplied goods (as a seller), or the demanded goods (as a buyer). + :param is_supply: Boolean indicating whether it is supply or demand. :return: the description (to advertise on the Service Directory). """ transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller=is_supply ) - good_id_to_quantities = ( + quantities_by_good_id = ( self._supplied_goods(ownership_state_after_locks.quantities_by_good_id) if is_supply else self._demanded_goods(ownership_state_after_locks.quantities_by_good_id) ) - currency_id = list(ownership_state_after_locks.amount_by_currency_id.keys())[0] + currency_id = next( + iter(ownership_state_after_locks.amount_by_currency_id.keys()) + ) desc = build_goods_description( - good_id_to_quantities=good_id_to_quantities, + quantities_by_good_id=quantities_by_good_id, currency_id=currency_id, + ledger_id=self.ledger_id, is_supply=is_supply, - is_search_description=is_search_description, ) return desc @@ -168,9 +284,7 @@ def _demanded_goods(good_holdings: Dict[str, int]) -> Dict[str, int]: demand[good_id] = 1 return demand - def get_own_services_query( - self, is_searching_for_sellers: bool, is_search_query: bool - ) -> Query: + def get_own_services_query(self, is_searching_for_sellers: bool,) -> Query: """ Build a query. @@ -179,7 +293,6 @@ def get_own_services_query( - which demand the agent's supplied goods (i.e. buyers). :param is_searching_for_sellers: Boolean indicating whether the search is for sellers or buyers. - :param is_search_query: whether or not the query is used for search on OEF :return: the Query, or None. """ @@ -192,12 +305,14 @@ def get_own_services_query( if is_searching_for_sellers else self._supplied_goods(ownership_state_after_locks.quantities_by_good_id) ) - currency_id = list(ownership_state_after_locks.amount_by_currency_id.keys())[0] + currency_id = next( + iter(ownership_state_after_locks.amount_by_currency_id.keys()) + ) query = build_goods_query( good_ids=list(good_id_to_quantities.keys()), currency_id=currency_id, + ledger_id=self.ledger_id, is_searching_for_sellers=is_searching_for_sellers, - is_search_query=is_search_query, ) return query @@ -224,7 +339,7 @@ def _get_proposal_for_query( return random.choice(proposals) # nosec def get_proposal_for_query( - self, query: Query, role: Dialogue.Role + self, query: Query, role: FipaDialogue.Role ) -> Optional[Description]: """ Generate proposal (in the form of a description) which matches the query. @@ -234,17 +349,11 @@ def get_proposal_for_query( :return: a description """ - is_seller = role == Dialogue.Role.SELLER + is_seller = role == FipaDialogue.Role.SELLER - own_service_description = self.get_own_service_description( - is_supply=is_seller, is_search_description=False - ) + own_service_description = self.get_own_service_description(is_supply=is_seller,) if not query.check(own_service_description): - self.context.logger.debug( - "[{}]: Current holdings do not satisfy CFP query.".format( - self.context.agent_name - ) - ) + self.context.logger.debug("current holdings do not satisfy CFP query.") return None else: proposal_description = self._get_proposal_for_query( @@ -252,9 +361,7 @@ def get_proposal_for_query( ) if proposal_description is None: self.context.logger.debug( - "[{}]: Current strategy does not generate proposal that satisfies CFP query.".format( - self.context.agent_name - ) + "current strategy does not generate proposal that satisfies CFP query." ) return proposal_description @@ -279,25 +386,25 @@ def _generate_candidate_proposals(self, is_seller: bool): good_id: 0 for good_id in good_id_to_quantities.keys() } # type: Dict[str, int] proposals = [] - seller_tx_fee = ( - self.context.decision_maker_handler_context.preferences.seller_transaction_fee + fee_by_currency_id = self.context.shared_state.get("tx_fee", {"FET": 0}) + buyer_tx_fee = next(iter(fee_by_currency_id.values())) + ownership_state = cast( + OwnershipState, self.context.decision_maker_handler_context.ownership_state ) - buyer_tx_fee = ( - self.context.decision_maker_handler_context.preferences.buyer_transaction_fee + currency_id = list(ownership_state.amount_by_currency_id.keys())[0] + preferences = cast( + Preferences, self.context.decision_maker_handler_context.preferences ) - currency_id = list( - self.context.decision_maker_handler_context.ownership_state.amount_by_currency_id.keys() - )[0] for good_id, quantity in good_id_to_quantities.items(): if is_seller and quantity == 0: continue proposal_dict = copy.copy(nil_proposal_dict) proposal_dict[good_id] = 1 proposal = build_goods_description( - good_id_to_quantities=proposal_dict, + quantities_by_good_id=proposal_dict, currency_id=currency_id, + ledger_id=self.ledger_id, is_supply=is_seller, - is_search_description=False, ) if is_seller: delta_quantities_by_good_id = { @@ -306,7 +413,7 @@ def _generate_candidate_proposals(self, is_seller: bool): } # type: Dict[str, int] else: delta_quantities_by_good_id = proposal_dict - marginal_utility_from_delta_good_holdings = self.context.decision_maker_handler_context.preferences.marginal_utility( + marginal_utility_from_delta_good_holdings = preferences.marginal_utility( ownership_state=ownership_state_after_locks, delta_quantities_by_good_id=delta_quantities_by_good_id, ) @@ -315,24 +422,21 @@ def _generate_candidate_proposals(self, is_seller: bool): round(marginal_utility_from_delta_good_holdings) * switch ) if is_seller: - proposal.values["price"] = ( - breakeven_price_rounded + seller_tx_fee + ROUNDING_ADJUSTMENT - ) + proposal.values["price"] = breakeven_price_rounded + ROUNDING_ADJUSTMENT else: proposal.values["price"] = ( breakeven_price_rounded - buyer_tx_fee - ROUNDING_ADJUSTMENT ) - proposal.values["seller_tx_fee"] = seller_tx_fee - proposal.values["buyer_tx_fee"] = buyer_tx_fee + proposal.values["fee"] = buyer_tx_fee if not proposal.values["price"] > 0: continue - tx_nonce = transactions.get_next_tx_nonce() - proposal.values["tx_nonce"] = tx_nonce + nonce = transactions.get_next_nonce() + proposal.values["nonce"] = nonce proposals.append(proposal) return proposals def is_profitable_transaction( - self, transaction_msg: SigningMessage, role: Dialogue.Role + self, signing_msg: SigningMessage, role: FipaDialogue.Role ) -> bool: """ Check if a transaction is profitable. @@ -342,23 +446,24 @@ def is_profitable_transaction( - check if the transaction is consistent with the locks (enough money/holdings) - check that we gain score. - :param transaction_msg: the transaction_msg + :param signing_msg: the signing_msg :param role: the role of the agent (seller or buyer) :return: True if the transaction is good (as stated above), False otherwise. """ - is_seller = role == Dialogue.Role.SELLER + is_seller = role == FipaDialogue.Role.SELLER transactions = cast(Transactions, self.context.transactions) ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller ) - if not ownership_state_after_locks.is_affordable_transaction( - transaction_msg.terms - ): + if not ownership_state_after_locks.is_affordable_transaction(signing_msg.terms): return False - proposal_delta_score = self.context.decision_maker_handler_context.preferences.utility_diff_from_transaction( - ownership_state_after_locks, transaction_msg + preferences = cast( + Preferences, self.context.decision_maker_handler_context.preferences + ) + proposal_delta_score = preferences.utility_diff_from_transaction( + ownership_state_after_locks, signing_msg.terms ) if proposal_delta_score >= 0: return True diff --git a/packages/fetchai/skills/tac_negotiation/transactions.py b/packages/fetchai/skills/tac_negotiation/transactions.py index 95c9823784..e811b70688 100644 --- a/packages/fetchai/skills/tac_negotiation/transactions.py +++ b/packages/fetchai/skills/tac_negotiation/transactions.py @@ -22,19 +22,20 @@ import copy import datetime from collections import defaultdict, deque -from typing import Deque, Dict, Tuple +from typing import Deque, Dict, List, Tuple, cast -from aea.configurations.base import PublicId from aea.decision_maker.default import OwnershipState from aea.helpers.dialogue.base import DialogueLabel from aea.helpers.search.models import Description -from aea.helpers.transaction.base import Terms +from aea.helpers.transaction.base import RawMessage, Terms from aea.mail.base import Address from aea.protocols.signing.message import SigningMessage from aea.skills.base import Model -from packages.fetchai.skills.tac_negotiation.dialogues import Dialogue -from packages.fetchai.skills.tac_negotiation.helpers import tx_hash_from_values +from packages.fetchai.skills.tac_negotiation.dialogues import ( + FipaDialogue, + SigningDialogues, +) MessageId = int @@ -62,8 +63,7 @@ def __init__(self, **kwargs) -> None: self._last_update_for_transactions = ( deque() ) # type: Deque[Tuple[datetime.datetime, str]] - self._tx_nonce = 0 - self._tx_id = 0 + self._nonce = 0 @property def pending_proposals( @@ -79,22 +79,17 @@ def pending_initial_acceptances( """Get the pending initial acceptances.""" return self._pending_initial_acceptances - def get_next_tx_nonce(self) -> int: + def get_next_nonce(self) -> str: """Get the next nonce.""" - self._tx_nonce += 1 - return self._tx_nonce + self._nonce += 1 + return str(self._nonce) - def get_internal_tx_id(self) -> str: - """Get an id for internal reference of the tx.""" - self._tx_id += 1 - return str(self._tx_id) - - def generate_transaction_message( # pylint: disable=no-self-use + def generate_signing_message( self, performative: SigningMessage.Performative, proposal_description: Description, dialogue_label: DialogueLabel, - role: Dialogue.Role, + role: FipaDialogue.Role, agent_addr: Address, ) -> SigningMessage: """ @@ -106,28 +101,12 @@ def generate_transaction_message( # pylint: disable=no-self-use :param agent_addr: the address of the agent :return: a transaction message """ - is_seller = role == Dialogue.Role.SELLER - - # sender_tx_fee = ( - # proposal_description.values["seller_tx_fee"] - # if is_seller - # else proposal_description.values["buyer_tx_fee"] - # ) - # counterparty_tx_fee = ( - # proposal_description.values["buyer_tx_fee"] - # if is_seller - # else proposal_description.values["seller_tx_fee"] - # ) + is_seller = role == FipaDialogue.Role.SELLER + goods_component = copy.copy(proposal_description.values) [ # pylint: disable=expression-not-assigned goods_component.pop(key) - for key in [ - "seller_tx_fee", - "buyer_tx_fee", - "price", - "currency_id", - "tx_nonce", - ] + for key in ["fee", "price", "currency_id", "nonce", "ledger_id"] ] # switch signs based on whether seller or buyer role amount = ( @@ -135,43 +114,55 @@ def generate_transaction_message( # pylint: disable=no-self-use if is_seller else -proposal_description.values["price"] ) + fee = proposal_description.values["fee"] if is_seller: for good_id in goods_component.keys(): goods_component[good_id] = goods_component[good_id] * (-1) - tx_amount_by_currency_id = {proposal_description.values["currency_id"]: amount} - tx_fee_by_currency_id = {proposal_description.values["currency_id"]: 1} - tx_nonce = proposal_description.values["tx_nonce"] - # need to hash positive.negative side separately - tx_hash = tx_hash_from_values( - tx_sender_addr=agent_addr, - tx_counterparty_addr=dialogue_label.dialogue_opponent_addr, - tx_quantities_by_good_id=goods_component, - tx_amount_by_currency_id=tx_amount_by_currency_id, - tx_nonce=tx_nonce, + amount_by_currency_id = {proposal_description.values["currency_id"]: amount} + fee_by_currency_id = {proposal_description.values["currency_id"]: fee} + nonce = proposal_description.values["nonce"] + ledger_id = proposal_description.values["ledger_id"] + terms = Terms( + ledger_id=ledger_id, + sender_address=agent_addr, + counterparty_address=dialogue_label.dialogue_opponent_addr, + amount_by_currency_id=amount_by_currency_id, + is_sender_payable_tx_fee=not is_seller, + quantities_by_good_id=goods_component, + nonce=nonce, + fee_by_currency_id=fee_by_currency_id, ) - skill_callback_ids = ( - (PublicId.from_str("fetchai/tac_participation:0.4.0"),) - if performative == SigningMessage.Performative.SIGN_MESSAGE - else (PublicId.from_str("fetchai/tac_negotiation:0.5.0"),) + skill_callback_ids = (str(self.context.skill_id),) + signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) + skill_callback_info = {"dialogue_label": str(dialogue_label)} + raw_message = RawMessage( + ledger_id=ledger_id, body=terms.sender_hash.encode("utf-8") ) - transaction_msg = SigningMessage( + signing_msg = SigningMessage( performative=performative, + dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), skill_callback_ids=skill_callback_ids, - # tx_id=self.get_internal_tx_id(), - terms=Terms( - ledger_id="ethereum", - sender_address=agent_addr, - counterparty_address=dialogue_label.dialogue_opponent_addr, - amount_by_currency_id=tx_amount_by_currency_id, - is_sender_payable_tx_fee=True, # TODO: check! - quantities_by_good_id=goods_component, - nonce=tx_nonce, - fee_by_currency_id=tx_fee_by_currency_id, - ), - skill_callback_info={"dialogue_label": dialogue_label.json}, - message=tx_hash, + terms=terms, + skill_callback_info=skill_callback_info, + raw_message=raw_message, ) - return transaction_msg + signing_msg.counterparty = "decision_maker" + return signing_msg + + def update_confirmed_transactions(self) -> None: + """ + Update model wrt to confirmed transactions. + + :return: None + """ + confirmed_tx_ids = self.context.shared_state.get( + "confirmed_tx_ids", [] + ) # type: List[str] + for transaction_id in confirmed_tx_ids: + # remove (safely) the associated pending proposal (if present) + self._locked_txs.pop(transaction_id, None) + self._locked_txs_as_buyer.pop(transaction_id, None) + self._locked_txs_as_seller.pop(transaction_id, None) def cleanup_pending_transactions(self) -> None: """ @@ -195,9 +186,7 @@ def cleanup_pending_transactions(self) -> None: # extract dialogue label and message id transaction_id = next_item self.context.logger.debug( - "[{}]: Removing transaction from pending list: {}".format( - self.context.agent_name, transaction_id - ) + "removing transaction from pending list: {}".format(transaction_id) ) # remove (safely) the associated pending proposal (if present) @@ -214,14 +203,14 @@ def add_pending_proposal( self, dialogue_label: DialogueLabel, proposal_id: int, - transaction_msg: SigningMessage, + signing_msg: SigningMessage, ) -> None: """ Add a proposal (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal - :param transaction_msg: the transaction message + :param signing_msg: the transaction message :raise AssertionError: if the pending proposal is already present. :return: None @@ -230,7 +219,7 @@ def add_pending_proposal( dialogue_label not in self._pending_proposals and proposal_id not in self._pending_proposals[dialogue_label] ) - self._pending_proposals[dialogue_label][proposal_id] = transaction_msg + self._pending_proposals[dialogue_label][proposal_id] = signing_msg def pop_pending_proposal( self, dialogue_label: DialogueLabel, proposal_id: int @@ -248,21 +237,21 @@ def pop_pending_proposal( dialogue_label in self._pending_proposals and proposal_id in self._pending_proposals[dialogue_label] ) - transaction_msg = self._pending_proposals[dialogue_label].pop(proposal_id) - return transaction_msg + signing_msg = self._pending_proposals[dialogue_label].pop(proposal_id) + return signing_msg def add_pending_initial_acceptance( self, dialogue_label: DialogueLabel, proposal_id: int, - transaction_msg: SigningMessage, + signing_msg: SigningMessage, ) -> None: """ Add an acceptance (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal - :param transaction_msg: the transaction message + :param signing_msg: the transaction message :raise AssertionError: if the pending acceptance is already present. :return: None @@ -271,7 +260,7 @@ def add_pending_initial_acceptance( dialogue_label not in self._pending_initial_acceptances and proposal_id not in self._pending_initial_acceptances[dialogue_label] ) - self._pending_initial_acceptances[dialogue_label][proposal_id] = transaction_msg + self._pending_initial_acceptances[dialogue_label][proposal_id] = signing_msg def pop_pending_initial_acceptance( self, dialogue_label: DialogueLabel, proposal_id: int @@ -289,10 +278,8 @@ def pop_pending_initial_acceptance( dialogue_label in self._pending_initial_acceptances and proposal_id in self._pending_initial_acceptances[dialogue_label] ) - transaction_msg = self._pending_initial_acceptances[dialogue_label].pop( - proposal_id - ) - return transaction_msg + signing_msg = self._pending_initial_acceptances[dialogue_label].pop(proposal_id) + return signing_msg def _register_transaction_with_time(self, transaction_id: str) -> None: """ @@ -306,43 +293,43 @@ def _register_transaction_with_time(self, transaction_id: str) -> None: self._last_update_for_transactions.append((now, transaction_id)) def add_locked_tx( - self, transaction_msg: SigningMessage, role: Dialogue.Role + self, signing_msg: SigningMessage, role: FipaDialogue.Role ) -> None: """ Add a lock (in the form of a transaction). - :param transaction_msg: the transaction message + :param signing_msg: the transaction message :param role: the role of the agent (seller or buyer) :raise AssertionError: if the transaction is already present. :return: None """ - as_seller = role == Dialogue.Role.SELLER + as_seller = role == FipaDialogue.Role.SELLER - transaction_id = transaction_msg.dialogue_reference[0] # TODO: fix + transaction_id = signing_msg.terms.id assert transaction_id not in self._locked_txs self._register_transaction_with_time(transaction_id) - self._locked_txs[transaction_id] = transaction_msg + self._locked_txs[transaction_id] = signing_msg if as_seller: - self._locked_txs_as_seller[transaction_id] = transaction_msg + self._locked_txs_as_seller[transaction_id] = signing_msg else: - self._locked_txs_as_buyer[transaction_id] = transaction_msg + self._locked_txs_as_buyer[transaction_id] = signing_msg - def pop_locked_tx(self, transaction_msg: SigningMessage) -> SigningMessage: + def pop_locked_tx(self, signing_msg: SigningMessage) -> SigningMessage: """ Remove a lock (in the form of a transaction). - :param transaction_msg: the transaction message + :param signing_msg: the transaction message :raise AssertionError: if the transaction with the given transaction id has not been found. :return: the transaction """ - transaction_id = transaction_msg.dialogue_reference[0] # TODO: fix + transaction_id = signing_msg.terms.id assert transaction_id in self._locked_txs - transaction_msg = self._locked_txs.pop(transaction_id) + signing_msg = self._locked_txs.pop(transaction_id) self._locked_txs_as_buyer.pop(transaction_id, None) self._locked_txs_as_seller.pop(transaction_id, None) - return transaction_msg + return signing_msg def ownership_state_after_locks(self, is_seller: bool) -> OwnershipState: """ @@ -354,12 +341,14 @@ def ownership_state_after_locks(self, is_seller: bool) -> OwnershipState: :return: the agent state with the locks applied to current state """ - transaction_msgs = ( + signing_msgs = ( list(self._locked_txs_as_seller.values()) if is_seller else list(self._locked_txs_as_buyer.values()) ) - ownership_state_after_locks = self.context.decision_maker_handler_context.ownership_state.apply_transactions( - transaction_msgs + terms = [signing_msg.terms for signing_msg in signing_msgs] + ownership_state = cast( + OwnershipState, self.context.decision_maker_handler_context.ownership_state ) + ownership_state_after_locks = ownership_state.apply_transactions(terms) return ownership_state_after_locks diff --git a/packages/fetchai/skills/tac_participation/behaviours.py b/packages/fetchai/skills/tac_participation/behaviours.py index 23ab788355..14b215c663 100644 --- a/packages/fetchai/skills/tac_participation/behaviours.py +++ b/packages/fetchai/skills/tac_participation/behaviours.py @@ -19,11 +19,12 @@ """This package contains a tac search behaviour.""" -from typing import cast +from typing import Any, Dict, cast from aea.skills.behaviours import TickerBehaviour from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.protocols.tac.message import TacMessage from packages.fetchai.skills.tac_participation.dialogues import OefSearchDialogues from packages.fetchai.skills.tac_participation.game import Game, Phase @@ -80,7 +81,75 @@ def _search_for_tac(self) -> None: oef_search_dialogues.update(oef_search_msg) self.context.outbox.put_message(message=oef_search_msg) self.context.logger.info( - "[{}]: Searching for TAC, search_id={}".format( - self.context.agent_name, oef_search_msg.dialogue_reference - ) + "searching for TAC, search_id={}".format(oef_search_msg.dialogue_reference) ) + + +class TransactionProcessBehaviour(TickerBehaviour): + """This class implements the processing of the transactions class.""" + + def setup(self) -> None: + """ + Implement the setup. + + :return: None + """ + pass + + def act(self) -> None: + """ + Implement the task execution. + + :return: None + """ + game = cast(Game, self.context.game) + if game.phase.value == Phase.GAME.value: + self._process_transactions() + + def teardown(self) -> None: + """ + Implement the task teardown. + + :return: None + """ + pass + + def _process_transactions(self) -> None: + """ + Process transactions. + + :return: None + """ + game = cast(Game, self.context.game) + tac_dialogue = game.tac_dialogue + transactions = cast( + Dict[str, Dict[str, Any]], self.context.shared_state.get("transactions", {}) + ) + for tx_id, tx_content in transactions.items(): + self.context.logger.info( + "sending transaction {} to controller.".format(tx_id) + ) + last_msg = tac_dialogue.last_message + assert last_msg is not None, "No last message available." + terms = tx_content["terms"] + sender_signature = tx_content["sender_signature"] + counterparty_signature = tx_content["counterparty_signature"] + msg = TacMessage( + performative=TacMessage.Performative.TRANSACTION, + dialogue_reference=tac_dialogue.dialogue_label.dialogue_reference, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, + transaction_id=tx_id, + ledger_id=terms.ledger_id, + sender_address=terms.sender_address, + counterparty_address=terms.counterparty_address, + amount_by_currency_id=terms.amount_by_currency_id, + fee_by_currency_id=terms.fee_by_currency_id, + quantities_by_good_id=terms.quantities_by_good_id, + sender_signature=sender_signature, + counterparty_signature=counterparty_signature, + nonce=terms.nonce, + ) + msg.counterparty = game.conf.controller_addr + tac_dialogue.update(msg) + self.context.outbox.put_message(message=msg) diff --git a/packages/fetchai/skills/tac_participation/dialogues.py b/packages/fetchai/skills/tac_participation/dialogues.py index 9717b8f190..deaf872cdd 100644 --- a/packages/fetchai/skills/tac_participation/dialogues.py +++ b/packages/fetchai/skills/tac_participation/dialogues.py @@ -31,8 +31,12 @@ from aea.helpers.dialogue.base import Dialogue as BaseDialogue from aea.helpers.dialogue.base import DialogueLabel as BaseDialogueLabel from aea.protocols.base import Message -from aea.protocols.signing.dialogues import SigningDialogue as BaseSigningDialogue -from aea.protocols.signing.dialogues import SigningDialogues as BaseSigningDialogues +from aea.protocols.state_update.dialogues import ( + StateUpdateDialogue as BaseStateUpdateDialogue, +) +from aea.protocols.state_update.dialogues import ( + StateUpdateDialogues as BaseStateUpdateDialogues, +) from aea.skills.base import Model from packages.fetchai.protocols.oef_search.dialogues import ( @@ -59,7 +63,9 @@ def __init__(self, **kwargs) -> None: :return: None """ Model.__init__(self, **kwargs) - BaseOefSearchDialogues.__init__(self, self.context.agent_address) + BaseOefSearchDialogues.__init__( + self, self.context.agent_address + "_" + str(self.context.skill_id) + ) @staticmethod def role_from_first_message(message: Message) -> BaseDialogue.Role: @@ -87,11 +93,11 @@ def create_dialogue( return dialogue -SigningDialogue = BaseSigningDialogue +StateUpdateDialogue = BaseStateUpdateDialogue -class SigningDialogues(Model, BaseSigningDialogues): - """This class keeps track of all oef_search dialogues.""" +class StateUpdateDialogues(Model, BaseStateUpdateDialogues): + """This class keeps track of all state_update dialogues.""" def __init__(self, **kwargs) -> None: """ @@ -101,7 +107,9 @@ def __init__(self, **kwargs) -> None: :return: None """ Model.__init__(self, **kwargs) - BaseSigningDialogues.__init__(self, self.context.agent_address) + BaseStateUpdateDialogues.__init__( + self, self.context.agent_address + "_" + str(self.context.skill_id) + ) @staticmethod def role_from_first_message(message: Message) -> BaseDialogue.Role: @@ -110,11 +118,11 @@ def role_from_first_message(message: Message) -> BaseDialogue.Role: :param message: an incoming/outgoing first message :return: The role of the agent """ - return BaseSigningDialogue.Role.SKILL + return BaseStateUpdateDialogue.Role.SKILL def create_dialogue( self, dialogue_label: BaseDialogueLabel, role: BaseDialogue.Role, - ) -> SigningDialogue: + ) -> StateUpdateDialogue: """ Create an instance of fipa dialogue. @@ -123,7 +131,7 @@ def create_dialogue( :return: the created dialogue """ - dialogue = SigningDialogue( + dialogue = StateUpdateDialogue( dialogue_label=dialogue_label, agent_address=self.agent_address, role=role ) return dialogue diff --git a/packages/fetchai/skills/tac_participation/game.py b/packages/fetchai/skills/tac_participation/game.py index 5b03bc2021..414827693d 100644 --- a/packages/fetchai/skills/tac_participation/game.py +++ b/packages/fetchai/skills/tac_participation/game.py @@ -21,14 +21,26 @@ from enum import Enum from typing import Dict, List, Optional -from aea.helpers.search.models import Constraint, ConstraintType, Query +from aea.helpers.search.models import Constraint, ConstraintType, Location, Query from aea.mail.base import Address from aea.skills.base import Model from packages.fetchai.protocols.tac.message import TacMessage +from packages.fetchai.skills.tac_participation.dialogues import ( + StateUpdateDialogue, + TacDialogue, +) DEFAULT_LEDGER_ID = "ethereum" +DEFAULT_LOCATION = {"longitude": 51.5194, "latitude": 0.1270} +DEFAULT_SEARCH_QUERY = { + "search_key": "tac", + "search_value": "v1", + "constraint_type": "==", +} +DEFAULT_SEARCH_RADIUS = 5.0 + class Phase(Enum): """This class defines the phases of the game.""" @@ -46,7 +58,7 @@ class Configuration: def __init__( self, version_id: str, - tx_fee: int, + fee_by_currency_id: Dict[str, int], agent_addr_to_name: Dict[Address, str], good_id_to_name: Dict[str, str], controller_addr: Address, @@ -55,7 +67,7 @@ def __init__( Instantiate a game configuration. :param version_id: the version of the game. - :param tx_fee: the fee for a transaction. + :param fee_by_currency_id: the fee for a transaction by currency id. :param agent_addr_to_name: a dictionary mapping agent addresses to agent names (as strings). :param good_id_to_name: a dictionary mapping good ids to good names (as strings). :param controller_addr: the address of the controller @@ -63,7 +75,7 @@ def __init__( self._version_id = version_id self._nb_agents = len(agent_addr_to_name) self._nb_goods = len(good_id_to_name) - self._tx_fee = tx_fee + self._fee_by_currency_id = fee_by_currency_id self._agent_addr_to_name = agent_addr_to_name self._good_id_to_name = good_id_to_name self._controller_addr = controller_addr @@ -88,7 +100,14 @@ def nb_goods(self) -> int: @property def tx_fee(self) -> int: """Transaction fee for the TAC instance.""" - return self._tx_fee + assert len(self._fee_by_currency_id) == 1, "More than one currency id present!" + value = next(iter(self._fee_by_currency_id.values())) + return value + + @property + def fee_by_currency_id(self) -> Dict[str, int]: + """Transaction fee for the TAC instance.""" + return self._fee_by_currency_id @property def agent_addr_to_name(self) -> Dict[Address, str]: @@ -133,7 +152,9 @@ def _check_consistency(self): :raises: AssertionError: if some constraint is not satisfied. """ assert self.version_id is not None, "A version id must be set." - assert self.tx_fee >= 0, "Tx fee must be non-negative." + assert ( + len(self.fee_by_currency_id) == 1 and 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 ( @@ -159,12 +180,20 @@ def __init__(self, **kwargs): self._expected_controller_addr = kwargs.pop( "expected_controller_addr", None ) # type: Optional[str] + + self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) + location = kwargs.pop("location", DEFAULT_LOCATION) + self._agent_location = Location(location["longitude"], location["latitude"]) + self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) + self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) self._is_using_contract = kwargs.pop("is_using_contract", False) # type: bool super().__init__(**kwargs) self._phase = Phase.PRE_GAME self._conf = None # type: Optional[Configuration] self._contract_address = None # type: Optional[str] + self._tac_dialogue = None # type: Optional[TacDialogue] + self._state_update_dialogue = None # type: Optional[StateUpdateDialogue] @property def ledger_id(self) -> str: @@ -198,6 +227,30 @@ def contract_address(self, contract_address: str) -> None: assert self._contract_address is None, "Contract address already set!" self._contract_address = contract_address + @property + def tac_dialogue(self) -> TacDialogue: + """Retrieve the tac dialogue.""" + assert self._tac_dialogue is not None, "TacDialogue not set!" + return self._tac_dialogue + + @tac_dialogue.setter + def tac_dialogue(self, tac_dialogue: TacDialogue) -> None: + """Set the tac dialogue.""" + assert self._tac_dialogue is None, "TacDialogue already set!" + self._tac_dialogue = tac_dialogue + + @property + def state_update_dialogue(self) -> StateUpdateDialogue: + """Retrieve the state_update dialogue.""" + assert self._state_update_dialogue is not None, "StateUpdateDialogue not set!" + return self._state_update_dialogue + + @state_update_dialogue.setter + def state_update_dialogue(self, state_update_dialogue: StateUpdateDialogue) -> None: + """Set the state_update dialogue.""" + assert self._state_update_dialogue is None, "StateUpdateDialogue already set!" + self._state_update_dialogue = state_update_dialogue + @property def expected_controller_addr(self) -> Address: """Get the expected controller pbk.""" @@ -232,7 +285,7 @@ def init(self, tac_message: TacMessage, controller_addr: Address) -> None: ), "TacMessage for unexpected game." self._conf = Configuration( tac_message.version_id, - tac_message.tx_fee, + tac_message.fee_by_currency_id, tac_message.agent_addr_to_name, tac_message.good_id_to_name, controller_addr, @@ -247,9 +300,7 @@ def update_expected_controller_addr(self, controller_addr: Address): :return: None """ self.context.logger.warning( - "[{}]: TAKE CARE! Circumventing controller identity check! For added security provide the expected controller key as an argument to the Game instance and check against it.".format( - self.context.agent_name - ) + "TAKE CARE! Circumventing controller identity check! For added security provide the expected controller key as an argument to the Game instance and check against it." ) self._expected_controller_addr = controller_addr @@ -267,7 +318,15 @@ def get_game_query(self) -> Query: :return: the query """ - query = Query( - [Constraint("version", ConstraintType("==", self.expected_version_id))] + close_to_my_service = Constraint( + "location", ConstraintType("distance", (self._agent_location, self._radius)) + ) + service_key_filter = Constraint( + self._search_query["search_key"], + ConstraintType( + self._search_query["constraint_type"], + self._search_query["search_value"], + ), ) + query = Query([close_to_my_service, service_key_filter],) return query diff --git a/packages/fetchai/skills/tac_participation/handlers.py b/packages/fetchai/skills/tac_participation/handlers.py index e5abd22d95..eac7db285b 100644 --- a/packages/fetchai/skills/tac_participation/handlers.py +++ b/packages/fetchai/skills/tac_participation/handlers.py @@ -21,10 +21,8 @@ from typing import Dict, Optional, Tuple, cast -from aea.configurations.base import ProtocolId from aea.mail.base import Address from aea.protocols.base import Message -from aea.protocols.signing.message import SigningMessage from aea.protocols.state_update.message import StateUpdateMessage from aea.skills.base import Handler @@ -33,15 +31,15 @@ from packages.fetchai.skills.tac_participation.dialogues import ( OefSearchDialogue, OefSearchDialogues, - SigningDialogue, - SigningDialogues, + StateUpdateDialogue, + StateUpdateDialogues, TacDialogue, TacDialogues, ) from packages.fetchai.skills.tac_participation.game import Game, Phase -class OEFSearchHandler(Handler): +class OefSearchHandler(Handler): """This class handles oef messages.""" SUPPORTED_PROTOCOL = OefSearchMessage.protocol_id @@ -97,8 +95,8 @@ def _handle_unidentified_dialogue(self, oef_search_msg: OefSearchMessage) -> Non :param msg: the message """ self.context.logger.warning( - "[{}]: received invalid oef_search message={}, unidentified dialogue.".format( - self.context.agent_name, oef_search_msg + "received invalid oef_search message={}, unidentified dialogue.".format( + oef_search_msg ) ) @@ -113,10 +111,8 @@ def _on_oef_error( :return: None """ self.context.logger.warning( - "[{}]: Received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( - self.context.agent_name, - oef_search_msg.dialogue_reference, - oef_search_msg.oef_error_operation, + "received OEF Search error: dialogue_reference={}, oef_error_operation={}".format( + oef_search_msg.dialogue_reference, oef_search_msg.oef_error_operation, ) ) @@ -131,10 +127,8 @@ def _on_search_result( :return: None """ self.context.logger.debug( - "[{}]: on search result: dialogue_reference={} agents={}".format( - self.context.agent_name, - oef_search_msg.dialogue_reference, - oef_search_msg.agents, + "on search result: dialogue_reference={} agents={}".format( + oef_search_msg.dialogue_reference, oef_search_msg.agents, ) ) self._on_controller_search_result(oef_search_msg.agents) @@ -150,10 +144,8 @@ def _handle_invalid( :return: None """ self.context.logger.warning( - "[{}]: cannot handle oef_search message of performative={} in dialogue={}.".format( - self.context.agent_name, - oef_search_msg.performative, - oef_search_dialogue, + "cannot handle oef_search message of performative={} in dialogue={}.".format( + oef_search_msg.performative, oef_search_dialogue, ) ) @@ -170,30 +162,18 @@ def _on_controller_search_result( game = cast(Game, self.context.game) if game.phase.value != Phase.PRE_GAME.value: self.context.logger.debug( - "[{}]: Ignoring controller search result, the agent is already competing.".format( - self.context.agent_name - ) + "ignoring controller search result, the agent is already competing." ) return if len(agent_addresses) == 0: - self.context.logger.info( - "[{}]: Couldn't find the TAC controller. Retrying...".format( - self.context.agent_name - ) - ) + self.context.logger.info("couldn't find the TAC controller. Retrying...") elif len(agent_addresses) > 1: self.context.logger.warning( - "[{}]: Found more than one TAC controller. Retrying...".format( - self.context.agent_name - ) + "found more than one TAC controller. Retrying..." ) else: - self.context.logger.info( - "[{}]: Found the TAC controller. Registering...".format( - self.context.agent_name - ) - ) + self.context.logger.info("found the TAC controller. Registering...") controller_addr = agent_addresses[0] self._register_to_tac(controller_addr) @@ -208,13 +188,18 @@ def _register_to_tac(self, controller_addr: Address) -> None: game = cast(Game, self.context.game) game.update_expected_controller_addr(controller_addr) game.update_game_phase(Phase.GAME_REGISTRATION) + tac_dialogues = cast(TacDialogues, self.context.tac_dialogues) tac_msg = TacMessage( performative=TacMessage.Performative.REGISTER, + dialogue_reference=tac_dialogues.new_self_initiated_dialogue_reference(), agent_name=self.context.agent_name, ) tac_msg.counterparty = controller_addr + tac_dialogue = cast(Optional[TacDialogue], tac_dialogues.update(tac_msg)) + assert tac_dialogue is not None, "TacDialogue not created." + game.tac_dialogue = tac_dialogue self.context.outbox.put_message(message=tac_msg) - self.context.behaviours.tac.is_active = False + self.context.behaviours.tac_search.is_active = False class TacHandler(Handler): @@ -249,35 +234,21 @@ def handle(self, message: Message) -> None: # handle message game = cast(Game, self.context.game) self.context.logger.debug( - "[{}]: Handling controller response. performative={}".format( - self.context.agent_name, tac_msg.performative - ) + "handling controller response. performative={}".format(tac_msg.performative) ) - if message.counterparty != game.expected_controller_addr: + if tac_msg.counterparty != game.expected_controller_addr: raise ValueError( "The sender of the message is not the controller agent we registered with." ) if tac_msg.performative == TacMessage.Performative.TAC_ERROR: self._on_tac_error(tac_msg, tac_dialogue) - elif game.phase.value == Phase.PRE_GAME.value: - raise ValueError( - "We do not expect a controller agent message in the pre game phase." - ) - elif game.phase.value == Phase.GAME_REGISTRATION.value: - if tac_msg.performative == TacMessage.Performative.GAME_DATA: - self._on_start(tac_msg, tac_dialogue) - elif tac_msg.performative == TacMessage.Performative.CANCELLED: - self._on_cancelled(tac_msg, tac_dialogue) - elif game.phase.value == Phase.GAME.value: - if tac_msg.performative == TacMessage.Performative.TRANSACTION_CONFIRMATION: - self._on_transaction_confirmed(tac_msg, tac_dialogue) - elif tac_msg.performative == TacMessage.Performative.CANCELLED: - self._on_cancelled(tac_msg, tac_dialogue) - elif game.phase.value == Phase.POST_GAME.value: - raise ValueError( - "We do not expect a controller agent message in the post game phase." - ) + elif tac_msg.performative == TacMessage.Performative.GAME_DATA: + self._on_start(tac_msg, tac_dialogue) + elif tac_msg.performative == TacMessage.Performative.CANCELLED: + self._on_cancelled(tac_msg, tac_dialogue) + elif tac_msg.performative == TacMessage.Performative.TRANSACTION_CONFIRMATION: + self._on_transaction_confirmed(tac_msg, tac_dialogue) else: self._handle_invalid(tac_msg, tac_dialogue) @@ -296,9 +267,7 @@ def _handle_unidentified_dialogue(self, tac_msg: TacMessage) -> None: :param tac_msg: the message """ self.context.logger.warning( - "[{}]: received invalid tac message={}, unidentified dialogue.".format( - self.context.agent_name, tac_msg - ) + "received invalid tac message={}, unidentified dialogue.".format(tac_msg) ) def _on_tac_error(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: @@ -311,8 +280,8 @@ def _on_tac_error(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ error_code = tac_msg.error_code self.context.logger.debug( - "[{}]: Received error from the controller. error_msg={}".format( - self.context.agent_name, TacMessage.ErrorCode.to_msg(error_code.value) + "received error from the controller. error_msg={}".format( + TacMessage.ErrorCode.to_msg(error_code.value) ) ) if error_code == TacMessage.ErrorCode.TRANSACTION_NOT_VALID: @@ -323,9 +292,7 @@ def _on_tac_error(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: else "NO_TX_ID" ) self.context.logger.warning( - "[{}]: Received error on transaction id: {}".format( - self.context.agent_name, transaction_id[-10:] - ) + "received error on transaction id: {}".format(transaction_id[-10:]) ) def _on_start(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: @@ -336,10 +303,17 @@ def _on_start(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: :param tac_dialogue: the tac dialogue :return: None """ - self.context.logger.info( - "[{}]: Received start event from the controller. Starting to compete...".format( - self.context.agent_name + game = cast(Game, self.context.game) + if game.phase.value != Phase.GAME_REGISTRATION.value: + self.context.logger.warning( + "we do not expect a start message in game phase={}".format( + game.phase.value + ) ) + return + + self.context.logger.info( + "received start event from the controller. Starting to compete..." ) game = cast(Game, self.context.game) game.init(tac_msg, tac_msg.counterparty) @@ -354,18 +328,12 @@ def _on_start(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: game.contract_address = contract_address self.context.shared_state["erc1155_contract_address"] = contract_address self.context.logger.info( - "[{}]: Received a contract address: {}".format( - self.context.agent_name, contract_address - ) + "received a contract address: {}".format(contract_address) ) # TODO; verify on-chain matches off-chain wealth self._update_ownership_and_preferences(tac_msg, tac_dialogue) else: - self.context.logger.warning( - "[{}]: Did not receive a contract address!".format( - self.context.agent_name - ) - ) + self.context.logger.warning("did not receive a contract address!") else: self._update_ownership_and_preferences(tac_msg, tac_dialogue) @@ -379,14 +347,26 @@ def _update_ownership_and_preferences( :param tac_dialogue: the tac dialogue :return: None """ + state_update_dialogues = cast( + StateUpdateDialogues, self.context.state_update_dialogues + ) state_update_msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.INITIALIZE, + dialogue_reference=state_update_dialogues.new_self_initiated_dialogue_reference(), amount_by_currency_id=tac_msg.amount_by_currency_id, quantities_by_good_id=tac_msg.quantities_by_good_id, exchange_params_by_currency_id=tac_msg.exchange_params_by_currency_id, utility_params_by_good_id=tac_msg.utility_params_by_good_id, - tx_fee=tac_msg.tx_fee, ) + self.context.shared_state["fee_by_currency_id"] = tac_msg.fee_by_currency_id + state_update_msg.counterparty = "decision_maker" + state_update_dialogue = cast( + Optional[StateUpdateDialogue], + state_update_dialogues.update(state_update_msg), + ) + assert state_update_dialogue is not None, "StateUpdateDialogue not created." + game = cast(Game, self.context.game) + game.state_update_dialogue = state_update_dialogue self.context.decision_maker_message_queue.put_nowait(state_update_msg) def _on_cancelled(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: @@ -397,11 +377,16 @@ def _on_cancelled(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: :param tac_dialogue: the tac dialogue :return: None """ - self.context.logger.info( - "[{}]: Received cancellation from the controller.".format( - self.context.agent_name + game = cast(Game, self.context.game) + if game.phase.value not in [Phase.GAME_REGISTRATION.value, Phase.GAME.value]: + self.context.logger.warning( + "we do not expect a start message in game phase={}".format( + game.phase.value + ) ) - ) + return + + self.context.logger.info("received cancellation from the controller.") game = cast(Game, self.context.game) game.update_game_phase(Phase.POST_GAME) self.context.is_active = False @@ -417,20 +402,37 @@ def _on_transaction_confirmed( :param tac_dialogue: the tac dialogue :return: None """ + game = cast(Game, self.context.game) + if game.phase.value != Phase.GAME.value: + self.context.logger.warning( + "we do not expect a tranasaction in game phase={}".format( + game.phase.value + ) + ) + return + self.context.logger.info( - "[{}]: Received transaction confirmation from the controller: transaction_id={}".format( - self.context.agent_name, tac_msg.tx_id[-10:] + "received transaction confirmation from the controller: transaction_id={}".format( + tac_msg.transaction_id ) ) + state_update_dialogue = game.state_update_dialogue + last_msg = state_update_dialogue.last_message + assert last_msg is not None, "Could not retrieve last message." state_update_msg = StateUpdateMessage( performative=StateUpdateMessage.Performative.APPLY, + dialogue_reference=state_update_dialogue.dialogue_label.dialogue_reference, + message_id=last_msg.message_id + 1, + target=last_msg.message_id, amount_by_currency_id=tac_msg.amount_by_currency_id, quantities_by_good_id=tac_msg.quantities_by_good_id, ) + state_update_msg.counterparty = "decision_maker" + state_update_dialogue.update(state_update_msg) self.context.decision_maker_message_queue.put_nowait(state_update_msg) if "confirmed_tx_ids" not in self.context.shared_state.keys(): self.context.shared_state["confirmed_tx_ids"] = [] - self.context.shared_state["confirmed_tx_ids"].append(tac_msg.tx_id) + self.context.shared_state["confirmed_tx_ids"].append(tac_msg.transaction_id) def _handle_invalid(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> None: """ @@ -440,148 +442,8 @@ def _handle_invalid(self, tac_msg: TacMessage, tac_dialogue: TacDialogue) -> Non :param tac_dialogue: the tac dialogue :return: None """ - game = cast(Game, self.context.game) - self.context.logger.warning( - "[{}]: cannot handle tac message of performative={} in dialogue={} during game_phase={}.".format( - self.context.agent_name, tac_msg.performative, tac_dialogue, game.phase, - ) - ) - - -class SigningHandler(Handler): - """This class implements the transaction handler.""" - - SUPPORTED_PROTOCOL = SigningMessage.protocol_id # type: Optional[ProtocolId] - - def setup(self) -> None: - """ - Implement the setup. - - :return: None - """ - pass - - def handle(self, message: Message) -> None: - """ - Dispatch message to relevant handler and respond. - - :param message: the message - :return: None - """ - signing_msg = cast(SigningMessage, message) - - # recover dialogue - signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) - signing_dialogue = cast( - Optional[SigningDialogue], signing_dialogues.update(signing_msg) - ) - if signing_dialogue is None: - self._handle_unidentified_dialogue(signing_msg) - return - - # handle message - if signing_msg.performative is SigningMessage.Performative.SIGNED_TRANSACTION: - self._handle_signed_transaction(signing_msg, signing_dialogue) - elif signing_msg.performative is SigningMessage.Performative.ERROR: - self._handle_error(signing_msg, signing_dialogue) - else: - self._handle_invalid(signing_msg, signing_dialogue) - - def teardown(self) -> None: - """ - Implement the handler teardown. - - :return: None - """ - pass - - def _handle_unidentified_dialogue(self, signing_msg: SigningMessage) -> None: - """ - Handle an unidentified dialogue. - - :param msg: the message - """ - self.context.logger.info( - "[{}]: received invalid signing message={}, unidentified dialogue.".format( - self.context.agent_name, signing_msg - ) - ) - - def _handle_signed_transaction( - self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue - ) -> None: - """ - Handle an oef search message. - - :param signing_msg: the signing message - :param signing_dialogue: the dialogue - :return: None - """ - # TODO: Need to modify here and add the contract option in case we are using one. - self.context.logger.info( - "[{}]: transaction confirmed by decision maker, sending to controller.".format( - self.context.agent_name - ) - ) - game = cast(Game, self.context.game) - tx_counterparty_signature = cast( - str, signing_msg.skill_callback_info.get("tx_counterparty_signature") - ) - tx_counterparty_id = cast( - str, signing_msg.skill_callback_info.get("tx_counterparty_id") - ) - tx_id = cast(str, signing_msg.skill_callback_info.get("tx_id")) - if (tx_counterparty_signature is not None) and (tx_counterparty_id is not None): - # tx_id = tx_message.tx_id + "_" + tx_counterparty_id - msg = TacMessage( - performative=TacMessage.Performative.TRANSACTION, - tx_id=tx_id, - tx_sender_addr=signing_msg.terms.sender_address, - tx_counterparty_addr=signing_msg.terms.counterparty_address, - amount_by_currency_id=signing_msg.terms.amount_by_currency_id, - is_sender_payable_tx_fee=signing_msg.terms.is_sender_payable_tx_fee, - quantities_by_good_id=signing_msg.terms.quantities_by_good_id, - tx_sender_signature=signing_msg.signed_transaction.body, - tx_counterparty_signature=tx_counterparty_signature, - tx_nonce=signing_msg.terms.nonce, - ) - msg.counterparty = game.conf.controller_addr - self.context.outbox.put_message(message=msg) - else: - self.context.logger.warning( - "[{}]: transaction has no counterparty id or signature!".format( - self.context.agent_name - ) - ) - - def _handle_error( - self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue - ) -> None: - """ - Handle an oef search message. - - :param signing_msg: the signing message - :param signing_dialogue: the dialogue - :return: None - """ - self.context.logger.info( - "[{}]: transaction signing was not successful. Error_code={} in dialogue={}".format( - self.context.agent_name, signing_msg.error_code, signing_dialogue - ) - ) - - def _handle_invalid( - self, signing_msg: SigningMessage, signing_dialogue: SigningDialogue - ) -> None: - """ - Handle an oef search message. - - :param signing_msg: the signing message - :param signing_dialogue: the dialogue - :return: None - """ self.context.logger.warning( - "[{}]: cannot handle signing message of performative={} in dialogue={}.".format( - self.context.agent_name, signing_msg.performative, signing_dialogue + "cannot handle tac message of performative={} in dialogue={}.".format( + tac_msg.performative, tac_dialogue ) ) diff --git a/packages/fetchai/skills/tac_participation/skill.yaml b/packages/fetchai/skills/tac_participation/skill.yaml index 0cfaf005fd..43dec3b7c3 100644 --- a/packages/fetchai/skills/tac_participation/skill.yaml +++ b/packages/fetchai/skills/tac_participation/skill.yaml @@ -1,35 +1,36 @@ name: tac_participation author: fetchai -version: 0.4.0 +version: 0.5.0 description: The tac participation skill implements the logic for an AEA to participate in the TAC. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmcVpVrbV54Aogmowu6AomDiVMrVMo9BUvwKt9V1bJpBwp - behaviours.py: QmbTf28S46E5w1ytYAcRCZnrVxZ8DcVYAWn1QdNnHvZVLL - dialogues.py: QmZadrW961YwRQuDveoSFSVA7NjVVh2ZuvmbyRke2EqseF - game.py: QmXiKRfkEAbKZ84nauAwQcXuAekU4hD7kMsqskgWBGopAU - handlers.py: QmerbCSEoSVUsVXeN8bwKq4iZk4db3sjsurtfNoGN9Gtfv + behaviours.py: QmZo3d94G3q5wd9DNMN3TdH2DqpKXxMyti7sRrgYt28hrM + dialogues.py: QmV9NMmkCoNS3itj3cgRuKi3bTCrmae4cQ3X1tTyXx25Bj + game.py: QmVudLRDif5sawxRMmTPzdVhABk1Q3sGNmctNgs2c1QqSJ + handlers.py: QmcBhfgj8NSyisXEFedyiHmNXgEGXqyvziAeRpbkPHgvjD fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/oef_search:0.3.0 -- fetchai/tac:0.3.0 +- fetchai/oef_search:0.4.0 +- fetchai/tac:0.4.0 skills: [] behaviours: - tac: + tac_search: args: tick_interval: 5 class_name: TacSearchBehaviour + transaction_processing: + args: + tick_interval: 2 + class_name: TransactionProcessBehaviour handlers: oef: args: {} class_name: OefSearchHandler - signing: - args: {} - class_name: SigningHandler tac: args: {} class_name: TacHandler @@ -39,13 +40,21 @@ models: expected_version_id: v1 is_using_contract: false ledger_id: ethereum + location: + latitude: 0.127 + longitude: 51.5194 + search_query: + constraint_type: == + search_key: tac + search_value: v1 + search_radius: 5.0 class_name: Game oef_search_dialogues: args: {} class_name: OefSearchDialogues - signing_dialogues: + state_update_dialogues: args: {} - class_name: SigningDialogues + class_name: StateUpdateDialogues tac_dialogues: args: {} class_name: TacDialogues diff --git a/packages/fetchai/skills/thermometer/skill.yaml b/packages/fetchai/skills/thermometer/skill.yaml index ebf8c4d215..5c475b6493 100644 --- a/packages/fetchai/skills/thermometer/skill.yaml +++ b/packages/fetchai/skills/thermometer/skill.yaml @@ -1,6 +1,6 @@ name: thermometer author: fetchai -version: 0.7.0 +version: 0.8.0 description: The thermometer skill implements the functionality to sell data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -13,12 +13,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_seller:0.8.0 +- fetchai/generic_seller:0.9.0 behaviours: service_registration: args: diff --git a/packages/fetchai/skills/thermometer_client/skill.yaml b/packages/fetchai/skills/thermometer_client/skill.yaml index 93032c661b..edc4c944ad 100644 --- a/packages/fetchai/skills/thermometer_client/skill.yaml +++ b/packages/fetchai/skills/thermometer_client/skill.yaml @@ -1,6 +1,6 @@ name: thermometer_client author: fetchai -version: 0.6.0 +version: 0.7.0 description: The thermometer client skill implements the skill to purchase temperature data. license: Apache-2.0 @@ -14,12 +14,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_buyer:0.7.0 +- fetchai/generic_buyer:0.8.0 behaviours: search: args: diff --git a/packages/fetchai/skills/weather_client/skill.yaml b/packages/fetchai/skills/weather_client/skill.yaml index d9db213585..d0c42eb44a 100644 --- a/packages/fetchai/skills/weather_client/skill.yaml +++ b/packages/fetchai/skills/weather_client/skill.yaml @@ -1,6 +1,6 @@ name: weather_client author: fetchai -version: 0.6.0 +version: 0.7.0 description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' @@ -13,12 +13,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_buyer:0.7.0 +- fetchai/generic_buyer:0.8.0 behaviours: search: args: diff --git a/packages/fetchai/skills/weather_station/skill.yaml b/packages/fetchai/skills/weather_station/skill.yaml index e27797ecef..d5ac943b49 100644 --- a/packages/fetchai/skills/weather_station/skill.yaml +++ b/packages/fetchai/skills/weather_station/skill.yaml @@ -1,6 +1,6 @@ name: weather_station author: fetchai -version: 0.7.0 +version: 0.8.0 description: The weather station skill implements the functionality to sell weather data. license: Apache-2.0 @@ -17,12 +17,12 @@ fingerprint_ignore_patterns: - '*.db' contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: -- fetchai/generic_seller:0.8.0 +- fetchai/generic_seller:0.9.0 behaviours: service_registration: args: diff --git a/packages/hashes.csv b/packages/hashes.csv index 60746b6d53..50fc33d137 100644 --- a/packages/hashes.csv +++ b/packages/hashes.csv @@ -1,73 +1,73 @@ -fetchai/agents/aries_alice,QmacrJbA9Ei9mS6XTD4xv53hZydFqDJzGswJMJuRCSen9h -fetchai/agents/aries_faber,QmaTfqf2Ke3hWrzwsHBTaxgVeazLA3m5BYfU1XmAj7h7n9 -fetchai/agents/car_data_buyer,Qmb4YCcJ51M1bVjkBcJsLTcMYBo7FJWWnAzwpUVGFRz1BR -fetchai/agents/car_detector,QmRPBPo2vjcwHzmTaZDSy2JiRQ1vmcE5Dpi72TcUMRehyg -fetchai/agents/erc1155_client,Qmegev5efyDN6qf7fVDNV2KcDdWTyRtXiupvTh8o5hu6Y7 -fetchai/agents/erc1155_deployer,QmUGwqvJEntwcrSTj5hnBBjunzpJhjcUUDf76zqL5Zopgs -fetchai/agents/generic_buyer,QmeQeBKuHy5vUE3byJ7er9E5BhrjYABjRpLRV3x9svoyfq -fetchai/agents/generic_seller,QmQaSKyhoAPqNKhAqEhDaRTseSDMKfZZMxuxCeUXMDRFc3 -fetchai/agents/gym_aea,QmYPdX62wJ92CENCyL1s4jabJgb9TPjcoMujogSHc29Y5S -fetchai/agents/ml_data_provider,QmbbNx4QQscw85EuVxNHGFWaxWxgKxHzqxKi2dCZ2u4B4z -fetchai/agents/ml_model_trainer,QmRGaCh5QYAySPyPL5wyvgxsyrRGwscd17T4eSJ1rciBcu -fetchai/agents/my_first_aea,QmcaigCyMziXfBjFER7aQhMZnHtsGytii9QFehRQuiA44N -fetchai/agents/simple_service_registration,QmbavaDU7t2MUpQYwtHu76nvLN2mSTgA8YeW8EQ7GMbehF -fetchai/agents/tac_controller,QmbphSNH9YBBEfhB1Sa8BadZscscnJ9RVjv7epjTdGbcBG -fetchai/agents/tac_controller_contract,QmYT57WS4y6jPRkF3RpB7rHW4n4XL4rx83hF6YXyMZw1M1 -fetchai/agents/tac_participant,QmQJDTm92TzqD725cWeFt53J85psv6JVuvxJucMDiifW4k -fetchai/agents/thermometer_aea,QmbB54sVDmKsn9MfAoWqsLEfR5FbdEvL4ffCwLNhujAWjs -fetchai/agents/thermometer_client,QmXjQpq9WnhS7d8Dr57zN3RhJ9z2NpPJcEgknRPU88fUVs -fetchai/agents/weather_client,QmXibCD5eAJVCxJctC3Pc8ibGbSMcENWu31utxPUe9LLNY -fetchai/agents/weather_station,Qme4kTkGnGQdJ2TSrKeQmmaZJYqPPT7i7L3Nftgckuh2Zq -fetchai/connections/gym,QmXpTer28dVvxeXqsXzaBqX551QToh9w5KJC2oXcStpKJG -fetchai/connections/http_client,QmUjtATHombNqbwHRonc3pLUTfuvQJBxqGAj4K5zKT8beQ -fetchai/connections/http_server,QmXuGssPAahvRXHNmYrvtqYokgeCqavoiK7x9zmjQT8w23 -fetchai/connections/ledger,QmVXceMJCioA1Hro9aJgBwrF9yLgToaVXifDz6EVo6vTXn -fetchai/connections/local,QmZKciQTgE8LLHsgQX4F5Ecc7rNPp9BBSWQHEEe7jEMEmJ -fetchai/connections/oef,QmWcT6NA3jCsngAiEuCjLtWumGKScS6PrjngvGgLJXg9TK -fetchai/connections/p2p_client,Qmd47ry6ZCEzkT9pCo96irv9x5D1EcqSiMkhCgXPykaDbz -fetchai/connections/p2p_libp2p,QmQE7eUsiMJJ61ruqxgUGrpbTdoBQfusxkmuXTManufeWN -fetchai/connections/p2p_libp2p_client,QmZ1MQEacF6EEqfWaD7gAauwvk44eQfxzi6Ew23Wu3vPeP -fetchai/connections/p2p_stub,QmTFcniXvpUw5hR27SN1W1iLcW8eGsMzFvzPQ4s3g3bw3H -fetchai/connections/scaffold,QmTzEeEydjohZNTsAJnoGMtzTgCyzMBQCYgbTBLfqWtw5w -fetchai/connections/soef,QmamP24iyoN9xMNCShTkYgKyQg9cfMgcHRZyopeDis9nmD -fetchai/connections/stub,QmWP6tgcttnUY86ynAseyHuuFT85edT31QPSyideVveiyj -fetchai/connections/tcp,Qmec7QAC2xzvcyvcciNnkBzrv2rWt61jxA7H1KxKvCSbc1 -fetchai/connections/webhook,QmZqPmyD36hmowzUrV4MsjXjXM6GXYJuZjKg9r1XUMeGxW -fetchai/contracts/erc1155,QmPEae32YqmCmB7nAzoLokosvnu3u8ZN75xouzZEBvE5zM +fetchai/agents/aries_alice,QmVU2GVL6bSitqAwe7kdy9XqixBpS9QwBDH7SWW4cEAaE1 +fetchai/agents/aries_faber,QmU1VCNmYgWU8kyZ4L2ip4UfZEi8ZVRQKvMx32Hyswwgr1 +fetchai/agents/car_data_buyer,QmP5XTu5bbRGNRx4k4NB2iVpSx5gRAziNn5cVqiByq9NV5 +fetchai/agents/car_detector,QmRBFJ5EFm8HYzqRhhcYpHcwYWKv5dbzqQTHToXwJpVPs4 +fetchai/agents/erc1155_client,QmUqtsVGejFWmMVe7nEjuMZmgsRa5EGsjprjS7CjW9LQug +fetchai/agents/erc1155_deployer,QmVqL2HdwN2uGx6rzt3y4ZsEThuMzmpdAz89PGudgJVQYJ +fetchai/agents/generic_buyer,QmSw5UYvB2srkr9ety7FF9yyPfpvefQLeUZoDqNsDPY9hf +fetchai/agents/generic_seller,Qma9wX7T63v15kWbER3UvVJqVPMzfje8JcUzFyFDSycYyC +fetchai/agents/gym_aea,QmYiWRUjjFVk7QvpzQpTonN2sfPTXCYZprThwNpveQ8Lgo +fetchai/agents/ml_data_provider,QmazcospJkSrtbp23EERg3sNmVb6vrVd7NirBThtmChdYF +fetchai/agents/ml_model_trainer,QmRCLb4WkWiQ8Povx6RaBZzaT241hHSE4DPQwWxsiSf3e9 +fetchai/agents/my_first_aea,Qme2CV8RMJqg3FC7n9iTgSFsdh6PgiWHRMuhX1nAgYYU7M +fetchai/agents/simple_service_registration,Qmbgyep6iNgjVUhQbc5y9Aeahji58EUEHXWf1WP2NZuDiK +fetchai/agents/tac_controller,QmePNyf8guJz5oXGT4SkAHPb5NtHPFTAkiHiB6d21iJN1n +fetchai/agents/tac_controller_contract,QmUpJG6gHACeavn2tf7s7jS5eC2frWPy5PBzmMTAPfkR5a +fetchai/agents/tac_participant,QmcSePKdWEnpdWQShpmg1owsjbyKs6d5A2KfU9qGbbTGMU +fetchai/agents/thermometer_aea,QmWkMew1idZTd6KxTy7e8s8U3rbvyhNpSWt2i4XsNJeNg4 +fetchai/agents/thermometer_client,QmcBk27hp6Z88bJgMUgJQcJQEtwD6sMiRKSt5WAGUaMJza +fetchai/agents/weather_client,QmNPmHyVFy7Tf9wJkHJCL4sm4UeYLT3wXPYa1FYVkyWXq1 +fetchai/agents/weather_station,QmedEK6tRkBrBFbVfv7GBCUiy8iC3TWzNx8U98ebzqbQtG +fetchai/connections/gym,Qma74nB7YARSNTkS6vPZeDoDrKNH5SQCVomoPQhaQNFrfG +fetchai/connections/http_client,QmTSyCEfNAbNWkqLVQHPQj1KkQXgAN3eXKpcnwBxxsqWiP +fetchai/connections/http_server,QmXSBRoWRMcK6vQAxmw2KV7akY3sVKDDm9qmfpjTwkbJQA +fetchai/connections/ledger,QmRsmyhLZ8JYRob66syAU7JxLHEp1PLij9ppHrC5ja31mb +fetchai/connections/local,QmUbZPvqa8ThAJ9bUJoFB23td1rnr5Kf7hw9NQ6ptvcaVY +fetchai/connections/oef,QmedfR2v5uJo5AZeAMncTKgtofcDvaXi3Bgst5DN36bovN +fetchai/connections/p2p_client,Qma5AYuw824MUyfhiZzcJefo9Uykv25XZc6z38JhS72G1y +fetchai/connections/p2p_libp2p,QmNmpnCnumRve4wJeHnAMBhMYp39Ppzs3EYJyuNYmSHyJV +fetchai/connections/p2p_libp2p_client,QmZ6bz3TJ8Z2zvERcCWMSRW3n5YRy2i2b2XYk5syjt7TaK +fetchai/connections/p2p_stub,QmNi7G92g27qnqJmdLu5cr7CG4unsW4RdNfR2KwagiszzS +fetchai/connections/scaffold,QmYa1T9HNZcuubhf8p4ytdgr3h27HLjbPYsR4DYLYo24pC +fetchai/connections/soef,QmbbYKEP4fdTS9EMprVAfSAq55YAudsx9JocbBzzb9L5Fr +fetchai/connections/stub,Qmdo6c9X53ZKbtiHj3hNbtgFSe4rcyyZLET8TtRJHQVNs5 +fetchai/connections/tcp,QmaVKqs26qi9WHdUTJ9zKPPw5rhQbke48UvNdemZnHMhdL +fetchai/connections/webhook,QmUCWs3z31xoakJDz9rXNT5TAmLcWpf1CZfnsHuMXUSgiL +fetchai/contracts/erc1155,QmWMU8adudHWC6ZciZFR8YVnWbZsfugZbQmWwHnKBoDwrM fetchai/contracts/scaffold,Qme97drP4cwCyPs3zV6WaLz9K7c5ZWRtSWQ25hMUmMjFgo -fetchai/protocols/contract_api,QmcveAM85xPuhv2Dmo63adnhh5zgFVjPpPYQFEtKWxXvKj -fetchai/protocols/default,QmXuCJgN7oceBH1RTLjQFbMAF5ZqpxTGaH7Mtx3CQKMNSn -fetchai/protocols/fipa,QmSjtK4oegnfH7DUVAaFP1wBAz4B7M3eW51NgU12YpvnTy -fetchai/protocols/gym,QmaoqyKo6yYmXNerWfac5W8etwgHtozyiruH7KRW9hS3Ef -fetchai/protocols/http,Qma9MMqaJv4C3xWkcpukom3hxpJ8UiWBoao3C3mAgAf4Z3 -fetchai/protocols/ledger_api,QmPKixWAP333wRsXrFL7fHrdoaRxrXxHwbqG9gnkaXmQrR -fetchai/protocols/ml_trade,QmQH9j4bN7Nc5M8JM6z3vK4DsQxGoKbxVHJt4NgV5bjvG3 -fetchai/protocols/oef_search,QmepRaMYYjowyb2ZPKYrfcJj2kxUs6CDSxqvzJM9w22fGN -fetchai/protocols/scaffold,QmPSZhXhrqFUHoMVXpw7AFFBzPgGyX5hB2GDafZFWdziYQ -fetchai/protocols/signing,QmXKdJ7wtSPP7qrn8yuCHZZRC6FQavdcpt2Sq4tHhFJoZY -fetchai/protocols/state_update,QmR5hccpJta4x574RXwheeqLk1PwXBZZ23nd3LS432jFxp -fetchai/protocols/tac,QmSWJcpfZnhSapGQbyCL9hBGCHSBB7qKrmMBHjzvCXE3mf -fetchai/skills/aries_alice,QmVJsSTKgdRFpGSeXa642RD3GxZ4UxdykzuL9c4jjEWB8M -fetchai/skills/aries_faber,QmcqRhcdZ3v42bd9gX2wMVB81Xq7tztumknxcWeKYJm6cB -fetchai/skills/carpark_client,QmWJWwBKERdz4r4f6aHxsZtoXKHrsW4weaVKYcnLA1xph3 -fetchai/skills/carpark_detection,QmREVHt2N4k2PMsyh3LScqz7g5noUNM6md9cxr8VfP7HxX -fetchai/skills/echo,QmeSr4j8W9enijZvgeE3vXeWcEj9sS8fo6vNFRpyAMnZey -fetchai/skills/erc1155_client,QmYHeQTJEyieViE1j6s6wS43DmrgXfc3NoG7n61JsANizd -fetchai/skills/erc1155_deploy,QmU9Un4ktCcvEqBRHx89BKQm6VjGNKL5LDQaWwbXxGp8Hw -fetchai/skills/error,QmVirmcRGj6bc2i6iJZ2zoWGCfsCZMoGmZAXYq5aaYAqNb -fetchai/skills/generic_buyer,QmSYDHpe1AZpCEig7JKrjTMvCpqPo2E3Dyv4S9p1gzSeNw -fetchai/skills/generic_seller,Qmf9fg8nChsg2Sq9o7NpUxGhCFCQaUcygJ68GLebi3As6D -fetchai/skills/gym,QmbeF2SzEcK6Db62W1i6EZTsJqJReWmp9ZouLCnSqdsYou -fetchai/skills/http_echo,QmP5NXoCvXC9oxxJY4y846wmEhwP9NQS6pPKyN4knpfZTG -fetchai/skills/ml_data_provider,QmQtoSEhnrUT32tooovwsNSeYiNVtpyn64L5X584TrhctD -fetchai/skills/ml_train,QmeQwZSko3qxsmt2vqnBhJ9JX9dbKt6gM91Jqif1SQFedr -fetchai/skills/scaffold,QmUG5Dwo3Sw6bTn38PLVEEU6tyEAKffUjWjPRDL3XjKaDQ -fetchai/skills/simple_service_registration,QmNm3RvVyVRY94kwX7eqWkf1f8rPxPtWBywACPU13YKwxU -fetchai/skills/tac_control,QmPsmfi72nafUMcGyzGPfBgRRy8cPkSB9n8VkyrnXMfwWV -fetchai/skills/tac_control_contract,QmbSunYrCRE87dLK4G56RByY4dCWsmNRURu8Dj4ZpBgpKb -fetchai/skills/tac_negotiation,Qmc5drAe2jhLwGNP43iDjoP3s8VAaXFwSkxuu4wJ4WMSem -fetchai/skills/tac_participation,QmQi9zwYyxhjVjff24D2pjCJE96xae7zzv7231iqvn85tv -fetchai/skills/thermometer,QmRkKxbmQBdmYGXXuLgNhBqsX8KEpUC3TmfbZTJ5r9LyB3 -fetchai/skills/thermometer_client,QmP7J7iurvq98Nrp31C3XDc3E3sNf9Tq3ytrELE2VCoedq -fetchai/skills/weather_client,QmZeHxAXWh8RTToDAoa8zwC6aoRZjNLV3tV51H6UDfTxJo -fetchai/skills/weather_station,QmV2YiH4spJKjXoWRxyicMQJNhpzRB3iLcVcpCcWyJWxs1 +fetchai/protocols/contract_api,QmXBKagx4cmBr3xQE3yJGn3Mund2RxHK9TfASqoSu2Uz34 +fetchai/protocols/default,Qmd1Gy5oEk89zVYdjib37wKp7whTuSD9pFYJwa14AKfUin +fetchai/protocols/fipa,QmPMRERu7BreF1tHy6nfLjUPS3thTCM1WrRsfAQZRUEQbb +fetchai/protocols/gym,QmNShFTSARmkSXAZ9HvVWiX5DKdutKkZRC6g1NaqtGpzoi +fetchai/protocols/http,QmTpZ8GjMpDv9nvfWL4cRwUxDcmzvNqsT1z76TzsyeBUkh +fetchai/protocols/ledger_api,QmSob4wjYMMi74mNkMP2UpCTozt1WTjAfheoWficeB5TcR +fetchai/protocols/ml_trade,QmcwQb6zEWFSEDt8jDPkTLfe9LKodCHai1xjNtGKnAmykx +fetchai/protocols/oef_search,QmNnbw8CxFdUQ7E1K3ytKueq2gwPhpEXhaH7MVaxYjqV3U +fetchai/protocols/scaffold,QmZ1fUdPutYFwaAwjMU4SCLu9ubKxTx3y59PAFyRuHw7BZ +fetchai/protocols/signing,QmWfFpFhsbmN5dyLmmEzDUMjQRXLU5ni48YzsVaesuGGer +fetchai/protocols/state_update,QmTwnydQvg7aMeSDnUA5j3nDPYuHvtbbyo26xERM4bV3zC +fetchai/protocols/tac,QmSMMV9nfk2H7qua78izpmZwUgaccDbC9nty1ppiATJcvW +fetchai/skills/aries_alice,QmcPob8KpEcMt236ufTCiqiZi6FTob9s12Nqg8s28WWVKP +fetchai/skills/aries_faber,QmemjoaPWQyfoF7Pe4FB34KQwGP5KrA6KfRnFeqEepTmWZ +fetchai/skills/carpark_client,QmT9wRwQYby6zuNX5Lyz7Bm8KvLhmaCcpa35PsWFNkhuso +fetchai/skills/carpark_detection,QmScZT6oDae5BDfJ4Wq6nfH38ao5DZg22Cmkxp58iGTJDF +fetchai/skills/echo,QmcfWAcwy9Mu1EJ8ae5LKRCPhtKNwRfEzQtgYx1VKuXQCr +fetchai/skills/erc1155_client,QmbGnkLHWLXcpHdFnUWnNE3EuQVezZbwWBeV9WJhiEmfck +fetchai/skills/erc1155_deploy,QmfSfzFxo7TCw8NC93GXHvFw1AFFmL9zusymHaGLrbur1w +fetchai/skills/error,QmawE9AV4DnQiVHhzdUcEUQd9PToz4iL861VSc3KNpF1VB +fetchai/skills/generic_buyer,QmNhAqXy18qmAiX8Pb3MUJBLj9ZctTtWodvyM4AE6Mz1mQ +fetchai/skills/generic_seller,QmP9YAaYLcRYG5wMQf2KBfG7h4yNHAn5j8qtRCJiDkEXJM +fetchai/skills/gym,QmP3qriWyccRu2kCBtAVLuM6ve79ifmA5iFLagWdQFHAcx +fetchai/skills/http_echo,QmcV8bDeVdb51hQR9uigSiqJPzAkFPz7hqPzs6wcM8nVS5 +fetchai/skills/ml_data_provider,QmQP9ZsPJoxwAGpZCaz2uNqVuaUGtYo3R8wPFVgnMHZ63f +fetchai/skills/ml_train,QmXSnVw2FDUXefsgAwJaxxzhn27ViQS5x1s1ytEoZx7pmy +fetchai/skills/scaffold,QmPZfCsZDYvffThjzr8n2yYJFJ881wm8YsbBc1FKdjDXKR +fetchai/skills/simple_service_registration,QmSRGtSxU5EUZyGmmUGbxUyFdCYpQvmD3Vv3ihgY4aMJqf +fetchai/skills/tac_control,Qmc8Vj5FyzrbeopDrguUJfd4s5iEAN13FLJwZu12MhBTgs +fetchai/skills/tac_control_contract,QmWhGcvhDwi7DBByVMQSATKXxNCxAU5a1B8HhEhx8RK8Bm +fetchai/skills/tac_negotiation,QmTV8Bap864XJmjiHfDdfVT1ojQKEuYmzd63AabXCqDfRB +fetchai/skills/tac_participation,QmPFhsDaS6QA1NBxuNUKMwAhC6eep3K8bnNkChN3LowDf2 +fetchai/skills/thermometer,QmcNUpps3wPxWAs3a31DTY3nzVJmfz1AL4bNSXAj1NBWMM +fetchai/skills/thermometer_client,Qmc7Vp6xAm96k5NZ5AmU7fDFYNqb6FSsZXnBrGpbxsU6RZ +fetchai/skills/weather_client,QmX4K3XadNyuq6gvjdvVV3LQEnXueRHhukpgFYUD9JTsWY +fetchai/skills/weather_station,QmaoRHkEbmVm1zBtc3FSLjmBVfw3Xn6AvwzHjeniQ6wM3V diff --git a/scripts/generate_api_docs.py b/scripts/generate_api_docs.py index 343fb075b1..9c9c58ec06 100755 --- a/scripts/generate_api_docs.py +++ b/scripts/generate_api_docs.py @@ -141,9 +141,21 @@ def generate_api_docs(): save_to_file(path, text) +def install(package: str) -> int: + """ + Install a PyPI package by calling pip. + + :param package: the package name and version specifier. + :return: the return code. + """ + return subprocess.check_call( # nosec + [sys.executable, "-m", "pip", "install", package] + ) + + if __name__ == "__main__": res = shutil.which("pydoc-markdown") if res is None: - print("Please install pydoc-markdown first: `pip install pydoc-markdown`") + install("pydoc-markdown==3.3.0") sys.exit(1) generate_api_docs() diff --git a/scripts/generate_ipfs_hashes.py b/scripts/generate_ipfs_hashes.py index b7185d641d..3760fc1368 100755 --- a/scripts/generate_ipfs_hashes.py +++ b/scripts/generate_ipfs_hashes.py @@ -199,6 +199,14 @@ def __init__(self, timeout: float = 10.0): res = shutil.which("ipfs") if res is None: raise Exception("Please install IPFS first!") + process = subprocess.Popen( # nosec + ["ipfs", "--version"], stdout=subprocess.PIPE, env=os.environ.copy(), + ) + output, _ = process.communicate() + if b"0.4.23" not in output: + raise Exception( + "Please ensure you have version 0.4.23 of IPFS daemon installed." + ) self.process = None # type: Optional[subprocess.Popen] def __enter__(self): @@ -442,14 +450,14 @@ def check_same_ipfs_hash( :param all_expected_hashes: the dictionary of all the expected hashes. :return: True if the IPFS hash match, False otherwise. """ - if configuration.name in [ - "erc1155", - "carpark_detection", - "p2p_libp2p", - "Agent0", - "dummy", - ]: - return True # TODO: fix + # if configuration.name in [ + # "erc1155", + # "carpark_detection", + # "p2p_libp2p", + # "Agent0", + # "dummy", + # ]: + # return True # packages with nested dirs or symlinks, kept for reference key, actual_hash, result_list = ipfs_hashing(client, configuration, package_type) expected_hash = all_expected_hashes[key] result = actual_hash == expected_hash diff --git a/scripts/update_package_versions.py b/scripts/update_package_versions.py index 23b82bd917..fd5b659e78 100644 --- a/scripts/update_package_versions.py +++ b/scripts/update_package_versions.py @@ -186,8 +186,12 @@ def process_packages( is_bumped = False for type_ in TYPES: for key, value in last_by_type[type_].items(): + if key == "scaffold": + print("Package `{}` of type `{}` is never bumped!".format(key, type_)) + continue if key not in now_by_type[type_]: print("Package `{}` of type `{}` no longer present!".format(key, type_)) + continue if now_by_type[type_][key] == value: print( "Package `{}` of type `{}` has not changed since last release!".format( @@ -291,7 +295,6 @@ def process_package(type_: str, name: str) -> bool: def run_once() -> bool: """Run the upgrade logic once.""" - check_if_running_allowed() last = get_hashes_from_last_release() now = get_hashes_from_current_release() last_by_type = split_hashes_by_type(last) @@ -301,6 +304,12 @@ def run_once() -> bool: if __name__ == "__main__": + """ + First, check all hashes are up to date, exit if not. + Then, run the bumping algo, re-hashing upon each bump. + """ + run_hashing() + check_if_running_allowed() while run_once(): run_hashing() sys.exit(0) diff --git a/setup.cfg b/setup.cfg index eb82eeb645..a61724d70b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -118,6 +118,9 @@ ignore_missing_imports = True [mypy-tests/data/generator/t_protocol/*] ignore_errors = True +[mypy-tests/data/generator/t_protocol_no_ct/*] +ignore_errors = True + [mypy-tests/data/dummy_aea/vendor/*] ignore_errors = True diff --git a/setup.py b/setup.py index cd7ad757da..ca796e3642 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def parse_readme(): replacement = raw_url_root + r"\g<0>" readme = re.sub(r"(?<= MagicMock: """ diff --git a/tests/common/utils.py b/tests/common/utils.py index 2fc3cdb938..abdddc2531 100644 --- a/tests/common/utils.py +++ b/tests/common/utils.py @@ -86,6 +86,11 @@ def setup(self) -> "AeaTool": self.aea.start_setup() return self + def teardown(self) -> "AeaTool": + """Call AEA.teardown.""" + self.aea.teardown() + return self + def wait_outbox_empty( self, sleep: float = DEFAULT_SLEEP, timeout: float = DEFAULT_TIMEOUT ) -> "AeaTool": @@ -264,6 +269,7 @@ def wrap(*args, **kwargs) -> Any: @contextmanager def run_in_thread(fn, timeout=10, on_exit=None, **kwargs): + """Run a function in contextmanager and test and awaits it completed.""" thread = Thread(target=fn, **kwargs) thread.daemon = True thread.start() @@ -278,6 +284,7 @@ def run_in_thread(fn, timeout=10, on_exit=None, **kwargs): def wait_for_condition(condition_checker, timeout=2, error_msg="Timeout"): + """Wait for condition occures in selected timeout.""" start_time = time.time() while not condition_checker(): diff --git a/tests/conftest.py b/tests/conftest.py index 162796ae29..a4278974c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """Conftest module for Pytest.""" import asyncio import inspect @@ -58,7 +59,7 @@ DEFAULT_SKILL_CONFIG_FILE as SKILL_YAML, PublicId, ) -from aea.configurations.constants import DEFAULT_CONNECTION +from aea.configurations.constants import DEFAULT_CONNECTION, DEFAULT_LEDGER from aea.connections.base import Connection from aea.connections.stub.connection import StubConnection from aea.contracts import Contract, contract_registry @@ -71,6 +72,7 @@ FETCHAI_PRIVATE_KEY_FILE, ) from aea.crypto.registries import make_crypto +from aea.crypto.wallet import CryptoStore from aea.identity.base import Identity from aea.mail.base import Address from aea.test_tools.click_testing import CliRunner as ImportedCliRunner @@ -128,6 +130,8 @@ ETHEREUM = _ETHEREUM FETCHAI = _FETCHAI +COSMOS_PRIVATE_KEY_FILE_CONNECTION = "cosmos_connection_private_key.txt" + # private keys with value on testnet COSMOS_PRIVATE_KEY_PATH = os.path.join( ROOT_DIR, "tests", "data", COSMOS_PRIVATE_KEY_FILE @@ -174,6 +178,8 @@ "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001", } +PUBLIC_DHT_P2P_MADDR_1 = "/dns4/agents-p2p-dht.sandbox.fetch-ai.com/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx" +PUBLIC_DHT_P2P_MADDR_2 = "/dns4/agents-p2p-dht.sandbox.fetch-ai.com/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW" # testnets COSMOS_TESTNET_CONFIG = {"address": "https://rest-agent-land.prod.fetch-ai.com:443"} @@ -190,7 +196,7 @@ UNKNOWN_SKILL_PUBLIC_ID = PublicId("unknown_author", "unknown_skill", "0.1.0") LOCAL_CONNECTION_PUBLIC_ID = PublicId("fetchai", "local", "0.1.0") P2P_CLIENT_CONNECTION_PUBLIC_ID = PublicId("fetchai", "p2p_client", "0.1.0") -HTTP_CLIENT_CONNECTION_PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.5.0") +HTTP_CLIENT_CONNECTION_PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.6.0") HTTP_PROTOCOL_PUBLIC_ID = PublicId("fetchai", "http", "0.1.0") STUB_CONNECTION_PUBLIC_ID = DEFAULT_CONNECTION DUMMY_PROTOCOL_PUBLIC_ID = PublicId("dummy_author", "dummy", "0.1.0") @@ -199,10 +205,11 @@ MAX_FLAKY_RERUNS = 3 MAX_FLAKY_RERUNS_ETH = 1 -MAX_FLAKY_RERUNS_INTEGRATION = 2 +MAX_FLAKY_RERUNS_INTEGRATION = 1 FETCHAI_PREF = os.path.join(ROOT_DIR, "packages", "fetchai") -PROTOCOL_SPECS_PREF = os.path.join(ROOT_DIR, "examples", "protocol_specification_ex") +PROTOCOL_SPECS_PREF_1 = os.path.join(ROOT_DIR, "examples", "protocol_specification_ex") +PROTOCOL_SPECS_PREF_2 = os.path.join(ROOT_DIR, "tests", "data") contract_config_files = [ os.path.join(ROOT_DIR, "aea", "contracts", "scaffold", CONTRACT_YAML), @@ -303,18 +310,9 @@ ] protocol_specification_files = [ - os.path.join(PROTOCOL_SPECS_PREF, "contract_api.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "default.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "fipa.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "gym.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "http.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "ledger_api.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "ml_trade.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "oef_search.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "sample.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "signing.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "state_update.yaml",), - os.path.join(PROTOCOL_SPECS_PREF, "tac.yaml",), + os.path.join(PROTOCOL_SPECS_PREF_1, "sample.yaml",), + os.path.join(PROTOCOL_SPECS_PREF_2, "sample_specification.yaml",), + os.path.join(PROTOCOL_SPECS_PREF_2, "sample_specification_no_custom_types.yaml",), ] @@ -350,6 +348,7 @@ def action_for_platform(platform_name: str, skip: bool = True) -> Callable: def decorator(pytest_func): """ For the sake of clarity, assume the chosen platform for the action is "Windows". + If the following condition is true: - the current system is not Windows (is_different) AND we want to skip it (skip) OR @@ -378,7 +377,12 @@ def action(*args, **kwargs): return type( pytest_func.__name__, (pytest_func,), - {"setup_class": action, "setup": action, "setUp": action}, + { + "setup_class": action, + "setup": action, + "setUp": action, + "_skipped": True, + }, ) @wraps(pytest_func) @@ -784,15 +788,16 @@ def _make_libp2p_connection( entry_peers: Optional[Sequence[MultiAddr]] = None, delegate_port: int = 11234, delegate_host: str = "127.0.0.1", + node_key_file: Optional[str] = None, ) -> P2PLibp2pConnection: log_file = "libp2p_node_{}.log".format(port) if os.path.exists(log_file): os.remove(log_file) - crypto = make_crypto(FETCHAI) + crypto = make_crypto(COSMOS) identity = Identity("", address=crypto.address) if relay and delegate: configuration = ConnectionConfig( - node_key_file=None, + node_key_file=node_key_file, local_uri="{}:{}".format(host, port), public_uri="{}:{}".format(host, port), entry_peers=entry_peers, @@ -802,7 +807,7 @@ def _make_libp2p_connection( ) elif relay and not delegate: configuration = ConnectionConfig( - node_key_file=None, + node_key_file=node_key_file, local_uri="{}:{}".format(host, port), public_uri="{}:{}".format(host, port), entry_peers=entry_peers, @@ -811,7 +816,7 @@ def _make_libp2p_connection( ) else: configuration = ConnectionConfig( - node_key_file=None, + node_key_file=node_key_file, local_uri="{}:{}".format(host, port), entry_peers=entry_peers, log_file=log_file, @@ -823,7 +828,7 @@ def _make_libp2p_connection( def _make_libp2p_client_connection( node_port: int = 11234, node_host: str = "127.0.0.1" ) -> P2PLibp2pClientConnection: - crypto = make_crypto(FETCHAI) + crypto = make_crypto(COSMOS) identity = Identity("", address=crypto.address) configuration = ConnectionConfig( client_key_file=None, @@ -839,7 +844,7 @@ def libp2p_log_on_failure(fn: Callable) -> Callable: :return: decorated method. """ - + # for pydcostyle @wraps(fn) def wrapper(self, *args, **kwargs): try: @@ -857,7 +862,7 @@ def wrapper(self, *args, **kwargs): def libp2p_log_on_failure_all(cls): """ - Decorate every method of a class with `libp2p_log_on_failure` + Decorate every method of a class with `libp2p_log_on_failure`. :return: class with decorated methods. """ @@ -879,7 +884,7 @@ def libp2p_log_on_failure_all(cls): return cls -def do_for_all(method_decorator): +def _do_for_all(method_decorator): def class_decorator(cls): class GetAttributeMetaClass(type): def __getattribute__(cls, name): @@ -909,6 +914,22 @@ def __init__(self): super().__init__("CWD was not restored") +@pytest.fixture(scope="class", autouse=True) +def aea_testcase_teardown_check(request): + """Check BaseAEATestCase.teardown_class for BaseAEATestCase based test cases.""" + from aea.test_tools.test_cases import BaseAEATestCase # cause circular import + + yield + if ( + request.cls + and issubclass(request.cls, BaseAEATestCase) + and getattr(request.cls, "_skipped", False) is False + ): + assert getattr( + request.cls, "_is_teardown_class_called", None + ), "No BaseAEATestCase.teardown_class was called!" + + @pytest.fixture(scope="class", autouse=True) def check_test_class_cwd(): """Check test case class restore CWD.""" @@ -929,6 +950,7 @@ def check_test_cwd(request): old_cwd = os.getcwd() yield if old_cwd != os.getcwd(): + os.chdir(ROOT_DIR) raise CwdException() @@ -947,11 +969,29 @@ def check_test_threads(request): assert num_threads >= new_num_threads, "Non closed threads!" +@pytest.fixture() +async def ledger_apis_connection(request): + """Make a connection.""" + crypto = make_crypto(DEFAULT_LEDGER) + identity = Identity("name", crypto.address) + crypto_store = CryptoStore() + directory = Path(ROOT_DIR, "packages", "fetchai", "connections", "ledger") + connection = Connection.from_dir( + directory, identity=identity, crypto_store=crypto_store + ) + connection = cast(Connection, connection) + connection._logger = logging.getLogger("aea.packages.fetchai.connections.ledger") + await connection.connect() + yield connection + await connection.disconnect() + + @pytest.fixture() def erc1155_contract(): """ - Instantiate an ERC1155 contract instance. As a side effect, - register it to the registry, if not already registered. + Instantiate an ERC1155 contract instance. + + As a side effect, register it to the registry, if not already registered. """ directory = Path(ROOT_DIR, "packages", "fetchai", "contracts", "erc1155") configuration = ComponentConfiguration.load(ComponentType.CONTRACT, directory) diff --git a/tests/data/aea-config.example.yaml b/tests/data/aea-config.example.yaml index 17aa34e0fb..8421e05e94 100644 --- a/tests/data/aea-config.example.yaml +++ b/tests/data/aea-config.example.yaml @@ -7,16 +7,16 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.6.0 +- fetchai/oef:0.7.0 contracts: [] protocols: -- fetchai/oef_search:0.3.0 -- fetchai/default:0.3.0 -- fetchai/tac:0.3.0 -- fetchai/fipa:0.4.0 +- fetchai/oef_search:0.4.0 +- fetchai/default:0.4.0 +- fetchai/tac:0.4.0 +- fetchai/fipa:0.5.0 skills: -- fetchai/echo:0.3.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/echo:0.4.0 +default_connection: fetchai/oef:0.7.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/aea-config.example_w_keys.yaml b/tests/data/aea-config.example_w_keys.yaml index e685d96b53..385a9a65e1 100644 --- a/tests/data/aea-config.example_w_keys.yaml +++ b/tests/data/aea-config.example_w_keys.yaml @@ -7,16 +7,16 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.6.0 +- fetchai/oef:0.7.0 contracts: [] protocols: -- fetchai/oef_search:0.3.0 -- fetchai/default:0.3.0 -- fetchai/tac:0.3.0 -- fetchai/fipa:0.4.0 +- fetchai/oef_search:0.4.0 +- fetchai/default:0.4.0 +- fetchai/tac:0.4.0 +- fetchai/fipa:0.5.0 skills: -- fetchai/echo:0.3.0 -default_connection: fetchai/oef:0.6.0 +- fetchai/echo:0.4.0 +default_connection: fetchai/oef:0.7.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/dependencies_skill/skill.yaml b/tests/data/dependencies_skill/skill.yaml index 77ca268645..460a5a600e 100644 --- a/tests/data/dependencies_skill/skill.yaml +++ b/tests/data/dependencies_skill/skill.yaml @@ -9,7 +9,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: [] behaviours: {} handlers: {} diff --git a/tests/data/dummy_aea/aea-config.yaml b/tests/data/dummy_aea/aea-config.yaml index a32248463d..7f1679e540 100644 --- a/tests/data/dummy_aea/aea-config.yaml +++ b/tests/data/dummy_aea/aea-config.yaml @@ -7,16 +7,16 @@ aea_version: '>=0.5.0, <0.6.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/local:0.4.0 +- fetchai/local:0.5.0 contracts: -- fetchai/erc1155:0.6.0 +- fetchai/erc1155:0.7.0 protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 skills: - dummy_author/dummy:0.1.0 -- fetchai/error:0.3.0 -default_connection: fetchai/local:0.4.0 +- fetchai/error:0.4.0 +default_connection: fetchai/local:0.5.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/dummy_connection/connection.py b/tests/data/dummy_connection/connection.py index 5d41819b92..6fc43e633a 100644 --- a/tests/data/dummy_connection/connection.py +++ b/tests/data/dummy_connection/connection.py @@ -24,7 +24,7 @@ from typing import Optional from aea.configurations.base import ConnectionConfig, PublicId -from aea.connections.base import Connection +from aea.connections.base import Connection, ConnectionStates from aea.crypto.wallet import CryptoStore from aea.identity.base import Identity from aea.mail.base import Envelope @@ -38,18 +38,18 @@ class DummyConnection(Connection): def __init__(self, **kwargs): """Initialize.""" super().__init__(**kwargs) - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) self._queue = None async def connect(self, *args, **kwargs): """Connect.""" self._queue = asyncio.Queue(loop=self.loop) - self.connection_status.is_connected = True + self._state.set(ConnectionStates.connected) async def disconnect(self, *args, **kwargs): """Disconnect.""" await self._queue.put(None) - self.connection_status.is_connected = False + self._state.set(ConnectionStates.disconnected) async def send(self, envelope: "Envelope"): """Send an envelope.""" @@ -77,16 +77,20 @@ def put(self, envelope: Envelope): @classmethod def from_config( - cls, configuration: ConnectionConfig, identity: Identity, cryptos: CryptoStore + cls, + configuration: ConnectionConfig, + identity: Identity, + crypto_store: CryptoStore, + **kwargs ) -> "Connection": """ Get the dummy connection from the connection configuration. :param configuration: the connection configuration. :param identity: the identity object. - :param cryptos: object to access the connection crypto objects. + :param crypto_store: object to access the connection crypto objects. :return: the connection object """ return DummyConnection( - configuration=configuration, identity=identity, cryptos=cryptos + configuration=configuration, identity=identity, crypto_store=crypto_store ) diff --git a/tests/data/dummy_connection/connection.yaml b/tests/data/dummy_connection/connection.yaml index 69f48d77fd..6b3aa6a82e 100644 --- a/tests/data/dummy_connection/connection.yaml +++ b/tests/data/dummy_connection/connection.yaml @@ -6,14 +6,14 @@ license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: __init__.py: QmbjcWHRhRiYMqZbgeGkEGVYi8hQ1HnYM8pBYugGKx9YnK - connection.py: QmXriASvrroCAKRteP9wUdhAUxH1iZgVTAriGY6ApL3iJc + connection.py: QmYn4mpVJTjKUUU9sCDGQHsTzYPeK4mTjwEHepHQddMMjs fingerprint_ignore_patterns: [] protocols: [] class_name: DummyConnection config: {} excluded_protocols: [] restricted_to_protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 dependencies: dep1: version: ==1.0.0 diff --git a/tests/data/dummy_skill/skill.yaml b/tests/data/dummy_skill/skill.yaml index d9ee43dd29..45d8a26ed6 100644 --- a/tests/data/dummy_skill/skill.yaml +++ b/tests/data/dummy_skill/skill.yaml @@ -15,7 +15,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: [] behaviours: dummy: diff --git a/tests/data/generator/t_protocol/custom_types.py b/tests/data/generator/t_protocol/custom_types.py index ca6d72d843..0a2feeda2c 100644 --- a/tests/data/generator/t_protocol/custom_types.py +++ b/tests/data/generator/t_protocol/custom_types.py @@ -67,8 +67,8 @@ def encode(data_model_protobuf_object, data_model_object: "DataModel") -> None: data_model_protobuf_object.list_field.extend(data_model_object.list_field) data_model_protobuf_object.dict_field.update(data_model_object.dict_field) - @staticmethod - def decode(data_model_protobuf_object) -> "DataModel": + @classmethod + def decode(cls, data_model_protobuf_object) -> "DataModel": """ Decode a protocol buffer object that corresponds with this class into an instance of this class. diff --git a/tests/data/generator/t_protocol/dialogues.py b/tests/data/generator/t_protocol/dialogues.py new file mode 100644 index 0000000000..d7e60880ab --- /dev/null +++ b/tests/data/generator/t_protocol/dialogues.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2020 fetchai +# +# 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 t_protocol dialogue management. + +- TProtocolDialogue: The dialogue class maintains state of a dialogue and manages it. +- TProtocolDialogues: The dialogues class keeps track of all dialogues. +""" + +from abc import ABC +from typing import Dict, FrozenSet, Optional, cast + +from aea.helpers.dialogue.base import Dialogue, DialogueLabel, Dialogues +from aea.mail.base import Address +from aea.protocols.base import Message + +from tests.data.generator.t_protocol.message import TProtocolMessage + + +class TProtocolDialogue(Dialogue): + """The t_protocol dialogue class maintains state of a dialogue and manages it.""" + + INITIAL_PERFORMATIVES = frozenset( + { + TProtocolMessage.Performative.PERFORMATIVE_CT, + TProtocolMessage.Performative.PERFORMATIVE_PT, + } + ) + TERMINAL_PERFORMATIVES = frozenset( + { + TProtocolMessage.Performative.PERFORMATIVE_MT, + TProtocolMessage.Performative.PERFORMATIVE_O, + TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS, + } + ) + VALID_REPLIES = { + TProtocolMessage.Performative.PERFORMATIVE_CT: frozenset( + {TProtocolMessage.Performative.PERFORMATIVE_PCT} + ), + TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS: frozenset( + {TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS} + ), + TProtocolMessage.Performative.PERFORMATIVE_MT: frozenset(), + TProtocolMessage.Performative.PERFORMATIVE_O: frozenset(), + TProtocolMessage.Performative.PERFORMATIVE_PCT: frozenset( + { + TProtocolMessage.Performative.PERFORMATIVE_MT, + TProtocolMessage.Performative.PERFORMATIVE_O, + } + ), + TProtocolMessage.Performative.PERFORMATIVE_PMT: frozenset( + { + TProtocolMessage.Performative.PERFORMATIVE_MT, + TProtocolMessage.Performative.PERFORMATIVE_O, + } + ), + TProtocolMessage.Performative.PERFORMATIVE_PT: frozenset( + {TProtocolMessage.Performative.PERFORMATIVE_PMT} + ), + } + + class Role(Dialogue.Role): + """This class defines the agent's role in a t_protocol dialogue.""" + + ROLE_1 = "role_1" + ROLE_2 = "role_2" + + class EndState(Dialogue.EndState): + """This class defines the end states of a t_protocol dialogue.""" + + END_STATE_1 = 0 + END_STATE_2 = 1 + END_STATE_3 = 2 + + def __init__( + self, + dialogue_label: DialogueLabel, + agent_address: Optional[Address] = None, + role: Optional[Dialogue.Role] = None, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param agent_address: the address of the agent for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + :return: None + """ + Dialogue.__init__( + self, + dialogue_label=dialogue_label, + agent_address=agent_address, + role=role, + rules=Dialogue.Rules( + cast(FrozenSet[Message.Performative], self.INITIAL_PERFORMATIVES), + cast(FrozenSet[Message.Performative], self.TERMINAL_PERFORMATIVES), + cast( + Dict[Message.Performative, FrozenSet[Message.Performative]], + self.VALID_REPLIES, + ), + ), + ) + + def is_valid(self, message: Message) -> bool: + """ + Check whether 'message' is a valid next message in the dialogue. + + These rules capture specific constraints designed for dialogues which are instances of a concrete sub-class of this class. + Override this method with your additional dialogue rules. + + :param message: the message to be validated + :return: True if valid, False otherwise + """ + return True + + +class TProtocolDialogues(Dialogues, ABC): + """This class keeps track of all t_protocol dialogues.""" + + END_STATES = frozenset( + { + TProtocolDialogue.EndState.END_STATE_1, + TProtocolDialogue.EndState.END_STATE_2, + TProtocolDialogue.EndState.END_STATE_3, + } + ) + + def __init__(self, agent_address: Address) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Dialogues.__init__( + self, + agent_address=agent_address, + end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), + ) + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> TProtocolDialogue: + """ + Create an instance of t_protocol dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = TProtocolDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/tests/data/generator/t_protocol/message.py b/tests/data/generator/t_protocol/message.py index 576e9c0d3d..b98a955ed9 100644 --- a/tests/data/generator/t_protocol/message.py +++ b/tests/data/generator/t_protocol/message.py @@ -28,7 +28,7 @@ from tests.data.generator.t_protocol.custom_types import DataModel as CustomDataModel -logger = logging.getLogger("packages.fetchai.protocols.t_protocol.message") +logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol.message") DEFAULT_BODY_SIZE = 4 @@ -36,7 +36,7 @@ class TProtocolMessage(Message): """A protocol for testing purposes.""" - protocol_id = ProtocolId("fetchai", "t_protocol", "0.1.0") + protocol_id = ProtocolId.from_str("fetchai/t_protocol:0.1.0") DataModel = CustomDataModel @@ -106,7 +106,7 @@ def message_id(self) -> int: return cast(int, self.get("message_id")) @property - def performative(self) -> Performative: # noqa: F821 + def performative(self) -> Performative: # type: ignore # noqa: F821 """Get the performative of the message.""" assert self.is_set("performative"), "performative is not set." return cast(TProtocolMessage.Performative, self.get("performative")) @@ -135,6 +135,14 @@ def content_ct(self) -> CustomDataModel: assert self.is_set("content_ct"), "'content_ct' content is not set." return cast(CustomDataModel, self.get("content_ct")) + @property + def content_dict_bool_bool(self) -> Dict[bool, bool]: + """Get the 'content_dict_bool_bool' content from the message.""" + assert self.is_set( + "content_dict_bool_bool" + ), "'content_dict_bool_bool' content is not set." + return cast(Dict[bool, bool], self.get("content_dict_bool_bool")) + @property def content_dict_bool_bytes(self) -> Dict[bool, bytes]: """Get the 'content_dict_bool_bytes' content from the message.""" @@ -143,6 +151,86 @@ def content_dict_bool_bytes(self) -> Dict[bool, bytes]: ), "'content_dict_bool_bytes' content is not set." return cast(Dict[bool, bytes], self.get("content_dict_bool_bytes")) + @property + def content_dict_bool_float(self) -> Dict[bool, float]: + """Get the 'content_dict_bool_float' content from the message.""" + assert self.is_set( + "content_dict_bool_float" + ), "'content_dict_bool_float' content is not set." + return cast(Dict[bool, float], self.get("content_dict_bool_float")) + + @property + def content_dict_bool_int(self) -> Dict[bool, int]: + """Get the 'content_dict_bool_int' content from the message.""" + assert self.is_set( + "content_dict_bool_int" + ), "'content_dict_bool_int' content is not set." + return cast(Dict[bool, int], self.get("content_dict_bool_int")) + + @property + def content_dict_bool_str(self) -> Dict[bool, str]: + """Get the 'content_dict_bool_str' content from the message.""" + assert self.is_set( + "content_dict_bool_str" + ), "'content_dict_bool_str' content is not set." + return cast(Dict[bool, str], self.get("content_dict_bool_str")) + + @property + def content_dict_int_bool(self) -> Dict[int, bool]: + """Get the 'content_dict_int_bool' content from the message.""" + assert self.is_set( + "content_dict_int_bool" + ), "'content_dict_int_bool' content is not set." + return cast(Dict[int, bool], self.get("content_dict_int_bool")) + + @property + def content_dict_int_bytes(self) -> Dict[int, bytes]: + """Get the 'content_dict_int_bytes' content from the message.""" + assert self.is_set( + "content_dict_int_bytes" + ), "'content_dict_int_bytes' content is not set." + return cast(Dict[int, bytes], self.get("content_dict_int_bytes")) + + @property + def content_dict_int_float(self) -> Dict[int, float]: + """Get the 'content_dict_int_float' content from the message.""" + assert self.is_set( + "content_dict_int_float" + ), "'content_dict_int_float' content is not set." + return cast(Dict[int, float], self.get("content_dict_int_float")) + + @property + def content_dict_int_int(self) -> Dict[int, int]: + """Get the 'content_dict_int_int' content from the message.""" + assert self.is_set( + "content_dict_int_int" + ), "'content_dict_int_int' content is not set." + return cast(Dict[int, int], self.get("content_dict_int_int")) + + @property + def content_dict_int_str(self) -> Dict[int, str]: + """Get the 'content_dict_int_str' content from the message.""" + assert self.is_set( + "content_dict_int_str" + ), "'content_dict_int_str' content is not set." + return cast(Dict[int, str], self.get("content_dict_int_str")) + + @property + def content_dict_str_bool(self) -> Dict[str, bool]: + """Get the 'content_dict_str_bool' content from the message.""" + assert self.is_set( + "content_dict_str_bool" + ), "'content_dict_str_bool' content is not set." + return cast(Dict[str, bool], self.get("content_dict_str_bool")) + + @property + def content_dict_str_bytes(self) -> Dict[str, bytes]: + """Get the 'content_dict_str_bytes' content from the message.""" + assert self.is_set( + "content_dict_str_bytes" + ), "'content_dict_str_bytes' content is not set." + return cast(Dict[str, bytes], self.get("content_dict_str_bytes")) + @property def content_dict_str_float(self) -> Dict[str, float]: """Get the 'content_dict_str_float' content from the message.""" @@ -151,6 +239,22 @@ def content_dict_str_float(self) -> Dict[str, float]: ), "'content_dict_str_float' content is not set." return cast(Dict[str, float], self.get("content_dict_str_float")) + @property + def content_dict_str_int(self) -> Dict[str, int]: + """Get the 'content_dict_str_int' content from the message.""" + assert self.is_set( + "content_dict_str_int" + ), "'content_dict_str_int' content is not set." + return cast(Dict[str, int], self.get("content_dict_str_int")) + + @property + def content_dict_str_str(self) -> Dict[str, str]: + """Get the 'content_dict_str_str' content from the message.""" + assert self.is_set( + "content_dict_str_str" + ), "'content_dict_str_str' content is not set." + return cast(Dict[str, str], self.get("content_dict_str_str")) + @property def content_float(self) -> float: """Get the 'content_float' content from the message.""" @@ -220,37 +324,9 @@ def content_o_list_bytes(self) -> Optional[Tuple[bytes, ...]]: return cast(Optional[Tuple[bytes, ...]], self.get("content_o_list_bytes")) @property - def content_o_set_float(self) -> Optional[FrozenSet[float]]: - """Get the 'content_o_set_float' content from the message.""" - return cast(Optional[FrozenSet[float]], self.get("content_o_set_float")) - - @property - def content_o_union( - self, - ) -> Optional[ - Union[ - str, - Dict[str, int], - FrozenSet[int], - FrozenSet[bytes], - Tuple[bool, ...], - Dict[str, float], - ] - ]: - """Get the 'content_o_union' content from the message.""" - return cast( - Optional[ - Union[ - str, - Dict[str, int], - FrozenSet[int], - FrozenSet[bytes], - Tuple[bool, ...], - Dict[str, float], - ] - ], - self.get("content_o_union"), - ) + def content_o_set_int(self) -> Optional[FrozenSet[int]]: + """Get the 'content_o_set_int' content from the message.""" + return cast(Optional[FrozenSet[int]], self.get("content_o_set_int")) @property def content_set_bool(self) -> FrozenSet[bool]: @@ -511,7 +587,102 @@ def _is_consistent(self) -> bool: type(element) == str for element in self.content_list_str ), "Invalid type for tuple elements in content 'content_list_str'. Expected 'str'." elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_PMT: - expected_nb_of_contents = 2 + expected_nb_of_contents = 15 + assert ( + type(self.content_dict_int_bytes) == dict + ), "Invalid type for content 'content_dict_int_bytes'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_bytes) + ) + for ( + key_of_content_dict_int_bytes, + value_of_content_dict_int_bytes, + ) in self.content_dict_int_bytes.items(): + assert ( + type(key_of_content_dict_int_bytes) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_bytes'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_bytes) + ) + assert ( + type(value_of_content_dict_int_bytes) == bytes + ), "Invalid type for dictionary values in content 'content_dict_int_bytes'. Expected 'bytes'. Found '{}'.".format( + type(value_of_content_dict_int_bytes) + ) + assert ( + type(self.content_dict_int_int) == dict + ), "Invalid type for content 'content_dict_int_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_int) + ) + for ( + key_of_content_dict_int_int, + value_of_content_dict_int_int, + ) in self.content_dict_int_int.items(): + assert ( + type(key_of_content_dict_int_int) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_int) + ) + assert ( + type(value_of_content_dict_int_int) == int + ), "Invalid type for dictionary values in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_int_int) + ) + assert ( + type(self.content_dict_int_float) == dict + ), "Invalid type for content 'content_dict_int_float'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_float) + ) + for ( + key_of_content_dict_int_float, + value_of_content_dict_int_float, + ) in self.content_dict_int_float.items(): + assert ( + type(key_of_content_dict_int_float) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_float'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_float) + ) + assert ( + type(value_of_content_dict_int_float) == float + ), "Invalid type for dictionary values in content 'content_dict_int_float'. Expected 'float'. Found '{}'.".format( + type(value_of_content_dict_int_float) + ) + assert ( + type(self.content_dict_int_bool) == dict + ), "Invalid type for content 'content_dict_int_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_bool) + ) + for ( + key_of_content_dict_int_bool, + value_of_content_dict_int_bool, + ) in self.content_dict_int_bool.items(): + assert ( + type(key_of_content_dict_int_bool) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_bool'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_bool) + ) + assert ( + type(value_of_content_dict_int_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_int_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_int_bool) + ) + assert ( + type(self.content_dict_int_str) == dict + ), "Invalid type for content 'content_dict_int_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_str) + ) + for ( + key_of_content_dict_int_str, + value_of_content_dict_int_str, + ) in self.content_dict_int_str.items(): + assert ( + type(key_of_content_dict_int_str) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_str'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_str) + ) + assert ( + type(value_of_content_dict_int_str) == str + ), "Invalid type for dictionary values in content 'content_dict_int_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_int_str) + ) assert ( type(self.content_dict_bool_bytes) == dict ), "Invalid type for content 'content_dict_bool_bytes'. Expected 'dict'. Found '{}'.".format( @@ -531,6 +702,120 @@ def _is_consistent(self) -> bool: ), "Invalid type for dictionary values in content 'content_dict_bool_bytes'. Expected 'bytes'. Found '{}'.".format( type(value_of_content_dict_bool_bytes) ) + assert ( + type(self.content_dict_bool_int) == dict + ), "Invalid type for content 'content_dict_bool_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_int) + ) + for ( + key_of_content_dict_bool_int, + value_of_content_dict_bool_int, + ) in self.content_dict_bool_int.items(): + assert ( + type(key_of_content_dict_bool_int) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_int'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_int) + ) + assert ( + type(value_of_content_dict_bool_int) == int + ), "Invalid type for dictionary values in content 'content_dict_bool_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_bool_int) + ) + assert ( + type(self.content_dict_bool_float) == dict + ), "Invalid type for content 'content_dict_bool_float'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_float) + ) + for ( + key_of_content_dict_bool_float, + value_of_content_dict_bool_float, + ) in self.content_dict_bool_float.items(): + assert ( + type(key_of_content_dict_bool_float) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_float'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_float) + ) + assert ( + type(value_of_content_dict_bool_float) == float + ), "Invalid type for dictionary values in content 'content_dict_bool_float'. Expected 'float'. Found '{}'.".format( + type(value_of_content_dict_bool_float) + ) + assert ( + type(self.content_dict_bool_bool) == dict + ), "Invalid type for content 'content_dict_bool_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_bool) + ) + for ( + key_of_content_dict_bool_bool, + value_of_content_dict_bool_bool, + ) in self.content_dict_bool_bool.items(): + assert ( + type(key_of_content_dict_bool_bool) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_bool) + ) + assert ( + type(value_of_content_dict_bool_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_bool_bool) + ) + assert ( + type(self.content_dict_bool_str) == dict + ), "Invalid type for content 'content_dict_bool_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_str) + ) + for ( + key_of_content_dict_bool_str, + value_of_content_dict_bool_str, + ) in self.content_dict_bool_str.items(): + assert ( + type(key_of_content_dict_bool_str) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_str'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_str) + ) + assert ( + type(value_of_content_dict_bool_str) == str + ), "Invalid type for dictionary values in content 'content_dict_bool_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_bool_str) + ) + assert ( + type(self.content_dict_str_bytes) == dict + ), "Invalid type for content 'content_dict_str_bytes'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_bytes) + ) + for ( + key_of_content_dict_str_bytes, + value_of_content_dict_str_bytes, + ) in self.content_dict_str_bytes.items(): + assert ( + type(key_of_content_dict_str_bytes) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_bytes'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_bytes) + ) + assert ( + type(value_of_content_dict_str_bytes) == bytes + ), "Invalid type for dictionary values in content 'content_dict_str_bytes'. Expected 'bytes'. Found '{}'.".format( + type(value_of_content_dict_str_bytes) + ) + assert ( + type(self.content_dict_str_int) == dict + ), "Invalid type for content 'content_dict_str_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_int) + ) + for ( + key_of_content_dict_str_int, + value_of_content_dict_str_int, + ) in self.content_dict_str_int.items(): + assert ( + type(key_of_content_dict_str_int) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_int'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_int) + ) + assert ( + type(value_of_content_dict_str_int) == int + ), "Invalid type for dictionary values in content 'content_dict_str_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_str_int) + ) assert ( type(self.content_dict_str_float) == dict ), "Invalid type for content 'content_dict_str_float'. Expected 'dict'. Found '{}'.".format( @@ -550,6 +835,44 @@ def _is_consistent(self) -> bool: ), "Invalid type for dictionary values in content 'content_dict_str_float'. Expected 'float'. Found '{}'.".format( type(value_of_content_dict_str_float) ) + assert ( + type(self.content_dict_str_bool) == dict + ), "Invalid type for content 'content_dict_str_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_bool) + ) + for ( + key_of_content_dict_str_bool, + value_of_content_dict_str_bool, + ) in self.content_dict_str_bool.items(): + assert ( + type(key_of_content_dict_str_bool) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_bool'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_bool) + ) + assert ( + type(value_of_content_dict_str_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_str_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_str_bool) + ) + assert ( + type(self.content_dict_str_str) == dict + ), "Invalid type for content 'content_dict_str_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_str) + ) + for ( + key_of_content_dict_str_str, + value_of_content_dict_str_str, + ) in self.content_dict_str_str.items(): + assert ( + type(key_of_content_dict_str_str) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_str) + ) + assert ( + type(value_of_content_dict_str_str) == str + ), "Invalid type for dictionary values in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_str_str) + ) elif self.performative == TProtocolMessage.Performative.PERFORMATIVE_MT: expected_nb_of_contents = 2 assert ( @@ -642,19 +965,17 @@ def _is_consistent(self) -> bool: ), "Invalid type for content 'content_o_bool'. Expected 'bool'. Found '{}'.".format( type(content_o_bool) ) - if self.is_set("content_o_set_float"): + if self.is_set("content_o_set_int"): expected_nb_of_contents += 1 - content_o_set_float = cast( - FrozenSet[float], self.content_o_set_float - ) + content_o_set_int = cast(FrozenSet[int], self.content_o_set_int) assert ( - type(content_o_set_float) == frozenset - ), "Invalid type for content 'content_o_set_float'. Expected 'frozenset'. Found '{}'.".format( - type(content_o_set_float) + type(content_o_set_int) == frozenset + ), "Invalid type for content 'content_o_set_int'. Expected 'frozenset'. Found '{}'.".format( + type(content_o_set_int) ) assert all( - type(element) == float for element in content_o_set_float - ), "Invalid type for frozenset elements in content 'content_o_set_float'. Expected 'float'." + type(element) == int for element in content_o_set_int + ), "Invalid type for frozenset elements in content 'content_o_set_int'. Expected 'int'." if self.is_set("content_o_list_bytes"): expected_nb_of_contents += 1 content_o_list_bytes = cast( @@ -692,46 +1013,6 @@ def _is_consistent(self) -> bool: ), "Invalid type for dictionary values in content 'content_o_dict_str_int'. Expected 'int'. Found '{}'.".format( type(value_of_content_o_dict_str_int) ) - if self.is_set("content_o_union"): - expected_nb_of_contents += 1 - content_o_union = cast( - Union[ - str, - Dict[str, int], - FrozenSet[int], - FrozenSet[bytes], - Tuple[bool, ...], - Dict[str, float], - ], - self.content_o_union, - ) - assert ( - type(content_o_union) == dict - or type(content_o_union) == frozenset - or type(content_o_union) == str - or type(content_o_union) == tuple - ), "Invalid type for content 'content_o_union'. Expected either of '['dict', 'frozenset', 'str', 'tuple']'. Found '{}'.".format( - type(content_o_union) - ) - if type(content_o_union) == frozenset: - assert all( - type(element) == bytes for element in content_o_union - ) or all( - type(element) == int for element in content_o_union - ), "Invalid type for frozenset elements in content 'content_o_union'. Expected either 'bytes' or 'int'." - if type(content_o_union) == tuple: - assert all( - type(element) == bool for element in content_o_union - ), "Invalid type for tuple elements in content 'content_o_union'. Expected 'bool'." - if type(content_o_union) == dict: - for ( - key_of_content_o_union, - value_of_content_o_union, - ) in content_o_union.items(): - assert ( - type(key_of_content_o_union) == str - and type(value_of_content_o_union) == float - ), "Invalid type for dictionary key, value in content 'content_o_union'. Expected 'str', 'float'." elif ( self.performative == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS diff --git a/tests/data/generator/t_protocol/protocol.yaml b/tests/data/generator/t_protocol/protocol.yaml index 43a4591d20..a172f6bcd1 100644 --- a/tests/data/generator/t_protocol/protocol.yaml +++ b/tests/data/generator/t_protocol/protocol.yaml @@ -5,12 +5,13 @@ description: A protocol for testing purposes. license: Apache-2.0 aea_version: '>=0.5.0, <0.6.0' fingerprint: - __init__.py: QmaarNrn5mcEYupCdQxpzpvH4PY5Wto7rtkjUjmHTUShiH + __init__.py: QmTwLir2v2eYMkDeUomf9uL1hrQhjzVTTqrQwamGG5iwn4 custom_types.py: Qmd5CrULVdtcNQLz5R1i9LpJi9Nhzd7nQnwN737FqibgLs - message.py: QmZDjKrRRrfidEg2rxGC2Qoo4DBaEEfEvLu3bdeBbKL1tf - serialization.py: QmZDHQcL5KbDLuqm9dsWeS5e3xwBxVC9sBLehWeDGpgysr - t_protocol.proto: QmNryKk13EDZhx2m4YMFFMF33qEy41u7eNfWuhcadKowJF - t_protocol_pb2.py: QmfJDyZ8U5Ay81pc991sf7asZ9QCFVUhwEWEXfWyY3yP8h + dialogues.py: QmSh74pVsprgNbz4Y2B2EWt4C6tsNDsRfuYcjwJYiu8apz + message.py: QmUrAcMnqoBMs1nSDzp8c6fr82qPMgbjGQNdQgxa4YYCxc + serialization.py: QmcS33k6rHgCCkhBuQ5kiXVKFMxxEzcZManshPD51MEHbw + t_protocol.proto: QmRuYvnojwkyZLzeECH3snomgoMJTB3m48yJiLq8LYsVb8 + t_protocol_pb2.py: QmXrSgBBJCj8hbGCynKrvdkSDohQzHLPBA2vi5hDHmaGid fingerprint_ignore_patterns: [] dependencies: protobuf: {} diff --git a/tests/data/generator/t_protocol/serialization.py b/tests/data/generator/t_protocol/serialization.py index 413c2cc8b1..942f766ba8 100644 --- a/tests/data/generator/t_protocol/serialization.py +++ b/tests/data/generator/t_protocol/serialization.py @@ -92,10 +92,36 @@ def encode(msg: Message) -> bytes: t_protocol_msg.performative_pct.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PMT: performative = t_protocol_pb2.TProtocolMessage.Performative_Pmt_Performative() # type: ignore + content_dict_int_bytes = msg.content_dict_int_bytes + performative.content_dict_int_bytes.update(content_dict_int_bytes) + content_dict_int_int = msg.content_dict_int_int + performative.content_dict_int_int.update(content_dict_int_int) + content_dict_int_float = msg.content_dict_int_float + performative.content_dict_int_float.update(content_dict_int_float) + content_dict_int_bool = msg.content_dict_int_bool + performative.content_dict_int_bool.update(content_dict_int_bool) + content_dict_int_str = msg.content_dict_int_str + performative.content_dict_int_str.update(content_dict_int_str) content_dict_bool_bytes = msg.content_dict_bool_bytes performative.content_dict_bool_bytes.update(content_dict_bool_bytes) + content_dict_bool_int = msg.content_dict_bool_int + performative.content_dict_bool_int.update(content_dict_bool_int) + content_dict_bool_float = msg.content_dict_bool_float + performative.content_dict_bool_float.update(content_dict_bool_float) + content_dict_bool_bool = msg.content_dict_bool_bool + performative.content_dict_bool_bool.update(content_dict_bool_bool) + content_dict_bool_str = msg.content_dict_bool_str + performative.content_dict_bool_str.update(content_dict_bool_str) + content_dict_str_bytes = msg.content_dict_str_bytes + performative.content_dict_str_bytes.update(content_dict_str_bytes) + content_dict_str_int = msg.content_dict_str_int + performative.content_dict_str_int.update(content_dict_str_int) content_dict_str_float = msg.content_dict_str_float performative.content_dict_str_float.update(content_dict_str_float) + content_dict_str_bool = msg.content_dict_str_bool + performative.content_dict_str_bool.update(content_dict_str_bool) + content_dict_str_str = msg.content_dict_str_str + performative.content_dict_str_str.update(content_dict_str_str) t_protocol_msg.performative_pmt.CopyFrom(performative) elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_MT: performative = t_protocol_pb2.TProtocolMessage.Performative_Mt_Performative() # type: ignore @@ -227,10 +253,10 @@ def encode(msg: Message) -> bytes: performative.content_o_bool_is_set = True content_o_bool = msg.content_o_bool performative.content_o_bool = content_o_bool - if msg.is_set("content_o_set_float"): - performative.content_o_set_float_is_set = True - content_o_set_float = msg.content_o_set_float - performative.content_o_set_float.extend(content_o_set_float) + if msg.is_set("content_o_set_int"): + performative.content_o_set_int_is_set = True + content_o_set_int = msg.content_o_set_int + performative.content_o_set_int.extend(content_o_set_int) if msg.is_set("content_o_list_bytes"): performative.content_o_list_bytes_is_set = True content_o_list_bytes = msg.content_o_list_bytes @@ -239,48 +265,6 @@ def encode(msg: Message) -> bytes: performative.content_o_dict_str_int_is_set = True content_o_dict_str_int = msg.content_o_dict_str_int performative.content_o_dict_str_int.update(content_o_dict_str_int) - if msg.is_set("content_o_union_type_str"): - performative.content_o_union_type_str_is_set = True - content_o_union_type_str = msg.content_o_union_type_str - performative.content_o_union_type_str = content_o_union_type_str - if msg.is_set("content_o_union_type_dict_of_str_int"): - performative.content_o_union_type_dict_of_str_int_is_set = True - content_o_union_type_dict_of_str_int = ( - msg.content_o_union_type_dict_of_str_int - ) - performative.content_o_union_type_dict_of_str_int.update( - content_o_union_type_dict_of_str_int - ) - if msg.is_set("content_o_union_type_set_of_int"): - performative.content_o_union_type_set_of_int_is_set = True - content_o_union_type_set_of_int = msg.content_o_union_type_set_of_int - performative.content_o_union_type_set_of_int.extend( - content_o_union_type_set_of_int - ) - if msg.is_set("content_o_union_type_set_of_bytes"): - performative.content_o_union_type_set_of_bytes_is_set = True - content_o_union_type_set_of_bytes = ( - msg.content_o_union_type_set_of_bytes - ) - performative.content_o_union_type_set_of_bytes.extend( - content_o_union_type_set_of_bytes - ) - if msg.is_set("content_o_union_type_list_of_bool"): - performative.content_o_union_type_list_of_bool_is_set = True - content_o_union_type_list_of_bool = ( - msg.content_o_union_type_list_of_bool - ) - performative.content_o_union_type_list_of_bool.extend( - content_o_union_type_list_of_bool - ) - if msg.is_set("content_o_union_type_dict_of_str_float"): - performative.content_o_union_type_dict_of_str_float_is_set = True - content_o_union_type_dict_of_str_float = ( - msg.content_o_union_type_dict_of_str_float - ) - performative.content_o_union_type_dict_of_str_float.update( - content_o_union_type_dict_of_str_float - ) t_protocol_msg.performative_o.CopyFrom(performative) elif ( performative_id == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS @@ -360,6 +344,25 @@ def decode(obj: bytes) -> Message: content_list_str_tuple = tuple(content_list_str) performative_content["content_list_str"] = content_list_str_tuple elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_PMT: + content_dict_int_bytes = ( + t_protocol_pb.performative_pmt.content_dict_int_bytes + ) + content_dict_int_bytes_dict = dict(content_dict_int_bytes) + performative_content["content_dict_int_bytes"] = content_dict_int_bytes_dict + content_dict_int_int = t_protocol_pb.performative_pmt.content_dict_int_int + content_dict_int_int_dict = dict(content_dict_int_int) + performative_content["content_dict_int_int"] = content_dict_int_int_dict + content_dict_int_float = ( + t_protocol_pb.performative_pmt.content_dict_int_float + ) + content_dict_int_float_dict = dict(content_dict_int_float) + performative_content["content_dict_int_float"] = content_dict_int_float_dict + content_dict_int_bool = t_protocol_pb.performative_pmt.content_dict_int_bool + content_dict_int_bool_dict = dict(content_dict_int_bool) + performative_content["content_dict_int_bool"] = content_dict_int_bool_dict + content_dict_int_str = t_protocol_pb.performative_pmt.content_dict_int_str + content_dict_int_str_dict = dict(content_dict_int_str) + performative_content["content_dict_int_str"] = content_dict_int_str_dict content_dict_bool_bytes = ( t_protocol_pb.performative_pmt.content_dict_bool_bytes ) @@ -367,11 +370,43 @@ def decode(obj: bytes) -> Message: performative_content[ "content_dict_bool_bytes" ] = content_dict_bool_bytes_dict + content_dict_bool_int = t_protocol_pb.performative_pmt.content_dict_bool_int + content_dict_bool_int_dict = dict(content_dict_bool_int) + performative_content["content_dict_bool_int"] = content_dict_bool_int_dict + content_dict_bool_float = ( + t_protocol_pb.performative_pmt.content_dict_bool_float + ) + content_dict_bool_float_dict = dict(content_dict_bool_float) + performative_content[ + "content_dict_bool_float" + ] = content_dict_bool_float_dict + content_dict_bool_bool = ( + t_protocol_pb.performative_pmt.content_dict_bool_bool + ) + content_dict_bool_bool_dict = dict(content_dict_bool_bool) + performative_content["content_dict_bool_bool"] = content_dict_bool_bool_dict + content_dict_bool_str = t_protocol_pb.performative_pmt.content_dict_bool_str + content_dict_bool_str_dict = dict(content_dict_bool_str) + performative_content["content_dict_bool_str"] = content_dict_bool_str_dict + content_dict_str_bytes = ( + t_protocol_pb.performative_pmt.content_dict_str_bytes + ) + content_dict_str_bytes_dict = dict(content_dict_str_bytes) + performative_content["content_dict_str_bytes"] = content_dict_str_bytes_dict + content_dict_str_int = t_protocol_pb.performative_pmt.content_dict_str_int + content_dict_str_int_dict = dict(content_dict_str_int) + performative_content["content_dict_str_int"] = content_dict_str_int_dict content_dict_str_float = ( t_protocol_pb.performative_pmt.content_dict_str_float ) content_dict_str_float_dict = dict(content_dict_str_float) performative_content["content_dict_str_float"] = content_dict_str_float_dict + content_dict_str_bool = t_protocol_pb.performative_pmt.content_dict_str_bool + content_dict_str_bool_dict = dict(content_dict_str_bool) + performative_content["content_dict_str_bool"] = content_dict_str_bool_dict + content_dict_str_str = t_protocol_pb.performative_pmt.content_dict_str_str + content_dict_str_str_dict = dict(content_dict_str_str) + performative_content["content_dict_str_str"] = content_dict_str_str_dict elif performative_id == TProtocolMessage.Performative.PERFORMATIVE_MT: if t_protocol_pb.performative_mt.content_union_1_type_DataModel_is_set: pb2_content_union_1_type_DataModel = ( @@ -464,12 +499,10 @@ def decode(obj: bytes) -> Message: if t_protocol_pb.performative_o.content_o_bool_is_set: content_o_bool = t_protocol_pb.performative_o.content_o_bool performative_content["content_o_bool"] = content_o_bool - if t_protocol_pb.performative_o.content_o_set_float_is_set: - content_o_set_float = t_protocol_pb.performative_o.content_o_set_float - content_o_set_float_frozenset = frozenset(content_o_set_float) - performative_content[ - "content_o_set_float" - ] = content_o_set_float_frozenset + if t_protocol_pb.performative_o.content_o_set_int_is_set: + content_o_set_int = t_protocol_pb.performative_o.content_o_set_int + content_o_set_int_frozenset = frozenset(content_o_set_int) + performative_content["content_o_set_int"] = content_o_set_int_frozenset if t_protocol_pb.performative_o.content_o_list_bytes_is_set: content_o_list_bytes = t_protocol_pb.performative_o.content_o_list_bytes content_o_list_bytes_tuple = tuple(content_o_list_bytes) @@ -484,31 +517,6 @@ def decode(obj: bytes) -> Message: performative_content[ "content_o_dict_str_int" ] = content_o_dict_str_int_dict - if t_protocol_pb.performative_o.content_o_union_type_str_is_set: - content_o_union = t_protocol_pb.performative_o.content_o_union_type_str - performative_content["content_o_union"] = content_o_union - if t_protocol_pb.performative_o.content_o_union_type_dict_of_str_int_is_set: - content_o_union = t_protocol_pb.performative_o.content_o_union - content_o_union_dict = dict(content_o_union) - performative_content["content_o_union"] = content_o_union_dict - if t_protocol_pb.performative_o.content_o_union_type_set_of_int_is_set: - content_o_union = t_protocol_pb.performative_o.content_o_union - content_o_union_frozenset = frozenset(content_o_union) - performative_content["content_o_union"] = content_o_union_frozenset - if t_protocol_pb.performative_o.content_o_union_type_set_of_bytes_is_set: - content_o_union = t_protocol_pb.performative_o.content_o_union - content_o_union_frozenset = frozenset(content_o_union) - performative_content["content_o_union"] = content_o_union_frozenset - if t_protocol_pb.performative_o.content_o_union_type_list_of_bool_is_set: - content_o_union = t_protocol_pb.performative_o.content_o_union - content_o_union_tuple = tuple(content_o_union) - performative_content["content_o_union"] = content_o_union_tuple - if ( - t_protocol_pb.performative_o.content_o_union_type_dict_of_str_float_is_set - ): - content_o_union = t_protocol_pb.performative_o.content_o_union - content_o_union_dict = dict(content_o_union) - performative_content["content_o_union"] = content_o_union_dict elif ( performative_id == TProtocolMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS ): diff --git a/tests/data/generator/t_protocol/t_protocol.proto b/tests/data/generator/t_protocol/t_protocol.proto index e14a0309c1..b408c295d7 100644 --- a/tests/data/generator/t_protocol/t_protocol.proto +++ b/tests/data/generator/t_protocol/t_protocol.proto @@ -44,8 +44,21 @@ message TProtocolMessage{ } message Performative_Pmt_Performative{ - map content_dict_bool_bytes = 1; - map content_dict_str_float = 2; + map content_dict_int_bytes = 1; + map content_dict_int_int = 2; + map content_dict_int_float = 3; + map content_dict_int_bool = 4; + map content_dict_int_str = 5; + map content_dict_bool_bytes = 6; + map content_dict_bool_int = 7; + map content_dict_bool_float = 8; + map content_dict_bool_bool = 9; + map content_dict_bool_str = 10; + map content_dict_str_bytes = 11; + map content_dict_str_int = 12; + map content_dict_str_float = 13; + map content_dict_str_bool = 14; + map content_dict_str_str = 15; } message Performative_Mt_Performative{ @@ -74,19 +87,12 @@ message TProtocolMessage{ bool content_o_ct_is_set = 2; bool content_o_bool = 3; bool content_o_bool_is_set = 4; - repeated float content_o_set_float = 5; - bool content_o_set_float_is_set = 6; + repeated int32 content_o_set_int = 5; + bool content_o_set_int_is_set = 6; repeated bytes content_o_list_bytes = 7; bool content_o_list_bytes_is_set = 8; map content_o_dict_str_int = 9; bool content_o_dict_str_int_is_set = 10; - string content_o_union_type_str = 11; - map content_o_union_type_dict_of_str_int = 12; - repeated int32 content_o_union_type_set_of_int = 13; - repeated bytes content_o_union_type_set_of_bytes = 14; - repeated bool content_o_union_type_list_of_bool = 15; - map content_o_union_type_dict_of_str_float = 16; - bool content_o_union_is_set = 17; } message Performative_Empty_Contents_Performative{} diff --git a/tests/data/generator/t_protocol/t_protocol_pb2.py b/tests/data/generator/t_protocol/t_protocol_pb2.py index 8cbd680e3d..c021c11aa3 100644 --- a/tests/data/generator/t_protocol/t_protocol_pb2.py +++ b/tests/data/generator/t_protocol/t_protocol_pb2.py @@ -1,9 +1,7 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: t_protocol.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 @@ -19,9 +17,7 @@ package="fetch.aea.TProtocol", syntax="proto3", serialized_options=None, - serialized_pb=_b( - '\n\x10t_protocol.proto\x12\x13\x66\x65tch.aea.TProtocol"\xdb%\n\x10TProtocolMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12]\n\x0fperformative_ct\x18\x05 \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Ct_PerformativeH\x00\x12u\n\x1bperformative_empty_contents\x18\x06 \x01(\x0b\x32N.fetch.aea.TProtocol.TProtocolMessage.Performative_Empty_Contents_PerformativeH\x00\x12]\n\x0fperformative_mt\x18\x07 \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_PerformativeH\x00\x12[\n\x0eperformative_o\x18\x08 \x01(\x0b\x32\x41.fetch.aea.TProtocol.TProtocolMessage.Performative_O_PerformativeH\x00\x12_\n\x10performative_pct\x18\t \x01(\x0b\x32\x43.fetch.aea.TProtocol.TProtocolMessage.Performative_Pct_PerformativeH\x00\x12_\n\x10performative_pmt\x18\n \x01(\x0b\x32\x43.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_PerformativeH\x00\x12]\n\x0fperformative_pt\x18\x0b \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Pt_PerformativeH\x00\x1a\x9c\x02\n\tDataModel\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x05\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x05\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12R\n\ndict_field\x18\x08 \x03(\x0b\x32>.fetch.aea.TProtocol.TProtocolMessage.DataModel.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x63\n\x1cPerformative_Ct_Performative\x12\x43\n\ncontent_ct\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x1a\x8c\x01\n\x1cPerformative_Pt_Performative\x12\x15\n\rcontent_bytes\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontent_int\x18\x02 \x01(\x05\x12\x15\n\rcontent_float\x18\x03 \x01(\x02\x12\x14\n\x0c\x63ontent_bool\x18\x04 \x01(\x08\x12\x13\n\x0b\x63ontent_str\x18\x05 \x01(\t\x1a\xa8\x02\n\x1dPerformative_Pct_Performative\x12\x19\n\x11\x63ontent_set_bytes\x18\x01 \x03(\x0c\x12\x17\n\x0f\x63ontent_set_int\x18\x02 \x03(\x05\x12\x19\n\x11\x63ontent_set_float\x18\x03 \x03(\x02\x12\x18\n\x10\x63ontent_set_bool\x18\x04 \x03(\x08\x12\x17\n\x0f\x63ontent_set_str\x18\x05 \x03(\t\x12\x1a\n\x12\x63ontent_list_bytes\x18\x06 \x03(\x0c\x12\x18\n\x10\x63ontent_list_int\x18\x07 \x03(\x05\x12\x1a\n\x12\x63ontent_list_float\x18\x08 \x03(\x02\x12\x19\n\x11\x63ontent_list_bool\x18\t \x03(\x08\x12\x18\n\x10\x63ontent_list_str\x18\n \x03(\t\x1a\x96\x03\n\x1dPerformative_Pmt_Performative\x12~\n\x17\x63ontent_dict_bool_bytes\x18\x01 \x03(\x0b\x32].fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry\x12|\n\x16\x63ontent_dict_str_float\x18\x02 \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry\x1a;\n\x19\x43ontentDictBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\xf9\x0b\n\x1cPerformative_Mt_Performative\x12W\n\x1e\x63ontent_union_1_type_DataModel\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x12"\n\x1a\x63ontent_union_1_type_bytes\x18\x02 \x01(\x0c\x12 \n\x18\x63ontent_union_1_type_int\x18\x03 \x01(\x05\x12"\n\x1a\x63ontent_union_1_type_float\x18\x04 \x01(\x02\x12!\n\x19\x63ontent_union_1_type_bool\x18\x05 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_str\x18\x06 \x01(\t\x12\'\n\x1f\x63ontent_union_1_type_set_of_int\x18\x07 \x03(\x05\x12)\n!content_union_1_type_list_of_bool\x18\x08 \x03(\x08\x12\x93\x01\n$content_union_1_type_dict_of_str_int\x18\t \x03(\x0b\x32\x65.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry\x12)\n!content_union_2_type_set_of_bytes\x18\n \x03(\x0c\x12\'\n\x1f\x63ontent_union_2_type_set_of_int\x18\x0b \x03(\x05\x12\'\n\x1f\x63ontent_union_2_type_set_of_str\x18\x0c \x03(\t\x12*\n"content_union_2_type_list_of_float\x18\r \x03(\x02\x12)\n!content_union_2_type_list_of_bool\x18\x0e \x03(\x08\x12*\n"content_union_2_type_list_of_bytes\x18\x0f \x03(\x0c\x12\x93\x01\n$content_union_2_type_dict_of_str_int\x18\x10 \x03(\x0b\x32\x65.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry\x12\x97\x01\n&content_union_2_type_dict_of_int_float\x18\x11 \x03(\x0b\x32g.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry\x12\x99\x01\n\'content_union_2_type_dict_of_bool_bytes\x18\x12 \x03(\x0b\x32h.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry\x1a\x44\n"ContentUnion1TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x44\n"ContentUnion2TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x46\n$ContentUnion2TypeDictOfIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1aG\n%ContentUnion2TypeDictOfBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x96\t\n\x1bPerformative_O_Performative\x12\x45\n\x0c\x63ontent_o_ct\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x12\x1b\n\x13\x63ontent_o_ct_is_set\x18\x02 \x01(\x08\x12\x16\n\x0e\x63ontent_o_bool\x18\x03 \x01(\x08\x12\x1d\n\x15\x63ontent_o_bool_is_set\x18\x04 \x01(\x08\x12\x1b\n\x13\x63ontent_o_set_float\x18\x05 \x03(\x02\x12"\n\x1a\x63ontent_o_set_float_is_set\x18\x06 \x01(\x08\x12\x1c\n\x14\x63ontent_o_list_bytes\x18\x07 \x03(\x0c\x12#\n\x1b\x63ontent_o_list_bytes_is_set\x18\x08 \x01(\x08\x12y\n\x16\x63ontent_o_dict_str_int\x18\t \x03(\x0b\x32Y.fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry\x12%\n\x1d\x63ontent_o_dict_str_int_is_set\x18\n \x01(\x08\x12 \n\x18\x63ontent_o_union_type_str\x18\x0b \x01(\t\x12\x92\x01\n$content_o_union_type_dict_of_str_int\x18\x0c \x03(\x0b\x32\x64.fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry\x12\'\n\x1f\x63ontent_o_union_type_set_of_int\x18\r \x03(\x05\x12)\n!content_o_union_type_set_of_bytes\x18\x0e \x03(\x0c\x12)\n!content_o_union_type_list_of_bool\x18\x0f \x03(\x08\x12\x96\x01\n&content_o_union_type_dict_of_str_float\x18\x10 \x03(\x0b\x32\x66.fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry\x12\x1e\n\x16\x63ontent_o_union_is_set\x18\x11 \x01(\x08\x1a\x39\n\x17\x43ontentODictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x44\n"ContentOUnionTypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x46\n$ContentOUnionTypeDictOfStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a*\n(Performative_Empty_Contents_PerformativeB\x0e\n\x0cperformativeb\x06proto3' - ), + serialized_pb=b'\n\x10t_protocol.proto\x12\x13\x66\x65tch.aea.TProtocol"\xab\x33\n\x10TProtocolMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12]\n\x0fperformative_ct\x18\x05 \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Ct_PerformativeH\x00\x12u\n\x1bperformative_empty_contents\x18\x06 \x01(\x0b\x32N.fetch.aea.TProtocol.TProtocolMessage.Performative_Empty_Contents_PerformativeH\x00\x12]\n\x0fperformative_mt\x18\x07 \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_PerformativeH\x00\x12[\n\x0eperformative_o\x18\x08 \x01(\x0b\x32\x41.fetch.aea.TProtocol.TProtocolMessage.Performative_O_PerformativeH\x00\x12_\n\x10performative_pct\x18\t \x01(\x0b\x32\x43.fetch.aea.TProtocol.TProtocolMessage.Performative_Pct_PerformativeH\x00\x12_\n\x10performative_pmt\x18\n \x01(\x0b\x32\x43.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_PerformativeH\x00\x12]\n\x0fperformative_pt\x18\x0b \x01(\x0b\x32\x42.fetch.aea.TProtocol.TProtocolMessage.Performative_Pt_PerformativeH\x00\x1a\x9c\x02\n\tDataModel\x12\x13\n\x0b\x62ytes_field\x18\x01 \x01(\x0c\x12\x11\n\tint_field\x18\x02 \x01(\x05\x12\x13\n\x0b\x66loat_field\x18\x03 \x01(\x02\x12\x12\n\nbool_field\x18\x04 \x01(\x08\x12\x11\n\tstr_field\x18\x05 \x01(\t\x12\x11\n\tset_field\x18\x06 \x03(\x05\x12\x12\n\nlist_field\x18\x07 \x03(\t\x12R\n\ndict_field\x18\x08 \x03(\x0b\x32>.fetch.aea.TProtocol.TProtocolMessage.DataModel.DictFieldEntry\x1a\x30\n\x0e\x44ictFieldEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x63\n\x1cPerformative_Ct_Performative\x12\x43\n\ncontent_ct\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x1a\x8c\x01\n\x1cPerformative_Pt_Performative\x12\x15\n\rcontent_bytes\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontent_int\x18\x02 \x01(\x05\x12\x15\n\rcontent_float\x18\x03 \x01(\x02\x12\x14\n\x0c\x63ontent_bool\x18\x04 \x01(\x08\x12\x13\n\x0b\x63ontent_str\x18\x05 \x01(\t\x1a\xa8\x02\n\x1dPerformative_Pct_Performative\x12\x19\n\x11\x63ontent_set_bytes\x18\x01 \x03(\x0c\x12\x17\n\x0f\x63ontent_set_int\x18\x02 \x03(\x05\x12\x19\n\x11\x63ontent_set_float\x18\x03 \x03(\x02\x12\x18\n\x10\x63ontent_set_bool\x18\x04 \x03(\x08\x12\x17\n\x0f\x63ontent_set_str\x18\x05 \x03(\t\x12\x1a\n\x12\x63ontent_list_bytes\x18\x06 \x03(\x0c\x12\x18\n\x10\x63ontent_list_int\x18\x07 \x03(\x05\x12\x1a\n\x12\x63ontent_list_float\x18\x08 \x03(\x02\x12\x19\n\x11\x63ontent_list_bool\x18\t \x03(\x08\x12\x18\n\x10\x63ontent_list_str\x18\n \x03(\t\x1a\xe7\x15\n\x1dPerformative_Pmt_Performative\x12|\n\x16\x63ontent_dict_int_bytes\x18\x01 \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry\x12x\n\x14\x63ontent_dict_int_int\x18\x02 \x03(\x0b\x32Z.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry\x12|\n\x16\x63ontent_dict_int_float\x18\x03 \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry\x12z\n\x15\x63ontent_dict_int_bool\x18\x04 \x03(\x0b\x32[.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry\x12x\n\x14\x63ontent_dict_int_str\x18\x05 \x03(\x0b\x32Z.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry\x12~\n\x17\x63ontent_dict_bool_bytes\x18\x06 \x03(\x0b\x32].fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry\x12z\n\x15\x63ontent_dict_bool_int\x18\x07 \x03(\x0b\x32[.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry\x12~\n\x17\x63ontent_dict_bool_float\x18\x08 \x03(\x0b\x32].fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry\x12|\n\x16\x63ontent_dict_bool_bool\x18\t \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry\x12z\n\x15\x63ontent_dict_bool_str\x18\n \x03(\x0b\x32[.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry\x12|\n\x16\x63ontent_dict_str_bytes\x18\x0b \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry\x12x\n\x14\x63ontent_dict_str_int\x18\x0c \x03(\x0b\x32Z.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry\x12|\n\x16\x63ontent_dict_str_float\x18\r \x03(\x0b\x32\\.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry\x12z\n\x15\x63ontent_dict_str_bool\x18\x0e \x03(\x0b\x32[.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry\x12x\n\x14\x63ontent_dict_str_str\x18\x0f \x03(\x0b\x32Z.fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry\x1a:\n\x18\x43ontentDictIntBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a:\n\x18\x43ontentDictIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictIntBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a:\n\x18\x43ontentDictBoolBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictStrBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xf9\x0b\n\x1cPerformative_Mt_Performative\x12W\n\x1e\x63ontent_union_1_type_DataModel\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x12"\n\x1a\x63ontent_union_1_type_bytes\x18\x02 \x01(\x0c\x12 \n\x18\x63ontent_union_1_type_int\x18\x03 \x01(\x05\x12"\n\x1a\x63ontent_union_1_type_float\x18\x04 \x01(\x02\x12!\n\x19\x63ontent_union_1_type_bool\x18\x05 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_str\x18\x06 \x01(\t\x12\'\n\x1f\x63ontent_union_1_type_set_of_int\x18\x07 \x03(\x05\x12)\n!content_union_1_type_list_of_bool\x18\x08 \x03(\x08\x12\x93\x01\n$content_union_1_type_dict_of_str_int\x18\t \x03(\x0b\x32\x65.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry\x12)\n!content_union_2_type_set_of_bytes\x18\n \x03(\x0c\x12\'\n\x1f\x63ontent_union_2_type_set_of_int\x18\x0b \x03(\x05\x12\'\n\x1f\x63ontent_union_2_type_set_of_str\x18\x0c \x03(\t\x12*\n"content_union_2_type_list_of_float\x18\r \x03(\x02\x12)\n!content_union_2_type_list_of_bool\x18\x0e \x03(\x08\x12*\n"content_union_2_type_list_of_bytes\x18\x0f \x03(\x0c\x12\x93\x01\n$content_union_2_type_dict_of_str_int\x18\x10 \x03(\x0b\x32\x65.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry\x12\x97\x01\n&content_union_2_type_dict_of_int_float\x18\x11 \x03(\x0b\x32g.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry\x12\x99\x01\n\'content_union_2_type_dict_of_bool_bytes\x18\x12 \x03(\x0b\x32h.fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry\x1a\x44\n"ContentUnion1TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x44\n"ContentUnion2TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x46\n$ContentUnion2TypeDictOfIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1aG\n%ContentUnion2TypeDictOfBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x95\x04\n\x1bPerformative_O_Performative\x12\x45\n\x0c\x63ontent_o_ct\x18\x01 \x01(\x0b\x32/.fetch.aea.TProtocol.TProtocolMessage.DataModel\x12\x1b\n\x13\x63ontent_o_ct_is_set\x18\x02 \x01(\x08\x12\x16\n\x0e\x63ontent_o_bool\x18\x03 \x01(\x08\x12\x1d\n\x15\x63ontent_o_bool_is_set\x18\x04 \x01(\x08\x12\x19\n\x11\x63ontent_o_set_int\x18\x05 \x03(\x05\x12 \n\x18\x63ontent_o_set_int_is_set\x18\x06 \x01(\x08\x12\x1c\n\x14\x63ontent_o_list_bytes\x18\x07 \x03(\x0c\x12#\n\x1b\x63ontent_o_list_bytes_is_set\x18\x08 \x01(\x08\x12y\n\x16\x63ontent_o_dict_str_int\x18\t \x03(\x0b\x32Y.fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry\x12%\n\x1d\x63ontent_o_dict_str_int_is_set\x18\n \x01(\x08\x1a\x39\n\x17\x43ontentODictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a*\n(Performative_Empty_Contents_PerformativeB\x0e\n\x0cperformativeb\x06proto3', ) @@ -72,7 +68,7 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], @@ -97,7 +93,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b(""), + default_value=b"", message_type=None, enum_type=None, containing_type=None, @@ -169,7 +165,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -299,7 +295,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b(""), + default_value=b"", message_type=None, enum_type=None, containing_type=None, @@ -371,7 +367,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -593,23 +589,23 @@ serialized_end=1691, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY = _descriptor.Descriptor( - name="ContentDictBoolBytesEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY = _descriptor.Descriptor( + name="ContentDictIntBytesEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry.key", index=0, number=1, - type=8, - cpp_type=7, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=False, + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -620,14 +616,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry.value", index=1, number=2, type=12, cpp_type=9, label=1, has_default_value=False, - default_value=_b(""), + default_value=b"", message_type=None, enum_type=None, containing_type=None, @@ -640,32 +636,32 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1981, - serialized_end=2040, + serialized_start=3597, + serialized_end=3655, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY = _descriptor.Descriptor( - name="ContentDictStrFloatEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY = _descriptor.Descriptor( + name="ContentDictIntIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry.key", index=0, number=1, - type=9, - cpp_type=9, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -676,14 +672,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry.value", index=1, number=2, - type=2, - cpp_type=6, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=float(0), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -696,32 +692,32 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2042, - serialized_end=2100, + serialized_start=3657, + serialized_end=3713, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE = _descriptor.Descriptor( - name="Performative_Pmt_Performative", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY = _descriptor.Descriptor( + name="ContentDictIntFloatEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="content_dict_bool_bytes", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_bytes", + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry.key", index=0, number=1, - type=11, - cpp_type=10, - label=3, + type=5, + cpp_type=1, + label=1, has_default_value=False, - default_value=[], + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -731,15 +727,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_dict_str_float", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_float", + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry.value", index=1, number=2, - type=11, - cpp_type=10, - label=3, + type=2, + cpp_type=6, + label=1, has_default_value=False, - default_value=[], + default_value=float(0), message_type=None, enum_type=None, containing_type=None, @@ -750,37 +746,34 @@ ), ], extensions=[], - nested_types=[ - _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, - _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, - ], + nested_types=[], enum_types=[], - serialized_options=None, + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1694, - serialized_end=2100, + serialized_start=3715, + serialized_end=3773, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( - name="ContentUnion1TypeDictOfStrIntEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY = _descriptor.Descriptor( + name="ContentDictIntBoolEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry.key", index=0, number=1, - type=9, - cpp_type=9, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -791,14 +784,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry.value", index=1, number=2, - type=5, - cpp_type=1, + type=8, + cpp_type=7, label=1, has_default_value=False, - default_value=0, + default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -811,32 +804,32 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3349, - serialized_end=3417, + serialized_start=3775, + serialized_end=3832, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( - name="ContentUnion2TypeDictOfStrIntEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY = _descriptor.Descriptor( + name="ContentDictIntStrEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry.key", index=0, number=1, - type=9, - cpp_type=9, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -847,14 +840,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry.value", index=1, number=2, - type=5, - cpp_type=1, + type=9, + cpp_type=9, label=1, has_default_value=False, - default_value=0, + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -867,32 +860,32 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3419, - serialized_end=3487, + serialized_start=3834, + serialized_end=3890, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY = _descriptor.Descriptor( - name="ContentUnion2TypeDictOfIntFloatEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY = _descriptor.Descriptor( + name="ContentDictBoolBytesEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.key", index=0, number=1, - type=5, - cpp_type=1, + type=8, + cpp_type=7, label=1, has_default_value=False, - default_value=0, + default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -903,14 +896,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.value", index=1, number=2, - type=2, - cpp_type=6, + type=12, + cpp_type=9, label=1, has_default_value=False, - default_value=float(0), + default_value=b"", message_type=None, enum_type=None, containing_type=None, @@ -923,25 +916,25 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3489, - serialized_end=3559, + serialized_start=3892, + serialized_end=3951, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY = _descriptor.Descriptor( - name="ContentUnion2TypeDictOfBoolBytesEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY = _descriptor.Descriptor( + name="ContentDictBoolIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry.key", index=0, number=1, type=8, @@ -959,14 +952,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry.value", index=1, number=2, - type=12, - cpp_type=9, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=_b(""), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -979,32 +972,32 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3561, - serialized_end=3632, + serialized_start=3953, + serialized_end=4010, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE = _descriptor.Descriptor( - name="Performative_Mt_Performative", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative", +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY = _descriptor.Descriptor( + name="ContentDictBoolFloatEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( - name="content_union_1_type_DataModel", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_DataModel", + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry.key", index=0, number=1, - type=11, - cpp_type=10, + type=8, + cpp_type=7, label=1, has_default_value=False, - default_value=None, + default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -1014,15 +1007,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_union_1_type_bytes", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_bytes", + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry.value", index=1, number=2, - type=12, - cpp_type=9, + type=2, + cpp_type=6, label=1, has_default_value=False, - default_value=_b(""), + default_value=float(0), message_type=None, enum_type=None, containing_type=None, @@ -1031,16 +1024,36 @@ serialized_options=None, file=DESCRIPTOR, ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4012, + serialized_end=4071, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY = _descriptor.Descriptor( + name="ContentDictBoolBoolEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name="content_union_1_type_int", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_int", - index=2, - number=3, - type=5, - cpp_type=1, + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry.key", + index=0, + number=1, + type=8, + cpp_type=7, label=1, has_default_value=False, - default_value=0, + default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -1050,15 +1063,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_union_1_type_float", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_float", - index=3, - number=4, - type=2, - cpp_type=6, + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry.value", + index=1, + number=2, + type=8, + cpp_type=7, label=1, has_default_value=False, - default_value=float(0), + default_value=False, message_type=None, enum_type=None, containing_type=None, @@ -1067,11 +1080,31 @@ serialized_options=None, file=DESCRIPTOR, ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4073, + serialized_end=4131, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY = _descriptor.Descriptor( + name="ContentDictBoolStrEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name="content_union_1_type_bool", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_bool", - index=4, - number=5, + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry.key", + index=0, + number=1, type=8, cpp_type=7, label=1, @@ -1086,15 +1119,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_union_1_type_str", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_str", - index=5, - number=6, + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry.value", + index=1, + number=2, type=9, cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -1103,16 +1136,36 @@ serialized_options=None, file=DESCRIPTOR, ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4133, + serialized_end=4190, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY = _descriptor.Descriptor( + name="ContentDictStrBytesEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ _descriptor.FieldDescriptor( - name="content_union_1_type_set_of_int", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_set_of_int", - index=6, - number=7, - type=5, - cpp_type=1, - label=3, + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry.key", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, has_default_value=False, - default_value=[], + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -1122,15 +1175,15 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_union_1_type_list_of_bool", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_list_of_bool", - index=7, - number=8, - type=8, - cpp_type=7, - label=3, + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry.value", + index=1, + number=2, + type=12, + cpp_type=9, + label=1, has_default_value=False, - default_value=[], + default_value=b"", message_type=None, enum_type=None, containing_type=None, @@ -1139,10 +1192,928 @@ serialized_options=None, file=DESCRIPTOR, ), - _descriptor.FieldDescriptor( - name="content_union_1_type_dict_of_str_int", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_dict_of_str_int", - index=8, + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4192, + serialized_end=4250, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY = _descriptor.Descriptor( + name="ContentDictStrIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4252, + serialized_end=4308, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY = _descriptor.Descriptor( + name="ContentDictStrFloatEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4310, + serialized_end=4368, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY = _descriptor.Descriptor( + name="ContentDictStrBoolEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4370, + serialized_end=4427, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY = _descriptor.Descriptor( + name="ContentDictStrStrEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4429, + serialized_end=4485, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Pmt_Performative", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_dict_int_bytes", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_int_bytes", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_int_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_float", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_int_float", + index=2, + number=3, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_bool", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_int_bool", + index=3, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_str", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_int_str", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_bytes", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_bytes", + index=5, + number=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_float", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_float", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_bool", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_bool", + index=8, + number=9, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_str", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_bool_str", + index=9, + number=10, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_bytes", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_bytes", + index=10, + number=11, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_int", + index=11, + number=12, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_float", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_float", + index=12, + number=13, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_bool", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_bool", + index=13, + number=14, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_str", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.content_dict_str_str", + index=14, + number=15, + 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, + ), + ], + extensions=[], + nested_types=[ + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1694, + serialized_end=4485, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( + name="ContentUnion1TypeDictOfStrIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5734, + serialized_end=5802, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfStrIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5804, + serialized_end=5872, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfIntFloatEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5874, + serialized_end=5944, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfBoolBytesEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.value", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5946, + serialized_end=6017, +) + +_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Mt_Performative", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_union_1_type_DataModel", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_DataModel", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_bytes", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_int", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_float", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_float", + index=3, + number=4, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_bool", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_bool", + index=4, + number=5, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_str", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_str", + index=5, + number=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_set_of_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_set_of_int", + index=6, + number=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_list_of_bool", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_list_of_bool", + index=7, + number=8, + type=8, + cpp_type=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_dict_of_str_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.content_union_1_type_dict_of_str_int", + index=8, number=9, type=11, cpp_type=10, @@ -1324,148 +2295,36 @@ nested_types=[ _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, - _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, - _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, - ], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=2103, - serialized_end=3632, -) - -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY = _descriptor.Descriptor( - name="ContentODictStrIntEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry.key", - 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, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry.value", - 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, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=_b("8\001"), - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=4610, - serialized_end=4667, -) - -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( - name="ContentOUnionTypeDictOfStrIntEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry", - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry.key", - 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, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry.value", - 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, - ), + _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, + _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, ], - extensions=[], - nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=None, is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=4669, - serialized_end=4737, + serialized_start=4488, + serialized_end=6017, ) -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY = _descriptor.Descriptor( - name="ContentOUnionTypeDictOfStrFloatEntry", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry", +_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY = _descriptor.Descriptor( + name="ContentODictStrIntEntry", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry", filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name="key", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry.key", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry.key", index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -1476,14 +2335,14 @@ ), _descriptor.FieldDescriptor( name="value", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry.value", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry.value", index=1, number=2, - type=2, - cpp_type=6, + type=5, + cpp_type=1, label=1, has_default_value=False, - default_value=float(0), + default_value=0, message_type=None, enum_type=None, containing_type=None, @@ -1496,13 +2355,13 @@ extensions=[], nested_types=[], enum_types=[], - serialized_options=_b("8\001"), + serialized_options=b"8\001", is_extendable=False, syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=4739, - serialized_end=4809, + serialized_start=6496, + serialized_end=6553, ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE = _descriptor.Descriptor( @@ -1585,12 +2444,12 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_o_set_float", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_set_float", + name="content_o_set_int", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_set_int", index=4, number=5, - type=2, - cpp_type=6, + type=5, + cpp_type=1, label=3, has_default_value=False, default_value=[], @@ -1603,8 +2462,8 @@ file=DESCRIPTOR, ), _descriptor.FieldDescriptor( - name="content_o_set_float_is_set", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_set_float_is_set", + name="content_o_set_int_is_set", + full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_set_int_is_set", index=5, number=6, type=8, @@ -1692,138 +2551,10 @@ serialized_options=None, file=DESCRIPTOR, ), - _descriptor.FieldDescriptor( - name="content_o_union_type_str", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_str", - index=10, - number=11, - 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, - ), - _descriptor.FieldDescriptor( - name="content_o_union_type_dict_of_str_int", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_dict_of_str_int", - index=11, - number=12, - 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, - ), - _descriptor.FieldDescriptor( - name="content_o_union_type_set_of_int", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_set_of_int", - index=12, - number=13, - 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, - ), - _descriptor.FieldDescriptor( - name="content_o_union_type_set_of_bytes", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_set_of_bytes", - index=13, - number=14, - 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, - serialized_options=None, - file=DESCRIPTOR, - ), - _descriptor.FieldDescriptor( - name="content_o_union_type_list_of_bool", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_list_of_bool", - index=14, - number=15, - type=8, - cpp_type=7, - 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, - ), - _descriptor.FieldDescriptor( - name="content_o_union_type_dict_of_str_float", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_type_dict_of_str_float", - index=15, - number=16, - 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, - ), - _descriptor.FieldDescriptor( - name="content_o_union_is_set", - full_name="fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.content_o_union_is_set", - index=16, - number=17, - 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, - ), ], extensions=[], nested_types=[ _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY, - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY, ], enum_types=[], serialized_options=None, @@ -1831,8 +2562,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=3635, - serialized_end=4809, + serialized_start=6020, + serialized_end=6553, ) _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE = _descriptor.Descriptor( @@ -1850,8 +2581,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=4811, - serialized_end=4853, + serialized_start=6555, + serialized_end=6597, ) _TPROTOCOLMESSAGE = _descriptor.Descriptor( @@ -1888,7 +2619,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -1906,7 +2637,7 @@ cpp_type=9, label=1, has_default_value=False, - default_value=_b("").decode("utf-8"), + default_value=b"".decode("utf-8"), message_type=None, enum_type=None, containing_type=None, @@ -2086,7 +2817,7 @@ ), ], serialized_start=42, - serialized_end=4869, + serialized_end=6613, ) _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY.containing_type = _TPROTOCOLMESSAGE_DATAMODEL @@ -2100,22 +2831,110 @@ _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE.containing_type = _TPROTOCOLMESSAGE _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE.containing_type = _TPROTOCOLMESSAGE _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE.containing_type = _TPROTOCOLMESSAGE +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY.containing_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE ) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY.containing_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE ) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY.containing_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_bytes" +].message_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_int" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_float" +].message_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_bool" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_str" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ "content_dict_bool_bytes" ].message_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY ) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_int" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_float" +].message_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_bool" +].message_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_str" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_bytes" +].message_type = ( + _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_int" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ "content_dict_str_float" ].message_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY ) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_bool" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_str" +].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.containing_type = _TPROTOCOLMESSAGE _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY.containing_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE @@ -2156,28 +2975,12 @@ _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY.containing_type = ( _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE ) -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY.containing_type = ( - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE -) -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY.containing_type = ( - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE -) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.fields_by_name[ "content_o_ct" ].message_type = _TPROTOCOLMESSAGE_DATAMODEL _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.fields_by_name[ "content_o_dict_str_int" ].message_type = _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.fields_by_name[ - "content_o_union_type_dict_of_str_int" -].message_type = ( - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY -) -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.fields_by_name[ - "content_o_union_type_dict_of_str_float" -].message_type = ( - _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY -) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE.containing_type = _TPROTOCOLMESSAGE _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE.containing_type = ( _TPROTOCOLMESSAGE @@ -2251,173 +3054,272 @@ TProtocolMessage = _reflection.GeneratedProtocolMessageType( "TProtocolMessage", (_message.Message,), - dict( - DataModel=_reflection.GeneratedProtocolMessageType( + { + "DataModel": _reflection.GeneratedProtocolMessageType( "DataModel", (_message.Message,), - dict( - DictFieldEntry=_reflection.GeneratedProtocolMessageType( + { + "DictFieldEntry": _reflection.GeneratedProtocolMessageType( "DictFieldEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.DataModel.DictFieldEntry) - ), + }, ), - DESCRIPTOR=_TPROTOCOLMESSAGE_DATAMODEL, - __module__="t_protocol_pb2" + "DESCRIPTOR": _TPROTOCOLMESSAGE_DATAMODEL, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.DataModel) - ), + }, ), - Performative_Ct_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Ct_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Ct_Performative", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_CT_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Ct_Performative) - ), + }, ), - Performative_Pt_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Pt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pt_Performative", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pt_Performative) - ), + }, ), - Performative_Pct_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Pct_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pct_Performative", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pct_Performative) - ), + }, ), - Performative_Pmt_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Pmt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Pmt_Performative", (_message.Message,), - dict( - ContentDictBoolBytesEntry=_reflection.GeneratedProtocolMessageType( + { + "ContentDictIntBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry) + }, + ), + "ContentDictIntIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry) + }, + ), + "ContentDictIntFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry) + }, + ), + "ContentDictIntBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry) + }, + ), + "ContentDictIntStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry) + }, + ), + "ContentDictBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentDictBoolBytesEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry) - ), + }, + ), + "ContentDictBoolIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry) + }, + ), + "ContentDictBoolFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry) + }, + ), + "ContentDictBoolBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry) + }, + ), + "ContentDictBoolStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry) + }, ), - ContentDictStrFloatEntry=_reflection.GeneratedProtocolMessageType( + "ContentDictStrBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry) + }, + ), + "ContentDictStrIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry) + }, + ), + "ContentDictStrFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentDictStrFloatEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry) - ), + }, + ), + "ContentDictStrBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry) + }, + ), + "ContentDictStrStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, + "__module__": "t_protocol_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry) + }, ), - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, - __module__="t_protocol_pb2" + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Pmt_Performative) - ), + }, ), - Performative_Mt_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Mt_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Mt_Performative", (_message.Message,), - dict( - ContentUnion1TypeDictOfStrIntEntry=_reflection.GeneratedProtocolMessageType( + { + "ContentUnion1TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion1TypeDictOfStrIntEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry) - ), + }, ), - ContentUnion2TypeDictOfStrIntEntry=_reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfStrIntEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry) - ), + }, ), - ContentUnion2TypeDictOfIntFloatEntry=_reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfIntFloatEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfIntFloatEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry) - ), + }, ), - ContentUnion2TypeDictOfBoolBytesEntry=_reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfBoolBytesEntry": _reflection.GeneratedProtocolMessageType( "ContentUnion2TypeDictOfBoolBytesEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry) - ), + }, ), - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, - __module__="t_protocol_pb2" + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Mt_Performative) - ), + }, ), - Performative_O_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_O_Performative": _reflection.GeneratedProtocolMessageType( "Performative_O_Performative", (_message.Message,), - dict( - ContentODictStrIntEntry=_reflection.GeneratedProtocolMessageType( + { + "ContentODictStrIntEntry": _reflection.GeneratedProtocolMessageType( "ContentODictStrIntEntry", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry) - ), + }, ), - ContentOUnionTypeDictOfStrIntEntry=_reflection.GeneratedProtocolMessageType( - "ContentOUnionTypeDictOfStrIntEntry", - (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY, - __module__="t_protocol_pb2" - # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry) - ), - ), - ContentOUnionTypeDictOfStrFloatEntry=_reflection.GeneratedProtocolMessageType( - "ContentOUnionTypeDictOfStrFloatEntry", - (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY, - __module__="t_protocol_pb2" - # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry) - ), - ), - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE, - __module__="t_protocol_pb2" + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_O_Performative) - ), + }, ), - Performative_Empty_Contents_Performative=_reflection.GeneratedProtocolMessageType( + "Performative_Empty_Contents_Performative": _reflection.GeneratedProtocolMessageType( "Performative_Empty_Contents_Performative", (_message.Message,), - dict( - DESCRIPTOR=_TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, - __module__="t_protocol_pb2" + { + "DESCRIPTOR": _TPROTOCOLMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage.Performative_Empty_Contents_Performative) - ), + }, ), - DESCRIPTOR=_TPROTOCOLMESSAGE, - __module__="t_protocol_pb2" + "DESCRIPTOR": _TPROTOCOLMESSAGE, + "__module__": "t_protocol_pb2" # @@protoc_insertion_point(class_scope:fetch.aea.TProtocol.TProtocolMessage) - ), + }, ) _sym_db.RegisterMessage(TProtocolMessage) _sym_db.RegisterMessage(TProtocolMessage.DataModel) @@ -2426,12 +3328,51 @@ _sym_db.RegisterMessage(TProtocolMessage.Performative_Pt_Performative) _sym_db.RegisterMessage(TProtocolMessage.Performative_Pct_Performative) _sym_db.RegisterMessage(TProtocolMessage.Performative_Pmt_Performative) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictIntIntEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictIntStrEntry +) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry ) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictStrIntEntry +) _sym_db.RegisterMessage( TProtocolMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry ) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry +) +_sym_db.RegisterMessage( + TProtocolMessage.Performative_Pmt_Performative.ContentDictStrStrEntry +) _sym_db.RegisterMessage(TProtocolMessage.Performative_Mt_Performative) _sym_db.RegisterMessage( TProtocolMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry @@ -2449,20 +3390,29 @@ _sym_db.RegisterMessage( TProtocolMessage.Performative_O_Performative.ContentODictStrIntEntry ) -_sym_db.RegisterMessage( - TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrIntEntry -) -_sym_db.RegisterMessage( - TProtocolMessage.Performative_O_Performative.ContentOUnionTypeDictOfStrFloatEntry -) _sym_db.RegisterMessage(TProtocolMessage.Performative_Empty_Contents_Performative) _TPROTOCOLMESSAGE_DATAMODEL_DICTFIELDENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._options = None _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._options = ( None ) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._options = ( + None +) +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._options = None _TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._options = None +_TPROTOCOLMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._options = None _TPROTOCOLMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._options = ( None ) @@ -2476,10 +3426,4 @@ None ) _TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._options = None -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRINTENTRY._options = ( - None -) -_TPROTOCOLMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTOUNIONTYPEDICTOFSTRFLOATENTRY._options = ( - None -) # @@protoc_insertion_point(module_scope) diff --git a/tests/data/generator/t_protocol_no_ct/__init__.py b/tests/data/generator/t_protocol_no_ct/__init__.py new file mode 100644 index 0000000000..4ce3b6e598 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2020 fetchai +# +# 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 t_protocol_no_ct protocol.""" + +from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage +from tests.data.generator.t_protocol_no_ct.serialization import TProtocolNoCtSerializer + +TProtocolNoCtMessage.serializer = TProtocolNoCtSerializer diff --git a/tests/data/generator/t_protocol_no_ct/dialogues.py b/tests/data/generator/t_protocol_no_ct/dialogues.py new file mode 100644 index 0000000000..bf5e985f1a --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/dialogues.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2020 fetchai +# +# 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 t_protocol_no_ct dialogue management. + +- TProtocolNoCtDialogue: The dialogue class maintains state of a dialogue and manages it. +- TProtocolNoCtDialogues: The dialogues class keeps track of all dialogues. +""" + +from abc import ABC +from typing import Dict, FrozenSet, Optional, cast + +from aea.helpers.dialogue.base import Dialogue, DialogueLabel, Dialogues +from aea.mail.base import Address +from aea.protocols.base import Message + +from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage + + +class TProtocolNoCtDialogue(Dialogue): + """The t_protocol_no_ct dialogue class maintains state of a dialogue and manages it.""" + + INITIAL_PERFORMATIVES = frozenset( + {TProtocolNoCtMessage.Performative.PERFORMATIVE_PT} + ) + TERMINAL_PERFORMATIVES = frozenset( + { + TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, + TProtocolNoCtMessage.Performative.PERFORMATIVE_O, + TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS, + } + ) + VALID_REPLIES = { + TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS: frozenset( + {TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS} + ), + TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: frozenset(), + TProtocolNoCtMessage.Performative.PERFORMATIVE_O: frozenset(), + TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: frozenset( + { + TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, + TProtocolNoCtMessage.Performative.PERFORMATIVE_O, + } + ), + TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: frozenset( + { + TProtocolNoCtMessage.Performative.PERFORMATIVE_MT, + TProtocolNoCtMessage.Performative.PERFORMATIVE_O, + } + ), + TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: frozenset( + { + TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT, + TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT, + } + ), + } + + class Role(Dialogue.Role): + """This class defines the agent's role in a t_protocol_no_ct dialogue.""" + + ROLE_1 = "role_1" + ROLE_2 = "role_2" + + class EndState(Dialogue.EndState): + """This class defines the end states of a t_protocol_no_ct dialogue.""" + + END_STATE_1 = 0 + END_STATE_2 = 1 + END_STATE_3 = 2 + + def __init__( + self, + dialogue_label: DialogueLabel, + agent_address: Optional[Address] = None, + role: Optional[Dialogue.Role] = None, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param agent_address: the address of the agent for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + :return: None + """ + Dialogue.__init__( + self, + dialogue_label=dialogue_label, + agent_address=agent_address, + role=role, + rules=Dialogue.Rules( + cast(FrozenSet[Message.Performative], self.INITIAL_PERFORMATIVES), + cast(FrozenSet[Message.Performative], self.TERMINAL_PERFORMATIVES), + cast( + Dict[Message.Performative, FrozenSet[Message.Performative]], + self.VALID_REPLIES, + ), + ), + ) + + def is_valid(self, message: Message) -> bool: + """ + Check whether 'message' is a valid next message in the dialogue. + + These rules capture specific constraints designed for dialogues which are instances of a concrete sub-class of this class. + Override this method with your additional dialogue rules. + + :param message: the message to be validated + :return: True if valid, False otherwise + """ + return True + + +class TProtocolNoCtDialogues(Dialogues, ABC): + """This class keeps track of all t_protocol_no_ct dialogues.""" + + END_STATES = frozenset( + { + TProtocolNoCtDialogue.EndState.END_STATE_1, + TProtocolNoCtDialogue.EndState.END_STATE_2, + TProtocolNoCtDialogue.EndState.END_STATE_3, + } + ) + + def __init__(self, agent_address: Address) -> None: + """ + Initialize dialogues. + + :param agent_address: the address of the agent for whom dialogues are maintained + :return: None + """ + Dialogues.__init__( + self, + agent_address=agent_address, + end_states=cast(FrozenSet[Dialogue.EndState], self.END_STATES), + ) + + def create_dialogue( + self, dialogue_label: DialogueLabel, role: Dialogue.Role, + ) -> TProtocolNoCtDialogue: + """ + Create an instance of t_protocol_no_ct dialogue. + + :param dialogue_label: the identifier of the dialogue + :param role: the role of the agent this dialogue is maintained for + + :return: the created dialogue + """ + dialogue = TProtocolNoCtDialogue( + dialogue_label=dialogue_label, agent_address=self.agent_address, role=role + ) + return dialogue diff --git a/tests/data/generator/t_protocol_no_ct/message.py b/tests/data/generator/t_protocol_no_ct/message.py new file mode 100644 index 0000000000..468ba1e1f5 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/message.py @@ -0,0 +1,1008 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2020 fetchai +# +# 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 t_protocol_no_ct's message definition.""" + +import logging +from enum import Enum +from typing import Dict, FrozenSet, Optional, Set, Tuple, Union, cast + +from aea.configurations.base import ProtocolId +from aea.protocols.base import Message + +logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol_no_ct.message") + +DEFAULT_BODY_SIZE = 4 + + +class TProtocolNoCtMessage(Message): + """A protocol for testing purposes.""" + + protocol_id = ProtocolId.from_str("fetchai/t_protocol_no_ct:0.1.0") + + class Performative(Enum): + """Performatives for the t_protocol_no_ct protocol.""" + + PERFORMATIVE_EMPTY_CONTENTS = "performative_empty_contents" + PERFORMATIVE_MT = "performative_mt" + PERFORMATIVE_O = "performative_o" + PERFORMATIVE_PCT = "performative_pct" + PERFORMATIVE_PMT = "performative_pmt" + PERFORMATIVE_PT = "performative_pt" + + def __str__(self): + """Get the string representation.""" + return str(self.value) + + def __init__( + self, + performative: Performative, + dialogue_reference: Tuple[str, str] = ("", ""), + message_id: int = 1, + target: int = 0, + **kwargs, + ): + """ + Initialise an instance of TProtocolNoCtMessage. + + :param message_id: the message id. + :param dialogue_reference: the dialogue reference. + :param target: the message target. + :param performative: the message performative. + """ + super().__init__( + dialogue_reference=dialogue_reference, + message_id=message_id, + target=target, + performative=TProtocolNoCtMessage.Performative(performative), + **kwargs, + ) + self._performatives = { + "performative_empty_contents", + "performative_mt", + "performative_o", + "performative_pct", + "performative_pmt", + "performative_pt", + } + + @property + def valid_performatives(self) -> Set[str]: + """Get valid performatives.""" + return self._performatives + + @property + def dialogue_reference(self) -> Tuple[str, str]: + """Get the dialogue_reference of the message.""" + assert self.is_set("dialogue_reference"), "dialogue_reference is not set." + return cast(Tuple[str, str], self.get("dialogue_reference")) + + @property + def message_id(self) -> int: + """Get the message_id of the message.""" + assert self.is_set("message_id"), "message_id is not set." + return cast(int, self.get("message_id")) + + @property + def performative(self) -> Performative: # type: ignore # noqa: F821 + """Get the performative of the message.""" + assert self.is_set("performative"), "performative is not set." + return cast(TProtocolNoCtMessage.Performative, self.get("performative")) + + @property + def target(self) -> int: + """Get the target of the message.""" + assert self.is_set("target"), "target is not set." + return cast(int, self.get("target")) + + @property + def content_bool(self) -> bool: + """Get the 'content_bool' content from the message.""" + assert self.is_set("content_bool"), "'content_bool' content is not set." + return cast(bool, self.get("content_bool")) + + @property + def content_bytes(self) -> bytes: + """Get the 'content_bytes' content from the message.""" + assert self.is_set("content_bytes"), "'content_bytes' content is not set." + return cast(bytes, self.get("content_bytes")) + + @property + def content_dict_bool_bool(self) -> Dict[bool, bool]: + """Get the 'content_dict_bool_bool' content from the message.""" + assert self.is_set( + "content_dict_bool_bool" + ), "'content_dict_bool_bool' content is not set." + return cast(Dict[bool, bool], self.get("content_dict_bool_bool")) + + @property + def content_dict_bool_bytes(self) -> Dict[bool, bytes]: + """Get the 'content_dict_bool_bytes' content from the message.""" + assert self.is_set( + "content_dict_bool_bytes" + ), "'content_dict_bool_bytes' content is not set." + return cast(Dict[bool, bytes], self.get("content_dict_bool_bytes")) + + @property + def content_dict_bool_float(self) -> Dict[bool, float]: + """Get the 'content_dict_bool_float' content from the message.""" + assert self.is_set( + "content_dict_bool_float" + ), "'content_dict_bool_float' content is not set." + return cast(Dict[bool, float], self.get("content_dict_bool_float")) + + @property + def content_dict_bool_int(self) -> Dict[bool, int]: + """Get the 'content_dict_bool_int' content from the message.""" + assert self.is_set( + "content_dict_bool_int" + ), "'content_dict_bool_int' content is not set." + return cast(Dict[bool, int], self.get("content_dict_bool_int")) + + @property + def content_dict_bool_str(self) -> Dict[bool, str]: + """Get the 'content_dict_bool_str' content from the message.""" + assert self.is_set( + "content_dict_bool_str" + ), "'content_dict_bool_str' content is not set." + return cast(Dict[bool, str], self.get("content_dict_bool_str")) + + @property + def content_dict_int_bool(self) -> Dict[int, bool]: + """Get the 'content_dict_int_bool' content from the message.""" + assert self.is_set( + "content_dict_int_bool" + ), "'content_dict_int_bool' content is not set." + return cast(Dict[int, bool], self.get("content_dict_int_bool")) + + @property + def content_dict_int_bytes(self) -> Dict[int, bytes]: + """Get the 'content_dict_int_bytes' content from the message.""" + assert self.is_set( + "content_dict_int_bytes" + ), "'content_dict_int_bytes' content is not set." + return cast(Dict[int, bytes], self.get("content_dict_int_bytes")) + + @property + def content_dict_int_float(self) -> Dict[int, float]: + """Get the 'content_dict_int_float' content from the message.""" + assert self.is_set( + "content_dict_int_float" + ), "'content_dict_int_float' content is not set." + return cast(Dict[int, float], self.get("content_dict_int_float")) + + @property + def content_dict_int_int(self) -> Dict[int, int]: + """Get the 'content_dict_int_int' content from the message.""" + assert self.is_set( + "content_dict_int_int" + ), "'content_dict_int_int' content is not set." + return cast(Dict[int, int], self.get("content_dict_int_int")) + + @property + def content_dict_int_str(self) -> Dict[int, str]: + """Get the 'content_dict_int_str' content from the message.""" + assert self.is_set( + "content_dict_int_str" + ), "'content_dict_int_str' content is not set." + return cast(Dict[int, str], self.get("content_dict_int_str")) + + @property + def content_dict_str_bool(self) -> Dict[str, bool]: + """Get the 'content_dict_str_bool' content from the message.""" + assert self.is_set( + "content_dict_str_bool" + ), "'content_dict_str_bool' content is not set." + return cast(Dict[str, bool], self.get("content_dict_str_bool")) + + @property + def content_dict_str_bytes(self) -> Dict[str, bytes]: + """Get the 'content_dict_str_bytes' content from the message.""" + assert self.is_set( + "content_dict_str_bytes" + ), "'content_dict_str_bytes' content is not set." + return cast(Dict[str, bytes], self.get("content_dict_str_bytes")) + + @property + def content_dict_str_float(self) -> Dict[str, float]: + """Get the 'content_dict_str_float' content from the message.""" + assert self.is_set( + "content_dict_str_float" + ), "'content_dict_str_float' content is not set." + return cast(Dict[str, float], self.get("content_dict_str_float")) + + @property + def content_dict_str_int(self) -> Dict[str, int]: + """Get the 'content_dict_str_int' content from the message.""" + assert self.is_set( + "content_dict_str_int" + ), "'content_dict_str_int' content is not set." + return cast(Dict[str, int], self.get("content_dict_str_int")) + + @property + def content_dict_str_str(self) -> Dict[str, str]: + """Get the 'content_dict_str_str' content from the message.""" + assert self.is_set( + "content_dict_str_str" + ), "'content_dict_str_str' content is not set." + return cast(Dict[str, str], self.get("content_dict_str_str")) + + @property + def content_float(self) -> float: + """Get the 'content_float' content from the message.""" + assert self.is_set("content_float"), "'content_float' content is not set." + return cast(float, self.get("content_float")) + + @property + def content_int(self) -> int: + """Get the 'content_int' content from the message.""" + assert self.is_set("content_int"), "'content_int' content is not set." + return cast(int, self.get("content_int")) + + @property + def content_list_bool(self) -> Tuple[bool, ...]: + """Get the 'content_list_bool' content from the message.""" + assert self.is_set( + "content_list_bool" + ), "'content_list_bool' content is not set." + return cast(Tuple[bool, ...], self.get("content_list_bool")) + + @property + def content_list_bytes(self) -> Tuple[bytes, ...]: + """Get the 'content_list_bytes' content from the message.""" + assert self.is_set( + "content_list_bytes" + ), "'content_list_bytes' content is not set." + return cast(Tuple[bytes, ...], self.get("content_list_bytes")) + + @property + def content_list_float(self) -> Tuple[float, ...]: + """Get the 'content_list_float' content from the message.""" + assert self.is_set( + "content_list_float" + ), "'content_list_float' content is not set." + return cast(Tuple[float, ...], self.get("content_list_float")) + + @property + def content_list_int(self) -> Tuple[int, ...]: + """Get the 'content_list_int' content from the message.""" + assert self.is_set("content_list_int"), "'content_list_int' content is not set." + return cast(Tuple[int, ...], self.get("content_list_int")) + + @property + def content_list_str(self) -> Tuple[str, ...]: + """Get the 'content_list_str' content from the message.""" + assert self.is_set("content_list_str"), "'content_list_str' content is not set." + return cast(Tuple[str, ...], self.get("content_list_str")) + + @property + def content_o_bool(self) -> Optional[bool]: + """Get the 'content_o_bool' content from the message.""" + return cast(Optional[bool], self.get("content_o_bool")) + + @property + def content_o_dict_str_int(self) -> Optional[Dict[str, int]]: + """Get the 'content_o_dict_str_int' content from the message.""" + return cast(Optional[Dict[str, int]], self.get("content_o_dict_str_int")) + + @property + def content_o_list_bytes(self) -> Optional[Tuple[bytes, ...]]: + """Get the 'content_o_list_bytes' content from the message.""" + return cast(Optional[Tuple[bytes, ...]], self.get("content_o_list_bytes")) + + @property + def content_o_set_int(self) -> Optional[FrozenSet[int]]: + """Get the 'content_o_set_int' content from the message.""" + return cast(Optional[FrozenSet[int]], self.get("content_o_set_int")) + + @property + def content_set_bool(self) -> FrozenSet[bool]: + """Get the 'content_set_bool' content from the message.""" + assert self.is_set("content_set_bool"), "'content_set_bool' content is not set." + return cast(FrozenSet[bool], self.get("content_set_bool")) + + @property + def content_set_bytes(self) -> FrozenSet[bytes]: + """Get the 'content_set_bytes' content from the message.""" + assert self.is_set( + "content_set_bytes" + ), "'content_set_bytes' content is not set." + return cast(FrozenSet[bytes], self.get("content_set_bytes")) + + @property + def content_set_float(self) -> FrozenSet[float]: + """Get the 'content_set_float' content from the message.""" + assert self.is_set( + "content_set_float" + ), "'content_set_float' content is not set." + return cast(FrozenSet[float], self.get("content_set_float")) + + @property + def content_set_int(self) -> FrozenSet[int]: + """Get the 'content_set_int' content from the message.""" + assert self.is_set("content_set_int"), "'content_set_int' content is not set." + return cast(FrozenSet[int], self.get("content_set_int")) + + @property + def content_set_str(self) -> FrozenSet[str]: + """Get the 'content_set_str' content from the message.""" + assert self.is_set("content_set_str"), "'content_set_str' content is not set." + return cast(FrozenSet[str], self.get("content_set_str")) + + @property + def content_str(self) -> str: + """Get the 'content_str' content from the message.""" + assert self.is_set("content_str"), "'content_str' content is not set." + return cast(str, self.get("content_str")) + + @property + def content_union_1( + self, + ) -> Union[ + bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int] + ]: + """Get the 'content_union_1' content from the message.""" + assert self.is_set("content_union_1"), "'content_union_1' content is not set." + return cast( + Union[ + bytes, + int, + float, + bool, + str, + FrozenSet[int], + Tuple[bool, ...], + Dict[str, int], + ], + self.get("content_union_1"), + ) + + @property + def content_union_2( + self, + ) -> Union[ + FrozenSet[bytes], + FrozenSet[int], + FrozenSet[str], + Tuple[float, ...], + Tuple[bool, ...], + Tuple[bytes, ...], + Dict[str, int], + Dict[int, float], + Dict[bool, bytes], + ]: + """Get the 'content_union_2' content from the message.""" + assert self.is_set("content_union_2"), "'content_union_2' content is not set." + return cast( + Union[ + FrozenSet[bytes], + FrozenSet[int], + FrozenSet[str], + Tuple[float, ...], + Tuple[bool, ...], + Tuple[bytes, ...], + Dict[str, int], + Dict[int, float], + Dict[bool, bytes], + ], + self.get("content_union_2"), + ) + + def _is_consistent(self) -> bool: + """Check that the message follows the t_protocol_no_ct protocol.""" + try: + assert ( + type(self.dialogue_reference) == tuple + ), "Invalid type for 'dialogue_reference'. Expected 'tuple'. Found '{}'.".format( + type(self.dialogue_reference) + ) + assert ( + type(self.dialogue_reference[0]) == str + ), "Invalid type for 'dialogue_reference[0]'. Expected 'str'. Found '{}'.".format( + type(self.dialogue_reference[0]) + ) + assert ( + type(self.dialogue_reference[1]) == str + ), "Invalid type for 'dialogue_reference[1]'. Expected 'str'. Found '{}'.".format( + type(self.dialogue_reference[1]) + ) + assert ( + type(self.message_id) == int + ), "Invalid type for 'message_id'. Expected 'int'. Found '{}'.".format( + type(self.message_id) + ) + assert ( + type(self.target) == int + ), "Invalid type for 'target'. Expected 'int'. Found '{}'.".format( + type(self.target) + ) + + # Light Protocol Rule 2 + # Check correct performative + assert ( + type(self.performative) == TProtocolNoCtMessage.Performative + ), "Invalid 'performative'. Expected either of '{}'. Found '{}'.".format( + self.valid_performatives, self.performative + ) + + # Check correct contents + actual_nb_of_contents = len(self.body) - DEFAULT_BODY_SIZE + expected_nb_of_contents = 0 + if self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: + expected_nb_of_contents = 5 + assert ( + type(self.content_bytes) == bytes + ), "Invalid type for content 'content_bytes'. Expected 'bytes'. Found '{}'.".format( + type(self.content_bytes) + ) + assert ( + type(self.content_int) == int + ), "Invalid type for content 'content_int'. Expected 'int'. Found '{}'.".format( + type(self.content_int) + ) + assert ( + type(self.content_float) == float + ), "Invalid type for content 'content_float'. Expected 'float'. Found '{}'.".format( + type(self.content_float) + ) + assert ( + type(self.content_bool) == bool + ), "Invalid type for content 'content_bool'. Expected 'bool'. Found '{}'.".format( + type(self.content_bool) + ) + assert ( + type(self.content_str) == str + ), "Invalid type for content 'content_str'. Expected 'str'. Found '{}'.".format( + type(self.content_str) + ) + elif ( + self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT + ): + expected_nb_of_contents = 10 + assert ( + type(self.content_set_bytes) == frozenset + ), "Invalid type for content 'content_set_bytes'. Expected 'frozenset'. Found '{}'.".format( + type(self.content_set_bytes) + ) + assert all( + type(element) == bytes for element in self.content_set_bytes + ), "Invalid type for frozenset elements in content 'content_set_bytes'. Expected 'bytes'." + assert ( + type(self.content_set_int) == frozenset + ), "Invalid type for content 'content_set_int'. Expected 'frozenset'. Found '{}'.".format( + type(self.content_set_int) + ) + assert all( + type(element) == int for element in self.content_set_int + ), "Invalid type for frozenset elements in content 'content_set_int'. Expected 'int'." + assert ( + type(self.content_set_float) == frozenset + ), "Invalid type for content 'content_set_float'. Expected 'frozenset'. Found '{}'.".format( + type(self.content_set_float) + ) + assert all( + type(element) == float for element in self.content_set_float + ), "Invalid type for frozenset elements in content 'content_set_float'. Expected 'float'." + assert ( + type(self.content_set_bool) == frozenset + ), "Invalid type for content 'content_set_bool'. Expected 'frozenset'. Found '{}'.".format( + type(self.content_set_bool) + ) + assert all( + type(element) == bool for element in self.content_set_bool + ), "Invalid type for frozenset elements in content 'content_set_bool'. Expected 'bool'." + assert ( + type(self.content_set_str) == frozenset + ), "Invalid type for content 'content_set_str'. Expected 'frozenset'. Found '{}'.".format( + type(self.content_set_str) + ) + assert all( + type(element) == str for element in self.content_set_str + ), "Invalid type for frozenset elements in content 'content_set_str'. Expected 'str'." + assert ( + type(self.content_list_bytes) == tuple + ), "Invalid type for content 'content_list_bytes'. Expected 'tuple'. Found '{}'.".format( + type(self.content_list_bytes) + ) + assert all( + type(element) == bytes for element in self.content_list_bytes + ), "Invalid type for tuple elements in content 'content_list_bytes'. Expected 'bytes'." + assert ( + type(self.content_list_int) == tuple + ), "Invalid type for content 'content_list_int'. Expected 'tuple'. Found '{}'.".format( + type(self.content_list_int) + ) + assert all( + type(element) == int for element in self.content_list_int + ), "Invalid type for tuple elements in content 'content_list_int'. Expected 'int'." + assert ( + type(self.content_list_float) == tuple + ), "Invalid type for content 'content_list_float'. Expected 'tuple'. Found '{}'.".format( + type(self.content_list_float) + ) + assert all( + type(element) == float for element in self.content_list_float + ), "Invalid type for tuple elements in content 'content_list_float'. Expected 'float'." + assert ( + type(self.content_list_bool) == tuple + ), "Invalid type for content 'content_list_bool'. Expected 'tuple'. Found '{}'.".format( + type(self.content_list_bool) + ) + assert all( + type(element) == bool for element in self.content_list_bool + ), "Invalid type for tuple elements in content 'content_list_bool'. Expected 'bool'." + assert ( + type(self.content_list_str) == tuple + ), "Invalid type for content 'content_list_str'. Expected 'tuple'. Found '{}'.".format( + type(self.content_list_str) + ) + assert all( + type(element) == str for element in self.content_list_str + ), "Invalid type for tuple elements in content 'content_list_str'. Expected 'str'." + elif ( + self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT + ): + expected_nb_of_contents = 15 + assert ( + type(self.content_dict_int_bytes) == dict + ), "Invalid type for content 'content_dict_int_bytes'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_bytes) + ) + for ( + key_of_content_dict_int_bytes, + value_of_content_dict_int_bytes, + ) in self.content_dict_int_bytes.items(): + assert ( + type(key_of_content_dict_int_bytes) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_bytes'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_bytes) + ) + assert ( + type(value_of_content_dict_int_bytes) == bytes + ), "Invalid type for dictionary values in content 'content_dict_int_bytes'. Expected 'bytes'. Found '{}'.".format( + type(value_of_content_dict_int_bytes) + ) + assert ( + type(self.content_dict_int_int) == dict + ), "Invalid type for content 'content_dict_int_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_int) + ) + for ( + key_of_content_dict_int_int, + value_of_content_dict_int_int, + ) in self.content_dict_int_int.items(): + assert ( + type(key_of_content_dict_int_int) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_int) + ) + assert ( + type(value_of_content_dict_int_int) == int + ), "Invalid type for dictionary values in content 'content_dict_int_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_int_int) + ) + assert ( + type(self.content_dict_int_float) == dict + ), "Invalid type for content 'content_dict_int_float'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_float) + ) + for ( + key_of_content_dict_int_float, + value_of_content_dict_int_float, + ) in self.content_dict_int_float.items(): + assert ( + type(key_of_content_dict_int_float) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_float'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_float) + ) + assert ( + type(value_of_content_dict_int_float) == float + ), "Invalid type for dictionary values in content 'content_dict_int_float'. Expected 'float'. Found '{}'.".format( + type(value_of_content_dict_int_float) + ) + assert ( + type(self.content_dict_int_bool) == dict + ), "Invalid type for content 'content_dict_int_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_bool) + ) + for ( + key_of_content_dict_int_bool, + value_of_content_dict_int_bool, + ) in self.content_dict_int_bool.items(): + assert ( + type(key_of_content_dict_int_bool) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_bool'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_bool) + ) + assert ( + type(value_of_content_dict_int_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_int_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_int_bool) + ) + assert ( + type(self.content_dict_int_str) == dict + ), "Invalid type for content 'content_dict_int_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_int_str) + ) + for ( + key_of_content_dict_int_str, + value_of_content_dict_int_str, + ) in self.content_dict_int_str.items(): + assert ( + type(key_of_content_dict_int_str) == int + ), "Invalid type for dictionary keys in content 'content_dict_int_str'. Expected 'int'. Found '{}'.".format( + type(key_of_content_dict_int_str) + ) + assert ( + type(value_of_content_dict_int_str) == str + ), "Invalid type for dictionary values in content 'content_dict_int_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_int_str) + ) + assert ( + type(self.content_dict_bool_bytes) == dict + ), "Invalid type for content 'content_dict_bool_bytes'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_bytes) + ) + for ( + key_of_content_dict_bool_bytes, + value_of_content_dict_bool_bytes, + ) in self.content_dict_bool_bytes.items(): + assert ( + type(key_of_content_dict_bool_bytes) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_bytes'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_bytes) + ) + assert ( + type(value_of_content_dict_bool_bytes) == bytes + ), "Invalid type for dictionary values in content 'content_dict_bool_bytes'. Expected 'bytes'. Found '{}'.".format( + type(value_of_content_dict_bool_bytes) + ) + assert ( + type(self.content_dict_bool_int) == dict + ), "Invalid type for content 'content_dict_bool_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_int) + ) + for ( + key_of_content_dict_bool_int, + value_of_content_dict_bool_int, + ) in self.content_dict_bool_int.items(): + assert ( + type(key_of_content_dict_bool_int) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_int'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_int) + ) + assert ( + type(value_of_content_dict_bool_int) == int + ), "Invalid type for dictionary values in content 'content_dict_bool_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_bool_int) + ) + assert ( + type(self.content_dict_bool_float) == dict + ), "Invalid type for content 'content_dict_bool_float'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_float) + ) + for ( + key_of_content_dict_bool_float, + value_of_content_dict_bool_float, + ) in self.content_dict_bool_float.items(): + assert ( + type(key_of_content_dict_bool_float) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_float'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_float) + ) + assert ( + type(value_of_content_dict_bool_float) == float + ), "Invalid type for dictionary values in content 'content_dict_bool_float'. Expected 'float'. Found '{}'.".format( + type(value_of_content_dict_bool_float) + ) + assert ( + type(self.content_dict_bool_bool) == dict + ), "Invalid type for content 'content_dict_bool_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_bool) + ) + for ( + key_of_content_dict_bool_bool, + value_of_content_dict_bool_bool, + ) in self.content_dict_bool_bool.items(): + assert ( + type(key_of_content_dict_bool_bool) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_bool) + ) + assert ( + type(value_of_content_dict_bool_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_bool_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_bool_bool) + ) + assert ( + type(self.content_dict_bool_str) == dict + ), "Invalid type for content 'content_dict_bool_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_bool_str) + ) + for ( + key_of_content_dict_bool_str, + value_of_content_dict_bool_str, + ) in self.content_dict_bool_str.items(): + assert ( + type(key_of_content_dict_bool_str) == bool + ), "Invalid type for dictionary keys in content 'content_dict_bool_str'. Expected 'bool'. Found '{}'.".format( + type(key_of_content_dict_bool_str) + ) + assert ( + type(value_of_content_dict_bool_str) == str + ), "Invalid type for dictionary values in content 'content_dict_bool_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_bool_str) + ) + assert ( + type(self.content_dict_str_bytes) == dict + ), "Invalid type for content 'content_dict_str_bytes'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_bytes) + ) + for ( + key_of_content_dict_str_bytes, + value_of_content_dict_str_bytes, + ) in self.content_dict_str_bytes.items(): + assert ( + type(key_of_content_dict_str_bytes) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_bytes'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_bytes) + ) + assert ( + type(value_of_content_dict_str_bytes) == bytes + ), "Invalid type for dictionary values in content 'content_dict_str_bytes'. Expected 'bytes'. Found '{}'.".format( + type(value_of_content_dict_str_bytes) + ) + assert ( + type(self.content_dict_str_int) == dict + ), "Invalid type for content 'content_dict_str_int'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_int) + ) + for ( + key_of_content_dict_str_int, + value_of_content_dict_str_int, + ) in self.content_dict_str_int.items(): + assert ( + type(key_of_content_dict_str_int) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_int'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_int) + ) + assert ( + type(value_of_content_dict_str_int) == int + ), "Invalid type for dictionary values in content 'content_dict_str_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_dict_str_int) + ) + assert ( + type(self.content_dict_str_float) == dict + ), "Invalid type for content 'content_dict_str_float'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_float) + ) + for ( + key_of_content_dict_str_float, + value_of_content_dict_str_float, + ) in self.content_dict_str_float.items(): + assert ( + type(key_of_content_dict_str_float) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_float'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_float) + ) + assert ( + type(value_of_content_dict_str_float) == float + ), "Invalid type for dictionary values in content 'content_dict_str_float'. Expected 'float'. Found '{}'.".format( + type(value_of_content_dict_str_float) + ) + assert ( + type(self.content_dict_str_bool) == dict + ), "Invalid type for content 'content_dict_str_bool'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_bool) + ) + for ( + key_of_content_dict_str_bool, + value_of_content_dict_str_bool, + ) in self.content_dict_str_bool.items(): + assert ( + type(key_of_content_dict_str_bool) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_bool'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_bool) + ) + assert ( + type(value_of_content_dict_str_bool) == bool + ), "Invalid type for dictionary values in content 'content_dict_str_bool'. Expected 'bool'. Found '{}'.".format( + type(value_of_content_dict_str_bool) + ) + assert ( + type(self.content_dict_str_str) == dict + ), "Invalid type for content 'content_dict_str_str'. Expected 'dict'. Found '{}'.".format( + type(self.content_dict_str_str) + ) + for ( + key_of_content_dict_str_str, + value_of_content_dict_str_str, + ) in self.content_dict_str_str.items(): + assert ( + type(key_of_content_dict_str_str) == str + ), "Invalid type for dictionary keys in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( + type(key_of_content_dict_str_str) + ) + assert ( + type(value_of_content_dict_str_str) == str + ), "Invalid type for dictionary values in content 'content_dict_str_str'. Expected 'str'. Found '{}'.".format( + type(value_of_content_dict_str_str) + ) + elif self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: + expected_nb_of_contents = 2 + assert ( + type(self.content_union_1) == bool + or type(self.content_union_1) == bytes + or type(self.content_union_1) == dict + or type(self.content_union_1) == float + or type(self.content_union_1) == frozenset + or type(self.content_union_1) == int + or type(self.content_union_1) == str + or type(self.content_union_1) == tuple + ), "Invalid type for content 'content_union_1'. Expected either of '['bool', 'bytes', 'dict', 'float', 'frozenset', 'int', 'str', 'tuple']'. Found '{}'.".format( + type(self.content_union_1) + ) + if type(self.content_union_1) == frozenset: + assert all( + type(element) == int for element in self.content_union_1 + ), "Invalid type for elements of content 'content_union_1'. Expected 'int'." + if type(self.content_union_1) == tuple: + assert all( + type(element) == bool for element in self.content_union_1 + ), "Invalid type for tuple elements in content 'content_union_1'. Expected 'bool'." + if type(self.content_union_1) == dict: + for ( + key_of_content_union_1, + value_of_content_union_1, + ) in self.content_union_1.items(): + assert ( + type(key_of_content_union_1) == str + and type(value_of_content_union_1) == int + ), "Invalid type for dictionary key, value in content 'content_union_1'. Expected 'str', 'int'." + assert ( + type(self.content_union_2) == dict + or type(self.content_union_2) == frozenset + or type(self.content_union_2) == tuple + ), "Invalid type for content 'content_union_2'. Expected either of '['dict', 'frozenset', 'tuple']'. Found '{}'.".format( + type(self.content_union_2) + ) + if type(self.content_union_2) == frozenset: + assert ( + all(type(element) == bytes for element in self.content_union_2) + or all(type(element) == int for element in self.content_union_2) + or all(type(element) == str for element in self.content_union_2) + ), "Invalid type for frozenset elements in content 'content_union_2'. Expected either 'bytes' or 'int' or 'str'." + if type(self.content_union_2) == tuple: + assert ( + all(type(element) == bool for element in self.content_union_2) + or all( + type(element) == bytes for element in self.content_union_2 + ) + or all( + type(element) == float for element in self.content_union_2 + ) + ), "Invalid type for tuple elements in content 'content_union_2'. Expected either 'bool' or 'bytes' or 'float'." + if type(self.content_union_2) == dict: + for ( + key_of_content_union_2, + value_of_content_union_2, + ) in self.content_union_2.items(): + assert ( + ( + type(key_of_content_union_2) == bool + and type(value_of_content_union_2) == bytes + ) + or ( + type(key_of_content_union_2) == int + and type(value_of_content_union_2) == float + ) + or ( + type(key_of_content_union_2) == str + and type(value_of_content_union_2) == int + ) + ), "Invalid type for dictionary key, value in content 'content_union_2'. Expected 'bool','bytes' or 'int','float' or 'str','int'." + elif self.performative == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: + expected_nb_of_contents = 0 + if self.is_set("content_o_bool"): + expected_nb_of_contents += 1 + content_o_bool = cast(bool, self.content_o_bool) + assert ( + type(content_o_bool) == bool + ), "Invalid type for content 'content_o_bool'. Expected 'bool'. Found '{}'.".format( + type(content_o_bool) + ) + if self.is_set("content_o_set_int"): + expected_nb_of_contents += 1 + content_o_set_int = cast(FrozenSet[int], self.content_o_set_int) + assert ( + type(content_o_set_int) == frozenset + ), "Invalid type for content 'content_o_set_int'. Expected 'frozenset'. Found '{}'.".format( + type(content_o_set_int) + ) + assert all( + type(element) == int for element in content_o_set_int + ), "Invalid type for frozenset elements in content 'content_o_set_int'. Expected 'int'." + if self.is_set("content_o_list_bytes"): + expected_nb_of_contents += 1 + content_o_list_bytes = cast( + Tuple[bytes, ...], self.content_o_list_bytes + ) + assert ( + type(content_o_list_bytes) == tuple + ), "Invalid type for content 'content_o_list_bytes'. Expected 'tuple'. Found '{}'.".format( + type(content_o_list_bytes) + ) + assert all( + type(element) == bytes for element in content_o_list_bytes + ), "Invalid type for tuple elements in content 'content_o_list_bytes'. Expected 'bytes'." + if self.is_set("content_o_dict_str_int"): + expected_nb_of_contents += 1 + content_o_dict_str_int = cast( + Dict[str, int], self.content_o_dict_str_int + ) + assert ( + type(content_o_dict_str_int) == dict + ), "Invalid type for content 'content_o_dict_str_int'. Expected 'dict'. Found '{}'.".format( + type(content_o_dict_str_int) + ) + for ( + key_of_content_o_dict_str_int, + value_of_content_o_dict_str_int, + ) in content_o_dict_str_int.items(): + assert ( + type(key_of_content_o_dict_str_int) == str + ), "Invalid type for dictionary keys in content 'content_o_dict_str_int'. Expected 'str'. Found '{}'.".format( + type(key_of_content_o_dict_str_int) + ) + assert ( + type(value_of_content_o_dict_str_int) == int + ), "Invalid type for dictionary values in content 'content_o_dict_str_int'. Expected 'int'. Found '{}'.".format( + type(value_of_content_o_dict_str_int) + ) + elif ( + self.performative + == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS + ): + expected_nb_of_contents = 0 + + # Check correct content count + assert ( + expected_nb_of_contents == actual_nb_of_contents + ), "Incorrect number of contents. Expected {}. Found {}".format( + expected_nb_of_contents, actual_nb_of_contents + ) + + # Light Protocol Rule 3 + if self.message_id == 1: + assert ( + self.target == 0 + ), "Invalid 'target'. Expected 0 (because 'message_id' is 1). Found {}.".format( + self.target + ) + else: + assert ( + 0 < self.target < self.message_id + ), "Invalid 'target'. Expected an integer between 1 and {} inclusive. Found {}.".format( + self.message_id - 1, self.target, + ) + except (AssertionError, ValueError, KeyError) as e: + logger.error(str(e)) + return False + + return True diff --git a/tests/data/generator/t_protocol_no_ct/protocol.yaml b/tests/data/generator/t_protocol_no_ct/protocol.yaml new file mode 100644 index 0000000000..a6d9693f14 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/protocol.yaml @@ -0,0 +1,16 @@ +name: t_protocol_no_ct +author: fetchai +version: 0.1.0 +description: A protocol for testing purposes. +license: Apache-2.0 +aea_version: '>=0.5.0, <0.6.0' +fingerprint: + __init__.py: QmRGHGRoZHGCXQ29v3q93Nt6J5TuhggYvUvZoQfrM6c3yp + dialogues.py: QmR8zDLTaKpxeuJU2wRXo4vnVQVHGcY2FX9xjy62c5n9jX + message.py: QmcMYvB4MwQgZ8Z33aGAUUBMqx3Eq7uXBZwhdeKWMyvZWg + serialization.py: Qmc3tJ5vk1AbtkF5BrPUeuyrnvVUTrfuUMF9MgDfkiiMkB + t_protocol_no_ct.proto: QmeZWVLhb6EUGr5AgVwgf2YTEZTSuCskpmxCwAE3sDU9sY + t_protocol_no_ct_pb2.py: QmYtjyYTv1fQrwTS2x5ZkrNB8bpgH2vpPUJsUV29B7E4d9 +fingerprint_ignore_patterns: [] +dependencies: + protobuf: {} diff --git a/tests/data/generator/t_protocol_no_ct/serialization.py b/tests/data/generator/t_protocol_no_ct/serialization.py new file mode 100644 index 0000000000..bf7b3face3 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/serialization.py @@ -0,0 +1,542 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2020 fetchai +# +# 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 t_protocol_no_ct protocol.""" + +from typing import Any, Dict, cast + +from aea.protocols.base import Message +from aea.protocols.base import Serializer + +from tests.data.generator.t_protocol_no_ct import t_protocol_no_ct_pb2 +from tests.data.generator.t_protocol_no_ct.message import TProtocolNoCtMessage + + +class TProtocolNoCtSerializer(Serializer): + """Serialization for the 't_protocol_no_ct' protocol.""" + + @staticmethod + def encode(msg: Message) -> bytes: + """ + Encode a 'TProtocolNoCt' message into bytes. + + :param msg: the message object. + :return: the bytes. + """ + msg = cast(TProtocolNoCtMessage, msg) + t_protocol_no_ct_msg = t_protocol_no_ct_pb2.TProtocolNoCtMessage() + t_protocol_no_ct_msg.message_id = msg.message_id + dialogue_reference = msg.dialogue_reference + t_protocol_no_ct_msg.dialogue_starter_reference = dialogue_reference[0] + t_protocol_no_ct_msg.dialogue_responder_reference = dialogue_reference[1] + t_protocol_no_ct_msg.target = msg.target + + performative_id = msg.performative + if performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pt_Performative() # type: ignore + content_bytes = msg.content_bytes + performative.content_bytes = content_bytes + content_int = msg.content_int + performative.content_int = content_int + content_float = msg.content_float + performative.content_float = content_float + content_bool = msg.content_bool + performative.content_bool = content_bool + content_str = msg.content_str + performative.content_str = content_str + t_protocol_no_ct_msg.performative_pt.CopyFrom(performative) + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pct_Performative() # type: ignore + content_set_bytes = msg.content_set_bytes + performative.content_set_bytes.extend(content_set_bytes) + content_set_int = msg.content_set_int + performative.content_set_int.extend(content_set_int) + content_set_float = msg.content_set_float + performative.content_set_float.extend(content_set_float) + content_set_bool = msg.content_set_bool + performative.content_set_bool.extend(content_set_bool) + content_set_str = msg.content_set_str + performative.content_set_str.extend(content_set_str) + content_list_bytes = msg.content_list_bytes + performative.content_list_bytes.extend(content_list_bytes) + content_list_int = msg.content_list_int + performative.content_list_int.extend(content_list_int) + content_list_float = msg.content_list_float + performative.content_list_float.extend(content_list_float) + content_list_bool = msg.content_list_bool + performative.content_list_bool.extend(content_list_bool) + content_list_str = msg.content_list_str + performative.content_list_str.extend(content_list_str) + t_protocol_no_ct_msg.performative_pct.CopyFrom(performative) + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Pmt_Performative() # type: ignore + content_dict_int_bytes = msg.content_dict_int_bytes + performative.content_dict_int_bytes.update(content_dict_int_bytes) + content_dict_int_int = msg.content_dict_int_int + performative.content_dict_int_int.update(content_dict_int_int) + content_dict_int_float = msg.content_dict_int_float + performative.content_dict_int_float.update(content_dict_int_float) + content_dict_int_bool = msg.content_dict_int_bool + performative.content_dict_int_bool.update(content_dict_int_bool) + content_dict_int_str = msg.content_dict_int_str + performative.content_dict_int_str.update(content_dict_int_str) + content_dict_bool_bytes = msg.content_dict_bool_bytes + performative.content_dict_bool_bytes.update(content_dict_bool_bytes) + content_dict_bool_int = msg.content_dict_bool_int + performative.content_dict_bool_int.update(content_dict_bool_int) + content_dict_bool_float = msg.content_dict_bool_float + performative.content_dict_bool_float.update(content_dict_bool_float) + content_dict_bool_bool = msg.content_dict_bool_bool + performative.content_dict_bool_bool.update(content_dict_bool_bool) + content_dict_bool_str = msg.content_dict_bool_str + performative.content_dict_bool_str.update(content_dict_bool_str) + content_dict_str_bytes = msg.content_dict_str_bytes + performative.content_dict_str_bytes.update(content_dict_str_bytes) + content_dict_str_int = msg.content_dict_str_int + performative.content_dict_str_int.update(content_dict_str_int) + content_dict_str_float = msg.content_dict_str_float + performative.content_dict_str_float.update(content_dict_str_float) + content_dict_str_bool = msg.content_dict_str_bool + performative.content_dict_str_bool.update(content_dict_str_bool) + content_dict_str_str = msg.content_dict_str_str + performative.content_dict_str_str.update(content_dict_str_str) + t_protocol_no_ct_msg.performative_pmt.CopyFrom(performative) + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Mt_Performative() # type: ignore + if msg.is_set("content_union_1_type_bytes"): + performative.content_union_1_type_bytes_is_set = True + content_union_1_type_bytes = msg.content_union_1_type_bytes + performative.content_union_1_type_bytes = content_union_1_type_bytes + if msg.is_set("content_union_1_type_int"): + performative.content_union_1_type_int_is_set = True + content_union_1_type_int = msg.content_union_1_type_int + performative.content_union_1_type_int = content_union_1_type_int + if msg.is_set("content_union_1_type_float"): + performative.content_union_1_type_float_is_set = True + content_union_1_type_float = msg.content_union_1_type_float + performative.content_union_1_type_float = content_union_1_type_float + if msg.is_set("content_union_1_type_bool"): + performative.content_union_1_type_bool_is_set = True + content_union_1_type_bool = msg.content_union_1_type_bool + performative.content_union_1_type_bool = content_union_1_type_bool + if msg.is_set("content_union_1_type_str"): + performative.content_union_1_type_str_is_set = True + content_union_1_type_str = msg.content_union_1_type_str + performative.content_union_1_type_str = content_union_1_type_str + if msg.is_set("content_union_1_type_set_of_int"): + performative.content_union_1_type_set_of_int_is_set = True + content_union_1_type_set_of_int = msg.content_union_1_type_set_of_int + performative.content_union_1_type_set_of_int.extend( + content_union_1_type_set_of_int + ) + if msg.is_set("content_union_1_type_list_of_bool"): + performative.content_union_1_type_list_of_bool_is_set = True + content_union_1_type_list_of_bool = ( + msg.content_union_1_type_list_of_bool + ) + performative.content_union_1_type_list_of_bool.extend( + content_union_1_type_list_of_bool + ) + if msg.is_set("content_union_1_type_dict_of_str_int"): + performative.content_union_1_type_dict_of_str_int_is_set = True + content_union_1_type_dict_of_str_int = ( + msg.content_union_1_type_dict_of_str_int + ) + performative.content_union_1_type_dict_of_str_int.update( + content_union_1_type_dict_of_str_int + ) + if msg.is_set("content_union_2_type_set_of_bytes"): + performative.content_union_2_type_set_of_bytes_is_set = True + content_union_2_type_set_of_bytes = ( + msg.content_union_2_type_set_of_bytes + ) + performative.content_union_2_type_set_of_bytes.extend( + content_union_2_type_set_of_bytes + ) + if msg.is_set("content_union_2_type_set_of_int"): + performative.content_union_2_type_set_of_int_is_set = True + content_union_2_type_set_of_int = msg.content_union_2_type_set_of_int + performative.content_union_2_type_set_of_int.extend( + content_union_2_type_set_of_int + ) + if msg.is_set("content_union_2_type_set_of_str"): + performative.content_union_2_type_set_of_str_is_set = True + content_union_2_type_set_of_str = msg.content_union_2_type_set_of_str + performative.content_union_2_type_set_of_str.extend( + content_union_2_type_set_of_str + ) + if msg.is_set("content_union_2_type_list_of_float"): + performative.content_union_2_type_list_of_float_is_set = True + content_union_2_type_list_of_float = ( + msg.content_union_2_type_list_of_float + ) + performative.content_union_2_type_list_of_float.extend( + content_union_2_type_list_of_float + ) + if msg.is_set("content_union_2_type_list_of_bool"): + performative.content_union_2_type_list_of_bool_is_set = True + content_union_2_type_list_of_bool = ( + msg.content_union_2_type_list_of_bool + ) + performative.content_union_2_type_list_of_bool.extend( + content_union_2_type_list_of_bool + ) + if msg.is_set("content_union_2_type_list_of_bytes"): + performative.content_union_2_type_list_of_bytes_is_set = True + content_union_2_type_list_of_bytes = ( + msg.content_union_2_type_list_of_bytes + ) + performative.content_union_2_type_list_of_bytes.extend( + content_union_2_type_list_of_bytes + ) + if msg.is_set("content_union_2_type_dict_of_str_int"): + performative.content_union_2_type_dict_of_str_int_is_set = True + content_union_2_type_dict_of_str_int = ( + msg.content_union_2_type_dict_of_str_int + ) + performative.content_union_2_type_dict_of_str_int.update( + content_union_2_type_dict_of_str_int + ) + if msg.is_set("content_union_2_type_dict_of_int_float"): + performative.content_union_2_type_dict_of_int_float_is_set = True + content_union_2_type_dict_of_int_float = ( + msg.content_union_2_type_dict_of_int_float + ) + performative.content_union_2_type_dict_of_int_float.update( + content_union_2_type_dict_of_int_float + ) + if msg.is_set("content_union_2_type_dict_of_bool_bytes"): + performative.content_union_2_type_dict_of_bool_bytes_is_set = True + content_union_2_type_dict_of_bool_bytes = ( + msg.content_union_2_type_dict_of_bool_bytes + ) + performative.content_union_2_type_dict_of_bool_bytes.update( + content_union_2_type_dict_of_bool_bytes + ) + t_protocol_no_ct_msg.performative_mt.CopyFrom(performative) + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_O_Performative() # type: ignore + if msg.is_set("content_o_bool"): + performative.content_o_bool_is_set = True + content_o_bool = msg.content_o_bool + performative.content_o_bool = content_o_bool + if msg.is_set("content_o_set_int"): + performative.content_o_set_int_is_set = True + content_o_set_int = msg.content_o_set_int + performative.content_o_set_int.extend(content_o_set_int) + if msg.is_set("content_o_list_bytes"): + performative.content_o_list_bytes_is_set = True + content_o_list_bytes = msg.content_o_list_bytes + performative.content_o_list_bytes.extend(content_o_list_bytes) + if msg.is_set("content_o_dict_str_int"): + performative.content_o_dict_str_int_is_set = True + content_o_dict_str_int = msg.content_o_dict_str_int + performative.content_o_dict_str_int.update(content_o_dict_str_int) + t_protocol_no_ct_msg.performative_o.CopyFrom(performative) + elif ( + performative_id + == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS + ): + performative = t_protocol_no_ct_pb2.TProtocolNoCtMessage.Performative_Empty_Contents_Performative() # type: ignore + t_protocol_no_ct_msg.performative_empty_contents.CopyFrom(performative) + else: + raise ValueError("Performative not valid: {}".format(performative_id)) + + t_protocol_no_ct_bytes = t_protocol_no_ct_msg.SerializeToString() + return t_protocol_no_ct_bytes + + @staticmethod + def decode(obj: bytes) -> Message: + """ + Decode bytes into a 'TProtocolNoCt' message. + + :param obj: the bytes object. + :return: the 'TProtocolNoCt' message. + """ + t_protocol_no_ct_pb = t_protocol_no_ct_pb2.TProtocolNoCtMessage() + t_protocol_no_ct_pb.ParseFromString(obj) + message_id = t_protocol_no_ct_pb.message_id + dialogue_reference = ( + t_protocol_no_ct_pb.dialogue_starter_reference, + t_protocol_no_ct_pb.dialogue_responder_reference, + ) + target = t_protocol_no_ct_pb.target + + performative = t_protocol_no_ct_pb.WhichOneof("performative") + performative_id = TProtocolNoCtMessage.Performative(str(performative)) + performative_content = dict() # type: Dict[str, Any] + if performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PT: + content_bytes = t_protocol_no_ct_pb.performative_pt.content_bytes + performative_content["content_bytes"] = content_bytes + content_int = t_protocol_no_ct_pb.performative_pt.content_int + performative_content["content_int"] = content_int + content_float = t_protocol_no_ct_pb.performative_pt.content_float + performative_content["content_float"] = content_float + content_bool = t_protocol_no_ct_pb.performative_pt.content_bool + performative_content["content_bool"] = content_bool + content_str = t_protocol_no_ct_pb.performative_pt.content_str + performative_content["content_str"] = content_str + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PCT: + content_set_bytes = t_protocol_no_ct_pb.performative_pct.content_set_bytes + content_set_bytes_frozenset = frozenset(content_set_bytes) + performative_content["content_set_bytes"] = content_set_bytes_frozenset + content_set_int = t_protocol_no_ct_pb.performative_pct.content_set_int + content_set_int_frozenset = frozenset(content_set_int) + performative_content["content_set_int"] = content_set_int_frozenset + content_set_float = t_protocol_no_ct_pb.performative_pct.content_set_float + content_set_float_frozenset = frozenset(content_set_float) + performative_content["content_set_float"] = content_set_float_frozenset + content_set_bool = t_protocol_no_ct_pb.performative_pct.content_set_bool + content_set_bool_frozenset = frozenset(content_set_bool) + performative_content["content_set_bool"] = content_set_bool_frozenset + content_set_str = t_protocol_no_ct_pb.performative_pct.content_set_str + content_set_str_frozenset = frozenset(content_set_str) + performative_content["content_set_str"] = content_set_str_frozenset + content_list_bytes = t_protocol_no_ct_pb.performative_pct.content_list_bytes + content_list_bytes_tuple = tuple(content_list_bytes) + performative_content["content_list_bytes"] = content_list_bytes_tuple + content_list_int = t_protocol_no_ct_pb.performative_pct.content_list_int + content_list_int_tuple = tuple(content_list_int) + performative_content["content_list_int"] = content_list_int_tuple + content_list_float = t_protocol_no_ct_pb.performative_pct.content_list_float + content_list_float_tuple = tuple(content_list_float) + performative_content["content_list_float"] = content_list_float_tuple + content_list_bool = t_protocol_no_ct_pb.performative_pct.content_list_bool + content_list_bool_tuple = tuple(content_list_bool) + performative_content["content_list_bool"] = content_list_bool_tuple + content_list_str = t_protocol_no_ct_pb.performative_pct.content_list_str + content_list_str_tuple = tuple(content_list_str) + performative_content["content_list_str"] = content_list_str_tuple + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_PMT: + content_dict_int_bytes = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_int_bytes + ) + content_dict_int_bytes_dict = dict(content_dict_int_bytes) + performative_content["content_dict_int_bytes"] = content_dict_int_bytes_dict + content_dict_int_int = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_int_int + ) + content_dict_int_int_dict = dict(content_dict_int_int) + performative_content["content_dict_int_int"] = content_dict_int_int_dict + content_dict_int_float = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_int_float + ) + content_dict_int_float_dict = dict(content_dict_int_float) + performative_content["content_dict_int_float"] = content_dict_int_float_dict + content_dict_int_bool = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_int_bool + ) + content_dict_int_bool_dict = dict(content_dict_int_bool) + performative_content["content_dict_int_bool"] = content_dict_int_bool_dict + content_dict_int_str = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_int_str + ) + content_dict_int_str_dict = dict(content_dict_int_str) + performative_content["content_dict_int_str"] = content_dict_int_str_dict + content_dict_bool_bytes = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_bool_bytes + ) + content_dict_bool_bytes_dict = dict(content_dict_bool_bytes) + performative_content[ + "content_dict_bool_bytes" + ] = content_dict_bool_bytes_dict + content_dict_bool_int = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_bool_int + ) + content_dict_bool_int_dict = dict(content_dict_bool_int) + performative_content["content_dict_bool_int"] = content_dict_bool_int_dict + content_dict_bool_float = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_bool_float + ) + content_dict_bool_float_dict = dict(content_dict_bool_float) + performative_content[ + "content_dict_bool_float" + ] = content_dict_bool_float_dict + content_dict_bool_bool = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_bool_bool + ) + content_dict_bool_bool_dict = dict(content_dict_bool_bool) + performative_content["content_dict_bool_bool"] = content_dict_bool_bool_dict + content_dict_bool_str = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_bool_str + ) + content_dict_bool_str_dict = dict(content_dict_bool_str) + performative_content["content_dict_bool_str"] = content_dict_bool_str_dict + content_dict_str_bytes = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_str_bytes + ) + content_dict_str_bytes_dict = dict(content_dict_str_bytes) + performative_content["content_dict_str_bytes"] = content_dict_str_bytes_dict + content_dict_str_int = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_str_int + ) + content_dict_str_int_dict = dict(content_dict_str_int) + performative_content["content_dict_str_int"] = content_dict_str_int_dict + content_dict_str_float = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_str_float + ) + content_dict_str_float_dict = dict(content_dict_str_float) + performative_content["content_dict_str_float"] = content_dict_str_float_dict + content_dict_str_bool = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_str_bool + ) + content_dict_str_bool_dict = dict(content_dict_str_bool) + performative_content["content_dict_str_bool"] = content_dict_str_bool_dict + content_dict_str_str = ( + t_protocol_no_ct_pb.performative_pmt.content_dict_str_str + ) + content_dict_str_str_dict = dict(content_dict_str_str) + performative_content["content_dict_str_str"] = content_dict_str_str_dict + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_MT: + if t_protocol_no_ct_pb.performative_mt.content_union_1_type_bytes_is_set: + content_union_1 = ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_bytes + ) + performative_content["content_union_1"] = content_union_1 + if t_protocol_no_ct_pb.performative_mt.content_union_1_type_int_is_set: + content_union_1 = ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_int + ) + performative_content["content_union_1"] = content_union_1 + if t_protocol_no_ct_pb.performative_mt.content_union_1_type_float_is_set: + content_union_1 = ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_float + ) + performative_content["content_union_1"] = content_union_1 + if t_protocol_no_ct_pb.performative_mt.content_union_1_type_bool_is_set: + content_union_1 = ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_bool + ) + performative_content["content_union_1"] = content_union_1 + if t_protocol_no_ct_pb.performative_mt.content_union_1_type_str_is_set: + content_union_1 = ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_str + ) + performative_content["content_union_1"] = content_union_1 + if ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_set_of_int_is_set + ): + content_union_1 = t_protocol_no_ct_pb.performative_mt.content_union_1 + content_union_1_frozenset = frozenset(content_union_1) + performative_content["content_union_1"] = content_union_1_frozenset + if ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_list_of_bool_is_set + ): + content_union_1 = t_protocol_no_ct_pb.performative_mt.content_union_1 + content_union_1_tuple = tuple(content_union_1) + performative_content["content_union_1"] = content_union_1_tuple + if ( + t_protocol_no_ct_pb.performative_mt.content_union_1_type_dict_of_str_int_is_set + ): + content_union_1 = t_protocol_no_ct_pb.performative_mt.content_union_1 + content_union_1_dict = dict(content_union_1) + performative_content["content_union_1"] = content_union_1_dict + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_bytes_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_frozenset = frozenset(content_union_2) + performative_content["content_union_2"] = content_union_2_frozenset + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_int_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_frozenset = frozenset(content_union_2) + performative_content["content_union_2"] = content_union_2_frozenset + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_set_of_str_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_frozenset = frozenset(content_union_2) + performative_content["content_union_2"] = content_union_2_frozenset + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_float_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_tuple = tuple(content_union_2) + performative_content["content_union_2"] = content_union_2_tuple + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bool_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_tuple = tuple(content_union_2) + performative_content["content_union_2"] = content_union_2_tuple + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_list_of_bytes_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_tuple = tuple(content_union_2) + performative_content["content_union_2"] = content_union_2_tuple + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_str_int_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_dict = dict(content_union_2) + performative_content["content_union_2"] = content_union_2_dict + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_int_float_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_dict = dict(content_union_2) + performative_content["content_union_2"] = content_union_2_dict + if ( + t_protocol_no_ct_pb.performative_mt.content_union_2_type_dict_of_bool_bytes_is_set + ): + content_union_2 = t_protocol_no_ct_pb.performative_mt.content_union_2 + content_union_2_dict = dict(content_union_2) + performative_content["content_union_2"] = content_union_2_dict + elif performative_id == TProtocolNoCtMessage.Performative.PERFORMATIVE_O: + if t_protocol_no_ct_pb.performative_o.content_o_bool_is_set: + content_o_bool = t_protocol_no_ct_pb.performative_o.content_o_bool + performative_content["content_o_bool"] = content_o_bool + if t_protocol_no_ct_pb.performative_o.content_o_set_int_is_set: + content_o_set_int = t_protocol_no_ct_pb.performative_o.content_o_set_int + content_o_set_int_frozenset = frozenset(content_o_set_int) + performative_content["content_o_set_int"] = content_o_set_int_frozenset + if t_protocol_no_ct_pb.performative_o.content_o_list_bytes_is_set: + content_o_list_bytes = ( + t_protocol_no_ct_pb.performative_o.content_o_list_bytes + ) + content_o_list_bytes_tuple = tuple(content_o_list_bytes) + performative_content[ + "content_o_list_bytes" + ] = content_o_list_bytes_tuple + if t_protocol_no_ct_pb.performative_o.content_o_dict_str_int_is_set: + content_o_dict_str_int = ( + t_protocol_no_ct_pb.performative_o.content_o_dict_str_int + ) + content_o_dict_str_int_dict = dict(content_o_dict_str_int) + performative_content[ + "content_o_dict_str_int" + ] = content_o_dict_str_int_dict + elif ( + performative_id + == TProtocolNoCtMessage.Performative.PERFORMATIVE_EMPTY_CONTENTS + ): + pass + else: + raise ValueError("Performative not valid: {}.".format(performative_id)) + + return TProtocolNoCtMessage( + message_id=message_id, + dialogue_reference=dialogue_reference, + target=target, + performative=performative, + **performative_content + ) diff --git a/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct.proto b/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct.proto new file mode 100644 index 0000000000..c9a4b9a145 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +package fetch.aea.TProtocolNoCt; + +message TProtocolNoCtMessage{ + + // Performatives and contents + message Performative_Pt_Performative{ + bytes content_bytes = 1; + int32 content_int = 2; + float content_float = 3; + bool content_bool = 4; + string content_str = 5; + } + + message Performative_Pct_Performative{ + repeated bytes content_set_bytes = 1; + repeated int32 content_set_int = 2; + repeated float content_set_float = 3; + repeated bool content_set_bool = 4; + repeated string content_set_str = 5; + repeated bytes content_list_bytes = 6; + repeated int32 content_list_int = 7; + repeated float content_list_float = 8; + repeated bool content_list_bool = 9; + repeated string content_list_str = 10; + } + + message Performative_Pmt_Performative{ + map content_dict_int_bytes = 1; + map content_dict_int_int = 2; + map content_dict_int_float = 3; + map content_dict_int_bool = 4; + map content_dict_int_str = 5; + map content_dict_bool_bytes = 6; + map content_dict_bool_int = 7; + map content_dict_bool_float = 8; + map content_dict_bool_bool = 9; + map content_dict_bool_str = 10; + map content_dict_str_bytes = 11; + map content_dict_str_int = 12; + map content_dict_str_float = 13; + map content_dict_str_bool = 14; + map content_dict_str_str = 15; + } + + message Performative_Mt_Performative{ + bytes content_union_1_type_bytes = 1; + int32 content_union_1_type_int = 2; + float content_union_1_type_float = 3; + bool content_union_1_type_bool = 4; + string content_union_1_type_str = 5; + repeated int32 content_union_1_type_set_of_int = 6; + repeated bool content_union_1_type_list_of_bool = 7; + map content_union_1_type_dict_of_str_int = 8; + repeated bytes content_union_2_type_set_of_bytes = 9; + repeated int32 content_union_2_type_set_of_int = 10; + repeated string content_union_2_type_set_of_str = 11; + repeated float content_union_2_type_list_of_float = 12; + repeated bool content_union_2_type_list_of_bool = 13; + repeated bytes content_union_2_type_list_of_bytes = 14; + map content_union_2_type_dict_of_str_int = 15; + map content_union_2_type_dict_of_int_float = 16; + map content_union_2_type_dict_of_bool_bytes = 17; + } + + message Performative_O_Performative{ + bool content_o_bool = 1; + bool content_o_bool_is_set = 2; + repeated int32 content_o_set_int = 3; + bool content_o_set_int_is_set = 4; + repeated bytes content_o_list_bytes = 5; + bool content_o_list_bytes_is_set = 6; + map content_o_dict_str_int = 7; + bool content_o_dict_str_int_is_set = 8; + } + + message Performative_Empty_Contents_Performative{} + + + // Standard TProtocolNoCtMessage fields + int32 message_id = 1; + string dialogue_starter_reference = 2; + string dialogue_responder_reference = 3; + int32 target = 4; + oneof performative{ + Performative_Empty_Contents_Performative performative_empty_contents = 5; + Performative_Mt_Performative performative_mt = 6; + Performative_O_Performative performative_o = 7; + Performative_Pct_Performative performative_pct = 8; + Performative_Pmt_Performative performative_pmt = 9; + Performative_Pt_Performative performative_pt = 10; + } +} diff --git a/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct_pb2.py b/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct_pb2.py new file mode 100644 index 0000000000..c018654cf4 --- /dev/null +++ b/tests/data/generator/t_protocol_no_ct/t_protocol_no_ct_pb2.py @@ -0,0 +1,3090 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: t_protocol_no_ct.proto + +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 + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor.FileDescriptor( + name="t_protocol_no_ct.proto", + package="fetch.aea.TProtocolNoCt", + syntax="proto3", + serialized_options=None, + serialized_pb=b'\n\x16t_protocol_no_ct.proto\x12\x17\x66\x65tch.aea.TProtocolNoCt"\xef/\n\x14TProtocolNoCtMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12}\n\x1bperformative_empty_contents\x18\x05 \x01(\x0b\x32V.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Empty_Contents_PerformativeH\x00\x12\x65\n\x0fperformative_mt\x18\x06 \x01(\x0b\x32J.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_PerformativeH\x00\x12\x63\n\x0eperformative_o\x18\x07 \x01(\x0b\x32I.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_PerformativeH\x00\x12g\n\x10performative_pct\x18\x08 \x01(\x0b\x32K.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_PerformativeH\x00\x12g\n\x10performative_pmt\x18\t \x01(\x0b\x32K.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_PerformativeH\x00\x12\x65\n\x0fperformative_pt\x18\n \x01(\x0b\x32J.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_PerformativeH\x00\x1a\x8c\x01\n\x1cPerformative_Pt_Performative\x12\x15\n\rcontent_bytes\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63ontent_int\x18\x02 \x01(\x05\x12\x15\n\rcontent_float\x18\x03 \x01(\x02\x12\x14\n\x0c\x63ontent_bool\x18\x04 \x01(\x08\x12\x13\n\x0b\x63ontent_str\x18\x05 \x01(\t\x1a\xa8\x02\n\x1dPerformative_Pct_Performative\x12\x19\n\x11\x63ontent_set_bytes\x18\x01 \x03(\x0c\x12\x17\n\x0f\x63ontent_set_int\x18\x02 \x03(\x05\x12\x19\n\x11\x63ontent_set_float\x18\x03 \x03(\x02\x12\x18\n\x10\x63ontent_set_bool\x18\x04 \x03(\x08\x12\x17\n\x0f\x63ontent_set_str\x18\x05 \x03(\t\x12\x1a\n\x12\x63ontent_list_bytes\x18\x06 \x03(\x0c\x12\x18\n\x10\x63ontent_list_int\x18\x07 \x03(\x05\x12\x1a\n\x12\x63ontent_list_float\x18\x08 \x03(\x02\x12\x19\n\x11\x63ontent_list_bool\x18\t \x03(\x08\x12\x18\n\x10\x63ontent_list_str\x18\n \x03(\t\x1a\xee\x16\n\x1dPerformative_Pmt_Performative\x12\x84\x01\n\x16\x63ontent_dict_int_bytes\x18\x01 \x03(\x0b\x32\x64.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry\x12\x80\x01\n\x14\x63ontent_dict_int_int\x18\x02 \x03(\x0b\x32\x62.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry\x12\x84\x01\n\x16\x63ontent_dict_int_float\x18\x03 \x03(\x0b\x32\x64.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry\x12\x82\x01\n\x15\x63ontent_dict_int_bool\x18\x04 \x03(\x0b\x32\x63.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry\x12\x80\x01\n\x14\x63ontent_dict_int_str\x18\x05 \x03(\x0b\x32\x62.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry\x12\x86\x01\n\x17\x63ontent_dict_bool_bytes\x18\x06 \x03(\x0b\x32\x65.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry\x12\x82\x01\n\x15\x63ontent_dict_bool_int\x18\x07 \x03(\x0b\x32\x63.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry\x12\x86\x01\n\x17\x63ontent_dict_bool_float\x18\x08 \x03(\x0b\x32\x65.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry\x12\x84\x01\n\x16\x63ontent_dict_bool_bool\x18\t \x03(\x0b\x32\x64.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry\x12\x82\x01\n\x15\x63ontent_dict_bool_str\x18\n \x03(\x0b\x32\x63.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry\x12\x84\x01\n\x16\x63ontent_dict_str_bytes\x18\x0b \x03(\x0b\x32\x64.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry\x12\x80\x01\n\x14\x63ontent_dict_str_int\x18\x0c \x03(\x0b\x32\x62.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry\x12\x84\x01\n\x16\x63ontent_dict_str_float\x18\r \x03(\x0b\x32\x64.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry\x12\x82\x01\n\x15\x63ontent_dict_str_bool\x18\x0e \x03(\x0b\x32\x63.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry\x12\x80\x01\n\x14\x63ontent_dict_str_str\x18\x0f \x03(\x0b\x32\x62.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry\x1a:\n\x18\x43ontentDictIntBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a:\n\x18\x43ontentDictIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictIntBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictIntStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a;\n\x19\x43ontentDictBoolFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a:\n\x18\x43ontentDictBoolBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictBoolStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a:\n\x18\x43ontentDictStrFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1a\x39\n\x17\x43ontentDictStrBoolEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\x1a\x38\n\x16\x43ontentDictStrStrEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\xc0\x0b\n\x1cPerformative_Mt_Performative\x12"\n\x1a\x63ontent_union_1_type_bytes\x18\x01 \x01(\x0c\x12 \n\x18\x63ontent_union_1_type_int\x18\x02 \x01(\x05\x12"\n\x1a\x63ontent_union_1_type_float\x18\x03 \x01(\x02\x12!\n\x19\x63ontent_union_1_type_bool\x18\x04 \x01(\x08\x12 \n\x18\x63ontent_union_1_type_str\x18\x05 \x01(\t\x12\'\n\x1f\x63ontent_union_1_type_set_of_int\x18\x06 \x03(\x05\x12)\n!content_union_1_type_list_of_bool\x18\x07 \x03(\x08\x12\x9b\x01\n$content_union_1_type_dict_of_str_int\x18\x08 \x03(\x0b\x32m.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry\x12)\n!content_union_2_type_set_of_bytes\x18\t \x03(\x0c\x12\'\n\x1f\x63ontent_union_2_type_set_of_int\x18\n \x03(\x05\x12\'\n\x1f\x63ontent_union_2_type_set_of_str\x18\x0b \x03(\t\x12*\n"content_union_2_type_list_of_float\x18\x0c \x03(\x02\x12)\n!content_union_2_type_list_of_bool\x18\r \x03(\x08\x12*\n"content_union_2_type_list_of_bytes\x18\x0e \x03(\x0c\x12\x9b\x01\n$content_union_2_type_dict_of_str_int\x18\x0f \x03(\x0b\x32m.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry\x12\x9f\x01\n&content_union_2_type_dict_of_int_float\x18\x10 \x03(\x0b\x32o.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry\x12\xa1\x01\n\'content_union_2_type_dict_of_bool_bytes\x18\x11 \x03(\x0b\x32p.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry\x1a\x44\n"ContentUnion1TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x44\n"ContentUnion2TypeDictOfStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a\x46\n$ContentUnion2TypeDictOfIntFloatEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\x1aG\n%ContentUnion2TypeDictOfBoolBytesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\xba\x03\n\x1bPerformative_O_Performative\x12\x16\n\x0e\x63ontent_o_bool\x18\x01 \x01(\x08\x12\x1d\n\x15\x63ontent_o_bool_is_set\x18\x02 \x01(\x08\x12\x19\n\x11\x63ontent_o_set_int\x18\x03 \x03(\x05\x12 \n\x18\x63ontent_o_set_int_is_set\x18\x04 \x01(\x08\x12\x1c\n\x14\x63ontent_o_list_bytes\x18\x05 \x03(\x0c\x12#\n\x1b\x63ontent_o_list_bytes_is_set\x18\x06 \x01(\x08\x12\x81\x01\n\x16\x63ontent_o_dict_str_int\x18\x07 \x03(\x0b\x32\x61.fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry\x12%\n\x1d\x63ontent_o_dict_str_int_is_set\x18\x08 \x01(\x08\x1a\x39\n\x17\x43ontentODictStrIntEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x05:\x02\x38\x01\x1a*\n(Performative_Empty_Contents_PerformativeB\x0e\n\x0cperformativeb\x06proto3', +) + + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Pt_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative.content_bytes", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative.content_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative.content_float", + index=2, + number=3, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative.content_bool", + index=3, + number=4, + 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, + ), + _descriptor.FieldDescriptor( + name="content_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative.content_str", + index=4, + number=5, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=831, + serialized_end=971, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Pct_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_set_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_set_bytes", + index=0, + number=1, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_set_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_set_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_set_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_set_float", + index=2, + number=3, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_set_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_set_bool", + index=3, + number=4, + type=8, + cpp_type=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_set_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_set_str", + index=4, + number=5, + type=9, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_list_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_list_bytes", + index=5, + number=6, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_list_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_list_int", + index=6, + number=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_list_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_list_float", + index=7, + number=8, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_list_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_list_bool", + index=8, + number=9, + type=8, + cpp_type=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_list_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative.content_list_str", + index=9, + number=10, + type=9, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=974, + serialized_end=1270, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY = _descriptor.Descriptor( + name="ContentDictIntBytesEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry.value", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3311, + serialized_end=3369, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY = _descriptor.Descriptor( + name="ContentDictIntIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3371, + serialized_end=3427, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY = _descriptor.Descriptor( + name="ContentDictIntFloatEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3429, + serialized_end=3487, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY = _descriptor.Descriptor( + name="ContentDictIntBoolEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3489, + serialized_end=3546, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY = _descriptor.Descriptor( + name="ContentDictIntStrEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3548, + serialized_end=3604, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY = _descriptor.Descriptor( + name="ContentDictBoolBytesEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry.value", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3606, + serialized_end=3665, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY = _descriptor.Descriptor( + name="ContentDictBoolIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3667, + serialized_end=3724, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY = _descriptor.Descriptor( + name="ContentDictBoolFloatEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3726, + serialized_end=3785, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY = _descriptor.Descriptor( + name="ContentDictBoolBoolEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3787, + serialized_end=3845, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY = _descriptor.Descriptor( + name="ContentDictBoolStrEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3847, + serialized_end=3904, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY = _descriptor.Descriptor( + name="ContentDictStrBytesEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry.value", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3906, + serialized_end=3964, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY = _descriptor.Descriptor( + name="ContentDictStrIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=3966, + serialized_end=4022, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY = _descriptor.Descriptor( + name="ContentDictStrFloatEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4024, + serialized_end=4082, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY = _descriptor.Descriptor( + name="ContentDictStrBoolEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4084, + serialized_end=4141, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY = _descriptor.Descriptor( + name="ContentDictStrStrEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4143, + serialized_end=4199, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Pmt_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_dict_int_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_int_bytes", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_int_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_int_float", + index=2, + number=3, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_int_bool", + index=3, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_dict_int_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_int_str", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_bool_bytes", + index=5, + number=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_bool_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_bool_float", + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_bool_bool", + index=8, + number=9, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_bool_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_bool_str", + index=9, + number=10, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_str_bytes", + index=10, + number=11, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_str_int", + index=11, + number=12, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_str_float", + index=12, + number=13, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_str_bool", + index=13, + number=14, + 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, + ), + _descriptor.FieldDescriptor( + name="content_dict_str_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.content_dict_str_str", + index=14, + number=15, + 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, + ), + ], + extensions=[], + nested_types=[ + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1273, + serialized_end=4199, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( + name="ContentUnion1TypeDictOfStrIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5391, + serialized_end=5459, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfStrIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5461, + serialized_end=5529, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfIntFloatEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry.value", + index=1, + number=2, + type=2, + cpp_type=6, + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5531, + serialized_end=5601, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY = _descriptor.Descriptor( + name="ContentUnion2TypeDictOfBoolBytesEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.key", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry.value", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5603, + serialized_end=5674, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Mt_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_union_1_type_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_bytes", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_float", + index=2, + number=3, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_bool", + index=3, + number=4, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_str", + index=4, + number=5, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_set_of_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_set_of_int", + index=5, + number=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_list_of_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_list_of_bool", + index=6, + number=7, + type=8, + cpp_type=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_1_type_dict_of_str_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_1_type_dict_of_str_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_set_of_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_set_of_bytes", + index=8, + number=9, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_set_of_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_set_of_int", + index=9, + number=10, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_set_of_str", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_set_of_str", + index=10, + number=11, + type=9, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_list_of_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_list_of_float", + index=11, + number=12, + type=2, + cpp_type=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_list_of_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_list_of_bool", + index=12, + number=13, + type=8, + cpp_type=7, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_list_of_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_list_of_bytes", + index=13, + number=14, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_dict_of_str_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_dict_of_str_int", + index=14, + number=15, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_dict_of_int_float", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_dict_of_int_float", + index=15, + number=16, + 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, + ), + _descriptor.FieldDescriptor( + name="content_union_2_type_dict_of_bool_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.content_union_2_type_dict_of_bool_bytes", + index=16, + number=17, + 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, + ), + ], + extensions=[], + nested_types=[ + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=4202, + serialized_end=5674, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY = _descriptor.Descriptor( + name="ContentODictStrIntEntry", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry.key", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry.value", + 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, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=b"8\001", + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=6062, + serialized_end=6119, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_O_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="content_o_bool", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_bool", + index=0, + number=1, + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_bool_is_set", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_bool_is_set", + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_set_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_set_int", + index=2, + number=3, + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_set_int_is_set", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_set_int_is_set", + index=3, + number=4, + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_list_bytes", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_list_bytes", + index=4, + number=5, + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="content_o_list_bytes_is_set", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_list_bytes_is_set", + index=5, + number=6, + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_dict_str_int", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_dict_str_int", + 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, + ), + _descriptor.FieldDescriptor( + name="content_o_dict_str_int_is_set", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.content_o_dict_str_int_is_set", + index=7, + number=8, + 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, + ), + ], + extensions=[], + nested_types=[ + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=5677, + serialized_end=6119, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE = _descriptor.Descriptor( + name="Performative_Empty_Contents_Performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Empty_Contents_Performative", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=6121, + serialized_end=6163, +) + +_TPROTOCOLNOCTMESSAGE = _descriptor.Descriptor( + name="TProtocolNoCtMessage", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="message_id", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="dialogue_starter_reference", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.dialogue_starter_reference", + 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, + ), + _descriptor.FieldDescriptor( + name="dialogue_responder_reference", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.dialogue_responder_reference", + 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, + ), + _descriptor.FieldDescriptor( + name="target", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.target", + 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, + ), + _descriptor.FieldDescriptor( + name="performative_empty_contents", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_empty_contents", + 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, + ), + _descriptor.FieldDescriptor( + name="performative_mt", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_mt", + 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, + ), + _descriptor.FieldDescriptor( + name="performative_o", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_o", + 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, + ), + _descriptor.FieldDescriptor( + name="performative_pct", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_pct", + 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, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="performative_pmt", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_pmt", + index=8, + number=9, + 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, + ), + _descriptor.FieldDescriptor( + name="performative_pt", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative_pt", + index=9, + number=10, + 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, + ), + ], + extensions=[], + nested_types=[ + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE, + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, + ], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name="performative", + full_name="fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.performative", + index=0, + containing_type=None, + fields=[], + ), + ], + serialized_start=52, + serialized_end=6179, +) + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_bytes" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_int" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_float" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_bool" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_int_str" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_bytes" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_int" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_float" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_bool" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_bool_str" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_bytes" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_int" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_float" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_bool" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.fields_by_name[ + "content_dict_str_str" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.fields_by_name[ + "content_union_1_type_dict_of_str_int" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.fields_by_name[ + "content_union_2_type_dict_of_str_int" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.fields_by_name[ + "content_union_2_type_dict_of_int_float" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.fields_by_name[ + "content_union_2_type_dict_of_bool_bytes" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY.containing_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE.fields_by_name[ + "content_o_dict_str_int" +].message_type = ( + _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE.containing_type = ( + _TPROTOCOLNOCTMESSAGE +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_empty_contents" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_mt" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_o" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pct" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pmt" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pt" +].message_type = _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_empty_contents"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_empty_contents" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_mt"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_mt" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_o"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_o" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_pct"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pct" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_pmt"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pmt" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +_TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"].fields.append( + _TPROTOCOLNOCTMESSAGE.fields_by_name["performative_pt"] +) +_TPROTOCOLNOCTMESSAGE.fields_by_name[ + "performative_pt" +].containing_oneof = _TPROTOCOLNOCTMESSAGE.oneofs_by_name["performative"] +DESCRIPTOR.message_types_by_name["TProtocolNoCtMessage"] = _TPROTOCOLNOCTMESSAGE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +TProtocolNoCtMessage = _reflection.GeneratedProtocolMessageType( + "TProtocolNoCtMessage", + (_message.Message,), + { + "Performative_Pt_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_Pt_Performative", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PT_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pt_Performative) + }, + ), + "Performative_Pct_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_Pct_Performative", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PCT_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pct_Performative) + }, + ), + "Performative_Pmt_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_Pmt_Performative", + (_message.Message,), + { + "ContentDictIntBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry) + }, + ), + "ContentDictIntIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry) + }, + ), + "ContentDictIntFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry) + }, + ), + "ContentDictIntBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry) + }, + ), + "ContentDictIntStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictIntStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry) + }, + ), + "ContentDictBoolBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry) + }, + ), + "ContentDictBoolIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry) + }, + ), + "ContentDictBoolFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry) + }, + ), + "ContentDictBoolBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry) + }, + ), + "ContentDictBoolStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictBoolStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry) + }, + ), + "ContentDictStrBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry) + }, + ), + "ContentDictStrIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry) + }, + ), + "ContentDictStrFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry) + }, + ), + "ContentDictStrBoolEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrBoolEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry) + }, + ), + "ContentDictStrStrEntry": _reflection.GeneratedProtocolMessageType( + "ContentDictStrStrEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry) + }, + ), + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Pmt_Performative) + }, + ), + "Performative_Mt_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_Mt_Performative", + (_message.Message,), + { + "ContentUnion1TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentUnion1TypeDictOfStrIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry) + }, + ), + "ContentUnion2TypeDictOfStrIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfStrIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry) + }, + ), + "ContentUnion2TypeDictOfIntFloatEntry": _reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfIntFloatEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry) + }, + ), + "ContentUnion2TypeDictOfBoolBytesEntry": _reflection.GeneratedProtocolMessageType( + "ContentUnion2TypeDictOfBoolBytesEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry) + }, + ), + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Mt_Performative) + }, + ), + "Performative_O_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_O_Performative", + (_message.Message,), + { + "ContentODictStrIntEntry": _reflection.GeneratedProtocolMessageType( + "ContentODictStrIntEntry", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry) + }, + ), + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_O_Performative) + }, + ), + "Performative_Empty_Contents_Performative": _reflection.GeneratedProtocolMessageType( + "Performative_Empty_Contents_Performative", + (_message.Message,), + { + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE_PERFORMATIVE_EMPTY_CONTENTS_PERFORMATIVE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage.Performative_Empty_Contents_Performative) + }, + ), + "DESCRIPTOR": _TPROTOCOLNOCTMESSAGE, + "__module__": "t_protocol_no_ct_pb2" + # @@protoc_insertion_point(class_scope:fetch.aea.TProtocolNoCt.TProtocolNoCtMessage) + }, +) +_sym_db.RegisterMessage(TProtocolNoCtMessage) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pt_Performative) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pct_Performative) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Pmt_Performative) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBytesEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntIntEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntFloatEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntBoolEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictIntStrEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBytesEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolIntEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolFloatEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolBoolEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictBoolStrEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBytesEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrIntEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrFloatEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrBoolEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Pmt_Performative.ContentDictStrStrEntry +) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Mt_Performative) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion1TypeDictOfStrIntEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfStrIntEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfIntFloatEntry +) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_Mt_Performative.ContentUnion2TypeDictOfBoolBytesEntry +) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_O_Performative) +_sym_db.RegisterMessage( + TProtocolNoCtMessage.Performative_O_Performative.ContentODictStrIntEntry +) +_sym_db.RegisterMessage(TProtocolNoCtMessage.Performative_Empty_Contents_Performative) + + +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBYTESENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTINTENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTFLOATENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTBOOLENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTINTSTRENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBYTESENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLINTENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLFLOATENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLBOOLENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTBOOLSTRENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBYTESENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRINTENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRFLOATENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRBOOLENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_PMT_PERFORMATIVE_CONTENTDICTSTRSTRENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION1TYPEDICTOFSTRINTENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFSTRINTENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFINTFLOATENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_MT_PERFORMATIVE_CONTENTUNION2TYPEDICTOFBOOLBYTESENTRY._options = ( + None +) +_TPROTOCOLNOCTMESSAGE_PERFORMATIVE_O_PERFORMATIVE_CONTENTODICTSTRINTENTRY._options = ( + None +) +# @@protoc_insertion_point(module_scope) diff --git a/tests/data/gym-connection.yaml b/tests/data/gym-connection.yaml index 4a35e327b0..847f473ba5 100644 --- a/tests/data/gym-connection.yaml +++ b/tests/data/gym-connection.yaml @@ -8,8 +8,8 @@ fingerprint: aea_version: '>=0.5.0, <0.6.0' description: "The gym connection wraps an OpenAI gym." class_name: GymConnection -protocols: ["fetchai/gym:0.3.0"] -restricted_to_protocols: ["fetchai/gym:0.3.0"] +protocols: ["fetchai/gym:0.4.0"] +restricted_to_protocols: ["fetchai/gym:0.4.0"] excluded_protocols: [] config: env: 'gyms.env.BanditNArmedRandom' diff --git a/tests/data/hashes.csv b/tests/data/hashes.csv index 194769981f..60bacd1190 100644 --- a/tests/data/hashes.csv +++ b/tests/data/hashes.csv @@ -1,6 +1,6 @@ -dummy_author/agents/dummy_aea,QmTfa3sBgLbnpD7DJuzVmqcSebnAsxqL1cndSYsskJANvt -dummy_author/skills/dummy_skill,Qme2ehYviSzGVKNZfS5N7A7Jayd7QJ4nn9EEnXdVrL231X -fetchai/connections/dummy_connection,QmVAEYzswDE7CxEKQpz51f8GV7UVm7WE6AHZGqWj9QMMUK +dummy_author/agents/dummy_aea,QmR4SuPKbe4qjgF5DVwVwHPm5eFqCx83sdavs4QEXgF1mR +dummy_author/skills/dummy_skill,QmZFoPjg3byuvLSbpdGDzu9n7U31LMwg8idtPuk26izFPX +fetchai/connections/dummy_connection,QmRLdNwog7rx8hrYG5DGu35BNZt2782ZMxGJXjfjJy3D4d fetchai/contracts/dummy_contract,QmTBc9MJrKa66iRmvfHKpR1xmT6P5cGML5S5RUsW6yVwbm -fetchai/skills/dependencies_skill,Qmasrc9nMApq7qZYU8n78n5K2DKzY2TUZWp9pYfzcRRmoP +fetchai/skills/dependencies_skill,QmZzuuX3HbpuY4niyqFp3b5jq4tmUokXknf3iyKtRo8Y7N fetchai/skills/exception_skill,QmWXXnoHarx7WLhuFuzdas2Pe1WCprS4sDkdaPH1w4kTo2 diff --git a/tests/data/sample_specification.yaml b/tests/data/sample_specification.yaml index 18963861b1..8ee1b65289 100644 --- a/tests/data/sample_specification.yaml +++ b/tests/data/sample_specification.yaml @@ -1,3 +1,4 @@ +--- name: t_protocol author: fetchai version: 0.1.0 @@ -27,23 +28,49 @@ speech_acts: content_list_bool: pt:list[pt:bool] content_list_str: pt:list[pt:str] performative_pmt: -# content_dict_int_ct: pt:dict[pt:int, ct:DataModel] # custom type inside of set, list, and dict isn't allowed. +# custom type inside of set, list, and dict isn't allowed. +# content_dict_int_ct: pt:dict[pt:int, ct:DataModel] +# content_dict_ct_ct: pt:dict[ct:DataModel, ct:DataModel] +# invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') +# content_dict_bytes_bytes: pt:dict[pt:bytes, pt:bytes] +# content_dict_bytes_int: pt:dict[pt:bytes, pt:int] +# content_dict_bytes_float: pt:dict[pt:bytes, pt:float] +# content_dict_bytes_bool: pt:dict[pt:bytes, pt:bool] +# content_dict_bytes_str: pt:dict[pt:bytes, pt:str] + content_dict_int_bytes: pt:dict[pt:int, pt:bytes] + content_dict_int_int: pt:dict[pt:int, pt:int] + content_dict_int_float: pt:dict[pt:int, pt:float] + content_dict_int_bool: pt:dict[pt:int, pt:bool] + content_dict_int_str: pt:dict[pt:int, pt:str] +# invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') +# content_dict_float_bytes: pt:dict[pt:int, pt:bytes] +# content_dict_float_int: pt:dict[pt:int, pt:int] +# content_dict_float_float: pt:dict[pt:int, pt:float] +# content_dict_float_bool: pt:dict[pt:int, pt:bool] +# content_dict_float_str: pt:dict[pt:int, pt:str] content_dict_bool_bytes: pt:dict[pt:bool, pt:bytes] + content_dict_bool_int: pt:dict[pt:bool, pt:int] + content_dict_bool_float: pt:dict[pt:bool, pt:float] + content_dict_bool_bool: pt:dict[pt:bool, pt:bool] + content_dict_bool_str: pt:dict[pt:bool, pt:str] + content_dict_str_bytes: pt:dict[pt:str, pt:bytes] + content_dict_str_int: pt:dict[pt:str, pt:int] content_dict_str_float: pt:dict[pt:str, pt:float] -# content_dict_ct_ct: pt:dict[ct:DataModel, ct:DataModel] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') -# content_dict_bytes_int: pt:dict[pt:bytes, pt:int] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') -# content_dict_float_int: pt:dict[pt:float, pt:int] # invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') + content_dict_str_bool: pt:dict[pt:str, pt:bool] + content_dict_str_str: pt:dict[pt:str, pt:str] performative_mt: content_union_1: pt:union[ct:DataModel, pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str, pt:int]] content_union_2: pt:union[pt:set[pt:bytes], pt:set[pt:int], pt:set[pt:str], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:bool, pt:bytes]] performative_o: content_o_ct: pt:optional[ct:DataModel] content_o_bool: pt:optional[pt:bool] - content_o_set_float: pt:optional[pt:set[pt:float]] + content_o_set_int: pt:optional[pt:set[pt:int]] content_o_list_bytes: pt:optional[pt:list[pt:bytes]] content_o_dict_str_int: pt:optional[pt:dict[pt:str, pt:int]] - content_o_union: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:int], pt:set[pt:int], pt:set[pt:bytes], pt:list[pt:bool], pt:dict[pt:str, pt:float]]] +# union does not work properly in the generator +# content_o_union: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:int], pt:set[pt:int], pt:set[pt:bytes], pt:list[pt:bool], pt:dict[pt:str, pt:float]]] performative_empty_contents: {} +... --- ct:DataModel: | bytes bytes_field = 1; @@ -54,3 +81,18 @@ ct:DataModel: | repeated int32 set_field = 6; repeated string list_field = 7; map dict_field = 8; +... +--- +initiation: [performative_ct, performative_pt] +reply: + performative_ct: [performative_pct] + performative_pt: [performative_pmt] + performative_pct: [performative_mt, performative_o] + performative_pmt: [performative_mt, performative_o] + performative_mt: [] + performative_o: [] + performative_empty_contents: [performative_empty_contents] +termination: [performative_mt, performative_o, performative_empty_contents] +roles: {role_1, role_2} +end_states: [end_state_1, end_state_2, end_state_3] +... diff --git a/tests/data/sample_specification_no_custom_types.yaml b/tests/data/sample_specification_no_custom_types.yaml new file mode 100644 index 0000000000..a5ef48863d --- /dev/null +++ b/tests/data/sample_specification_no_custom_types.yaml @@ -0,0 +1,85 @@ +--- +name: t_protocol_no_ct +author: fetchai +version: 0.1.0 +license: Apache-2.0 +aea_version: '>=0.5.0, <0.6.0' +description: 'A protocol for testing purposes.' +speech_acts: + performative_pt: + content_bytes: pt:bytes + content_int: pt:int + content_float: pt:float + content_bool: pt:bool + content_str: pt:str + performative_pct: +# content_set_ct: pt:set[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. + content_set_bytes: pt:set[pt:bytes] + content_set_int: pt:set[pt:int] + content_set_float: pt:set[pt:float] + content_set_bool: pt:set[pt:bool] + content_set_str: pt:set[pt:str] +# content_list_ct: pt:list[ct:DataModel] # custom type inside of set, list, and dict isn't allowed. + content_list_bytes: pt:list[pt:bytes] + content_list_int: pt:list[pt:int] + content_list_float: pt:list[pt:float] + content_list_bool: pt:list[pt:bool] + content_list_str: pt:list[pt:str] + performative_pmt: +# custom type inside of set, list, and dict isn't allowed. +# content_dict_int_ct: pt:dict[pt:int, ct:DataModel] +# content_dict_ct_ct: pt:dict[ct:DataModel, ct:DataModel] +# invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') +# content_dict_bytes_bytes: pt:dict[pt:bytes, pt:bytes] +# content_dict_bytes_int: pt:dict[pt:bytes, pt:int] +# content_dict_bytes_float: pt:dict[pt:bytes, pt:float] +# content_dict_bytes_bool: pt:dict[pt:bytes, pt:bool] +# content_dict_bytes_str: pt:dict[pt:bytes, pt:str] + content_dict_int_bytes: pt:dict[pt:int, pt:bytes] + content_dict_int_int: pt:dict[pt:int, pt:int] + content_dict_int_float: pt:dict[pt:int, pt:float] + content_dict_int_bool: pt:dict[pt:int, pt:bool] + content_dict_int_str: pt:dict[pt:int, pt:str] +# invalid in protobuf (key in map cannot be 'bytes', 'float', 'double', 'message') +# content_dict_float_bytes: pt:dict[pt:int, pt:bytes] +# content_dict_float_int: pt:dict[pt:int, pt:int] +# content_dict_float_float: pt:dict[pt:int, pt:float] +# content_dict_float_bool: pt:dict[pt:int, pt:bool] +# content_dict_float_str: pt:dict[pt:int, pt:str] + content_dict_bool_bytes: pt:dict[pt:bool, pt:bytes] + content_dict_bool_int: pt:dict[pt:bool, pt:int] + content_dict_bool_float: pt:dict[pt:bool, pt:float] + content_dict_bool_bool: pt:dict[pt:bool, pt:bool] + content_dict_bool_str: pt:dict[pt:bool, pt:str] + content_dict_str_bytes: pt:dict[pt:str, pt:bytes] + content_dict_str_int: pt:dict[pt:str, pt:int] + content_dict_str_float: pt:dict[pt:str, pt:float] + content_dict_str_bool: pt:dict[pt:str, pt:bool] + content_dict_str_str: pt:dict[pt:str, pt:str] + performative_mt: + content_union_1: pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str, pt:int]] + content_union_2: pt:union[pt:set[pt:bytes], pt:set[pt:int], pt:set[pt:str], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:bool, pt:bytes]] + performative_o: + content_o_bool: pt:optional[pt:bool] + content_o_set_int: pt:optional[pt:set[pt:int]] + content_o_list_bytes: pt:optional[pt:list[pt:bytes]] + content_o_dict_str_int: pt:optional[pt:dict[pt:str, pt:int]] +# union does not work properly in the generator +# content_o_union: pt:optional[pt:union[pt:str, pt:dict[pt:str,pt:int], pt:set[pt:int], pt:set[pt:bytes], pt:list[pt:bool], pt:dict[pt:str, pt:float]]] + performative_empty_contents: {} +... +--- +... +--- +initiation: [performative_pt] +reply: + performative_pt: [performative_pct, performative_pmt] + performative_pct: [performative_mt, performative_o] + performative_pmt: [performative_mt, performative_o] + performative_mt: [] + performative_o: [] + performative_empty_contents: [performative_empty_contents] +termination: [performative_mt, performative_o, performative_empty_contents] +roles: {role_1, role_2} +end_states: [end_state_1, end_state_2, end_state_3] +... diff --git a/tests/test_aea.py b/tests/test_aea.py index 47313dd400..abedcf596e 100644 --- a/tests/test_aea.py +++ b/tests/test_aea.py @@ -16,11 +16,13 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """This module contains the tests for aea/aea.py.""" -import logging import os import tempfile +import time from pathlib import Path +from threading import Thread from unittest.mock import patch from aea import AEA_DIR @@ -52,7 +54,7 @@ from .data.dummy_skill.behaviours import DummyBehaviour # type: ignore -def test_initialise_aea(): +def test_setup_aea(): """Tests the initialisation of the AEA.""" private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) builder = AEABuilder() @@ -71,7 +73,7 @@ def test_initialise_aea(): ), "Shared state must not be None after set" assert my_AEA.context.task_manager is not None assert my_AEA.context.identity is not None, "Identity must not be None after set." - my_AEA.stop() + my_AEA.teardown() def test_act(): @@ -85,11 +87,8 @@ def test_act(): agent = builder.build() with run_in_thread(agent.start, timeout=20): - wait_for_condition( - lambda: agent._main_loop and agent._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: agent.is_running, timeout=10) behaviour = agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") - import time time.sleep(1) wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=10) @@ -107,12 +106,32 @@ def test_start_stop(): agent = builder.build() with run_in_thread(agent.start, timeout=20): - wait_for_condition( - lambda: agent._main_loop and agent._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: agent.is_running, timeout=10) agent.stop() +def test_double_start(): + """Tests the act function of the AEA.""" + agent_name = "MyAgent" + private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) + builder = AEABuilder() + builder.set_name(agent_name) + builder.add_private_key(DEFAULT_LEDGER, private_key_path) + builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) + agent = builder.build() + + with run_in_thread(agent.start, timeout=20): + try: + wait_for_condition(lambda: agent.is_running, timeout=10) + + t = Thread(target=agent.start) + t.start() + time.sleep(1) + assert not t.is_alive() + finally: + agent.stop() + + def test_react(): """Tests income messages.""" with LocalNode() as node: @@ -127,10 +146,10 @@ def test_react(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.4.0") + local_connection_id = PublicId.from_str("fetchai/local:0.5.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - agent = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.4.0")]) + agent = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.5.0")]) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = agent.resources.get_connection(local_connection_id) @@ -144,17 +163,16 @@ def test_react(): content=b"hello", ) msg.counterparty = agent.identity.address + msg.sender = agent.identity.address envelope = Envelope( - to=agent.identity.address, - sender=agent.identity.address, - protocol_id=DefaultMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) with run_in_thread(agent.start, timeout=20, on_exit=agent.stop): - wait_for_condition( - lambda: agent._main_loop and agent._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: agent.is_running, timeout=10) agent.outbox.put(envelope) default_protocol_public_id = DefaultMessage.protocol_id dummy_skill_public_id = DUMMY_SKILL_PUBLIC_ID @@ -167,7 +185,6 @@ def test_react(): timeout=10, error_msg="The message is not inside the handled_messages.", ) - agent.stop() def test_handle(): @@ -184,10 +201,10 @@ def test_handle(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.4.0") + local_connection_id = PublicId.from_str("fetchai/local:0.5.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.4.0")]) + aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.5.0")]) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = aea.resources.get_connection(local_connection_id) @@ -201,17 +218,16 @@ def test_handle(): content=b"hello", ) msg.counterparty = aea.identity.address + msg.sender = aea.identity.address envelope = Envelope( - to=aea.identity.address, - sender=aea.identity.address, + to=msg.counterparty, + sender=msg.sender, protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=msg, ) with run_in_thread(aea.start, timeout=5): - wait_for_condition( - lambda: aea._main_loop and aea._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: aea.is_running, timeout=10) dummy_skill = aea.resources.get_skill(DUMMY_SKILL_PUBLIC_ID) dummy_handler = dummy_skill.handlers["dummy"] @@ -242,10 +258,11 @@ def test_handle(): target=0, ) msg.counterparty = aea.identity.address + msg.sender = aea.identity.address envelope = Envelope( - to=aea.identity.address, - sender=aea.identity.address, - protocol_id=FipaMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) # send envelope via localnode back to agent @@ -270,10 +287,10 @@ def test_initialize_aea_programmatically(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.4.0") + local_connection_id = PublicId.from_str("fetchai/local:0.5.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.4.0")]) + aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.5.0")]) local_connection = aea.resources.get_connection(local_connection_id) local_connection._local_node = node @@ -285,17 +302,16 @@ def test_initialize_aea_programmatically(): content=b"hello", ) expected_message.counterparty = aea.identity.address + expected_message.sender = aea.identity.address envelope = Envelope( - to=aea.identity.address, - sender=aea.identity.address, - protocol_id=DefaultMessage.protocol_id, + to=expected_message.counterparty, + sender=expected_message.sender, + protocol_id=expected_message.protocol_id, message=expected_message, ) with run_in_thread(aea.start, timeout=5, on_exit=aea.stop): - wait_for_condition( - lambda: aea._main_loop and aea._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: aea.is_running, timeout=10) aea.outbox.put(envelope) dummy_skill_id = DUMMY_SKILL_PUBLIC_ID @@ -377,11 +393,10 @@ def test_initialize_aea_programmatically_build_resources(): content=b"hello", ) expected_message.counterparty = agent_name + expected_message.sender = agent_name with run_in_thread(aea.start, timeout=5, on_exit=aea.stop): - wait_for_condition( - lambda: aea._main_loop and aea._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: aea.is_running, timeout=10) aea.outbox.put( Envelope( to=agent_name, @@ -449,9 +464,7 @@ def test_add_behaviour_dynamically(): skill.skill_context.set_agent_context(agent.context) with run_in_thread(agent.start, timeout=5, on_exit=agent.stop): - wait_for_condition( - lambda: agent._main_loop and agent._main_loop.is_running, timeout=10 - ) + wait_for_condition(lambda: agent.is_running, timeout=10) dummy_skill_id = PublicId("dummy_author", "dummy", "0.1.0") dummy_skill = agent.resources.get_skill(dummy_skill_id) @@ -500,7 +513,7 @@ def test_error_handler_is_not_set(): mocked_stop.assert_called() -def test_no_handlers_registered(caplog): +def test_no_handlers_registered(): """Test no handlers are registered for message processing.""" agent_name = "MyAgent" builder = AEABuilder() @@ -511,9 +524,9 @@ def test_no_handlers_registered(caplog): # builder.set_default_connection(local_connection_id) aea = builder.build() - with caplog.at_level( - logging.WARNING, logger=aea._get_error_handler().context.logger.name - ): + with patch.object( + aea._get_error_handler().context._logger, "warning" + ) as mock_logger: msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, @@ -530,8 +543,9 @@ def test_no_handlers_registered(caplog): ) with patch.object(aea.filter, "get_active_handlers", return_value=[]): aea._handle(envelope) - - assert "Cannot handle envelope: no active handler registered" in caplog.text + mock_logger.assert_any_call( + f"Cannot handle envelope: no active handler registered for the protocol_id='{DefaultMessage.protocol_id}'." + ) class TestContextNamespace: @@ -566,3 +580,36 @@ def test_access_context_namespace(self): for skill in self.agent.resources.get_all_skills(): assert skill.skill_context.namespace.key1 == 1 assert skill.skill_context.namespace.key2 == 2 + + +def test_start_stop_and_start_stop_again(): + """Tests AEA can be started/stopped twice.""" + agent_name = "MyAgent" + private_key_path = os.path.join(CUR_PATH, "data", DEFAULT_PRIVATE_KEY_FILE) + builder = AEABuilder() + builder.set_name(agent_name) + builder.add_private_key(DEFAULT_LEDGER, private_key_path) + builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) + agent = builder.build() + + with run_in_thread(agent.start, timeout=20): + wait_for_condition(lambda: agent.is_running, timeout=10) + behaviour = agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") + + time.sleep(1) + wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=5) + agent.stop() + wait_for_condition(lambda: agent.is_stopped, timeout=10) + + behaviour.nb_act_called = 0 + + time.sleep(2) + assert behaviour.nb_act_called == 0 + + with run_in_thread(agent.start, timeout=20): + wait_for_condition(lambda: agent.is_running, timeout=10) + + time.sleep(1) + wait_for_condition(lambda: behaviour.nb_act_called > 0, timeout=5) + agent.stop() + wait_for_condition(lambda: agent.is_stopped, timeout=10) diff --git a/tests/test_aea_builder.py b/tests/test_aea_builder.py index 007021f3e4..dd84ae7e70 100644 --- a/tests/test_aea_builder.py +++ b/tests/test_aea_builder.py @@ -90,7 +90,7 @@ def test_add_package_already_existing(): builder.add_component(ComponentType.PROTOCOL, fipa_package_path) expected_message = re.escape( - "Component 'fetchai/fipa:0.4.0' of type 'protocol' already added." + "Component 'fetchai/fipa:0.5.0' of type 'protocol' already added." ) with pytest.raises(AEAException, match=expected_message): builder.add_component(ComponentType.PROTOCOL, fipa_package_path) @@ -100,12 +100,12 @@ def test_when_package_has_missing_dependency(): """Test the case when the builder tries to load the packages, but fails because of a missing dependency.""" builder = AEABuilder() expected_message = re.escape( - "Package 'fetchai/oef:0.6.0' of type 'connection' cannot be added. " - "Missing dependencies: ['(protocol, fetchai/oef_search:0.3.0)']" + "Package 'fetchai/oef:0.7.0' of type 'connection' cannot be added. " + "Missing dependencies: ['(protocol, fetchai/oef_search:0.4.0)']" ) with pytest.raises(AEAException, match=expected_message): # connection "fetchai/oef:0.1.0" requires - # "fetchai/oef_search:0.3.0" and "fetchai/fipa:0.4.0" protocols. + # "fetchai/oef_search:0.4.0" and "fetchai/fipa:0.5.0" protocols. builder.add_component( ComponentType.CONNECTION, Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "oef", diff --git a/tests/test_aea_exception_policy.py b/tests/test_aea_exception_policy.py index da12d39f6e..dc67b0a647 100644 --- a/tests/test_aea_exception_policy.py +++ b/tests/test_aea_exception_policy.py @@ -23,7 +23,6 @@ import pytest -from aea.aea import logger from aea.aea_builder import AEABuilder from aea.configurations.constants import DEFAULT_LEDGER from aea.exceptions import AEAException @@ -120,7 +119,7 @@ def test_handle_just_log(self) -> None: self.aea._skills_exception_policy = ExceptionPolicyEnum.just_log self.handler.handle = self.raise_exception # type: ignore # cause error: Cannot assign to a method - with patch.object(logger, "exception") as patched: + with patch.object(self.aea._logger, "exception") as patched: t = Thread(target=self.aea.start) t.start() @@ -157,7 +156,7 @@ def test_act_just_log(self) -> None: self.aea._skills_exception_policy = ExceptionPolicyEnum.just_log self.behaviour.act = self.raise_exception # type: ignore # cause error: Cannot assign to a method - with patch.object(logger, "exception") as patched: + with patch.object(self.aea._logger, "exception") as patched: t = Thread(target=self.aea.start) t.start() @@ -177,4 +176,5 @@ def test_act_bad_policy(self) -> None: def teardown(self) -> None: """Stop AEA if not stopped.""" + self.aea.teardown() self.aea.stop() diff --git a/tests/test_aea_exectimeout.py b/tests/test_aea_exectimeout.py index 0eaf9cabc5..1cffd06578 100644 --- a/tests/test_aea_exectimeout.py +++ b/tests/test_aea_exectimeout.py @@ -69,7 +69,7 @@ def setUpClass(cls) -> None: def tearDown(self) -> None: """Tear down.""" - self.aea_tool.stop() + self.aea_tool.teardown() def prepare(self, function: Callable) -> None: """Prepare aea_tool for testing. @@ -110,7 +110,6 @@ def handler_func(*args, **kwargs): builder.add_component_instance(test_skill) aea = builder.build() - self.aea_tool = AeaTool(aea) self.aea_tool.put_inbox(AeaTool.dummy_envelope()) @@ -131,7 +130,6 @@ def test_long_handler_cancelled_by_timeout(self): assert execution_timeout <= timeit.time_passed <= function_sleep_time assert not self.function_finished - self.aea_tool.stop() def test_short_handler_not_cancelled_by_timeout(self): """Test short function NOTterminated by timeout.""" @@ -151,7 +149,6 @@ def test_short_handler_not_cancelled_by_timeout(self): assert function_sleep_time <= timeit.time_passed <= execution_timeout assert self.function_finished - self.aea_tool.stop() def test_no_timeout(self): """Test function NOT terminated by timeout cause timeout == 0.""" @@ -169,7 +166,6 @@ def test_no_timeout(self): assert function_sleep_time <= timeit.time_passed assert self.function_finished - self.aea_tool.stop() class HandleTimeoutExecutionCase(BaseTimeExecutionCase): diff --git a/tests/test_agent.py b/tests/test_agent.py index a2b2980954..d01b86c781 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -17,14 +17,15 @@ # # ------------------------------------------------------------------------------ -"""This module contains the tests of the agent module.""" +"""This module contains the tests of the agent module.""" +import asyncio from threading import Thread import pytest -from aea.agent import Agent, AgentState, Identity, Liveness -from aea.multiplexer import InBox, OutBox +from aea.agent import Agent, Identity +from aea.runtime import RuntimeStates from packages.fetchai.connections.local.connection import LocalNode @@ -69,56 +70,30 @@ def test_run_agent(): identity = Identity(agent_name, address=agent_address) oef_local_connection = _make_local_connection(agent_address, node) oef_local_connection._local_node = node - agent = DummyAgent(identity, [oef_local_connection],) - assert agent.name == identity.name - assert agent.tick == 0 - assert ( - agent.agent_state == AgentState.INITIATED - ), "Agent state must be 'initiated'" - - agent.multiplexer.connect() - assert ( - agent.agent_state == AgentState.CONNECTED - ), "Agent state must be 'connected'" - - assert isinstance(agent.inbox, InBox) - assert isinstance(agent.outbox, OutBox) - - agent.multiplexer.disconnect() - - import asyncio agent = DummyAgent( identity, [oef_local_connection], loop=asyncio.new_event_loop() ) agent_thread = Thread(target=agent.start) + assert agent.state == RuntimeStates.stopped agent_thread.start() try: wait_for_condition( - lambda: agent._main_loop and agent._main_loop.is_running, + lambda: agent.state == RuntimeStates.starting, timeout=5, - error_msg="Agent loop not started!'", + error_msg="Agent state must be 'starting'", ) wait_for_condition( - lambda: agent.agent_state == AgentState.RUNNING, + lambda: agent.state == RuntimeStates.running, timeout=5, error_msg="Agent state must be 'running'", ) finally: agent.stop() + assert agent.state == RuntimeStates.stopped agent_thread.join() -def test_liveness(): - """Test liveness object states.""" - liveness = Liveness() - assert liveness.is_stopped - liveness.start() - assert not liveness.is_stopped - liveness.stop() - assert liveness.is_stopped - - def test_runtime_modes(): """Test runtime modes are set.""" agent_name = "dummyagent" diff --git a/tests/test_cli/test_add/test_connection.py b/tests/test_cli/test_add/test_connection.py index 0e8c632535..e57b523d62 100644 --- a/tests/test_cli/test_add/test_connection.py +++ b/tests/test_cli/test_add/test_connection.py @@ -59,7 +59,7 @@ def setup_class(cls): cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -150,7 +150,7 @@ def setup_class(cls): cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -347,7 +347,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. @@ -415,7 +415,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. @@ -482,7 +482,7 @@ class TestAddConnectionFromRemoteRegistry(AEATestCaseEmpty): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_connection_from_remote_registry_positive(self): """Test add connection from Registry positive result.""" - self.add_item("connection", "fetchai/local:0.1.0", local=False) + self.add_item("connection", "fetchai/local:0.4.0", local=False) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "connections") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_contract.py b/tests/test_cli/test_add/test_contract.py index f1ff0e59cc..030278f521 100644 --- a/tests/test_cli/test_add/test_contract.py +++ b/tests/test_cli/test_add/test_contract.py @@ -62,7 +62,7 @@ class TestAddContractFromRemoteRegistry(AEATestCaseEmpty): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_contract_from_remote_registry_positive(self): """Test add contract from Registry positive result.""" - self.add_item("contract", "fetchai/erc1155:0.1.0", local=False) + self.add_item("contract", "fetchai/erc1155:0.6.0", local=False) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_protocol.py b/tests/test_cli/test_add/test_protocol.py index 99d5b80497..08d5439ed9 100644 --- a/tests/test_cli/test_add/test_protocol.py +++ b/tests/test_cli/test_add/test_protocol.py @@ -56,7 +56,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = PublicId.from_str("fetchai/gym:0.3.0") + cls.protocol_id = PublicId.from_str("fetchai/gym:0.4.0") cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version @@ -139,7 +139,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = PublicId.from_str("fetchai/gym:0.3.0") + cls.protocol_id = PublicId.from_str("fetchai/gym:0.4.0") cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version @@ -351,7 +351,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.3.0" + cls.protocol_id = "fetchai/gym:0.4.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -417,7 +417,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.3.0" + cls.protocol_id = "fetchai/gym:0.4.0" cls.protocol_name = "gym" # copy the 'packages' directory in the parent of the agent folder. @@ -478,7 +478,7 @@ class TestAddProtocolFromRemoteRegistry(AEATestCaseEmpty): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_protocol_from_remote_registry_positive(self): """Test add protocol from Registry positive result.""" - self.add_item("protocol", "fetchai/fipa:0.1.0", local=False) + self.add_item("protocol", "fetchai/fipa:0.4.0", local=False) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "protocols") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_skill.py b/tests/test_cli/test_add/test_skill.py index 1519288a18..d008ba1214 100644 --- a/tests/test_cli/test_add/test_skill.py +++ b/tests/test_cli/test_add/test_skill.py @@ -62,7 +62,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = PublicId.from_str("fetchai/error:0.3.0") + cls.skill_id = PublicId.from_str("fetchai/error:0.4.0") cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version @@ -144,7 +144,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = PublicId.from_str("fetchai/echo:0.3.0") + cls.skill_id = PublicId.from_str("fetchai/echo:0.4.0") cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version @@ -354,7 +354,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/echo:0.3.0" + cls.skill_id = "fetchai/echo:0.4.0" cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. @@ -426,7 +426,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/echo:0.3.0" + cls.skill_id = "fetchai/echo:0.4.0" cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. @@ -488,7 +488,7 @@ class TestAddSkillWithContractsDeps(AEATestCaseEmpty): def test_add_skill_with_contracts_positive(self): """Test add skill with contract dependencies positive result.""" - self.add_item("skill", "fetchai/erc1155_client:0.8.0") + self.add_item("skill", "fetchai/erc1155_client:0.9.0") contracts_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") contracts_folders = os.listdir(contracts_path) @@ -503,6 +503,9 @@ class TestAddSkillFromRemoteRegistry(AEATestCaseEmpty): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_skill_from_remote_registry_positive(self): """Test add skill from Registry positive result.""" + self.run_cli_command( + *["remove", "protocol", "fetchai/default:0.4.0"], cwd=self._get_cwd() + ) self.add_item("skill", "fetchai/echo:0.3.0", local=False) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") diff --git a/tests/test_cli/test_create.py b/tests/test_cli/test_create.py index 6618a7b3d3..2253b4198b 100644 --- a/tests/test_cli/test_create.py +++ b/tests/test_cli/test_create.py @@ -75,6 +75,11 @@ def setup_class(cls): cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() os.chdir(cls.t) + cls.cli_config_file = f"{cls.t}/cli_config.yaml" + cls.cli_config_patch = patch( + "aea.cli.utils.config.CLI_CONFIG_PATH", cls.cli_config_file + ) + cls.cli_config_patch.start() result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR] ) @@ -262,6 +267,7 @@ def test_skills_directory_content(self): @classmethod def teardown_class(cls): """Tear the test down.""" + cls.cli_config_patch.start() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) diff --git a/tests/test_cli/test_eject.py b/tests/test_cli/test_eject.py index 790e832c4e..8ceed84c58 100644 --- a/tests/test_cli/test_eject.py +++ b/tests/test_cli/test_eject.py @@ -33,29 +33,29 @@ def test_eject_commands_positive(self): self.set_agent_context(agent_name) cwd = os.path.join(self.t, agent_name) - self.add_item("connection", "fetchai/gym:0.4.0") - self.add_item("skill", "fetchai/gym:0.4.0") - self.add_item("contract", "fetchai/erc1155:0.6.0") + self.add_item("connection", "fetchai/gym:0.5.0") + self.add_item("skill", "fetchai/gym:0.5.0") + self.add_item("contract", "fetchai/erc1155:0.7.0") - self.run_cli_command("eject", "connection", "fetchai/gym:0.4.0", cwd=cwd) + self.run_cli_command("eject", "connection", "fetchai/gym:0.5.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "connections")) ) assert "gym" in os.listdir((os.path.join(cwd, "connections"))) - self.run_cli_command("eject", "protocol", "fetchai/gym:0.3.0", cwd=cwd) + self.run_cli_command("eject", "protocol", "fetchai/gym:0.4.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "protocols")) ) assert "gym" in os.listdir((os.path.join(cwd, "protocols"))) - self.run_cli_command("eject", "skill", "fetchai/gym:0.4.0", cwd=cwd) + self.run_cli_command("eject", "skill", "fetchai/gym:0.5.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "gym" in os.listdir((os.path.join(cwd, "skills"))) - self.run_cli_command("eject", "contract", "fetchai/erc1155:0.6.0", cwd=cwd) + self.run_cli_command("eject", "contract", "fetchai/erc1155:0.7.0", cwd=cwd) assert "erc1155" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "contracts")) ) diff --git a/tests/test_cli/test_fetch.py b/tests/test_cli/test_fetch.py index e45ad11424..2f6781d5ce 100644 --- a/tests/test_cli/test_fetch.py +++ b/tests/test_cli/test_fetch.py @@ -149,5 +149,5 @@ class TestFetchFromRemoteRegistry(AEATestCaseMany): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_fetch_agent_from_remote_registry_positive(self): """Test fetch agent from Registry for positive result.""" - self.run_cli_command("fetch", "fetchai/my_first_aea:0.6.0") + self.run_cli_command("fetch", "fetchai/my_first_aea:0.7.0") assert "my_first_aea" in os.listdir(self.t) diff --git a/tests/test_cli/test_generate_key.py b/tests/test_cli/test_generate_key.py index 48d6e0e191..f30dd52e19 100644 --- a/tests/test_cli/test_generate_key.py +++ b/tests/test_cli/test_generate_key.py @@ -129,3 +129,44 @@ def teardown_class(cls): """Tear the test down.""" os.chdir(cls.cwd) shutil.rmtree(cls.t) + + +class TestGenerateKeyWithFile: + """Test that the command 'aea generate-key' can accept a file path.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.runner = CliRunner() + cls.agent_name = "myagent" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + def test_fetchai(self): + """Test that the fetchai private key can be deposited in a custom file.""" + test_file = "test.txt" + result = self.runner.invoke( + cli, [*CLI_LOG_OPTION, "generate-key", FETCHAI, test_file] + ) + assert result.exit_code == 0 + assert Path(test_file).exists() + + # This tests if the file has been created and its content is correct. + crypto = make_crypto(FETCHAI, private_key_path=test_file) + content = Path(test_file).read_bytes() + assert content.decode("utf-8") == crypto.private_key + + def test_all(self): + """Test that the all command does not allow a file to be provided.""" + test_file = "test.txt" + result = self.runner.invoke( + cli, [*CLI_LOG_OPTION, "generate-key", "all", test_file] + ) + assert result.exit_code == 1 + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + shutil.rmtree(cls.t) diff --git a/tests/test_cli/test_generate_wealth.py b/tests/test_cli/test_generate_wealth.py index 1b88f962e2..f5b83f2549 100644 --- a/tests/test_cli/test_generate_wealth.py +++ b/tests/test_cli/test_generate_wealth.py @@ -27,7 +27,12 @@ from aea.test_tools.exceptions import AEATestingException from aea.test_tools.test_cases import AEATestCaseMany -from tests.conftest import CLI_LOG_OPTION, CliRunner, FETCHAI +from tests.conftest import ( + CLI_LOG_OPTION, + CliRunner, + FETCHAI, + MAX_FLAKY_RERUNS_INTEGRATION, +) from tests.test_cli.tools_for_testing import ContextMock @@ -38,26 +43,26 @@ class WaitFundsReleaseTestCase(TestCase): def test__wait_funds_release_positive(self, try_get_balance_mock): """Test for _wait_funds_release method positive result.""" - _wait_funds_release("agent_config", "wallet", "type_") + with pytest.raises(ValueError): + _wait_funds_release("agent_config", "wallet", "type_") class GenerateWealthTestCase(TestCase): """Test case for _generate_wealth method.""" @mock.patch("aea.cli.generate_wealth.Wallet") - @mock.patch("aea.cli.generate_wealth.TESTNETS", {"type": "value"}) @mock.patch("aea.cli.generate_wealth.click.echo") @mock.patch("aea.cli.generate_wealth.try_generate_testnet_wealth") @mock.patch("aea.cli.generate_wealth._wait_funds_release") - @mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys") + @mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys_ctx") def test__generate_wealth_positive(self, *mocks): """Test for _generate_wealth method positive result.""" ctx = ContextMock() - _try_generate_wealth(ctx, "type", True) + _try_generate_wealth(ctx, "cosmos", True) @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys") +@mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.generate_wealth._try_generate_wealth") class GenerateWealthCommandTestCase(TestCase): """Test case for CLI generate_wealth command.""" @@ -82,9 +87,11 @@ def test_run_positive(self, *mocks): self.assertEqual(result.exit_code, 0) -class TestWealthCommands(AEATestCaseMany): +class TestWealthCommandsPositive(AEATestCaseMany): """Test case for CLI wealth commands.""" + @pytest.mark.integration + @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) def test_wealth_commands(self): """Test wealth commands.""" agent_name = "test_aea" @@ -97,6 +104,20 @@ def test_wealth_commands(self): self.generate_wealth() + +class TestWealthCommandsNegative(AEATestCaseMany): + """Test case for CLI wealth commands, negative case.""" + + def test_wealth_commands_negative(self): + """Test wealth commands.""" + agent_name = "test_aea" + self.create_agents(agent_name) + + self.set_agent_context(agent_name) + + self.generate_private_key() + self.add_private_key() + settings = {"unsupported_crypto": "path"} self.force_set_config("agent.private_key_paths", settings) with pytest.raises(AEATestingException) as excinfo: diff --git a/tests/test_cli/test_get_address.py b/tests/test_cli/test_get_address.py index 5badc75cbf..81ef1dffef 100644 --- a/tests/test_cli/test_get_address.py +++ b/tests/test_cli/test_get_address.py @@ -31,7 +31,7 @@ class GetAddressTestCase(TestCase): """Test case for _get_address method.""" @mock.patch("aea.cli.get_address.Wallet") - @mock.patch("aea.cli.get_address.verify_or_create_private_keys") + @mock.patch("aea.cli.get_address.verify_or_create_private_keys_ctx") def test__get_address_positive(self, *mocks): """Test for _get_address method positive result.""" ctx = ContextMock() @@ -39,7 +39,7 @@ def test__get_address_positive(self, *mocks): @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.get_address.verify_or_create_private_keys") +@mock.patch("aea.cli.get_address.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_address._try_get_address") @mock.patch("aea.cli.get_address.click.echo") class GetAddressCommandTestCase(TestCase): diff --git a/tests/test_cli/test_get_wealth.py b/tests/test_cli/test_get_wealth.py index eb1fa6f624..1d7f45d3a6 100644 --- a/tests/test_cli/test_get_wealth.py +++ b/tests/test_cli/test_get_wealth.py @@ -31,7 +31,7 @@ class GetWealthTestCase(TestCase): """Test case for _get_wealth method.""" @mock.patch("aea.cli.get_wealth.Wallet") - @mock.patch("aea.cli.get_wealth.verify_or_create_private_keys") + @mock.patch("aea.cli.get_wealth.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_wealth.try_get_balance") def test__get_wealth_positive(self, *mocks): """Test for _get_wealth method positive result.""" @@ -40,7 +40,7 @@ def test__get_wealth_positive(self, *mocks): @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.get_wealth.verify_or_create_private_keys") +@mock.patch("aea.cli.get_wealth.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_wealth._try_get_wealth") @mock.patch("aea.cli.get_wealth.click.echo") class GetWealthCommandTestCase(TestCase): diff --git a/tests/test_cli/test_remove/test_connection.py b/tests/test_cli/test_remove/test_connection.py index 6b3741c51a..06923f5cd7 100644 --- a/tests/test_cli/test_remove/test_connection.py +++ b/tests/test_cli/test_remove/test_connection.py @@ -47,7 +47,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" cls.connection_name = "http_client" os.chdir(cls.t) @@ -109,7 +109,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/local:0.4.0" + cls.connection_id = "fetchai/local:0.5.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -164,7 +164,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" cls.connection_name = "http_client" os.chdir(cls.t) diff --git a/tests/test_cli/test_remove/test_protocol.py b/tests/test_cli/test_remove/test_protocol.py index f9dfd1e25f..17a0d102ee 100644 --- a/tests/test_cli/test_remove/test_protocol.py +++ b/tests/test_cli/test_remove/test_protocol.py @@ -47,7 +47,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.protocol_id = "fetchai/gym:0.3.0" + cls.protocol_id = "fetchai/gym:0.4.0" cls.protocol_name = "gym" os.chdir(cls.t) @@ -109,7 +109,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.3.0" + cls.protocol_id = "fetchai/gym:0.4.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -164,7 +164,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.protocol_id = "fetchai/gym:0.3.0" + cls.protocol_id = "fetchai/gym:0.4.0" os.chdir(cls.t) result = cls.runner.invoke( diff --git a/tests/test_cli/test_remove/test_skill.py b/tests/test_cli/test_remove/test_skill.py index 1e2d6af90d..3eb2e7d7a7 100644 --- a/tests/test_cli/test_remove/test_skill.py +++ b/tests/test_cli/test_remove/test_skill.py @@ -45,7 +45,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.4.0" + cls.skill_id = "fetchai/gym:0.5.0" cls.skill_name = "gym" os.chdir(cls.t) @@ -114,7 +114,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.4.0" + cls.skill_id = "fetchai/gym:0.5.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -168,7 +168,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.4.0" + cls.skill_id = "fetchai/gym:0.5.0" cls.skill_name = "gym" os.chdir(cls.t) diff --git a/tests/test_cli/test_run.py b/tests/test_cli/test_run.py index 87757e0976..deb41536c2 100644 --- a/tests/test_cli/test_run.py +++ b/tests/test_cli/test_run.py @@ -16,6 +16,8 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + + """This test module contains the tests for the `aea run` sub-command.""" import os import shutil @@ -28,6 +30,8 @@ from click import ClickException +from pexpect.exceptions import EOF # type: ignore + import pytest import yaml @@ -75,7 +79,7 @@ def test_run(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 @@ -86,7 +90,7 @@ def test_run(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -167,9 +171,9 @@ def test_run_with_default_connection(): @pytest.mark.parametrize( argnames=["connection_ids"], argvalues=[ - ["fetchai/http_client:0.5.0,{}".format(str(DEFAULT_CONNECTION))], - ["'fetchai/http_client:0.5.0, {}'".format(str(DEFAULT_CONNECTION))], - ["fetchai/http_client:0.5.0,,{},".format(str(DEFAULT_CONNECTION))], + ["fetchai/http_client:0.6.0,{}".format(str(DEFAULT_CONNECTION))], + ["'fetchai/http_client:0.6.0, {}'".format(str(DEFAULT_CONNECTION))], + ["fetchai/http_client:0.6.0,,{},".format(str(DEFAULT_CONNECTION))], ], ) def test_run_multiple_connections(connection_ids): @@ -194,7 +198,7 @@ def test_run_multiple_connections(connection_ids): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 @@ -203,26 +207,24 @@ def test_run_multiple_connections(connection_ids): cli, [*CLI_LOG_OPTION, "add", "--local", "connection", str(DEFAULT_CONNECTION)] ) assert result.exit_code == 1 + process = PexpectWrapper( # nosec + [sys.executable, "-m", "aea.cli", "run", "--connections", connection_ids], + env=os.environ, + maxread=10000, + encoding="utf-8", + logfile=sys.stdout, + ) try: - process = subprocess.Popen( # nosec - [sys.executable, "-m", "aea.cli", "run", "--connections", connection_ids], - stdout=subprocess.PIPE, - env=os.environ.copy(), + process.expect_all(["Start processing messages"], timeout=20) + process.control_c() + process.expect( + EOF, timeout=20, ) - - time.sleep(5.0) - sigint_crossplatform(process) - process.wait(timeout=5) - + process.wait_to_complete(10) assert process.returncode == 0 - finally: - poll = process.poll() - if poll is None: - process.terminate() - process.wait(2) - + process.wait_to_complete(10) os.chdir(cwd) try: shutil.rmtree(t) @@ -252,7 +254,7 @@ def test_run_unknown_private_key(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -262,7 +264,7 @@ def test_run_unknown_private_key(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -291,7 +293,7 @@ def test_run_unknown_private_key(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.6.0"], standalone_mode=False, ) @@ -327,7 +329,7 @@ def test_run_fet_private_key_config(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 @@ -351,7 +353,7 @@ def test_run_fet_private_key_config(): error_msg = "" try: - cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.5.0"]) + cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.6.0"]) except SystemExit as e: error_msg = str(e) @@ -386,7 +388,7 @@ def test_run_ethereum_private_key_config(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 @@ -410,7 +412,7 @@ def test_run_ethereum_private_key_config(): error_msg = "" try: - cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.5.0"]) + cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.6.0"]) except SystemExit as e: error_msg = str(e) @@ -448,7 +450,7 @@ def test_run_with_install_deps(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -458,7 +460,7 @@ def test_run_with_install_deps(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -474,7 +476,7 @@ def test_run_with_install_deps(): "run", "--install-deps", "--connections", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], env=os.environ, maxread=10000, @@ -520,7 +522,7 @@ def test_run_with_install_deps_and_requirement_file(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.5.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.6.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -530,7 +532,7 @@ def test_run_with_install_deps_and_requirement_file(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -550,7 +552,7 @@ def test_run_with_install_deps_and_requirement_file(): "run", "--install-deps", "--connections", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], env=os.environ, maxread=10000, @@ -608,7 +610,7 @@ def setup_class(cls): "add", "--local", "connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], standalone_mode=False, ) @@ -625,7 +627,7 @@ def setup_class(cls): try: cli.main( - [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.5.0"] + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.6.0"] ) except SystemExit as e: cls.exit_code = e.code @@ -820,7 +822,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = PublicId.from_str("fetchai/http_client:0.5.0") + cls.connection_id = PublicId.from_str("fetchai/http_client:0.6.0") cls.connection_name = cls.connection_id.name cls.connection_author = cls.connection_id.author cls.cwd = os.getcwd() @@ -854,7 +856,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -913,7 +915,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = PublicId.from_str("fetchai/http_client:0.5.0") + cls.connection_id = PublicId.from_str("fetchai/http_client:0.6.0") cls.connection_author = cls.connection_id.author cls.connection_name = cls.connection_id.name cls.cwd = os.getcwd() @@ -947,7 +949,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -1005,7 +1007,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = "fetchai/http_client:0.5.0" + cls.connection_id = "fetchai/http_client:0.6.0" cls.connection_name = "http_client" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() @@ -1038,7 +1040,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.5.0", + "fetchai/http_client:0.6.0", ], ) assert result.exit_code == 0 @@ -1192,7 +1194,7 @@ def setup_class(cls): result = cls.runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "protocol", "fetchai/fipa:0.4.0"], + [*CLI_LOG_OPTION, "add", "--local", "protocol", "fetchai/fipa:0.5.0"], standalone_mode=False, ) assert result.exit_code == 0 diff --git a/tests/test_cli/test_search.py b/tests/test_cli/test_search.py index 34b9208800..abe4a8d29d 100644 --- a/tests/test_cli/test_search.py +++ b/tests/test_cli/test_search.py @@ -31,6 +31,7 @@ from aea import AEA_DIR from aea.cli import cli +from aea.configurations.base import PublicId from tests.conftest import ( AGENT_CONFIGURATION_SCHEMA, @@ -184,6 +185,11 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() os.chdir(cls.t) + cls.cli_config_file = f"{cls.t}/cli_config.yaml" + cls.cli_config_patch = mock.patch( + "aea.cli.utils.config.CLI_CONFIG_PATH", cls.cli_config_file + ) + cls.cli_config_patch.start() result = cls.runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR] ) @@ -233,6 +239,7 @@ def test_correct_output_default_registry(self): @classmethod def teardown_class(cls): """Tear the test down.""" + cls.cli_config_patch.stop() os.chdir(cls.cwd) try: shutil.rmtree(cls.t) @@ -351,23 +358,30 @@ def test_exit_code_equal_to_zero(self): def test_correct_output(self,): """Test that the command has printed the correct output..""" + public_id_echo = PublicId.from_str("fetchai/echo:0.4.0") + public_id_error = PublicId.from_str("fetchai/error:0.4.0") expected = ( 'Searching for ""...\n' "Skills found:\n\n" "------------------------------\n" - "Public ID: fetchai/echo:0.3.0\n" + "Public ID: {}\n" "Name: echo\n" "Description: The echo skill implements simple echo functionality.\n" "Author: fetchai\n" - "Version: 0.3.0\n" + "Version: {}\n" "------------------------------\n" "------------------------------\n" - "Public ID: fetchai/error:0.3.0\n" + "Public ID: {}\n" "Name: error\n" "Description: The error skill implements basic error handling required by all AEAs.\n" "Author: fetchai\n" - "Version: 0.3.0\n" + "Version: {}\n" "------------------------------\n\n" + ).format( + str(public_id_echo), + str(public_id_echo.version), + str(public_id_error), + str(public_id_error.version), ) assert self.result.output == expected @@ -426,23 +440,30 @@ def test_exit_code_equal_to_zero(self): def test_correct_output(self,): """Test that the command has printed the correct output..""" + public_id_echo = PublicId.from_str("fetchai/echo:0.4.0") + public_id_error = PublicId.from_str("fetchai/error:0.4.0") expected = ( 'Searching for ""...\n' "Skills found:\n\n" "------------------------------\n" - "Public ID: fetchai/echo:0.3.0\n" + "Public ID: {}\n" "Name: echo\n" "Description: The echo skill implements simple echo functionality.\n" "Author: fetchai\n" - "Version: 0.3.0\n" + "Version: {}\n" "------------------------------\n" "------------------------------\n" - "Public ID: fetchai/error:0.3.0\n" + "Public ID: {}\n" "Name: error\n" "Description: The error skill implements basic error handling required by all AEAs.\n" "Author: fetchai\n" - "Version: 0.3.0\n" + "Version: {}\n" "------------------------------\n\n" + ).format( + str(public_id_echo), + str(public_id_echo.version), + str(public_id_error), + str(public_id_error.version), ) assert self.result.output == expected diff --git a/tests/test_cli/test_utils/test_utils.py b/tests/test_cli/test_utils/test_utils.py index 2ebfa11971..da370da07a 100644 --- a/tests/test_cli/test_utils/test_utils.py +++ b/tests/test_cli/test_utils/test_utils.py @@ -275,7 +275,7 @@ class FindItemLocallyTestCase(TestCase): ) def test_find_item_locally_bad_config(self, *mocks): """Test find_item_locally for bad config result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.3.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.4.0") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) @@ -289,7 +289,7 @@ def test_find_item_locally_bad_config(self, *mocks): ) def test_find_item_locally_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.3.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.4.0") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) @@ -308,7 +308,7 @@ class FindItemInDistributionTestCase(TestCase): ) def testfind_item_in_distribution_bad_config(self, *mocks): """Test find_item_in_distribution for bad config result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.3.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.4.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) @@ -317,7 +317,7 @@ def testfind_item_in_distribution_bad_config(self, *mocks): @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=False) def testfind_item_in_distribution_not_found(self, *mocks): """Test find_item_in_distribution for not found result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.3.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.4.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) @@ -331,7 +331,7 @@ def testfind_item_in_distribution_not_found(self, *mocks): ) def testfind_item_in_distribution_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.3.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.4.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) diff --git a/tests/test_cli_gui/test_run_agent.py b/tests/test_cli_gui/test_run_agent.py index 0e840f7ade..e88796c009 100644 --- a/tests/test_cli_gui/test_run_agent.py +++ b/tests/test_cli_gui/test_run_agent.py @@ -64,7 +64,7 @@ def test_create_and_run_agent(): response_add = app.post( "api/agent/" + agent_id + "/connection", content_type="application/json", - data=json.dumps("fetchai/local:0.4.0"), + data=json.dumps("fetchai/local:0.5.0"), ) assert response_add.status_code == 201 diff --git a/tests/test_configurations/test_aea_config.py b/tests/test_configurations/test_aea_config.py index 2c717be957..60ee503df0 100644 --- a/tests/test_configurations/test_aea_config.py +++ b/tests/test_configurations/test_aea_config.py @@ -55,7 +55,7 @@ class NotSet(type): contracts: [] protocols: [] skills: [] -default_connection: fetchai/stub:0.6.0 +default_connection: fetchai/stub:0.7.0 default_ledger: cosmos private_key_paths: cosmos: tests/data/cosmos_private_key.txt diff --git a/tests/test_configurations/test_loader.py b/tests/test_configurations/test_loader.py index e96d1bc802..834178ade9 100644 --- a/tests/test_configurations/test_loader.py +++ b/tests/test_configurations/test_loader.py @@ -88,6 +88,24 @@ def test_load_protocol_specification_only_first_part(): load_protocol_specification("foo") +def test_load_protocol_specification_two_parts(): + """Test 'load_protocol_specification' with two parts.""" + valid_protocol_specification = dict( + name="name", + author="author", + version="0.1.0", + license="", + aea_version="0.1.0", + speech_acts={"example": {}}, + ) + with mock.patch.object( + yaml, + "safe_load_all", + return_value=[valid_protocol_specification, valid_protocol_specification], + ), mock.patch("builtins.open"), mock.patch("jsonschema.Draft4Validator.validate"): + load_protocol_specification("foo") + + def test_load_protocol_specification_too_many_parts(): """Test 'load_protocol_specification' with more than three parts.""" with pytest.raises( diff --git a/tests/test_connections/test_stub.py b/tests/test_connections/test_stub.py index 42a31ade40..b5b19ef376 100644 --- a/tests/test_connections/test_stub.py +++ b/tests/test_connections/test_stub.py @@ -137,11 +137,11 @@ def test_reception_b(self): def test_reception_c(self): """Test that the connection receives what has been enqueued in the input file.""" - encoded_envelope = b"0x5E22777dD831A459535AA4306AceC9cb22eC4cB5,default_oef,fetchai/oef_search:0.3.0,\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd," + encoded_envelope = b"0x5E22777dD831A459535AA4306AceC9cb22eC4cB5,default_oef,fetchai/oef_search:0.4.0,\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd," expected_envelope = Envelope( to="0x5E22777dD831A459535AA4306AceC9cb22eC4cB5", sender="default_oef", - protocol_id=PublicId.from_str("fetchai/oef_search:0.3.0"), + protocol_id=PublicId.from_str("fetchai/oef_search:0.4.0"), message=b"\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd", ) with open(self.input_file_path, "ab+") as f: @@ -199,7 +199,7 @@ def setup_class(cls): def test_connection_is_established(self): """Test the stub connection is established and then bad formatted messages.""" - assert self.connection.connection_status.is_connected + assert self.connection.is_connected msg = DefaultMessage( dialogue_reference=("", ""), message_id=1, @@ -287,9 +287,9 @@ async def test_disconnection_when_already_disconnected(): output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) - assert not connection.connection_status.is_connected + assert not connection.is_connected await connection.disconnect() - assert not connection.connection_status.is_connected + assert not connection.is_connected @pytest.mark.asyncio @@ -302,11 +302,11 @@ async def test_connection_when_already_connected(): output_file_path = d / "output_file.csv" connection = _make_stub_connection(input_file_path, output_file_path) - assert not connection.connection_status.is_connected + assert not connection.is_connected await connection.connect() - assert connection.connection_status.is_connected + assert connection.is_connected await connection.connect() - assert connection.connection_status.is_connected + assert connection.is_connected await connection.disconnect() diff --git a/tests/test_crypto/test_cosmos.py b/tests/test_crypto/test_cosmos.py index 84adf7630a..909390c7b0 100644 --- a/tests/test_crypto/test_cosmos.py +++ b/tests/test_crypto/test_cosmos.py @@ -66,6 +66,13 @@ def test_sign_and_recover_message(): ), "Failed to recover the correct address." +def test_get_hash(): + """Test the get hash functionality.""" + expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + hash_ = CosmosApi.get_hash(message=b"hello") + assert expected_hash == hash_ + + def test_dump_positive(): """Test dump.""" account = CosmosCrypto(COSMOS_PRIVATE_KEY_PATH) @@ -169,3 +176,97 @@ def test_get_wealth_positive(caplog): cc = CosmosCrypto() cosmos_faucet_api.get_wealth(cc.address) assert "Wealth generated" in caplog.text + + +@pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) +@pytest.mark.integration +@pytest.mark.ledger +def test_format_default(): + """Test if default CosmosSDK transaction is correctly formated.""" + account = CosmosCrypto(COSMOS_PRIVATE_KEY_PATH) + cc2 = CosmosCrypto() + cosmos_api = CosmosApi(**COSMOS_TESTNET_CONFIG) + + amount = 10000 + + transfer_transaction = cosmos_api.get_transfer_transaction( + sender_address=account.address, + destination_address=cc2.address, + amount=amount, + tx_fee=1000, + tx_nonce="something", + ) + + signed_transaction = cc2.sign_transaction(transfer_transaction) + + assert "tx" in signed_transaction + assert "signatures" in signed_transaction["tx"] + assert len(signed_transaction["tx"]["signatures"]) == 1 + + assert "pub_key" in signed_transaction["tx"]["signatures"][0] + assert "value" in signed_transaction["tx"]["signatures"][0]["pub_key"] + base64_pbk = signed_transaction["tx"]["signatures"][0]["pub_key"]["value"] + + assert "signature" in signed_transaction["tx"]["signatures"][0] + signature = signed_transaction["tx"]["signatures"][0]["signature"] + + default_formated_transaction = cc2.format_default_transaction( + transfer_transaction, signature, base64_pbk + ) + + # Compare default formatted transaction with signed transaction + assert signed_transaction == default_formated_transaction + + +@pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) +@pytest.mark.integration +@pytest.mark.ledger +def test_format_cosmwasm(): + """Test if CosmWasm transaction is correctly formated.""" + cc2 = CosmosCrypto() + + # Dummy CosmWasm transaction + wasm_transaction = { + "account_number": "8", + "chain_id": "agent-land", + "fee": {"amount": [], "gas": "200000"}, + "memo": "", + "msgs": [ + { + "type": "wasm/execute", + "value": { + "sender": "cosmos14xjnl2mwwfz6pztpwzj6s89npxr0e3lhxl52nv", + "contract": "cosmos1xzlgeyuuyqje79ma6vllregprkmgwgav5zshcm", + "msg": { + "create_single": { + "item_owner": "cosmos1fz0dcvvqv5at6dl39804jy92lnndf3d5saalx6", + "id": "1234", + "path": "SOME_URI", + } + }, + "sent_funds": [], + }, + } + ], + "sequence": "25", + } + + signed_transaction = cc2.sign_transaction(wasm_transaction) + + assert "value" in signed_transaction + assert "signatures" in signed_transaction["value"] + assert len(signed_transaction["value"]["signatures"]) == 1 + + assert "pub_key" in signed_transaction["value"]["signatures"][0] + assert "value" in signed_transaction["value"]["signatures"][0]["pub_key"] + base64_pbk = signed_transaction["value"]["signatures"][0]["pub_key"]["value"] + + assert "signature" in signed_transaction["value"]["signatures"][0] + signature = signed_transaction["value"]["signatures"][0]["signature"] + + wasm_formated_transaction = cc2.format_wasm_transaction( + wasm_transaction, signature, base64_pbk + ) + + # Compare Wasm formatted transaction with signed transaction + assert signed_transaction == wasm_formated_transaction diff --git a/tests/test_crypto/test_ethereum.py b/tests/test_crypto/test_ethereum.py index 3ff715ae20..6cbf0b79f7 100644 --- a/tests/test_crypto/test_ethereum.py +++ b/tests/test_crypto/test_ethereum.py @@ -95,6 +95,13 @@ def test_sign_and_recover_message_deprecated(): ), "Failed to recover the correct address." +def test_get_hash(): + """Test the get hash functionality.""" + expected_hash = "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8" + hash_ = EthereumApi.get_hash(message=b"hello") + assert expected_hash == hash_ + + def test_dump_positive(): """Test dump.""" account = EthereumCrypto(ETHEREUM_PRIVATE_KEY_PATH) diff --git a/tests/test_crypto/test_fetchai.py b/tests/test_crypto/test_fetchai.py index 7bdb7471d7..41421b7f8f 100644 --- a/tests/test_crypto/test_fetchai.py +++ b/tests/test_crypto/test_fetchai.py @@ -69,6 +69,13 @@ def test_get_address_from_public_key(): assert address == fet_crypto.address, "The address must be the same." +def test_get_hash(): + """Test the get hash functionality.""" + expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + hash_ = FetchAIApi.get_hash(message=b"hello") + assert expected_hash == hash_ + + def test_dump_positive(): """Test dump.""" account = FetchAICrypto(FETCHAI_PRIVATE_KEY_PATH) diff --git a/tests/test_crypto/test_helpers.py b/tests/test_crypto/test_helpers.py index 653e048b21..cbd558893b 100644 --- a/tests/test_crypto/test_helpers.py +++ b/tests/test_crypto/test_helpers.py @@ -21,6 +21,7 @@ import logging import os +from pathlib import Path from unittest.mock import mock_open, patch import pytest @@ -34,9 +35,16 @@ create_private_key, try_generate_testnet_wealth, try_validate_private_key_path, + verify_or_create_private_keys, ) -from tests.conftest import CUR_PATH, ETHEREUM_PRIVATE_KEY_PATH, FETCHAI_PRIVATE_KEY_PATH +from tests.conftest import ( + COSMOS_PRIVATE_KEY_FILE, + CUR_PATH, + ETHEREUM_PRIVATE_KEY_FILE, + ETHEREUM_PRIVATE_KEY_PATH, + FETCHAI_PRIVATE_KEY_PATH, +) logger = logging.getLogger(__name__) @@ -129,9 +137,15 @@ def test_try_validate_private_key_path_positive(self): @patch("builtins.open", mock_open()) def test__create_ethereum_private_key_positive(self, *mocks): """Test _create_ethereum_private_key positive result.""" - create_private_key(EthereumCrypto.identifier) + create_private_key(EthereumCrypto.identifier, ETHEREUM_PRIVATE_KEY_FILE) @patch("builtins.open", mock_open()) def test__create_cosmos_private_key_positive(self, *mocks): """Test _create_cosmos_private_key positive result.""" - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) + + @patch("aea.crypto.helpers.create_private_key") + @patch("aea.crypto.helpers.try_validate_private_key_path") + def test_verify_or_create_private_keys(self, *mocks): + """Test _create_ethereum_private_key positive result.""" + verify_or_create_private_keys(Path(os.path.join(CUR_PATH, "data", "dummy_aea"))) diff --git a/tests/test_crypto/test_ledger_apis.py b/tests/test_crypto/test_ledger_apis.py index 82a3f0a8b8..a308cc991b 100644 --- a/tests/test_crypto/test_ledger_apis.py +++ b/tests/test_crypto/test_ledger_apis.py @@ -28,15 +28,11 @@ from aea.crypto.ethereum import EthereumApi from aea.crypto.fetchai import FetchAIApi from aea.crypto.ledger_apis import LedgerApis -from aea.exceptions import AEAException from tests.conftest import ( - COSMOS_TESTNET_CONFIG, ETHEREUM_ADDRESS_ONE, - ETHEREUM_TESTNET_CONFIG, FETCHAI, FETCHAI_ADDRESS_ONE, - FETCHAI_TESTNET_CONFIG, ) logger = logging.getLogger(__name__) @@ -48,24 +44,15 @@ def _raise_exception(*args, **kwargs): def test_initialisation(): """Test the initialisation of the ledger APIs.""" - ledger_apis = LedgerApis( - { - EthereumApi.identifier: ETHEREUM_TESTNET_CONFIG, - FetchAIApi.identifier: FETCHAI_TESTNET_CONFIG, - CosmosApi.identifier: COSMOS_TESTNET_CONFIG, - }, - FetchAIApi.identifier, - ) - assert ledger_apis.configs.get(EthereumApi.identifier) == ETHEREUM_TESTNET_CONFIG + ledger_apis = LedgerApis assert ledger_apis.has_ledger(FetchAIApi.identifier) - assert type(ledger_apis.get_api(FetchAIApi.identifier)) == FetchAIApi - assert ledger_apis.has_ledger(EthereumApi.identifier) - assert type(ledger_apis.get_api(EthereumApi.identifier)) == EthereumApi - assert ledger_apis.has_ledger(CosmosApi.identifier) - assert type(ledger_apis.get_api(CosmosApi.identifier)) == CosmosApi - unknown_config = {"UnknownPath": 8080} - with pytest.raises(AEAException): - LedgerApis({"UNKNOWN": unknown_config}, FetchAIApi.identifier) + assert type(LedgerApis.get_api(FetchAIApi.identifier)) == FetchAIApi + assert LedgerApis.has_ledger(EthereumApi.identifier) + assert type(LedgerApis.get_api(EthereumApi.identifier)) == EthereumApi + assert LedgerApis.has_ledger(CosmosApi.identifier) + assert type(LedgerApis.get_api(CosmosApi.identifier)) == CosmosApi + with pytest.raises(AssertionError): + ledger_apis.get_api("UNKNOWN") class TestLedgerApis: @@ -74,37 +61,20 @@ class TestLedgerApis: @classmethod def setup_class(cls): """Setup the test case.""" - cls.ledger_apis = LedgerApis( - { - EthereumApi.identifier: ETHEREUM_TESTNET_CONFIG, - FetchAIApi.identifier: FETCHAI_TESTNET_CONFIG, - }, - FetchAIApi.identifier, - ) + cls.ledger_apis = LedgerApis def test_get_balance(self): """Test the get_balance.""" - api = self.ledger_apis.apis[EthereumApi.identifier] - with mock.patch.object(api.api.eth, "getBalance", return_value=10): + with mock.patch.object(EthereumApi, "get_balance", return_value=10): balance = self.ledger_apis.get_balance( EthereumApi.identifier, ETHEREUM_ADDRESS_ONE ) assert balance == 10 - with mock.patch.object( - api.api.eth, "getBalance", return_value=0, side_effect=Exception - ): - balance = self.ledger_apis.get_balance( - EthereumApi.identifier, FETCHAI_ADDRESS_ONE - ) - assert balance is None, "This must be None since the address is wrong" - def test_get_transfer_transaction(self): """Test the get_transfer_transaction.""" with mock.patch.object( - self.ledger_apis.apis.get(FetchAIApi.identifier), - "get_transfer_transaction", - return_value="mock_transaction", + FetchAIApi, "get_transfer_transaction", return_value="mock_transaction", ): tx = self.ledger_apis.get_transfer_transaction( identifier=FETCHAI, @@ -119,7 +89,7 @@ def test_get_transfer_transaction(self): def test_send_signed_transaction(self): """Test the send_signed_transaction.""" with mock.patch.object( - self.ledger_apis.apis.get(FetchAIApi.identifier), + FetchAIApi, "send_signed_transaction", return_value="mock_transaction_digest", ): @@ -131,7 +101,7 @@ def test_send_signed_transaction(self): def test_get_transaction_receipt(self): """Test the get_transaction_receipt.""" with mock.patch.object( - self.ledger_apis.apis.get(FetchAIApi.identifier), + FetchAIApi, "get_transaction_receipt", return_value="mock_transaction_receipt", ): @@ -143,9 +113,7 @@ def test_get_transaction_receipt(self): def test_get_transaction(self): """Test the get_transaction.""" with mock.patch.object( - self.ledger_apis.apis.get(FetchAIApi.identifier), - "get_transaction", - return_value="mock_transaction", + FetchAIApi, "get_transaction", return_value="mock_transaction", ): tx = self.ledger_apis.get_transaction( identifier=FETCHAI, tx_digest="tx_digest", @@ -177,13 +145,27 @@ def test_is_transaction_valid(self): ) assert is_valid + def test_recover_message(self): + """Test the is_transaction_valid.""" + expected_addresses = ("address_1", "address_2") + with mock.patch.object( + FetchAIApi, "recover_message", return_value=expected_addresses, + ): + addresses = self.ledger_apis.recover_message( + identifier="fetchai", message="message", signature="signature", + ) + assert addresses == expected_addresses + + def test_get_hash(self): + """Test the is_transaction_valid.""" + expected_hash = "hash" + with mock.patch.object( + FetchAIApi, "get_hash", return_value=expected_hash, + ): + hash_ = self.ledger_apis.get_hash(identifier="fetchai", message=b"message",) + assert hash_ == expected_hash + def test_generate_tx_nonce_positive(self): """Test generate_tx_nonce positive result.""" - result = self.ledger_apis.generate_tx_nonce( - FetchAIApi.identifier, "seller", "client" - ) + result = LedgerApis.generate_tx_nonce(FetchAIApi.identifier, "seller", "client") assert int(result, 16) - - def test_has_default_ledger_positive(self): - """Test has_default_ledger init positive result.""" - assert self.ledger_apis.has_default_ledger diff --git a/tests/test_crypto/test_wallet.py b/tests/test_crypto/test_wallet.py index b1eff2adf0..2cb4df79ab 100644 --- a/tests/test_crypto/test_wallet.py +++ b/tests/test_crypto/test_wallet.py @@ -104,6 +104,18 @@ def test_wallet_addresses_positive(self): tuple(addresses), (EthereumCrypto.identifier, FetchAICrypto.identifier) ) + def test_wallet_private_keys_positive(self): + """Test Wallet.private_keys init positive result.""" + private_key_paths = { + EthereumCrypto.identifier: ETHEREUM_PRIVATE_KEY_PATH, + FetchAICrypto.identifier: FETCHAI_PRIVATE_KEY_PATH, + } + wallet = Wallet(private_key_paths) + private_keys = wallet.private_keys + self.assertTupleEqual( + tuple(private_keys), (EthereumCrypto.identifier, FetchAICrypto.identifier) + ) + def test_wallet_cryptos_positive(self): """Test Wallet.main_cryptos and connection cryptos init positive result.""" private_key_paths = { diff --git a/tests/test_decision_maker/test_default.py b/tests/test_decision_maker/test_default.py index 906abd92fc..820070df92 100644 --- a/tests/test_decision_maker/test_default.py +++ b/tests/test_decision_maker/test_default.py @@ -157,7 +157,7 @@ def _unpatch_logger(cls): cls.mocked_logger_warning.__exit__() @classmethod - def setup_class(cls): + def setup(cls): """Initialise the decision maker.""" cls._patch_logger() cls.wallet = Wallet( @@ -239,7 +239,7 @@ def test_decision_maker_handle_state_update_initialize_and_apply(self): quantities_by_good_id=good_deltas, ) state_update_message_2.counterparty = "decision_maker" - state_update_dialogue.update(state_update_message_2) + assert state_update_dialogue.update(state_update_message_2) self.decision_maker.handle(state_update_message_2) expected_amount_by_currency_id = { key: currency_holdings.get(key, 0) + currency_deltas.get(key, 0) @@ -259,7 +259,7 @@ def test_decision_maker_handle_state_update_initialize_and_apply(self): ) @classmethod - def teardown_class(cls): + def teardown(cls): """Tear the tests down.""" cls._unpatch_logger() cls.decision_maker.stop() @@ -280,7 +280,7 @@ def _unpatch_logger(cls): cls.mocked_logger_warning.__exit__() @classmethod - def setup_class(cls): + def setup(cls): """Initialise the decision maker.""" cls._patch_logger() cls.wallet = Wallet( @@ -333,7 +333,7 @@ def test_handle_tx_signing_fetchai(self): 1, [], ) - signing_dialogues = SigningDialogues("agent1") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -351,8 +351,14 @@ def test_handle_tx_signing_fetchai(self): raw_transaction=RawTransaction(FETCHAI, tx), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_TRANSACTION @@ -363,7 +369,7 @@ def test_handle_tx_signing_fetchai(self): def test_handle_tx_signing_ethereum(self): """Test tx signing for ethereum.""" tx = {"gasPrice": 30, "nonce": 1, "gas": 20000} - signing_dialogues = SigningDialogues("agent2") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -381,8 +387,14 @@ def test_handle_tx_signing_ethereum(self): raw_transaction=RawTransaction(ETHEREUM, tx), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_TRANSACTION @@ -396,7 +408,7 @@ def test_handle_tx_signing_ethereum(self): def test_handle_tx_signing_unknown(self): """Test tx signing for unknown.""" tx = {} - signing_dialogues = SigningDialogues("agent3") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -414,8 +426,14 @@ def test_handle_tx_signing_unknown(self): raw_transaction=RawTransaction("unknown", tx), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert signing_msg_response.performative == SigningMessage.Performative.ERROR assert signing_msg_response.skill_callback_ids == signing_msg.skill_callback_ids assert ( @@ -426,7 +444,7 @@ def test_handle_tx_signing_unknown(self): def test_handle_message_signing_fetchai(self): """Test message signing for fetchai.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" - signing_dialogues = SigningDialogues("agent4") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -444,8 +462,14 @@ def test_handle_message_signing_fetchai(self): raw_message=RawMessage(FETCHAI, message), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE @@ -456,7 +480,7 @@ def test_handle_message_signing_fetchai(self): def test_handle_message_signing_ethereum(self): """Test message signing for ethereum.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" - signing_dialogues = SigningDialogues("agent4") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -474,8 +498,14 @@ def test_handle_message_signing_ethereum(self): raw_message=RawMessage(ETHEREUM, message), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE @@ -486,7 +516,7 @@ def test_handle_message_signing_ethereum(self): def test_handle_message_signing_ethereum_deprecated(self): """Test message signing for ethereum deprecated.""" message = b"0x11f3f9487724404e3a1fb7252a3226" - signing_dialogues = SigningDialogues("agent5") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -504,8 +534,14 @@ def test_handle_message_signing_ethereum_deprecated(self): raw_message=RawMessage(ETHEREUM, message, is_deprecated_mode=True), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_MESSAGE @@ -514,10 +550,10 @@ def test_handle_message_signing_ethereum_deprecated(self): assert type(signing_msg_response.signed_message) == SignedMessage assert signing_msg_response.signed_message.is_deprecated_mode - def test_handle_message_signing_unknown(self): + def test_handle_message_signing_unknown_and_two_dialogues(self): """Test message signing for unknown.""" message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" - signing_dialogues = SigningDialogues("agent6") + signing_dialogues = SigningDialogues("agent") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_MESSAGE, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), @@ -535,8 +571,14 @@ def test_handle_message_signing_unknown(self): raw_message=RawMessage("unknown", message), ) signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + signing_msg_response.counterparty = signing_msg.counterparty + signing_msg_response.is_incoming = True + recovered_dialogue = signing_dialogues.update(signing_msg_response) + assert recovered_dialogue is not None and recovered_dialogue == signing_dialogue assert signing_msg_response.performative == SigningMessage.Performative.ERROR assert signing_msg_response.skill_callback_ids == signing_msg.skill_callback_ids assert ( @@ -544,8 +586,59 @@ def test_handle_message_signing_unknown(self): == SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING ) + def test_handle_messages_from_two_dialogues_same_agent(self): + """Test message signing for unknown.""" + message = b"0x11f3f9487724404e3a1fb7252a322656b90ba0455a2ca5fcdcbe6eeee5f8126d" + signing_dialogues = SigningDialogues("agent") + signing_msg = SigningMessage( + performative=SigningMessage.Performative.SIGN_MESSAGE, + dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), + skill_callback_ids=(str(PublicId("author", "a_skill", "0.1.0")),), + skill_callback_info={}, + terms=Terms( + ledger_id="unknown", + sender_address="pk1", + counterparty_address="pk2", + amount_by_currency_id={"FET": -1}, + is_sender_payable_tx_fee=True, + quantities_by_good_id={"good_id": 10}, + nonce="transaction nonce", + ), + raw_message=RawMessage("unknown", message), + ) + signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None + self.decision_maker.message_in_queue.put_nowait(signing_msg) + signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) + assert signing_msg_response is not None + signing_dialogues = SigningDialogues("agent") + signing_msg = SigningMessage( + performative=SigningMessage.Performative.SIGN_MESSAGE, + dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), + skill_callback_ids=(str(PublicId("author", "a_skill", "0.1.0")),), + skill_callback_info={}, + terms=Terms( + ledger_id="unknown", + sender_address="pk1", + counterparty_address="pk2", + amount_by_currency_id={"FET": -1}, + is_sender_payable_tx_fee=True, + quantities_by_good_id={"good_id": 10}, + nonce="transaction nonce", + ), + raw_message=RawMessage("unknown", message), + ) + signing_msg.counterparty = "decision_maker" + signing_dialogue = signing_dialogues.update(signing_msg) + assert signing_dialogue is not None + self.decision_maker.message_in_queue.put_nowait(signing_msg) + with pytest.raises(Exception): + # Exception occurs because the same counterparty sends two identical dialogue references + self.decision_maker.message_out_queue.get(timeout=1) + @classmethod - def teardown_class(cls): + def teardown(cls): """Tear the tests down.""" cls._unpatch_logger() cls.decision_maker.stop() diff --git a/tests/test_docs/test_agent_vs_aea/agent_code_block.py b/tests/test_docs/test_agent_vs_aea/agent_code_block.py index 9ea0ac8d70..f7125f336e 100644 --- a/tests/test_docs/test_agent_vs_aea/agent_code_block.py +++ b/tests/test_docs/test_agent_vs_aea/agent_code_block.py @@ -60,6 +60,8 @@ def react(self): envelope.to = sender envelope.sender = receiver envelope.message = DefaultMessage.serializer.decode(envelope.message) + envelope.message.sender = receiver + envelope.message.counterparty = sender print( "Received envelope from {} with protocol_id={}".format( sender, envelope.protocol_id @@ -105,7 +107,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: f.write(message_text) diff --git a/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py b/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py index fc04436a4d..49c063d050 100644 --- a/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py +++ b/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py @@ -60,7 +60,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, "input_file")) message_text = ( - b"other_agent,my_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + b"other_agent,my_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(self.t, "output_file") with open(path, "rb") as file: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md b/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md index 01ed53e241..318725812a 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md @@ -11,53 +11,23 @@ aca-py start --admin 127.0.0.1 8021 --admin-insecure-mode --inbound-transport ht aca-py start --admin 127.0.0.1 8031 --admin-insecure-mode --inbound-transport http 0.0.0.0 8030 --outbound-transp http --webhook-url http://127.0.0.1:8032/webhooks ``` ``` bash -aea create aries_alice +aea fetch fetchai/aries_alice:0.7.0 cd aries_alice ``` ``` bash -aea add skill fetchai/aries_alice:0.3.0 -``` -``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_host 127.0.0.1 -``` -``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_port 8031 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_port 8031 -``` -``` bash -aea add connection fetchai/http_client:0.5.0 -aea add connection fetchai/webhook:0.4.0 -aea add connection fetchai/oef:0.6.0 -``` -``` bash -aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 -``` -``` bash -aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ -``` -``` bash -aea config set agent.default_connection fetchai/oef:0.6.0 -``` -``` bash -aea fetch fetchai/aries_alice:0.6.0 +aea create aries_alice cd aries_alice +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/http_client:0.6.0 +aea add connection fetchai/webhook:0.5.0 +aea add skill fetchai/aries_alice:0.4.0 ``` ``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_host 127.0.0.1 +aea config set vendor.fetchai.skills.aries_alice.behaviours.alice.args.admin_host 127.0.0.1 ``` ``` bash -aea config set vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_default.args.admin_port 8031 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_alice.handlers.aries_demo_http.args.admin_port 8031 +aea config set --type int vendor.fetchai.skills.aries_alice.behaviours.alice.args.admin_port 8031 ``` ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8032 @@ -72,34 +42,23 @@ aea install aea run ``` ``` bash -My address is: YrP7H2qdCb3VyPwpQa53o39cWCDHhVcjwCtJLes6HKWM8FpVK +aea fetch fetchai/aries_faber:0.7.0 +cd aries_faber ``` ``` bash aea create aries_faber cd aries_faber -``` -``` bash +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/http_client:0.6.0 +aea add connection fetchai/webhook:0.5.0 aea add skill fetchai/aries_faber:0.3.0 ``` ``` bash -aea config set vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_host 127.0.0.1 +aea config set vendor.fetchai.skills.aries_faber.behaviours.faber.args.admin_host 127.0.0.1 ``` ``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_port 8021 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_port 8021 -``` -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.alice_id -``` -``` bash -aea add connection fetchai/http_client:0.5.0 -aea add connection fetchai/webhook:0.4.0 -aea add connection fetchai/oef:0.6.0 +aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.faber.args.admin_port 8021 ``` ``` bash aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 @@ -107,33 +66,13 @@ aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port ``` bash aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ ``` -``` bash -aea config set agent.default_connection fetchai/http_client:0.5.0 -``` -``` bash -aea fetch fetchai/aries_faber:0.6.0 -cd aries_faber -``` -``` bash -aea config set vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_host 127.0.0.1 -``` -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_host 127.0.0.1 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.behaviours.aries_demo_faber.args.admin_port 8021 -``` -``` bash -aea config set --type int vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.admin_port 8021 -``` -``` bash -aea config set vendor.fetchai.skills.aries_faber.handlers.aries_demo_http.args.alice_id -``` -``` bash -aea config set --type int vendor.fetchai.connections.webhook.config.webhook_port 8022 -``` -``` bash -aea config set vendor.fetchai.connections.webhook.config.webhook_url_path /webhooks/topic/{topic}/ +``` yaml +config: + delegate_uri: 127.0.0.1:11001 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9001 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9001 ``` ``` bash aea install diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md index 0b697f31f2..bb97393016 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/car_detector:0.8.0 +aea fetch fetchai/car_detector:0.9.0 cd car_detector aea install ``` ``` bash aea create car_detector cd car_detector -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/carpark_detection:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/carpark_detection:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/car_data_buyer:0.8.0 +aea fetch fetchai/car_data_buyer:0.9.0 cd car_data_buyer aea install ``` ``` bash aea create car_data_buyer cd car_data_buyer -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/carpark_client:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/carpark_client:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea generate-key cosmos @@ -54,13 +54,13 @@ aea delete car_data_buyer ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-cli-how-to.md b/tests/test_docs/test_bash_yaml/md_files/bash-cli-how-to.md index 89452aad60..d125e1fbee 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-cli-how-to.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-cli-how-to.md @@ -7,3 +7,9 @@ pip install aea[all] ``` bash pip install aea[all] --force --no-cache-dir ``` +```bash +aea +``` +``` bash +python -m aea.cli +``` \ No newline at end of file diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md b/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md index 4cd58d6a40..1c74dc9202 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md @@ -2,7 +2,7 @@ svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` ``` bash -aea fetch fetchai/weather_station:0.8.0 +aea fetch fetchai/weather_station:0.9.0 cd weather_station ``` ``` bash diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-config.md b/tests/test_docs/test_bash_yaml/md_files/bash-config.md index d96c6f3cfe..b65193795b 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-config.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-config.md @@ -14,13 +14,13 @@ aea_version: '>=0.5.0, <0.6.0' # AEA framework version(s) compa fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/error:0.3.0 -default_connection: fetchai/oef:0.6.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). +- fetchai/error:0.4.0 +default_connection: fetchai/p2p_libp2p:0.6.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: cosmos # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md index 84c7c81516..c389beb6ab 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md @@ -1,19 +1,17 @@ ``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` -``` bash -aea fetch fetchai/erc1155_deployer:0.9.0 +aea fetch fetchai/erc1155_deployer:0.10.0 cd erc1155_deployer aea install ``` ``` bash aea create erc1155_deployer cd erc1155_deployer -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/erc1155_deploy:0.9.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/erc1155_deploy:0.10.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea config set agent.default_ledger ethereum @@ -23,18 +21,23 @@ aea generate-key ethereum aea add-key ethereum eth_private_key.txt ``` ``` bash -aea fetch fetchai/erc1155_client:0.9.0 +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt --connection +``` +``` bash +aea fetch fetchai/erc1155_client:0.10.0 cd erc1155_client aea install ``` ``` bash aea create erc1155_client cd erc1155_client -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/erc1155_client:0.8.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/erc1155_client:0.9.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea config set agent.default_ledger ethereum @@ -44,12 +47,19 @@ aea generate-key ethereum aea add-key ethereum eth_private_key.txt ``` ``` bash +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt --connection +``` +``` bash aea generate-wealth ethereum ``` ``` bash aea get-wealth ethereum ``` ``` bash +aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum +``` +``` bash aea run ``` ``` bash @@ -65,11 +75,13 @@ aea delete erc1155_client ``` ``` yaml default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/contract_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/contract_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md index d88dfec621..76e2da3eae 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md @@ -5,15 +5,15 @@ sudo nano 99-hidraw-permissions.rules KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev" ``` ``` bash -aea fetch fetchai/generic_seller:0.5.0 +aea fetch fetchai/generic_seller:0.6.0 cd generic_seller -aea eject skill fetchai/generic_seller:0.8.0 +aea eject skill fetchai/generic_seller:0.9.0 cd .. ``` ``` bash -aea fetch fetchai/generic_buyer:0.5.0 +aea fetch fetchai/generic_buyer:0.6.0 cd generic_buyer -aea eject skill fetchai/generic_buyer:0.7.0 +aea eject skill fetchai/generic_buyer:0.8.0 cd .. ``` ``` bash @@ -47,10 +47,11 @@ aea add-key fetchai fet_private_key.txt aea generate-wealth fetchai ``` ``` bash -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea run ``` ``` bash @@ -58,10 +59,11 @@ aea generate-key ethereum aea add-key ethereum eth_private_key.txt ``` ``` bash -aea add connection fetchai/oef:0.6.0 -aea add connection fetchai/ledger:0.2.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea run ``` ``` bash @@ -86,10 +88,10 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: service_registration: @@ -154,10 +156,10 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.3.0 -- fetchai/fipa:0.4.0 -- fetchai/ledger_api:0.1.0 -- fetchai/oef_search:0.3.0 +- fetchai/default:0.4.0 +- fetchai/fipa:0.5.0 +- fetchai/ledger_api:0.2.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: search: @@ -218,7 +220,7 @@ addr: ${OEF_ADDR: 127.0.0.1} ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 ``` ``` yaml currency_id: 'ETH' diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md index 179ffe3f5d..3b480ae1ee 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/generic_seller:0.5.0 --alias my_seller_aea +aea fetch fetchai/generic_seller:0.6.0 --alias my_seller_aea cd my_seller_aea aea install ``` ``` bash aea create my_seller_aea cd my_seller_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/generic_seller:0.8.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/generic_seller:0.9.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/generic_buyer:0.5.0 --alias my_buyer_aea +aea fetch fetchai/generic_buyer:0.6.0 --alias my_buyer_aea cd my_buyer_aea aea install ``` ``` bash aea create my_buyer_aea cd my_buyer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/generic_buyer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/generic_buyer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea generate-key cosmos @@ -62,13 +62,13 @@ aea delete my_buyer_aea ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml models: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md index 0fc25aad6c..7f0ac07917 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md @@ -1,5 +1,5 @@ ``` bash -aea fetch fetchai/gym_aea:0.6.0 --alias my_gym_aea +aea fetch fetchai/gym_aea:0.7.0 --alias my_gym_aea cd my_gym_aea aea install ``` @@ -8,11 +8,11 @@ aea create my_gym_aea cd my_gym_aea ``` ``` bash -aea add skill fetchai/gym:0.4.0 +aea add skill fetchai/gym:0.5.0 ``` ``` bash -aea add connection fetchai/gym:0.4.0 -aea config set agent.default_connection fetchai/gym:0.4.0 +aea add connection fetchai/gym:0.5.0 +aea config set agent.default_connection fetchai/gym:0.5.0 ``` ``` bash aea install diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md index 38af6c6f41..1d8ef32e2b 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md @@ -3,10 +3,10 @@ aea create my_aea cd my_aea ``` ``` bash -aea add connection fetchai/http_server:0.5.0 +aea add connection fetchai/http_server:0.6.0 ``` ``` bash -aea config set agent.default_connection fetchai/http_server:0.5.0 +aea config set agent.default_connection fetchai/http_server:0.6.0 ``` ``` bash aea config set vendor.fetchai.connections.http_server.config.api_spec_path "../examples/http_ex/petstore.yaml" @@ -18,7 +18,7 @@ aea install aea scaffold skill http_echo ``` ``` bash -aea fingerprint skill fetchai/http_echo:0.3.0 +aea fingerprint skill fetchai/http_echo:0.4.0 ``` ``` bash aea run diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-logging.md b/tests/test_docs/test_bash_yaml/md_files/bash-logging.md index 0d8b7ccc21..906884ba29 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-logging.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-logging.md @@ -8,17 +8,17 @@ author: fetchai version: 0.1.0 description: '' license: Apache-2.0 -aea_version: 0.5.2 +aea_version: 0.5.3 fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 contracts: [] protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 skills: -- fetchai/error:0.3.0 -default_connection: fetchai/stub:0.6.0 +- fetchai/error:0.4.0 +default_connection: fetchai/stub:0.7.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md index 64f8543f6b..93b4e6afc2 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md @@ -1,31 +1,31 @@ ``` bash -aea fetch fetchai/ml_data_provider:0.8.0 +aea fetch fetchai/ml_data_provider:0.9.0 cd ml_data_provider aea install ``` ``` bash aea create ml_data_provider cd ml_data_provider -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/ml_data_provider:0.7.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/ml_data_provider:0.8.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea install ``` ``` bash -aea fetch fetchai/ml_model_trainer:0.8.0 +aea fetch fetchai/ml_model_trainer:0.9.0 cd ml_model_trainer aea install ``` ``` bash aea create ml_model_trainer cd ml_model_trainer -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/ml_train:0.7.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/ml_train:0.8.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea install ``` ``` bash @@ -54,13 +54,13 @@ aea delete ml_model_trainer ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-oef-ledger.md b/tests/test_docs/test_bash_yaml/md_files/bash-oef-ledger.md index 8ad251bf99..f6c140d02e 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-oef-ledger.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-oef-ledger.md @@ -1,3 +1,7 @@ ``` bash python scripts/oef/launch.py -c ./scripts/oef/launch_config.json ``` +``` bash +svn export https://github.com/fetchai/agents-aea.git/trunk/examples +svn export https://github.com/fetchai/agents-aea.git/trunk/scripts +``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md b/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md index f7f3d6c466..afdb1aa3a7 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/thermometer_aea:0.6.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.7.0 --alias my_thermometer_aea cd my_thermometer_aea aea install ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/thermometer_client:0.6.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.7.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea generate-key cosmos @@ -45,7 +45,7 @@ aea generate-wealth cosmos aea install ``` ``` bash -aea eject skill fetchai/thermometer:0.7.0 +aea eject skill fetchai/thermometer:0.8.0 ``` ``` bash aea fingerprint skill {YOUR_AUTHOR_HANDLE}/thermometer:0.1.0 @@ -63,13 +63,13 @@ aea delete my_thermometer_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml models: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md b/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md index 35a788621d..40802b1517 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md @@ -1,31 +1,31 @@ ``` bash aea create my_genesis_aea cd my_genesis_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 -aea run --connections fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea run --connections fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea create my_other_aea cd my_other_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea run --connections fetchai/p2p_libp2p:0.5.0 +aea run --connections fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/weather_station:0.8.0 -aea fetch fetchai/weather_client:0.8.0 +aea fetch fetchai/weather_station:0.9.0 +aea fetch fetchai/weather_client:0.9.0 ``` ``` bash -aea add connection fetchai/p2p_libp2p:0.5.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` bash python scripts/oef/launch.py -c ./scripts/oef/launch_config.json ``` ``` bash -aea run --connections "fetchai/p2p_libp2p:0.5.0,fetchai/oef:0.6.0" +aea run --connections "fetchai/p2p_libp2p:0.6.0,fetchai/oef:0.7.0" ``` ``` bash My libp2p addresses: ... @@ -38,7 +38,7 @@ aea add-key fetchai fet_private_key.txt aea generate-wealth fetchai ``` ``` bash -aea run --connections "fetchai/p2p_libp2p:0.5.0,fetchai/oef:0.6.0" +aea run --connections "fetchai/p2p_libp2p:0.6.0,fetchai/oef:0.7.0" ``` ``` yaml config: @@ -50,8 +50,8 @@ config: ``` ``` yaml default_routing: - ? "fetchai/oef_search:0.3.0" - : "fetchai/oef:0.6.0" + ? "fetchai/oef_search:0.4.0" + : "fetchai/oef:0.7.0" ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md b/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md index 127b7473e5..dfdcf6efd9 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md @@ -29,5 +29,5 @@ aea_name/ ``` ``` yaml connections: -- fetchai/stub:0.6.0 +- fetchai/stub:0.7.0 ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md b/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md index 0910cf6eaa..876f641ede 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md @@ -36,12 +36,12 @@ Confirm password: / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.5.2 +v0.5.3 AEA configurations successfully initialized: {'author': 'fetchai'} ``` ``` bash -aea fetch fetchai/my_first_aea:0.7.0 +aea fetch fetchai/my_first_aea:0.8.0 cd my_first_aea ``` ``` bash @@ -49,19 +49,19 @@ aea create my_first_aea cd my_first_aea ``` ``` bash -aea add skill fetchai/echo:0.3.0 +aea add skill fetchai/echo:0.4.0 ``` ``` bash TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, ``` ``` bash -recipient_aea,sender_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello, +recipient_aea,sender_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello, ``` ``` bash aea run ``` ``` bash -aea run --connections fetchai/stub:0.6.0 +aea run --connections fetchai/stub:0.7.0 ``` ``` bash _ _____ _ @@ -70,7 +70,7 @@ aea run --connections fetchai/stub:0.6.0 / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.5.2 +v0.5.3 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. @@ -82,7 +82,7 @@ info: Echo Behaviour: act method called. ... ``` ``` bash -echo 'my_first_aea,sender_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello,' >> input_file +echo 'my_first_aea,sender_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello,' >> input_file ``` ``` bash info: Echo Behaviour: act method called. diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md b/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md index 3bd7c6f51b..492bd564fa 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md @@ -6,16 +6,16 @@ aea scaffold skill my_search aea fingerprint skill fetchai/my_search:0.1.0 ``` ``` bash -aea add protocol fetchai/oef_search:0.3.0 +aea add protocol fetchai/oef_search:0.4.0 ``` ``` bash -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/p2p_libp2p:0.5.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/simple_service_registration:0.8.0 && cd simple_service_registration +aea fetch fetchai/simple_service_registration:0.9.0 && cd simple_service_registration ``` ``` bash aea generate-key cosmos @@ -44,7 +44,7 @@ fingerprint: {} fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: my_search_behaviour: @@ -71,7 +71,7 @@ dependencies: {} ``` ``` yaml default_routing: - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml name: simple_service_registration @@ -89,7 +89,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.3.0 +- fetchai/oef_search:0.4.0 skills: [] behaviours: service: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-skill.md index a63e02ac49..c357c98963 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-skill.md @@ -16,5 +16,5 @@ handlers: models: {} dependencies: {} protocols: -- fetchai/default:0.3.0 +- fetchai/default:0.4.0 ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md index 874b3eb6c0..cdd661c9ca 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md @@ -1,18 +1,17 @@ ``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` -``` bash -aea fetch fetchai/tac_controller_contract:0.6.0 +aea fetch fetchai/tac_controller_contract:0.7.0 cd tac_controller_contract aea install ``` ``` bash aea create tac_controller_contract cd tac_controller_contract -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_control_contract:0.4.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_control_contract:0.5.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum ``` ``` bash @@ -26,12 +25,12 @@ aea generate-wealth ethereum aea get-wealth ethereum ``` ``` bash -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_one cd tac_participant_one aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool cd .. -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_two cd tac_participant_two aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -43,22 +42,26 @@ aea create tac_participant_two ``` ``` bash cd tac_participant_one -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool ``` ``` bash cd tac_participant_two -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -121,5 +124,5 @@ models: class_name: Transactions args: pending_transaction_timeout: 30 -protocols: ['fetchai/oef_search:0.3.0', 'fetchai/fipa:0.4.0'] +protocols: ['fetchai/oef_search:0.4.0', 'fetchai/fipa:0.5.0'] ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md index 0d2d887ca5..ace9e449ac 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md @@ -1,23 +1,22 @@ ``` bash -python scripts/oef/launch.py -c ./scripts/oef/launch_config.json -``` -``` bash -aea fetch fetchai/tac_controller:0.5.0 +aea fetch fetchai/tac_controller:0.6.0 cd tac_controller aea install ``` ``` bash aea create tac_controller cd tac_controller -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_control:0.3.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_control:0.4.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos ``` ``` bash -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_one -aea fetch fetchai/tac_participant:0.6.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.7.0 --alias tac_participant_two cd tac_participant_two aea install ``` @@ -27,27 +26,39 @@ aea create tac_participant_two ``` ``` bash cd tac_participant_one -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos ``` ``` bash cd tac_participant_two -aea add connection fetchai/oef:0.6.0 -aea add skill fetchai/tac_participation:0.4.0 -aea add skill fetchai/tac_negotiation:0.5.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/tac_participation:0.5.0 +aea add skill fetchai/tac_negotiation:0.6.0 aea install -aea config set agent.default_connection fetchai/oef:0.6.0 -aea config set agent.default_ledger ethereum +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 +aea config set agent.default_ledger cosmos +``` +``` bash +aea generate-key cosmos +aea add-key cosmos cosmos_private_key.txt +aea add-key cosmos cosmos_private_key.txt --connection ``` ``` bash aea config get vendor.fetchai.skills.tac_control.models.parameters.args.start_time aea config set vendor.fetchai.skills.tac_control.models.parameters.args.start_time '01 01 2020 00:01' ``` ``` bash +aea run +``` +``` bash aea launch tac_controller tac_participant_one tac_participant_two ``` ``` bash @@ -56,6 +67,22 @@ aea delete tac_participant_one aea delete tac_participant_two ``` ``` yaml +config: + delegate_uri: 127.0.0.1:11001 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9001 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9001 +``` +``` yaml +config: + delegate_uri: 127.0.0.1:11002 + entry_peers: ['SOME_ADDRESS'] + local_uri: 127.0.0.1:9002 + log_file: libp2p_node.log + public_uri: 127.0.0.1:9002 +``` +``` yaml name: tac_negotiation authors: fetchai version: 0.1.0 @@ -101,5 +128,5 @@ models: class_name: Transactions args: pending_transaction_timeout: 30 -protocols: ['fetchai/oef_search:0.3.0', 'fetchai/fipa:0.4.0'] +protocols: ['fetchai/oef_search:0.4.0', 'fetchai/fipa:0.5.0'] ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md index 70939c45b0..f3342bbf90 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/thermometer_aea:0.6.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.7.0 --alias my_thermometer_aea cd thermometer_aea aea install ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/thermometer_client:0.6.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.7.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/thermometer_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/thermometer_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea generate-key cosmos @@ -54,13 +54,13 @@ aea delete my_thermometer_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md index 62d92bff4d..78c468e108 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/weather_station:0.8.0 --alias my_weather_station +aea fetch fetchai/weather_station:0.9.0 --alias my_weather_station cd my_weather_station aea install ``` ``` bash aea create my_weather_station cd my_weather_station -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/weather_station:0.7.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/weather_station:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash -aea fetch fetchai/weather_client:0.8.0 --alias my_weather_client +aea fetch fetchai/weather_client:0.9.0 --alias my_weather_client cd my_weather_client aea install ``` ``` bash aea create my_weather_client cd my_weather_client -aea add connection fetchai/p2p_libp2p:0.5.0 -aea add connection fetchai/soef:0.5.0 -aea add connection fetchai/ledger:0.2.0 -aea add skill fetchai/weather_client:0.6.0 +aea add connection fetchai/p2p_libp2p:0.6.0 +aea add connection fetchai/soef:0.6.0 +aea add connection fetchai/ledger:0.3.0 +aea add skill fetchai/weather_client:0.7.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.5.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.6.0 ``` ``` bash aea generate-key cosmos @@ -54,13 +54,13 @@ aea delete my_weather_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.1.0: fetchai/ledger:0.2.0 - fetchai/oef_search:0.3.0: fetchai/soef:0.5.0 + fetchai/ledger_api:0.2.0: fetchai/ledger:0.3.0 + fetchai/oef_search:0.4.0: fetchai/soef:0.6.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py b/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py index cf4f8048c6..9f8aed8abf 100644 --- a/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py +++ b/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py @@ -36,7 +36,7 @@ def run(): # Create a private key - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) # Ensure the input and output files do not exist initially if os.path.isfile(INPUT_FILE): @@ -96,7 +96,7 @@ def handle(self, message: Message) -> None: # Create a message inside an envelope and get the stub connection to pass it on to the echo skill message_text = ( - "my_aea,other_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "my_aea,other_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) diff --git a/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py b/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py index 006a93a15a..401bc511ae 100644 --- a/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py +++ b/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py @@ -61,7 +61,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, DEFAULT_PRIVATE_KEY_FILE)) message_text = ( - "other_agent,my_aea,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "other_agent,my_aea,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(self.t, "output_file") with open(path, "r") as file: diff --git a/tests/test_docs/test_cli_vs_programmatic_aeas/cosmos_private_key.txt b/tests/test_docs/test_cli_vs_programmatic_aeas/cosmos_private_key.txt deleted file mode 100644 index e2b2a4b904..0000000000 --- a/tests/test_docs/test_cli_vs_programmatic_aeas/cosmos_private_key.txt +++ /dev/null @@ -1 +0,0 @@ -e613db13d8ba93cacb0ca3c0e0e074c9bdb404d816a4749f8d96d2c13d5fad5b \ No newline at end of file diff --git a/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py b/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py index 5bcaedba20..38691d6fa1 100644 --- a/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py +++ b/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py @@ -46,6 +46,7 @@ ENTRY_PEER_ADDRESS = ( "/dns4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmAzvu5uNbcnD2qaqrkSULhJsc6GJUg3iikWerJkoD72pr" ) +COSMOS_PRIVATE_KEY_FILE_CONNECTION = "cosmos_connection_private_key.txt" ROOT_DIR = os.getcwd() logger = logging.getLogger("aea") @@ -54,22 +55,25 @@ def run(): # Create a private key - create_private_key(CosmosCrypto.identifier) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE) + create_private_key(CosmosCrypto.identifier, COSMOS_PRIVATE_KEY_FILE_CONNECTION) # Set up the wallet, identity and (empty) resources wallet = Wallet( private_key_paths={CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE}, - connection_private_key_paths={CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE}, + connection_private_key_paths={ + CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_CONNECTION + }, ) identity = Identity("my_aea", address=wallet.addresses.get(CosmosCrypto.identifier)) resources = Resources() # specify the default routing for some protocols default_routing = { - PublicId.from_str("fetchai/ledger_api:0.1.0"): LedgerConnection.connection_id, - PublicId.from_str("fetchai/oef_search:0.3.0"): SOEFConnection.connection_id, + PublicId.from_str("fetchai/ledger_api:0.2.0"): LedgerConnection.connection_id, + PublicId.from_str("fetchai/oef_search:0.4.0"): SOEFConnection.connection_id, } - default_connection = SOEFConnection.connection_id + default_connection = P2PLibp2pConnection.connection_id # create the AEA my_aea = AEA( @@ -134,13 +138,8 @@ def run(): api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, - delegate_uri="127.0.0.1:11001", - entry_peers=[ENTRY_PEER_ADDRESS], - local_uri="127.0.0.1:9001", - log_file="libp2p_node.log", - public_uri="127.0.0.1:9001", ) soef_connection = SOEFConnection(configuration=configuration, identity=identity) resources.add_connection(soef_connection) diff --git a/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py b/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py index 20b34c64d1..dac85e9f68 100644 --- a/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py +++ b/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py @@ -21,6 +21,8 @@ import os import shutil +from pathlib import Path +from random import uniform import pytest @@ -29,9 +31,12 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, CUR_PATH, + MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, ROOT_DIR, + wait_for_localhost_ports_to_close, ) from tests.test_docs.helper import extract_code_blocks, extract_python_code @@ -51,12 +56,13 @@ def test_read_md_file(self): python_file = extract_python_code(test_code_path) assert code_blocks[-1] == python_file, "Files must be exactly the same." + @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS_INTEGRATION) @pytest.mark.integration def test_cli_programmatic_communication(self): """Test the communication of the two agents.""" weather_station = "weather_station" - self.fetch_agent("fetchai/weather_station:0.8.0", weather_station) + self.fetch_agent("fetchai/weather_station:0.9.0", weather_station) self.set_agent_context(weather_station) self.set_config( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx", @@ -67,11 +73,23 @@ def test_cli_programmatic_communication(self): # add non-funded key self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec + } + setting_path = ( + "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) + self.force_set_config(setting_path, location) weather_station_process = self.run_agent() @@ -93,6 +111,7 @@ def test_cli_programmatic_communication(self): src_file_path = os.path.join(ROOT_DIR, "tests", PY_FILE) dst_file_path = os.path.join(ROOT_DIR, self.t, DEST) shutil.copyfile(src_file_path, dst_file_path) + self._inject_location(location, dst_file_path) weather_client_process = self.start_subprocess(DEST, cwd=self.t) check_strings = ( @@ -147,3 +166,18 @@ def test_cli_programmatic_communication(self): assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." + wait_for_localhost_ports_to_close([9000, 9001]) + + def _inject_location(self, location, dst_file_path): + """Inject location into the weather client strategy.""" + file = Path(dst_file_path) + lines = file.read_text().splitlines() + lines.insert( + 157, + " from packages.fetchai.skills.generic_buyer.strategy import Location", + ) + lines.insert( + 158, + f" strategy._agent_location = Location({location['longitude']}, {location['latitude']})", + ) + file.write_text("\n".join(lines)) diff --git a/tests/test_docs/test_docs_protocol.py b/tests/test_docs/test_docs_protocol.py index 4ad323f298..2c3f1151f2 100644 --- a/tests/test_docs/test_docs_protocol.py +++ b/tests/test_docs/test_docs_protocol.py @@ -69,7 +69,7 @@ def test_custom_protocol(self): ) def test_oef_search_protocol(self): - """Test the fetchai/oef_search:0.3.0 protocol documentation.""" + """Test the fetchai/oef_search:0.4.0 protocol documentation.""" # this is the offset of code blocks for the section under testing offset = 4 @@ -106,7 +106,7 @@ def test_oef_search_protocol(self): compare_enum_classes(ExpectedOefErrorOperation, ActualOefErrorOperation) def test_fipa_protocol(self): - """Test the fetchai/fipa:0.4.0 documentation.""" + """Test the fetchai/fipa:0.5.0 documentation.""" offset = 15 locals_dict = {"Enum": Enum} compile_and_exec(self.code_blocks[offset]["text"], locals_dict=locals_dict) diff --git a/tests/test_docs/test_docs_skill.py b/tests/test_docs/test_docs_skill.py index 119adff7f3..00e2b47bb6 100644 --- a/tests/test_docs/test_docs_skill.py +++ b/tests/test_docs/test_docs_skill.py @@ -49,10 +49,12 @@ def test_context(self): assert block["text"].strip() == expected assert block["info"].strip() == "python" + # TODO add tests for new_handlers queue + def test_hello_world_behaviour(self): """Test the code in the 'behaviours.py' section.""" # here, we test the definition of a custom class - offset = 1 + offset = 2 block = self.code_blocks[offset] text = block["text"] @@ -85,7 +87,7 @@ def test_hello_world_behaviour(self): def test_task(self): """Test the code blocks of the 'tasks.py' section.""" # test code of task definition - offset = 4 + offset = 5 block = self.code_blocks[offset] locals_dict = compile_and_exec(block["text"]) diff --git a/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py b/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py index 022c58c6d6..5208e536b8 100644 --- a/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py +++ b/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py @@ -62,7 +62,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: f.write(message_text) diff --git a/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py b/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py index 299f6c19f1..c4ac19c26c 100644 --- a/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py +++ b/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py @@ -60,7 +60,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, "output.txt")) message_text = ( - "some_agent,multiplexer,fetchai/default:0.3.0,\x08\x01*\x07\n\x05hello," + "some_agent,multiplexer,fetchai/default:0.4.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(str(self.t), "output.txt") with open(path, "r", encoding="utf-8") as file: diff --git a/tests/test_docs/test_orm_integration/test_orm_integration.py b/tests/test_docs/test_orm_integration/test_orm_integration.py index da209667e9..39a8fd4baf 100644 --- a/tests/test_docs/test_orm_integration/test_orm_integration.py +++ b/tests/test_docs/test_orm_integration/test_orm_integration.py @@ -19,6 +19,7 @@ """This module contains the tests for the orm-integration.md guide.""" from pathlib import Path +from random import uniform import mistune @@ -31,7 +32,7 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -125,21 +126,27 @@ def test_orm_integration_docs_example(self): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # Setup seller self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) # ejecting changes author and version! - self.eject_item("skill", "fetchai/thermometer:0.7.0") + self.eject_item("skill", "fetchai/thermometer:0.8.0") seller_skill_config_replacement = yaml.safe_load(seller_strategy_replacement) self.force_set_config( "skills.thermometer.models", seller_skill_config_replacement["models"], @@ -152,27 +159,34 @@ def test_orm_integration_docs_example(self): seller_stategy_path = Path( seller_aea_name, "skills", "thermometer", "strategy.py", ) - self.replace_file_content(seller_stategy_path, ORM_SELLER_STRATEGY_PATH) + self.replace_file_content(ORM_SELLER_STRATEGY_PATH, seller_stategy_path) self.fingerprint_item( "skill", "{}/thermometer:0.1.0".format(self.author), ) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) + # replace location + setting_path = "skills.thermometer.models.strategy.args.location" + self.force_set_config(setting_path, location) + # Setup Buyer self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer_client:0.6.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer_client:0.7.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) buyer_skill_config_replacement = yaml.safe_load(buyer_strategy_replacement) @@ -182,16 +196,27 @@ def test_orm_integration_docs_example(self): ) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # Fire the sub-processes and the threads. self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() diff --git a/tests/test_docs/test_skill_guide/test_skill_guide.py b/tests/test_docs/test_skill_guide/test_skill_guide.py index b5dc15ec54..d260fecbd6 100644 --- a/tests/test_docs/test_skill_guide/test_skill_guide.py +++ b/tests/test_docs/test_skill_guide/test_skill_guide.py @@ -22,6 +22,7 @@ import filecmp import os from pathlib import Path +from random import uniform import pytest @@ -33,7 +34,7 @@ AUTHOR, COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -66,32 +67,45 @@ def test_update_skill_and_run(self): """Test that the resource folder contains scaffold handlers.py module.""" self.initialize_aea(AUTHOR) + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec + } + simple_service_registration_aea = "simple_service_registration" self.fetch_agent( - "fetchai/simple_service_registration:0.8.0", simple_service_registration_aea + "fetchai/simple_service_registration:0.9.0", simple_service_registration_aea ) self.set_agent_context(simple_service_registration_aea) # add non-funded key self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) default_routing = { - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", } + # replace location + setting_path = "vendor.fetchai.skills.simple_service_registration.models.strategy.args.location" + self.force_set_config(setting_path, location) + search_aea = "search_aea" self.create_agents(search_aea) self.set_agent_context(search_aea) skill_name = "my_search" skill_id = AUTHOR + "/" + skill_name + ":" + DEFAULT_VERSION self.scaffold_item("skill", skill_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) @@ -125,16 +139,27 @@ def test_update_skill_and_run(self): # update fingerprint self.fingerprint_item("skill", skill_id) - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = "skills.{}.behaviours.my_search_behaviour.args.location".format( + skill_name + ) + self.force_set_config(setting_path, location) + # run agents self.set_agent_context(simple_service_registration_aea) simple_service_registration_aea_process = self.run_agent() diff --git a/tests/test_docs/test_standalone_transaction/standalone_transaction.py b/tests/test_docs/test_standalone_transaction/standalone_transaction.py index 69c3a475e7..46f8a61608 100644 --- a/tests/test_docs/test_standalone_transaction/standalone_transaction.py +++ b/tests/test_docs/test_standalone_transaction/standalone_transaction.py @@ -23,7 +23,7 @@ from aea.crypto.cosmos import CosmosCrypto from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth -from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis +from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet @@ -47,9 +47,6 @@ def run(): wallet_1 = Wallet({CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_1}) wallet_2 = Wallet({CosmosCrypto.identifier: COSMOS_PRIVATE_KEY_FILE_2}) - # Set up the LedgerApis - ledger_apis = LedgerApis(DEFAULT_LEDGER_CONFIGS, CosmosCrypto.identifier) - # Generate some wealth try_generate_testnet_wealth( CosmosCrypto.identifier, wallet_1.addresses[CosmosCrypto.identifier] @@ -60,12 +57,12 @@ def run(): ) # Create the transaction and send it to the ledger. - tx_nonce = ledger_apis.generate_tx_nonce( + tx_nonce = LedgerApis.generate_tx_nonce( CosmosCrypto.identifier, wallet_2.addresses.get(CosmosCrypto.identifier), wallet_1.addresses.get(CosmosCrypto.identifier), ) - transaction = ledger_apis.get_transfer_transaction( + transaction = LedgerApis.get_transfer_transaction( identifier=CosmosCrypto.identifier, sender_address=wallet_1.addresses.get(CosmosCrypto.identifier), destination_address=wallet_2.addresses.get(CosmosCrypto.identifier), @@ -74,7 +71,7 @@ def run(): tx_nonce=tx_nonce, ) signed_transaction = wallet_1.sign_transaction(CosmosCrypto.identifier, transaction) - transaction_digest = ledger_apis.send_signed_transaction( + transaction_digest = LedgerApis.send_signed_transaction( CosmosCrypto.identifier, signed_transaction ) diff --git a/tests/test_docs/test_standalone_transaction/test_standalone_transaction.py b/tests/test_docs/test_standalone_transaction/test_standalone_transaction.py index f842c65c95..6c5ec74b1a 100644 --- a/tests/test_docs/test_standalone_transaction/test_standalone_transaction.py +++ b/tests/test_docs/test_standalone_transaction/test_standalone_transaction.py @@ -23,9 +23,11 @@ import os from unittest.mock import patch +import pytest + from aea.test_tools.test_cases import BaseAEATestCase -from tests.conftest import CUR_PATH, ROOT_DIR +from tests.conftest import CUR_PATH, MAX_FLAKY_RERUNS_INTEGRATION, ROOT_DIR from tests.test_docs.helper import extract_code_blocks, extract_python_code from .standalone_transaction import ( @@ -39,6 +41,7 @@ test_logger = logging.getLogger(__name__) +@pytest.mark.integration(reruns=MAX_FLAKY_RERUNS_INTEGRATION) class TestStandaloneTransaction(BaseAEATestCase): """This class contains the tests for the code-blocks in the agent-vs-aea.md file.""" diff --git a/tests/test_examples/test_http_client_connection_to_aries_cloud_agent.py b/tests/test_examples/test_http_client_connection_to_aries_cloud_agent.py index c78c91aa20..9fd0ed9e7f 100644 --- a/tests/test_examples/test_http_client_connection_to_aries_cloud_agent.py +++ b/tests/test_examples/test_http_client_connection_to_aries_cloud_agent.py @@ -113,7 +113,7 @@ async def test_connecting_to_aca(self): # Request messages request_http_message = HttpMessage( - dialogue_reference=("", ""), + dialogue_reference=("1", ""), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, @@ -136,7 +136,7 @@ async def test_connecting_to_aca(self): try: # connect to ACA await http_client_connection.connect() - assert http_client_connection.connection_status.is_connected is True + assert http_client_connection.is_connected is True # send request to ACA await http_client_connection.send(envelope=request_envelope) @@ -162,7 +162,7 @@ async def test_connecting_to_aca(self): finally: # disconnect from ACA await http_client_connection.disconnect() - assert http_client_connection.connection_status.is_connected is False + assert http_client_connection.is_connected is False @pytest.mark.asyncio async def test_end_to_end_aea_aca(self): diff --git a/tests/test_helpers/test_async_utils.py b/tests/test_helpers/test_async_utils.py index fe4501887e..dcfc5ba115 100644 --- a/tests/test_helpers/test_async_utils.py +++ b/tests/test_helpers/test_async_utils.py @@ -65,6 +65,43 @@ async def test_async_state(): await state.wait(2) +@pytest.mark.asyncio +async def test_asyncstate_with_list_of_valid_states(): + """Test various cases for AsyncState.""" + states = [1, 2, 3] + state = AsyncState(1, states) + + state.set(2) + assert state.get() == 2 + + with pytest.raises(ValueError): + state.set("anything") + + assert state.get() == 2 + + +@pytest.mark.asyncio +async def test_asyncstate_callback(): + """Test various cases for AsyncState.callback.""" + state = AsyncState() + + called = False + + def callback_err(state): + raise Exception("expected") + + def callback(state): + nonlocal called + called = True + + state.add_callback(callback_err) + state.add_callback(callback) + + state.set(2) + assert state.get() == 2 + assert called + + @pytest.mark.asyncio async def test_periodic_caller_start_stop(): """Test start stop calls for PeriodicCaller.""" diff --git a/tests/test_helpers/test_dialogue/test_base.py b/tests/test_helpers/test_dialogue/test_base.py index 910f59e2e4..0f64ed1d2f 100644 --- a/tests/test_helpers/test_dialogue/test_base.py +++ b/tests/test_helpers/test_dialogue/test_base.py @@ -19,7 +19,9 @@ """This module contains the tests for the dialogue/base.py module.""" -from typing import Dict, FrozenSet, Optional, cast +from typing import Dict, FrozenSet, Optional, Type, cast + +import pytest from aea.helpers.dialogue.base import Dialogue as BaseDialogue from aea.helpers.dialogue.base import DialogueLabel, DialogueStats @@ -27,6 +29,7 @@ from aea.mail.base import Address from aea.protocols.base import Message from aea.protocols.default.message import DefaultMessage +from aea.protocols.state_update.message import StateUpdateMessage class Dialogue(BaseDialogue): @@ -60,7 +63,7 @@ class EndState(BaseDialogue.EndState): def __init__( self, dialogue_label: DialogueLabel, - message_class=DefaultMessage, + message_class: Type[Message] = DefaultMessage, agent_address: Optional[Address] = "agent 1", role: Optional[BaseDialogue.Role] = Role.ROLE1, ) -> None: @@ -158,10 +161,12 @@ class TestDialogueLabel: @classmethod def setup(cls): """Initialise the environment to test DialogueLabel.""" + cls.agent_address = "agent 1" + cls.opponent_address = "agent 2" cls.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_opponent_addr=cls.opponent_address, + dialogue_starter_addr=cls.agent_address, ) def test_all_methods(self): @@ -169,8 +174,8 @@ def test_all_methods(self): assert self.dialogue_label.dialogue_reference == (str(1), "") assert self.dialogue_label.dialogue_starter_reference == str(1) assert self.dialogue_label.dialogue_responder_reference == "" - assert self.dialogue_label.dialogue_opponent_addr == "agent 2" - assert self.dialogue_label.dialogue_starter_addr == "agent 1" + assert self.dialogue_label.dialogue_opponent_addr == self.opponent_address + assert self.dialogue_label.dialogue_starter_addr == self.agent_address assert str(self.dialogue_label) == "{}_{}_{}_{}".format( self.dialogue_label.dialogue_starter_reference, self.dialogue_label.dialogue_responder_reference, @@ -180,8 +185,8 @@ def test_all_methods(self): dialogue_label_eq = DialogueLabel( dialogue_reference=(str(1), ""), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_opponent_addr=self.opponent_address, + dialogue_starter_addr=self.agent_address, ) assert dialogue_label_eq == self.dialogue_label @@ -195,10 +200,11 @@ def test_all_methods(self): assert self.dialogue_label.json == dict( dialogue_starter_reference=str(1), dialogue_responder_reference="", - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_opponent_addr=self.opponent_address, + dialogue_starter_addr=self.agent_address, ) assert DialogueLabel.from_json(self.dialogue_label.json) == self.dialogue_label + assert DialogueLabel.from_str(str(self.dialogue_label)) == self.dialogue_label class TestDialogueBase: @@ -207,12 +213,24 @@ class TestDialogueBase: @classmethod def setup(cls): """Initialise the environment to test Dialogue.""" + cls.incomplete_reference = (str(1), "") + cls.complete_reference = (str(1), str(1)) + cls.opponent_address = "agent 2" + cls.agent_address = "agent 1" cls.dialogue_label = DialogueLabel( - dialogue_reference=(str(1), ""), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_reference=cls.incomplete_reference, + dialogue_opponent_addr=cls.opponent_address, + dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) + cls.dialogue_label_opponent_started = DialogueLabel( + dialogue_reference=cls.complete_reference, + dialogue_opponent_addr=cls.opponent_address, + dialogue_starter_addr=cls.opponent_address, + ) + cls.dialogue_opponent_started = Dialogue( + dialogue_label=cls.dialogue_label_opponent_started + ) def test_inner_classes(self): """Test the inner classes: Role and EndStates.""" @@ -224,7 +242,8 @@ def test_inner_classes(self): def test_dialogue_properties(self): """Test dialogue properties.""" assert self.dialogue.dialogue_label == self.dialogue_label - assert self.dialogue.agent_address == "agent 1" + assert self.dialogue.incomplete_dialogue_label == self.dialogue_label + assert self.dialogue.agent_address == self.agent_address self.dialogue.agent_address = "this agent's address" assert self.dialogue.agent_address == "this agent's address" @@ -273,7 +292,7 @@ def test_update_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address assert self.dialogue.update(valid_initial_msg) assert self.dialogue.last_outgoing_message == valid_initial_msg @@ -304,49 +323,15 @@ def test_update_negative_not_is_extendible(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - invalid_message_id.counterparty = "agent 2" + invalid_message_id.counterparty = self.opponent_address assert not self.dialogue.update(invalid_message_id) assert self.dialogue.last_outgoing_message is None - def test_update_self_initiated_dialogue_label_on_second_message_positive(self): - """Positive test for the '_update_self_initiated_dialogue_label_on_second_message' method.""" - valid_initial_msg = DefaultMessage( - dialogue_reference=(str(1), ""), - message_id=1, - target=0, - performative=DefaultMessage.Performative.BYTES, - content=b"Hello", - ) - valid_initial_msg.counterparty = "agent 2" - valid_initial_msg._is_incoming = False - - assert self.dialogue.update(valid_initial_msg) - - valid_second_msg = DefaultMessage( - dialogue_reference=(str(1), str(1)), - message_id=2, - target=1, - performative=DefaultMessage.Performative.BYTES, - content=b"Hello Back", - ) - valid_second_msg.counterparty = "agent 2" - valid_second_msg._is_incoming = True - - try: - self.dialogue._update_self_initiated_dialogue_label_on_second_message( - valid_second_msg - ) - result = True - except Exception: - result = False - - assert result - - def test_update_self_initiated_dialogue_label_on_second_message_negative_empty_dialogue( + def test_update_self_initiated_dialogue_label_on_message_with_complete_reference_negative_not_second_message( self, ): - """Negative test for the '_update_self_initiated_dialogue_label_on_second_message' method: called on an empty dialogue.""" + """Negative test for the '_update_self_initiated_dialogue_label_on_message_with_complete_reference' method: input message is not the second message in the dialogue.""" initial_msg = DefaultMessage( dialogue_reference=(str(1), ""), message_id=1, @@ -354,34 +339,10 @@ def test_update_self_initiated_dialogue_label_on_second_message_negative_empty_d performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = False - - try: - self.dialogue._update_self_initiated_dialogue_label_on_second_message( - initial_msg - ) - result = True - except Exception: - result = False - - assert not result - - def test_update_self_initiated_dialogue_label_on_second_message_negative_not_second_message( - self, - ): - """Negative test for the '_update_self_initiated_dialogue_label_on_second_message' method: input message is not the second message in the dialogue.""" - initial_msg = DefaultMessage( - dialogue_reference=(str(1), ""), - message_id=1, - target=0, - performative=DefaultMessage.Performative.BYTES, - content=b"Hello", - ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = False - - assert self.dialogue.update(initial_msg) + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = False + dialogue = self.dialogue.update(initial_msg) + assert dialogue is not None second_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -390,10 +351,11 @@ def test_update_self_initiated_dialogue_label_on_second_message_negative_not_sec performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True - - assert self.dialogue.update(second_msg) + second_msg.counterparty = self.opponent_address + second_msg.sender = self.agent_address + second_msg.is_incoming = True + dialogue = self.dialogue.update(second_msg) + assert dialogue is not None third_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -402,11 +364,11 @@ def test_update_self_initiated_dialogue_label_on_second_message_negative_not_sec performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) - third_msg.counterparty = "agent 2" - third_msg._is_incoming = False + third_msg.counterparty = self.opponent_address + third_msg.is_incoming = False try: - self.dialogue._update_self_initiated_dialogue_label_on_second_message( + self.dialogue._update_self_initiated_dialogue_label_on_message_with_complete_reference( third_msg ) result = True @@ -424,8 +386,8 @@ def test_reply_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = False + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = False assert self.dialogue.update(initial_msg) @@ -446,8 +408,8 @@ def test_reply_negative_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = False + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = False assert self.dialogue.update(initial_msg) @@ -458,8 +420,8 @@ def test_reply_negative_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello There", ) - invalid_initial_msg.counterparty = "agent 2" - invalid_initial_msg._is_incoming = False + invalid_initial_msg.counterparty = self.opponent_address + invalid_initial_msg.is_incoming = False try: self.dialogue.reply( @@ -482,7 +444,7 @@ def test_basic_rules_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address assert self.dialogue._basic_rules(valid_initial_msg) @@ -495,7 +457,7 @@ def test_basic_rules_negative_initial_message_invalid_dialogue_reference(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - invalid_initial_msg.counterparty = "agent 2" + invalid_initial_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_initial_msg) @@ -508,7 +470,7 @@ def test_basic_rules_negative_initial_message_invalid_message_id(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - invalid_initial_msg.counterparty = "agent 2" + invalid_initial_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_initial_msg) @@ -521,7 +483,7 @@ def test_basic_rules_negative_initial_message_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - invalid_initial_msg.counterparty = "agent 2" + invalid_initial_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_initial_msg) @@ -536,7 +498,7 @@ def test_basic_rules_negative_initial_message_invalid_performative(self): error_msg="some_error_message", error_data={"some_data": b"some_bytes"}, ) - invalid_initial_msg.counterparty = "agent 2" + invalid_initial_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_initial_msg) @@ -549,7 +511,7 @@ def test_basic_rules_negative_non_initial_message_invalid_dialogue_reference(sel performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) @@ -561,7 +523,7 @@ def test_basic_rules_negative_non_initial_message_invalid_dialogue_reference(sel performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - invalid_msg.counterparty = "agent 2" + invalid_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_msg) @@ -574,7 +536,7 @@ def test_basic_rules_negative_non_initial_message_invalid_message_id(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) @@ -586,7 +548,7 @@ def test_basic_rules_negative_non_initial_message_invalid_message_id(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - invalid_msg.counterparty = "agent 2" + invalid_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_msg) @@ -599,7 +561,7 @@ def test_basic_rules_negative_non_initial_message_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) @@ -611,38 +573,23 @@ def test_basic_rules_negative_non_initial_message_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - invalid_msg.counterparty = "agent 2" + invalid_msg.counterparty = self.opponent_address assert not self.dialogue._basic_rules(invalid_msg) - # ToDo change to another Message class to test invalid non-initial performative - # since default message does not provide this - # def test_basic_rules_negative_non_initial_message_invalid_performative(self): - # """Negative test for the '_basic_rules' method: input message is not the first message, and its performative is invalid.""" - # valid_initial_msg = DefaultMessage( - # dialogue_reference=(str(1), ""), - # message_id=1, - # target=0, - # performative=DefaultMessage.Performative.BYTES, - # content=b"Hello", - # ) - # valid_initial_msg.counterparty = "agent 2" - # valid_initial_msg._is_incoming = False - # - # assert self.dialogue.update(valid_initial_msg) - # - # invalid_msg = DefaultMessage( - # dialogue_reference=(str(1), str(1)), - # message_id=1, - # target=0, - # performative=DefaultMessage.Performative.ERROR, - # error_code=DefaultMessage.ErrorCode.INVALID_MESSAGE, - # error_msg="some_error_message", - # error_data={"some_data": b"some_bytes"}, - # ) - # invalid_msg.counterparty = "agent 2" - # - # assert not self.dialogue._basic_rules(invalid_msg) + def test_basic_rules_negative_non_initial_message_invalid_performative(self): + """Negative test for the '_basic_rules' method: input message is not the first message, and its performative is invalid.""" + invalid_initial_msg = StateUpdateMessage( + dialogue_reference=(str(1), ""), + message_id=1, + target=0, + performative=StateUpdateMessage.Performative.APPLY, + amount_by_currency_id={}, + quantities_by_good_id={}, + ) + invalid_initial_msg.counterparty = self.opponent_address + + assert not self.dialogue.update(invalid_initial_msg) def test_additional_rules_positive(self): """Positive test for the '_additional_rules' method.""" @@ -653,7 +600,7 @@ def test_additional_rules_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) @@ -665,7 +612,7 @@ def test_additional_rules_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - valid_second_msg.counterparty = "agent 2" + valid_second_msg.counterparty = self.opponent_address valid_second_msg._is_incoming = True assert self.dialogue.update(valid_second_msg) @@ -677,7 +624,7 @@ def test_additional_rules_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) - valid_third_msg.counterparty = "agent 2" + valid_third_msg.counterparty = self.opponent_address valid_third_msg._is_incoming = False assert self.dialogue._additional_rules(valid_third_msg) @@ -691,8 +638,8 @@ def test_additional_rules_negative_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" - valid_initial_msg._is_incoming = False + valid_initial_msg.counterparty = self.opponent_address + valid_initial_msg.is_incoming = False assert self.dialogue.update(valid_initial_msg) @@ -703,8 +650,8 @@ def test_additional_rules_negative_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - valid_second_msg.counterparty = "agent 2" - valid_second_msg._is_incoming = True + valid_second_msg.counterparty = self.opponent_address + valid_second_msg.is_incoming = True assert self.dialogue.update(valid_second_msg) @@ -715,7 +662,7 @@ def test_additional_rules_negative_invalid_target(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) - invalid_third_msg.counterparty = "agent 2" + invalid_third_msg.counterparty = self.opponent_address invalid_third_msg._is_incoming = False assert not self.dialogue._additional_rules(invalid_third_msg) @@ -729,54 +676,74 @@ def test_update_dialogue_label_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) new_label = DialogueLabel( - (str(1), str(1)), valid_initial_msg.counterparty, "agent 1" + (str(1), str(1)), valid_initial_msg.counterparty, self.agent_address ) self.dialogue.update_dialogue_label(new_label) assert self.dialogue.dialogue_label == new_label + def test_update_dialogue_negative(self): + """Positive test for the 'update' method in dialogue with wrong message not belonging to dialogue.""" + valid_initial_msg = DefaultMessage( + dialogue_reference=(str(2), ""), + message_id=1, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello", + ) + valid_initial_msg.counterparty = self.opponent_address + valid_initial_msg.is_incoming = False + + assert not self.dialogue.update(valid_initial_msg) + def test_update_dialogue_label_negative_invalid_existing_label(self): """Negative test for the 'update_dialogue_label' method: existing dialogue reference is invalid.""" + incomplete_reference = (str(1), "") valid_initial_msg = DefaultMessage( - dialogue_reference=(str(1), ""), + dialogue_reference=incomplete_reference, message_id=1, target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" - valid_initial_msg._is_incoming = False + valid_initial_msg.counterparty = self.opponent_address + valid_initial_msg.is_incoming = False assert self.dialogue.update(valid_initial_msg) + complete_reference = (str(1), str(1)) valid_second_msg = DefaultMessage( - dialogue_reference=(str(1), str(1)), + dialogue_reference=complete_reference, message_id=2, target=1, performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - valid_second_msg.counterparty = "agent 2" - valid_second_msg._is_incoming = True + valid_second_msg.counterparty = self.opponent_address + valid_second_msg.sender = self.agent_address + valid_second_msg.is_incoming = True assert self.dialogue.update(valid_second_msg) new_label = DialogueLabel( - (str(1), str(2)), valid_initial_msg.counterparty, "agent 1" + complete_reference, valid_initial_msg.counterparty, self.agent_address ) - try: + self.dialogue.update_dialogue_label(new_label) + assert self.dialogue.dialogue_label == new_label + + new_label = DialogueLabel( + (str(1), str(2)), valid_initial_msg.counterparty, self.agent_address + ) + with pytest.raises(AssertionError): self.dialogue.update_dialogue_label(new_label) - result = True - except AssertionError: - result = False - assert not result and self.dialogue.dialogue_label != new_label + assert self.dialogue.dialogue_label != new_label def test_update_dialogue_label_negative_invalid_input_label(self): """Negative test for the 'update_dialogue_label' method: input dialogue label's dialogue reference is invalid.""" @@ -787,13 +754,13 @@ def test_update_dialogue_label_negative_invalid_input_label(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" + valid_initial_msg.counterparty = self.opponent_address valid_initial_msg._is_incoming = False assert self.dialogue.update(valid_initial_msg) new_label = DialogueLabel( - (str(2), ""), valid_initial_msg.counterparty, "agent 1" + (str(2), ""), valid_initial_msg.counterparty, self.agent_address ) try: self.dialogue.update_dialogue_label(new_label) @@ -823,10 +790,11 @@ def test___str__1(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" - valid_initial_msg._is_incoming = False + valid_initial_msg.counterparty = self.opponent_address + valid_initial_msg.is_incoming = False - assert self.dialogue.update(valid_initial_msg) + dialogue = self.dialogue.update(valid_initial_msg) + assert dialogue is not None valid_second_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -835,10 +803,12 @@ def test___str__1(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - valid_second_msg.counterparty = "agent 2" - valid_second_msg._is_incoming = True + valid_second_msg.counterparty = self.opponent_address + valid_second_msg.sender = self.agent_address + valid_second_msg.is_incoming = True - assert self.dialogue.update(valid_second_msg) + dialogue = self.dialogue.update(valid_second_msg) + assert dialogue is not None valid_third_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -847,26 +817,19 @@ def test___str__1(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) - valid_third_msg.counterparty = "agent 2" + valid_third_msg.counterparty = self.opponent_address valid_third_msg._is_incoming = False assert self.dialogue.update(valid_third_msg) dialogue_str = ( - "Dialogue Label: 1_1_agent 2_agent 1\nbytes( )\nbytes( )\nbytes( )" + "Dialogue Label: 1__agent 2_agent 1\nbytes( )\nbytes( )\nbytes( )" ) assert str(self.dialogue) == dialogue_str def test___str__2(self): """Test the '__str__' method: dialogue is other initiated""" - self.dialogue._dialogue_label = DialogueLabel( - dialogue_reference=(str(1), str(1)), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 2", - ) - self.dialogue._is_self_initiated = False - valid_initial_msg = DefaultMessage( dialogue_reference=(str(1), ""), message_id=1, @@ -874,10 +837,11 @@ def test___str__2(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - valid_initial_msg.counterparty = "agent 2" - valid_initial_msg._is_incoming = True + valid_initial_msg.counterparty = self.opponent_address + valid_initial_msg.sender = self.agent_address + valid_initial_msg.is_incoming = True - assert self.dialogue.update(valid_initial_msg) + assert self.dialogue_opponent_started.update(valid_initial_msg) valid_second_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -886,10 +850,10 @@ def test___str__2(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - valid_second_msg.counterparty = "agent 2" + valid_second_msg.counterparty = self.opponent_address valid_second_msg._is_incoming = False - assert self.dialogue.update(valid_second_msg) + assert self.dialogue_opponent_started.update(valid_second_msg) valid_third_msg = DefaultMessage( dialogue_reference=(str(1), str(1)), @@ -898,16 +862,17 @@ def test___str__2(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back 2", ) - valid_third_msg.counterparty = "agent 2" - valid_third_msg._is_incoming = True + valid_third_msg.counterparty = self.opponent_address + valid_third_msg.sender = self.agent_address + valid_third_msg.is_incoming = True - assert self.dialogue.update(valid_third_msg) + assert self.dialogue_opponent_started.update(valid_third_msg) dialogue_str = ( "Dialogue Label: 1_1_agent 2_agent 2\nbytes( )\nbytes( )\nbytes( )" ) - assert str(self.dialogue) == dialogue_str + assert str(self.dialogue_opponent_started) == dialogue_str class TestDialogueStats: @@ -916,10 +881,12 @@ class TestDialogueStats: @classmethod def setup(cls): """Initialise the environment to test DialogueStats.""" + cls.agent_address = "agent 1" + cls.opponent_address = "agent 2" cls.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_opponent_addr=cls.opponent_address, + dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) end_states = frozenset( @@ -977,18 +944,20 @@ class TestDialoguesBase: @classmethod def setup(cls): """Initialise the environment to test Dialogue.""" + cls.agent_address = "agent 1" + cls.opponent_address = "agent 2" cls.dialogue_label = DialogueLabel( dialogue_reference=(str(1), ""), - dialogue_opponent_addr="agent 2", - dialogue_starter_addr="agent 1", + dialogue_opponent_addr=cls.opponent_address, + dialogue_starter_addr=cls.agent_address, ) cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label) - cls.dialogues = Dialogues("agent 1") + cls.dialogues = Dialogues(cls.agent_address) def test_dialogues_properties(self): """Test dialogue properties.""" assert self.dialogues.dialogues == dict() - assert self.dialogues.agent_address == "agent 1" + assert self.dialogues.agent_address == self.agent_address assert self.dialogues.dialogue_stats.other_initiated == { Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, @@ -1000,18 +969,25 @@ def test_dialogues_properties(self): def test_new_self_initiated_dialogue_reference(self): """Test the 'new_self_initiated_dialogue_reference' method.""" - assert self.dialogues.new_self_initiated_dialogue_reference() == ("1", "") + nonce = self.dialogues._dialogue_nonce + assert self.dialogues.new_self_initiated_dialogue_reference() == ( + str(nonce + 1), + "", + ) self.dialogues._create_opponent_initiated( - "agent 2", ("1", ""), Dialogue.Role.ROLE1 + self.opponent_address, ("1", ""), Dialogue.Role.ROLE1 + ) # increments dialogue nonce + assert self.dialogues.new_self_initiated_dialogue_reference() == ( + str(nonce + 3), + "", ) - assert self.dialogues.new_self_initiated_dialogue_reference() == ("2", "") def test_create_positive(self): """Positive test for the 'create' method.""" assert len(self.dialogues.dialogues) == 0 self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) assert len(self.dialogues.dialogues) == 1 @@ -1020,7 +996,9 @@ def test_create_negative_incorrect_performative_content_combination(self): assert len(self.dialogues.dialogues) == 0 try: self.dialogues.create( - "agent 2", DefaultMessage.Performative.ERROR, content=b"Hello" + self.opponent_address, + DefaultMessage.Performative.ERROR, + content=b"Hello", ) result = True except Exception: @@ -1029,6 +1007,19 @@ def test_create_negative_incorrect_performative_content_combination(self): assert not result assert len(self.dialogues.dialogues) == 0 + def test_update_negative_invalid_label(self): + """Negative test for the 'update' method: dialogue is not extendable with the input message.""" + invalid_message_id = DefaultMessage( + dialogue_reference=("", ""), + message_id=0, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello", + ) + invalid_message_id.counterparty = self.opponent_address + + assert not self.dialogues.update(invalid_message_id) + def test_update_positive_new_dialogue_by_other(self): """Positive test for the 'update' method: the input message is for a new dialogue dialogue by other.""" initial_msg = DefaultMessage( @@ -1038,8 +1029,9 @@ def test_update_positive_new_dialogue_by_other(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = True + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = True + initial_msg.sender = self.agent_address assert len(self.dialogues.dialogues) == 0 @@ -1062,7 +1054,7 @@ def test_update_positive_new_dialogue_by_self(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" + initial_msg.counterparty = self.opponent_address initial_msg._is_incoming = False assert len(self.dialogues.dialogues) == 0 @@ -1080,7 +1072,7 @@ def test_update_positive_new_dialogue_by_self(self): def test_update_positive_existing_dialogue(self): """Positive test for the 'update' method: the input message is for an existing dialogue.""" self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1090,8 +1082,9 @@ def test_update_positive_existing_dialogue(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = True + second_msg.sender = self.agent_address assert len(self.dialogues.dialogues) == 1 @@ -1114,24 +1107,38 @@ def test_update_negative_new_dialogue_by_self_no_counterparty(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg._is_incoming = False + initial_msg.is_incoming = False assert len(self.dialogues.dialogues) == 0 - try: + with pytest.raises(ValueError): self.dialogues.update(initial_msg) - result = True - except AssertionError: - result = False - assert not result + assert len(self.dialogues.dialogues) == 0 + + def test_update_negative_new_dialogue_by_other_no_sender(self): + """Negative test for the 'update' method: the counterparty of the input message is not set.""" + initial_msg = DefaultMessage( + dialogue_reference=(str(1), ""), + message_id=1, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello", + ) + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = True + + assert len(self.dialogues.dialogues) == 0 + + with pytest.raises(ValueError): + self.dialogues.update(initial_msg) assert len(self.dialogues.dialogues) == 0 def test_update_negative_existing_dialogue_non_nonexistent(self): """Negative test for the 'update' method: the dialogue referred by the input message does not exist.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1141,8 +1148,9 @@ def test_update_negative_existing_dialogue_non_nonexistent(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = True + second_msg.sender = self.agent_address updated_dialogue = self.dialogues.update(second_msg) @@ -1166,10 +1174,12 @@ def test_update_negative_existing_dialogue_non_nonexistent(self): == b"Hello" ) - def test_update_self_initiated_dialogue_label_on_second_message_positive(self): - """Positive test for the '_update_self_initiated_dialogue_label_on_second_message' method.""" + def test_update_self_initiated_dialogue_label_on_message_with_complete_reference_positive( + self, + ): + """Positive test for the '_update_self_initiated_dialogue_label_on_message_with_complete_reference' method.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1179,10 +1189,11 @@ def test_update_self_initiated_dialogue_label_on_second_message_positive(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = True + second_msg.sender = self.agent_address - self.dialogues._update_self_initiated_dialogue_label_on_second_message( + self.dialogues._update_self_initiated_dialogue_label_on_message_with_complete_reference( second_msg ) @@ -1190,12 +1201,12 @@ def test_update_self_initiated_dialogue_label_on_second_message_positive(self): dialogue.dialogue_label ].dialogue_label.dialogue_reference == (str(1), str(1)) - def test_update_self_initiated_dialogue_label_on_second_message_negative_incorrect_reference( + def test_update_self_initiated_dialogue_label_on_message_with_complete_reference_negative_incorrect_reference( self, ): - """Negative test for the '_update_self_initiated_dialogue_label_on_second_message' method: the input message has invalid dialogue reference.""" + """Negative test for the '_update_self_initiated_dialogue_label_on_message_with_complete_reference' method: the input message has invalid dialogue reference.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1205,10 +1216,11 @@ def test_update_self_initiated_dialogue_label_on_second_message_negative_incorre performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = True + second_msg.sender = self.agent_address - self.dialogues._update_self_initiated_dialogue_label_on_second_message( + self.dialogues._update_self_initiated_dialogue_label_on_message_with_complete_reference( second_msg ) @@ -1219,7 +1231,7 @@ def test_update_self_initiated_dialogue_label_on_second_message_negative_incorre def test_get_dialogue_positive_1(self): """Positive test for the 'get_dialogue' method: the dialogue is self initiated and the second message is by the other agent.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1229,10 +1241,10 @@ def test_get_dialogue_positive_1(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" + second_msg.counterparty = self.opponent_address second_msg._is_incoming = True - self.dialogues._update_self_initiated_dialogue_label_on_second_message( + self.dialogues._update_self_initiated_dialogue_label_on_message_with_complete_reference( second_msg ) @@ -1248,13 +1260,14 @@ def test_get_dialogue_positive_2(self): """Positive test for the 'get_dialogue' method: the dialogue is other initiated and the second message is by this agent.""" initial_msg = DefaultMessage( dialogue_reference=(str(1), ""), - message_id=2, - target=1, + message_id=1, + target=0, performative=DefaultMessage.Performative.BYTES, content=b"Hello", ) - initial_msg.counterparty = "agent 2" - initial_msg._is_incoming = True + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = True + initial_msg.sender = self.agent_address dialogue = self.dialogues.update(initial_msg) @@ -1265,17 +1278,45 @@ def test_get_dialogue_positive_2(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" + second_msg.counterparty = self.opponent_address second_msg._is_incoming = False retrieved_dialogue = self.dialogues.get_dialogue(second_msg) assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label + def test_update_positive_3(self): + """Positive test for the 'get_dialogue' method: the dialogue is reference is incomplete and not a first message.""" + initial_msg = DefaultMessage( + dialogue_reference=(str(1), ""), + message_id=1, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello", + ) + initial_msg.counterparty = self.opponent_address + initial_msg.is_incoming = False + + dialogue = self.dialogues.update(initial_msg) + + second_msg = DefaultMessage( + dialogue_reference=(str(1), ""), + message_id=2, + target=1, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello back", + ) + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = False + + retrieved_dialogue = self.dialogues.update(second_msg) + + assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label + def test_get_dialogue_negative_invalid_reference(self): """Negative test for the 'get_dialogue' method: the inpute message has invalid dialogue reference.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) second_msg = DefaultMessage( @@ -1285,10 +1326,11 @@ def test_get_dialogue_negative_invalid_reference(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - second_msg.counterparty = "agent 2" - second_msg._is_incoming = True - - self.dialogues.update(second_msg) + second_msg.counterparty = self.opponent_address + second_msg.is_incoming = True + second_msg.sender = self.agent_address + dialogue = self.dialogues.update(second_msg) + assert dialogue is not None third_msg = DefaultMessage( dialogue_reference=(str(2), str(1)), @@ -1297,8 +1339,8 @@ def test_get_dialogue_negative_invalid_reference(self): performative=DefaultMessage.Performative.BYTES, content=b"Hello back", ) - third_msg.counterparty = "agent 2" - third_msg._is_incoming = False + third_msg.counterparty = self.opponent_address + third_msg.is_incoming = False retrieved_dialogue = self.dialogues.get_dialogue(third_msg) @@ -1307,7 +1349,7 @@ def test_get_dialogue_negative_invalid_reference(self): def test_get_dialogue_from_label_positive(self): """Positive test for the 'get_dialogue_from_label' method.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) retrieved_dialogue = self.dialogues.get_dialogue_from_label( @@ -1318,10 +1360,12 @@ def test_get_dialogue_from_label_positive(self): def test_get_dialogue_from_label_negative_incorrect_input_label(self): """Negative test for the 'get_dialogue_from_label' method: the input dialogue label does not exist.""" _, dialogue = self.dialogues.create( - "agent 2", DefaultMessage.Performative.BYTES, content=b"Hello" + self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello" ) - incorrect_label = DialogueLabel((str(1), "error"), "agent 2", "agent 1") + incorrect_label = DialogueLabel( + (str(1), "error"), self.opponent_address, self.agent_address + ) retrieved_dialogue = self.dialogues.get_dialogue_from_label(incorrect_label) assert retrieved_dialogue is None @@ -1330,7 +1374,9 @@ def test_create_self_initiated_positive(self): """Positive test for the '_create_self_initiated' method.""" assert len(self.dialogues.dialogues) == 0 - self.dialogues._create_self_initiated("agent 2", Dialogue.Role.ROLE1) + self.dialogues._create_self_initiated( + self.opponent_address, (str(1), ""), Dialogue.Role.ROLE1 + ) assert len(self.dialogues.dialogues) == 1 def test_create_opponent_initiated_positive(self): @@ -1338,7 +1384,7 @@ def test_create_opponent_initiated_positive(self): assert len(self.dialogues.dialogues) == 0 self.dialogues._create_opponent_initiated( - "agent 2", (str(1), ""), Dialogue.Role.ROLE2 + self.opponent_address, (str(1), ""), Dialogue.Role.ROLE2 ) assert len(self.dialogues.dialogues) == 1 @@ -1348,7 +1394,7 @@ def test_create_opponent_initiated_negative_invalid_input_dialogue_reference(sel try: self.dialogues._create_opponent_initiated( - "agent 2", ("", str(1)), Dialogue.Role.ROLE2 + self.opponent_address, ("", str(1)), Dialogue.Role.ROLE2 ) result = True except AssertionError: diff --git a/tests/test_helpers/test_logging.py b/tests/test_helpers/test_logging.py new file mode 100644 index 0000000000..1a0b893875 --- /dev/null +++ b/tests/test_helpers/test_logging.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 tests for the helpers/logging module.""" +import logging + +from aea.helpers.logging import AgentLoggerAdapter, WithLogger + + +def test_agent_logger_adapter(caplog): + """Test the agent logger adapter.""" + logger = logging.getLogger("some.logger") + logger = AgentLoggerAdapter(logger, agent_name="some_agent") + with caplog.at_level(logging.DEBUG, logger="some.logger"): + logger.debug("Some log message.") + assert "[some_agent] Some log message." in caplog.text + + +def test_with_logger_default_logger_name(caplog): + """Test the WithLogger interface, default logger name.""" + + class SomeClass(WithLogger): + pass + + x = SomeClass() + assert isinstance(x.logger, logging.Logger) + + with caplog.at_level(logging.DEBUG, logger="aea"): + x.logger.debug("Some log message.") + assert "Some log message." in caplog.text + + +def test_with_logger_custom_logger_name(caplog): + """Test the WithLogger interface, custom logger name.""" + + class SomeClass(WithLogger): + pass + + x = SomeClass(default_logger_name="some.logger") + assert isinstance(x.logger, logging.Logger) + + with caplog.at_level(logging.DEBUG, logger="some.logger"): + x.logger.debug("Some log message.") + assert "Some log message." in caplog.text + + +def test_with_logger_custom_logger(caplog): + """Test the WithLogger interface, custom logger.""" + + class SomeClass(WithLogger): + pass + + logger = logging.getLogger("some.logger") + x = SomeClass(logger=logger) + assert isinstance(x.logger, logging.Logger) + + with caplog.at_level(logging.DEBUG, logger="some.logger"): + x.logger.debug("Some log message.") + assert "Some log message." in caplog.text + + +def test_with_logger_setter(): + """Test the WithLogger interface, logger setter.""" + + class SomeClass(WithLogger): + pass + + logger_1 = logging.getLogger("some.logger") + x = SomeClass(logger=logger_1) + assert isinstance(x.logger, logging.Logger) + assert x.logger.name == "some.logger" + logger_2 = logging.getLogger("another.logger") + x.logger = logger_2 + assert x.logger.name == "another.logger" diff --git a/tests/test_helpers/test_transaction/test_base.py b/tests/test_helpers/test_transaction/test_base.py index 63d1cc5574..e1a96f7238 100644 --- a/tests/test_helpers/test_transaction/test_base.py +++ b/tests/test_helpers/test_transaction/test_base.py @@ -21,6 +21,7 @@ import pytest +from aea.configurations.constants import DEFAULT_LEDGER from aea.helpers.transaction.base import ( RawMessage, RawTransaction, @@ -35,7 +36,7 @@ def test_init_terms(): """Test the terms object initialization.""" - ledger_id = "some_ledger" + ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} @@ -43,6 +44,7 @@ def test_init_terms(): is_sender_payable_tx_fee = True nonce = "somestring" kwargs = {"key": "value"} + fee_by_currency_id = {} terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, @@ -53,6 +55,10 @@ def test_init_terms(): nonce=nonce, **kwargs ) + sender_hash = "9af02c24bdb18b73aad129291dc9eee008f9bcf62f5a6e91b5cb7427f146ca3b" + counterparty_hash = ( + "174c1321c0eb4a49bf99d783b56f4fc30d0ee558106454c56d1c0fad295ccc79" + ) assert terms.ledger_id == ledger_id assert terms.sender_address == sender_addr assert terms.counterparty_address == counterparty_addr @@ -61,9 +67,23 @@ def test_init_terms(): assert terms.is_sender_payable_tx_fee == is_sender_payable_tx_fee assert terms.nonce == nonce assert terms.kwargs == kwargs - assert ( - str(terms) - == "Terms: ledger_id=some_ledger, sender_address=SenderAddress, counterparty_address=CounterpartyAddress, amount_by_currency_id={'FET': -10}, quantities_by_good_id={'good_1': 20}, is_sender_payable_tx_fee=True, nonce=somestring, fee_by_currency_id=None, kwargs={'key': 'value'}" + assert terms.fee_by_currency_id == fee_by_currency_id + assert terms.id == sender_hash + assert terms.sender_hash == sender_hash + assert terms.counterparty_hash == counterparty_hash + assert terms.currency_id == next(iter(amount_by_currency_id.keys())) + assert str( + terms + ) == "Terms: ledger_id={}, sender_address={}, counterparty_address={}, amount_by_currency_id={}, quantities_by_good_id={}, is_sender_payable_tx_fee={}, nonce={}, fee_by_currency_id={}, kwargs={}".format( + ledger_id, + sender_addr, + counterparty_addr, + amount_by_currency_id, + quantities_by_good_id, + is_sender_payable_tx_fee, + nonce, + fee_by_currency_id, + kwargs, ) assert terms == terms with pytest.raises(AssertionError): @@ -72,14 +92,14 @@ def test_init_terms(): def test_init_terms_w_fee(): """Test the terms object initialization with fee.""" - ledger_id = "some_ledger" + ledger_id = DEFAULT_LEDGER sender_addr = "SenderAddress" counterparty_addr = "CounterpartyAddress" amount_by_currency_id = {"FET": -10} quantities_by_good_id = {"good_1": 20} is_sender_payable_tx_fee = True nonce = "somestring" - fee = {"FET": 1} + fee_by_currency_id = {"FET": 1} terms = Terms( ledger_id=ledger_id, sender_address=sender_addr, @@ -88,17 +108,176 @@ def test_init_terms_w_fee(): quantities_by_good_id=quantities_by_good_id, is_sender_payable_tx_fee=is_sender_payable_tx_fee, nonce=nonce, - fee_by_currency_id=fee, + fee_by_currency_id=fee_by_currency_id, ) new_counterparty_address = "CounterpartyAddressNew" terms.counterparty_address = new_counterparty_address assert terms.counterparty_address == new_counterparty_address - assert terms.fee == fee["FET"] - assert terms.fee_by_currency_id == fee + assert terms.fee == next(iter(fee_by_currency_id.values())) + assert terms.fee_by_currency_id == fee_by_currency_id + assert terms.counterparty_payable_amount == 0 + assert terms.sender_payable_amount == -next(iter(amount_by_currency_id.values())) + assert terms.sender_payable_amount_incl_fee == -next( + iter(amount_by_currency_id.values()) + ) + next(iter(fee_by_currency_id.values())) + assert terms.sender_fee == next(iter(fee_by_currency_id.values())) + assert terms.counterparty_fee == 0 + + +def test_init_terms_w_fee_counterparty(): + """Test the terms object initialization with fee.""" + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {"FET": 10} + quantities_by_good_id = {"good_1": -20} + is_sender_payable_tx_fee = False + nonce = "somestring" + fee_by_currency_id = {"FET": 1} + terms = Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + fee_by_currency_id=fee_by_currency_id, + ) + new_counterparty_address = "CounterpartyAddressNew" + terms.counterparty_address = new_counterparty_address + assert terms.counterparty_address == new_counterparty_address + assert terms.fee == next(iter(fee_by_currency_id.values())) + assert terms.fee_by_currency_id == fee_by_currency_id assert terms.counterparty_payable_amount == next( iter(amount_by_currency_id.values()) ) - assert terms.sender_payable_amount == -next(iter(amount_by_currency_id.values())) + assert terms.counterparty_payable_amount_incl_fee == next( + iter(amount_by_currency_id.values()) + ) + next(iter(fee_by_currency_id.values())) + assert terms.sender_payable_amount == 0 + assert terms.sender_fee == 0 + assert terms.counterparty_fee == next(iter(fee_by_currency_id.values())) + + +def test_init_terms_strict_positive(): + """Test the terms object initialization in strict mode.""" + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {"FET": -10} + quantities_by_good_id = {"good_1": 20} + is_sender_payable_tx_fee = True + nonce = "somestring" + assert Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + is_strict=True, + ) + + +def test_init_terms_strict_negative(): + """Test the terms object initialization in strict mode.""" + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {"FET": 10} + quantities_by_good_id = {"good_1": 20} + is_sender_payable_tx_fee = True + nonce = "somestring" + with pytest.raises(AssertionError): + Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + is_strict=True, + ) + + +def test_init_terms_multiple_goods(): + """Test the terms object initialization with multiple goods.""" + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {"FET": -10} + quantities_by_good_id = {"good_1": 20, "good_2": -10} + is_sender_payable_tx_fee = True + nonce = "somestring" + terms = Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + ) + assert ( + terms.id == "f81812773f5242d0cb52cfa82bc08bdba8d17b1e56e2cf02b3056749184e198c" + ) + + +def test_init_terms_no_amount_and_quantity(): + """Test the terms object initialization with no amount.""" + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {} + quantities_by_good_id = {} + nonce = "somestring" + terms = Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + nonce=nonce, + ) + new_counterparty_address = "CounterpartyAddressNew" + terms.counterparty_address = new_counterparty_address + assert terms.counterparty_address == new_counterparty_address + assert not terms.has_fee + assert terms.counterparty_payable_amount == 0 + assert terms.counterparty_payable_amount_incl_fee == 0 + assert terms.sender_payable_amount == 0 + assert terms.sender_payable_amount_incl_fee == 0 + + +def test_terms_encode_decode(): + """Test encoding and decoding of terms.""" + + class TermsProtobufObject: + terms_bytes = b"" + + ledger_id = DEFAULT_LEDGER + sender_addr = "SenderAddress" + counterparty_addr = "CounterpartyAddress" + amount_by_currency_id = {"FET": -10} + quantities_by_good_id = {"good_1": 20} + is_sender_payable_tx_fee = True + nonce = "somestring" + terms = Terms( + ledger_id=ledger_id, + sender_address=sender_addr, + counterparty_address=counterparty_addr, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=quantities_by_good_id, + is_sender_payable_tx_fee=is_sender_payable_tx_fee, + nonce=nonce, + is_strict=True, + ) + Terms.encode(TermsProtobufObject, terms) + recovered_terms = Terms.decode(TermsProtobufObject) + assert terms == recovered_terms def test_init_raw_transaction(): @@ -112,6 +291,20 @@ def test_init_raw_transaction(): assert rt == rt +def test_raw_transaction_encode_decode(): + """Test encoding and decoding of terms.""" + + class RawTransactionProtobufObject: + raw_transaction_bytes = b"" + + ledger_id = "some_ledger" + body = "body" + rt = RawTransaction(ledger_id, body) + RawTransaction.encode(RawTransactionProtobufObject, rt) + recovered_rt = RawTransaction.decode(RawTransactionProtobufObject) + assert rt == recovered_rt + + def test_init_raw_message(): """Test the raw_message object initialization.""" ledger_id = "some_ledger" @@ -127,6 +320,20 @@ def test_init_raw_message(): assert rm == rm +def test_raw_message_encode_decode(): + """Test encoding and decoding of raw_message.""" + + class RawMessageProtobufObject: + raw_message_bytes = b"" + + ledger_id = "some_ledger" + body = "body" + rm = RawMessage(ledger_id, body) + RawMessage.encode(RawMessageProtobufObject, rm) + recovered_rm = RawMessage.decode(RawMessageProtobufObject) + assert rm == recovered_rm + + def test_init_signed_transaction(): """Test the signed_transaction object initialization.""" ledger_id = "some_ledger" @@ -138,6 +345,20 @@ def test_init_signed_transaction(): assert st == st +def test_signed_transaction_encode_decode(): + """Test encoding and decoding of signed_transaction.""" + + class SignedTransactionProtobufObject: + signed_transaction_bytes = b"" + + ledger_id = "some_ledger" + body = "body" + st = SignedTransaction(ledger_id, body) + SignedTransaction.encode(SignedTransactionProtobufObject, st) + recovered_st = SignedTransaction.decode(SignedTransactionProtobufObject) + assert st == recovered_st + + def test_init_signed_message(): """Test the signed_message object initialization.""" ledger_id = "some_ledger" @@ -153,6 +374,20 @@ def test_init_signed_message(): assert sm == sm +def test_signed_message_encode_decode(): + """Test encoding and decoding of signed_message.""" + + class SignedMessageProtobufObject: + signed_message_bytes = b"" + + ledger_id = "some_ledger" + body = "body" + sm = SignedMessage(ledger_id, body) + SignedMessage.encode(SignedMessageProtobufObject, sm) + recovered_sm = SignedMessage.decode(SignedMessageProtobufObject) + assert sm == recovered_sm + + def test_init_transaction_receipt(): """Test the transaction_receipt object initialization.""" ledger_id = "some_ledger" @@ -169,6 +404,21 @@ def test_init_transaction_receipt(): assert tr == tr +def test_transaction_receipt_encode_decode(): + """Test encoding and decoding of transaction_receipt.""" + + class TransactionReceiptProtobufObject: + transaction_receipt_bytes = b"" + + ledger_id = "some_ledger" + receipt = "receipt" + transaction = "transaction" + tr = TransactionReceipt(ledger_id, receipt, transaction) + TransactionReceipt.encode(TransactionReceiptProtobufObject, tr) + recovered_tr = TransactionReceipt.decode(TransactionReceiptProtobufObject) + assert tr == recovered_tr + + def test_init_state(): """Test the state object initialization.""" ledger_id = "some_ledger" @@ -180,12 +430,40 @@ def test_init_state(): assert state == state +def test_state_encode_decode(): + """Test encoding and decoding of state.""" + + class StateProtobufObject: + state_bytes = b"" + + ledger_id = "some_ledger" + body = "state" + state = State(ledger_id, body) + State.encode(StateProtobufObject, state) + recovered_state = State.decode(StateProtobufObject) + assert state == recovered_state + + def test_init_transaction_digest(): """Test the transaction_digest object initialization.""" ledger_id = "some_ledger" - body = "state" + body = "digest" td = TransactionDigest(ledger_id, body) assert td.ledger_id == ledger_id assert td.body == body - assert str(td) == "TransactionDigest: ledger_id=some_ledger, body=state" + assert str(td) == "TransactionDigest: ledger_id={}, body={}".format(ledger_id, body) assert td == td + + +def test_transaction_digest_encode_decode(): + """Test encoding and decoding of transaction_digest.""" + + class TransactionDigestProtobufObject: + transaction_digest_bytes = b"" + + ledger_id = "some_ledger" + body = "digest" + td = TransactionDigest(ledger_id, body) + TransactionDigest.encode(TransactionDigestProtobufObject, td) + recovered_td = TransactionDigest.decode(TransactionDigestProtobufObject) + assert td == recovered_td diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 61c8f154c3..aa41aded02 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -141,10 +141,11 @@ def test_one_fails(self) -> None: runner.stop() def test_run_agent_in_thread(self): - """Test agent started ans stopped in thread.""" + """Test agent started and stopped in thread.""" stop_event = Event() t = Thread(target=_run_agent, args=(self.agent_name_1, stop_event)) t.start() + time.sleep(1) stop_event.set() t.join(10) diff --git a/tests/test_mail/test_base.py b/tests/test_mail/test_base.py index f6aace7334..3b4a3db371 100644 --- a/tests/test_mail/test_base.py +++ b/tests/test_mail/test_base.py @@ -175,6 +175,7 @@ def test_outbox_put(): content=b"hello", ) msg.counterparty = receiver_address + msg.sender = agent_address dummy_connection = _make_dummy_connection() multiplexer = Multiplexer([dummy_connection]) outbox = OutBox(multiplexer, agent_address) @@ -204,6 +205,7 @@ def test_outbox_put_message(): content=b"hello", ) msg.counterparty = receiver_address + msg.sender = agent_address dummy_connection = _make_dummy_connection() multiplexer = Multiplexer([dummy_connection]) outbox = OutBox(multiplexer, agent_address) diff --git a/tests/test_multiplexer.py b/tests/test_multiplexer.py index 9fc8210532..21dedf10fc 100644 --- a/tests/test_multiplexer.py +++ b/tests/test_multiplexer.py @@ -35,7 +35,7 @@ from aea.configurations.base import PublicId from aea.identity.base import Identity from aea.mail.base import AEAConnectionError, Envelope, EnvelopeContext -from aea.multiplexer import AsyncMultiplexer, InBox, Multiplexer +from aea.multiplexer import AsyncMultiplexer, InBox, Multiplexer, OutBox from aea.protocols.default.message import DefaultMessage from packages.fetchai.connections.local.connection import LocalNode @@ -156,9 +156,9 @@ def test_multiplexer_connect_one_raises_error_many_connections(): connection_3 = _make_dummy_connection() multiplexer = Multiplexer([connection_1, connection_2, connection_3]) - assert not connection_1.connection_status.is_connected - assert not connection_2.connection_status.is_connected - assert not connection_3.connection_status.is_connected + assert not connection_1.is_connected + assert not connection_2.is_connected + assert not connection_3.is_connected with unittest.mock.patch.object(connection_3, "connect", side_effect=Exception): with pytest.raises( @@ -166,9 +166,9 @@ def test_multiplexer_connect_one_raises_error_many_connections(): ): multiplexer.connect() - assert not connection_1.connection_status.is_connected - assert not connection_2.connection_status.is_connected - assert not connection_3.connection_status.is_connected + assert not connection_1.is_connected + assert not connection_2.is_connected + assert not connection_3.is_connected multiplexer.disconnect() try: @@ -228,15 +228,15 @@ async def test_multiplexer_disconnect_one_raises_error_many_connections(): connection_3 = _make_dummy_connection() multiplexer = Multiplexer([connection_1, connection_2, connection_3]) - assert not connection_1.connection_status.is_connected - assert not connection_2.connection_status.is_connected - assert not connection_3.connection_status.is_connected + assert not connection_1.is_connected + assert not connection_2.is_connected + assert not connection_3.is_connected multiplexer.connect() - assert connection_1.connection_status.is_connected - assert connection_2.connection_status.is_connected - assert connection_3.connection_status.is_connected + assert connection_1.is_connected + assert connection_2.is_connected + assert connection_3.is_connected with unittest.mock.patch.object( connection_3, "disconnect", side_effect=Exception @@ -246,9 +246,9 @@ async def test_multiplexer_disconnect_one_raises_error_many_connections(): ): multiplexer.disconnect() - assert not connection_1.connection_status.is_connected - assert not connection_2.connection_status.is_connected - assert connection_3.connection_status.is_connected + assert not connection_1.is_connected + assert not connection_2.is_connected + assert connection_3.is_connected # clean the test up. await connection_3.disconnect() @@ -371,13 +371,13 @@ def test_get_from_multiplexer_when_empty(): # multiplexer = Multiplexer([connection_1, connection_2]) -# assert not connection_1.connection_status.is_connected -# assert not connection_2.connection_status.is_connected +# assert not connection_1.is_connected +# assert not connection_2.is_connected # multiplexer.connect() -# assert connection_1.connection_status.is_connected -# assert connection_2.connection_status.is_connected +# assert connection_1.is_connected +# assert connection_2.is_connected # message = DefaultMessage( # dialogue_reference=("", ""), # message_id=1, @@ -489,23 +489,91 @@ async def test_inbox_outbox(): connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) + msg = DefaultMessage(performative=DefaultMessage.Performative.BYTES, content=b"",) + msg.counterparty = "to" + msg.sender = "sender" + context = EnvelopeContext(connection_id=connection_1.connection_id) envelope = Envelope( - to="", - sender="", - protocol_id=DefaultMessage.protocol_id, - message=b"", - context=EnvelopeContext(connection_id=connection_1.connection_id), + to="to", + sender="sender", + protocol_id=msg.protocol_id, + message=msg, + context=context, ) try: await multiplexer.connect() inbox = InBox(multiplexer) - outbox = InBox(multiplexer) + outbox = OutBox(multiplexer, "default_address") assert inbox.empty() assert outbox.empty() - multiplexer.put(envelope) - await outbox.async_get() + outbox.put(envelope) + received = await inbox.async_get() + assert received == envelope + + assert inbox.empty() + assert outbox.empty() + + outbox.put_message(msg, context=context) + await inbox.async_wait() + received = inbox.get_nowait() + assert received == envelope + + finally: + await multiplexer.disconnect() + + +@pytest.mark.asyncio +async def test_outbox_negative(): + """Test InBox OutBox objects.""" + connection_1 = _make_dummy_connection() + connections = [connection_1] + multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) + msg = DefaultMessage(performative=DefaultMessage.Performative.BYTES, content=b"",) + context = EnvelopeContext(connection_id=connection_1.connection_id) + envelope = Envelope( + to="to", + sender="sender", + protocol_id=msg.protocol_id, + message=b"", + context=context, + ) + + try: + await multiplexer.connect() + outbox = OutBox(multiplexer, "default_address") + + assert outbox.empty() + + with pytest.raises(ValueError) as execinfo: + outbox.put(envelope) + assert ( + str(execinfo.value) + == "Only Message type allowed in envelope message field when putting into outbox." + ) + + assert outbox.empty() + + with pytest.raises(ValueError) as execinfo: + outbox.put_message("") + assert str(execinfo.value) == "Provided message not of type Message." + + assert outbox.empty() + + with pytest.raises(ValueError) as execinfo: + outbox.put_message(msg) + assert ( + str(execinfo.value) == "Provided message has message.counterparty not set." + ) + + assert outbox.empty() + msg.counterparty = "to" + + with pytest.raises(ValueError) as execinfo: + outbox.put_message(msg) + assert str(execinfo.value) == "Provided message has message.sender not set." + finally: await multiplexer.disconnect() diff --git a/tests/test_packages/test_connections/test_gym/test_gym.py b/tests/test_packages/test_connections/test_gym/test_gym.py index 32c23eba3b..077823c301 100644 --- a/tests/test_packages/test_connections/test_gym/test_gym.py +++ b/tests/test_packages/test_connections/test_gym/test_gym.py @@ -18,8 +18,10 @@ # ------------------------------------------------------------------------------ """This module contains the tests of the gym connection module.""" import asyncio +import copy import logging import os +from typing import cast from unittest.mock import patch import gym @@ -30,7 +32,9 @@ from aea.identity.base import Identity from aea.mail.base import Envelope + from packages.fetchai.connections.gym.connection import GymConnection +from packages.fetchai.protocols.gym.dialogues import GymDialogue, GymDialogues from packages.fetchai.protocols.gym.message import GymMessage from tests.conftest import ROOT_DIR, UNKNOWN_PROTOCOL_PUBLIC_ID @@ -45,12 +49,14 @@ def setup(self): """Initialise the class.""" self.env = gym.GoalEnv() configuration = ConnectionConfig(connection_id=GymConnection.connection_id) - self.my_address = "my_key" - identity = Identity("name", address=self.my_address) + self.agent_address = "my_address" + identity = Identity("name", address=self.agent_address) self.gym_con = GymConnection( gym_env=self.env, identity=identity, configuration=configuration ) self.loop = asyncio.get_event_loop() + self.gym_address = str(GymConnection.connection_id) + self.dialogues = GymDialogues(self.gym_address) def teardown(self): """Clean up after tests.""" @@ -68,8 +74,8 @@ async def test_decode_envelope_error(self): """Test the decoding error for the envelopes.""" await self.gym_con.connect() envelope = Envelope( - to="_to_key", - sender=self.my_address, + to=self.gym_address, + sender=self.agent_address, protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=b"hello", ) @@ -81,15 +87,16 @@ async def test_decode_envelope_error(self): async def test_send_connection_error(self): """Test send connection error.""" msg = GymMessage( - performative=GymMessage.Performative.ACT, - action=GymMessage.AnyObject("any_action"), - step_id=1, + performative=GymMessage.Performative.RESET, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), ) - msg.counterparty = "_to_key" + msg.counterparty = self.gym_address + sending_dialogue = self.dialogues.update(msg) + assert sending_dialogue is not None envelope = Envelope( - to="_to_key", - sender="_from_key", - protocol_id=GymMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) @@ -99,64 +106,138 @@ async def test_send_connection_error(self): @pytest.mark.asyncio async def test_send_act(self): """Test send act message.""" + sending_dialogue = await self.send_reset() + last_message = sending_dialogue.last_message + assert last_message is not None msg = GymMessage( performative=GymMessage.Performative.ACT, action=GymMessage.AnyObject("any_action"), step_id=1, + dialogue_reference=sending_dialogue.dialogue_label.dialogue_reference, + message_id=last_message.message_id + 1, + target=last_message.message_id, ) - msg.counterparty = "_to_key" + msg.counterparty = self.gym_address + assert sending_dialogue.update(msg) envelope = Envelope( - to="_to_key", - sender=self.my_address, - protocol_id=GymMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) await self.gym_con.connect() + observation = 1 + reward = 1.0 + done = True + info = "some info" with patch.object( - self.env, "step", return_value=(1, 1.0, True, "some info") + self.env, "step", return_value=(observation, reward, done, info) ) as mock: await self.gym_con.send(envelope) mock.assert_called() - assert await asyncio.wait_for(self.gym_con.receive(), timeout=3) is not None + response = await asyncio.wait_for(self.gym_con.receive(), timeout=3) + response_msg_orig = cast(GymMessage, response.message) + response_msg = copy.copy(response_msg_orig) + response_msg.is_incoming = True + response_msg.counterparty = response_msg_orig.sender + response_dialogue = self.dialogues.update(response_msg) + + assert response_msg.performative == GymMessage.Performative.PERCEPT + assert response_msg.step_id == msg.step_id + assert response_msg.observation.any == observation + assert response_msg.reward == reward + assert response_msg.done == done + assert response_msg.info.any == info + assert sending_dialogue == response_dialogue @pytest.mark.asyncio async def test_send_reset(self): """Test send reset message.""" - msg = GymMessage(performative=GymMessage.Performative.RESET,) - msg.counterparty = "_to_key" + _ = await self.send_reset() + + @pytest.mark.asyncio + async def test_send_close(self): + """Test send close message.""" + sending_dialogue = await self.send_reset() + last_message = sending_dialogue.last_message + assert last_message is not None + msg = GymMessage( + performative=GymMessage.Performative.CLOSE, + dialogue_reference=sending_dialogue.dialogue_label.dialogue_reference, + message_id=last_message.message_id + 1, + target=last_message.message_id, + ) + msg.counterparty = self.gym_address + assert sending_dialogue.update(msg) envelope = Envelope( - to="_to_key", - sender=self.my_address, - protocol_id=GymMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) await self.gym_con.connect() - with pytest.raises(gym.error.Error): + with patch.object(self.env, "close") as mock: await self.gym_con.send(envelope) - - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for(self.gym_con.receive(), timeout=0.5) + mock.assert_called() @pytest.mark.asyncio - async def test_send_close(self): - """Test send close message.""" - msg = GymMessage(performative=GymMessage.Performative.CLOSE,) - msg.counterparty = "_to_key" + async def test_send_close_negative(self): + """Test send close message with invalid reference and message id and target.""" + msg = GymMessage( + performative=GymMessage.Performative.CLOSE, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), + ) + msg.counterparty = self.gym_address + dialogue = self.dialogues.update(msg) + assert dialogue is None + msg.sender = self.agent_address + envelope = Envelope( + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, + message=msg, + ) + await self.gym_con.connect() + + with patch.object(self.gym_con.channel.logger, "warning") as mock_logger: + await self.gym_con.send(envelope) + mock_logger.assert_any_call(f"Could not create dialogue from message={msg}") + + async def send_reset(self) -> GymDialogue: + """Send a reset.""" + msg = GymMessage( + performative=GymMessage.Performative.RESET, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), + ) + msg.counterparty = self.gym_address + sending_dialogue = self.dialogues.update(msg) + assert sending_dialogue is not None envelope = Envelope( - to="_to_key", - sender=self.my_address, - protocol_id=GymMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) await self.gym_con.connect() - await self.gym_con.send(envelope) + with patch.object(self.env, "reset") as mock: + await self.gym_con.send(envelope) + mock.assert_called() + + response = await asyncio.wait_for(self.gym_con.receive(), timeout=3) + response_msg_orig = cast(GymMessage, response.message) + response_msg = copy.copy(response_msg_orig) + response_msg.is_incoming = True + response_msg.counterparty = response_msg_orig.sender + response_dialogue = self.dialogues.update(response_msg) - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for(self.gym_con.receive(), timeout=0.5) + assert response_msg.performative == GymMessage.Performative.STATUS + assert response_msg.content == {"reset": "success"} + assert sending_dialogue == response_dialogue + return sending_dialogue @pytest.mark.asyncio async def test_receive_connection_error(self): @@ -172,7 +253,7 @@ def test_gym_env_load(self): configuration = ConnectionConfig( connection_id=GymConnection.connection_id, env=gym_env_path ) - identity = Identity("name", address=self.my_address) + identity = Identity("name", address=self.agent_address) gym_con = GymConnection( gym_env=None, identity=identity, configuration=configuration ) diff --git a/tests/test_packages/test_connections/test_http_client/test_http_client.py b/tests/test_packages/test_connections/test_http_client/test_http_client.py index 8479732f5e..f2709a08fd 100644 --- a/tests/test_packages/test_connections/test_http_client/test_http_client.py +++ b/tests/test_packages/test_connections/test_http_client/test_http_client.py @@ -18,6 +18,7 @@ # ------------------------------------------------------------------------------ """Tests for the HTTP Client connection and channel.""" import asyncio +import copy import logging from asyncio import CancelledError from unittest.mock import Mock, patch @@ -32,8 +33,10 @@ from aea.mail.base import Envelope from packages.fetchai.connections.http_client.connection import HTTPClientConnection +from packages.fetchai.protocols.http.dialogues import HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage +from tests.common.mocks import AnyStringWith from tests.conftest import ( UNKNOWN_PROTOCOL_PUBLIC_ID, get_host, @@ -68,6 +71,7 @@ def setup(self): self.address = get_host() self.port = get_unused_tcp_port() self.agent_identity = Identity("name", address="some string") + self.agent_address = self.agent_identity.address configuration = ConnectionConfig( host=self.address, port=self.port, @@ -77,6 +81,8 @@ def setup(self): configuration=configuration, identity=self.agent_identity ) self.http_client_connection.loop = asyncio.get_event_loop() + self.connection_address = str(HTTPClientConnection.connection_id) + self.http_dialogs = HttpDialogues(self.connection_address) @pytest.mark.asyncio async def test_initialization(self): @@ -87,16 +93,16 @@ async def test_initialization(self): async def test_connection(self): """Test the connect functionality of the http client connection.""" await self.http_client_connection.connect() - assert self.http_client_connection.connection_status.is_connected is True + assert self.http_client_connection.is_connected is True @pytest.mark.asyncio async def test_disconnect(self): """Test the disconnect functionality of the http client connection.""" await self.http_client_connection.connect() - assert self.http_client_connection.connection_status.is_connected is True + assert self.http_client_connection.is_connected is True await self.http_client_connection.disconnect() - assert self.http_client_connection.connection_status.is_connected is False + assert self.http_client_connection.is_connected is False @pytest.mark.asyncio async def test_http_send_error(self): @@ -104,9 +110,7 @@ async def test_http_send_error(self): await self.http_client_connection.connect() request_http_message = HttpMessage( - dialogue_reference=("", ""), - target=0, - message_id=1, + dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="get", url="bad url", @@ -114,9 +118,12 @@ async def test_http_send_error(self): version="", bodyy=b"", ) + request_http_message.counterparty = self.connection_address + sending_dialogue = self.http_dialogs.update(request_http_message) + assert sending_dialogue is not None request_envelope = Envelope( - to="receiver", - sender="sender", + to=self.connection_address, + sender=self.agent_address, protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=request_http_message, ) @@ -150,9 +157,7 @@ async def test_http_channel_send_not_connected_error(self): async def test_send_envelope_excluded_protocol_fail(self): """Test send error if protocol not supported.""" request_http_message = HttpMessage( - dialogue_reference=("", ""), - target=0, - message_id=1, + dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="get", url="bad url", @@ -160,9 +165,12 @@ async def test_send_envelope_excluded_protocol_fail(self): version="", bodyy=b"", ) + request_http_message.counterparty = self.connection_address + sending_dialogue = self.http_dialogs.update(request_http_message) + assert sending_dialogue is not None request_envelope = Envelope( - to="receiver", - sender="sender", + to=self.connection_address, + sender=self.agent_address, protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=request_http_message, ) @@ -198,9 +206,7 @@ async def test_channel_cancel_tasks_on_disconnect(self): await self.http_client_connection.connect() request_http_message = HttpMessage( - dialogue_reference=("", ""), - target=0, - message_id=1, + dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="get", url="https://not-a-google.com", @@ -208,9 +214,12 @@ async def test_channel_cancel_tasks_on_disconnect(self): version="", bodyy=b"", ) + request_http_message.counterparty = self.connection_address + sending_dialogue = self.http_dialogs.update(request_http_message) + assert sending_dialogue is not None request_envelope = Envelope( - to="receiver", - sender="sender", + to=self.connection_address, + sender=self.agent_address, protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, message=request_http_message, ) @@ -246,9 +255,7 @@ async def test_http_send_ok(self): await self.http_client_connection.connect() request_http_message = HttpMessage( - dialogue_reference=("", ""), - target=0, - message_id=1, + dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), performative=HttpMessage.Performative.REQUEST, method="get", url="https://not-a-google.com", @@ -256,10 +263,13 @@ async def test_http_send_ok(self): version="", bodyy=b"", ) + request_http_message.counterparty = self.connection_address + sending_dialogue = self.http_dialogs.update(request_http_message) + assert sending_dialogue is not None request_envelope = Envelope( - to="receiver", - sender="sender", - protocol_id=UNKNOWN_PROTOCOL_PUBLIC_ID, + to=self.connection_address, + sender=self.agent_address, + protocol_id=request_http_message.protocol_id, message=request_http_message, ) @@ -283,9 +293,45 @@ async def test_http_send_ok(self): self.http_client_connection.receive(), timeout=10 ) - assert envelope - assert ( - envelope.message.status_code == response_mock.status - ), envelope.message.bodyy.decode("utf-8") - + assert envelope is not None and envelope.message is not None + response = copy.copy(envelope.message) + response.is_incoming = True + response.counterparty = envelope.message.sender + response_dialogue = self.http_dialogs.update(response) + assert response.status_code == response_mock.status, response.bodyy.decode( + "utf-8" + ) + assert sending_dialogue == response_dialogue await self.http_client_connection.disconnect() + + @pytest.mark.asyncio + async def test_http_dialogue_construct_fail(self): + """Test dialogue not properly constructed.""" + await self.http_client_connection.connect() + + http_message = HttpMessage( + dialogue_reference=self.http_dialogs.new_self_initiated_dialogue_reference(), + performative=HttpMessage.Performative.RESPONSE, + status_code=500, + headers="", + status_text="", + bodyy=b"", + version="", + ) + http_message.counterparty = self.connection_address + http_dialogue = self.http_dialogs.update(http_message) + http_message.sender = self.agent_address + assert http_dialogue is None + envelope = Envelope( + to=http_message.counterparty, + sender=http_message.sender, + protocol_id=http_message.protocol_id, + message=http_message, + ) + with patch.object( + self.http_client_connection.channel.logger, "warning" + ) as mock_logger: + await self.http_client_connection.channel._http_request_task(envelope) + mock_logger.assert_any_call( + AnyStringWith("Could not create dialogue for message=") + ) diff --git a/tests/test_packages/test_connections/test_http_server/__init__.py b/tests/test_packages/test_connections/test_http_server/__init__.py index 548d74c2c5..8b4d45bce1 100644 --- a/tests/test_packages/test_connections/test_http_server/__init__.py +++ b/tests/test_packages/test_connections/test_http_server/__init__.py @@ -16,5 +16,4 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This module contains the tests of the HTTP Server connection implementation.""" diff --git a/tests/test_packages/test_connections/test_http_server/test_http_server.py b/tests/test_packages/test_connections/test_http_server/test_http_server.py index d568fb66eb..c60e514d78 100644 --- a/tests/test_packages/test_connections/test_http_server/test_http_server.py +++ b/tests/test_packages/test_connections/test_http_server/test_http_server.py @@ -16,12 +16,15 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """This module contains the tests of the HTTP Server connection module.""" + import asyncio +import copy import logging import os from traceback import print_exc -from typing import cast +from typing import Tuple, cast from unittest.mock import Mock, patch import aiohttp @@ -38,8 +41,10 @@ HTTPServerConnection, Response, ) +from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage +from tests.common.mocks import RegexComparator from tests.conftest import ( HTTP_PROTOCOL_PUBLIC_ID, ROOT_DIR, @@ -77,13 +82,14 @@ async def request(self, method: str, path: str, **kwargs) -> ClientResponse: def setup(self): """Initialise the test case.""" self.identity = Identity("name", address="my_key") + self.agent_address = self.identity.address self.host = get_host() self.port = get_unused_tcp_port() self.api_spec_path = os.path.join( ROOT_DIR, "tests", "data", "petstore_sim.yaml" ) self.connection_id = HTTPServerConnection.connection_id - self.protocol_id = PublicId.from_str("fetchai/http:0.3.0") + self.protocol_id = PublicId.from_str("fetchai/http:0.4.0") self.configuration = ConnectionConfig( host=self.host, @@ -97,6 +103,9 @@ def setup(self): ) self.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.http_connection.connect()) + self.connection_address = str(HTTPServerConnection.connection_id) + self._dialogues = HttpDialogues(self.connection_address) + self.original_timeout = self.http_connection.channel.RESPONSE_TIMEOUT @pytest.mark.asyncio async def test_http_connection_disconnect_channel(self): @@ -104,24 +113,39 @@ async def test_http_connection_disconnect_channel(self): await self.http_connection.channel.disconnect() assert self.http_connection.channel.is_stopped + def _get_message_and_dialogue( + self, envelope: Envelope + ) -> Tuple[HttpMessage, HttpDialogue]: + message = cast(HttpMessage, envelope.message) + message = copy.copy( + message + ) # TODO: fix; need to copy atm to avoid overwriting "is_incoming" + message.is_incoming = True # TODO: fix; should be done by framework + message.counterparty = envelope.sender # TODO: fix; should be done by framework + dialogue = cast(HttpDialogue, self._dialogues.update(message)) + assert dialogue is not None + return message, dialogue + @pytest.mark.asyncio async def test_get_200(self): """Test send get request w/ 200 response.""" request_task = self.loop.create_task(self.request("get", "/pets")) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope - incoming_message = cast(HttpMessage, envelope.message) + incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = HttpMessage( + dialogue_reference=dialogue.dialogue_label.dialogue_reference, performative=HttpMessage.Performative.RESPONSE, - dialogue_reference=("", ""), - target=incoming_message.message_id, - message_id=incoming_message.message_id + 1, version=incoming_message.version, headers=incoming_message.headers, + message_id=incoming_message.message_id + 1, + target=incoming_message.message_id, status_code=200, status_text="Success", bodyy=b"Response body", ) + message.counterparty = incoming_message.counterparty + assert dialogue.update(message) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, @@ -140,15 +164,58 @@ async def test_get_200(self): ) @pytest.mark.asyncio - async def test_bad_performative_get_server_error(self): + async def test_bad_performative_get_timeout_error(self): """Test send get request w/ 200 response.""" + self.http_connection.channel.RESPONSE_TIMEOUT = 3 request_task = self.loop.create_task(self.request("get", "/pets")) - envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) + envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=10) assert envelope - incoming_message = cast(HttpMessage, envelope.message) + incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = HttpMessage( performative=HttpMessage.Performative.REQUEST, - dialogue_reference=("", ""), + dialogue_reference=dialogue.dialogue_label.dialogue_reference, + target=incoming_message.message_id, + message_id=incoming_message.message_id + 1, + method="post", + url="/pets", + version=incoming_message.version, + headers=incoming_message.headers, + bodyy=b"Request body", + ) + message.counterparty = incoming_message.counterparty + assert not dialogue.update(message) + response_envelope = Envelope( + to=envelope.sender, + sender=envelope.to, + protocol_id=envelope.protocol_id, + context=envelope.context, + message=message, + ) + with patch.object(self.http_connection.logger, "warning") as mock_logger: + await self.http_connection.send(response_envelope) + mock_logger.assert_any_call( + f"Could not create dialogue for message={message}" + ) + + response = await asyncio.wait_for(request_task, timeout=10) + + assert ( + response.status == 408 + and response.reason == "Request Timeout" + and await response.text() == "" + ) + + @pytest.mark.asyncio + async def test_late_message_get_timeout_error(self): + """Test send get request w/ 200 response.""" + self.http_connection.channel.RESPONSE_TIMEOUT = 1 + request_task = self.loop.create_task(self.request("get", "/pets")) + envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=10) + assert envelope + incoming_message, dialogue = self._get_message_and_dialogue(envelope) + message = HttpMessage( + performative=HttpMessage.Performative.RESPONSE, + dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=incoming_message.message_id, message_id=incoming_message.message_id + 1, version=incoming_message.version, @@ -157,6 +224,8 @@ async def test_bad_performative_get_server_error(self): status_text="Success", bodyy=b"Response body", ) + message.counterparty = incoming_message.counterparty + assert dialogue.update(message) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, @@ -164,11 +233,22 @@ async def test_bad_performative_get_server_error(self): context=envelope.context, message=message, ) - await self.http_connection.send(response_envelope) + await asyncio.sleep(1.5) + with patch.object(self.http_connection.logger, "warning") as mock_logger: + await self.http_connection.send(response_envelope) + mock_logger.assert_any_call( + RegexComparator( + "Dropping message=.* for incomplete_dialogue_label=.* which has timed out." + ) + ) - response = await asyncio.wait_for(request_task, timeout=20,) + response = await asyncio.wait_for(request_task, timeout=10) - assert response.status == 500 and await response.text() == "Server error" + assert ( + response.status == 408 + and response.reason == "Request Timeout" + and await response.text() == "" + ) @pytest.mark.asyncio async def test_post_201(self): @@ -176,10 +256,10 @@ async def test_post_201(self): request_task = self.loop.create_task(self.request("post", "/pets",)) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope - incoming_message = cast(HttpMessage, envelope.message) + incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, - dialogue_reference=("", ""), + dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=incoming_message.message_id, message_id=incoming_message.message_id + 1, version=incoming_message.version, @@ -188,6 +268,8 @@ async def test_post_201(self): status_text="Created", bodyy=b"Response body", ) + message.counterparty = incoming_message.counterparty + assert dialogue.update(message) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, @@ -195,6 +277,7 @@ async def test_post_201(self): context=envelope.context, message=message, ) + await self.http_connection.send(response_envelope) response = await asyncio.wait_for(request_task, timeout=20,) @@ -254,7 +337,6 @@ async def test_post_408(self): @pytest.mark.asyncio async def test_send_connection_drop(self): """Test unexpected response.""" - client_id = "to_key" message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, dialogue_reference=("", ""), @@ -266,10 +348,12 @@ async def test_send_connection_drop(self): status_text="Success", bodyy=b"", ) + message.counterparty = "to_key" + message.sender = "from_key" envelope = Envelope( - to=client_id, - sender="from_key", - protocol_id=self.protocol_id, + to=message.counterparty, + sender=message.sender, + protocol_id=message.protocol_id, message=message, ) await self.http_connection.send(envelope) @@ -292,7 +376,7 @@ async def test_fail_connect(self): side_effect=Exception("expected"), ): await self.http_connection.connect() - assert not self.http_connection.connection_status.is_connected + assert not self.http_connection.is_connected @pytest.mark.asyncio async def test_server_error_on_send_response(self): @@ -300,10 +384,10 @@ async def test_server_error_on_send_response(self): request_task = self.loop.create_task(self.request("post", "/pets",)) envelope = await asyncio.wait_for(self.http_connection.receive(), timeout=20) assert envelope - incoming_message = cast(HttpMessage, envelope.message) + incoming_message, dialogue = self._get_message_and_dialogue(envelope) message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, - dialogue_reference=("", ""), + dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=incoming_message.message_id, message_id=incoming_message.message_id + 1, version=incoming_message.version, @@ -312,6 +396,8 @@ async def test_server_error_on_send_response(self): status_text="Created", bodyy=b"Response body", ) + message.counterparty = incoming_message.counterparty + assert dialogue.update(message) response_envelope = Envelope( to=envelope.sender, sender=envelope.to, @@ -320,7 +406,7 @@ async def test_server_error_on_send_response(self): message=message, ) - with patch.object(Response, "from_envelope", side_effect=Exception("expected")): + with patch.object(Response, "from_message", side_effect=Exception("expected")): await self.http_connection.send(response_envelope) response = await asyncio.wait_for(request_task, timeout=20,) @@ -358,6 +444,7 @@ async def test_send_envelope_restricted_to_protocols_fail(self): def teardown(self): """Teardown the test case.""" self.loop.run_until_complete(self.http_connection.disconnect()) + self.http_connection.channel.RESPONSE_TIMEOUT = self.original_timeout def test_bad_api_spec(): diff --git a/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py b/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py index 4552231406..065df4994b 100644 --- a/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py +++ b/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py @@ -16,6 +16,8 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + + """Tests for the HTTP Client and Server connections together.""" import asyncio import logging @@ -29,10 +31,10 @@ from packages.fetchai.connections.http_client.connection import HTTPClientConnection from packages.fetchai.connections.http_server.connection import HTTPServerConnection +from packages.fetchai.protocols.http.dialogues import HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage from tests.conftest import ( - HTTP_PROTOCOL_PUBLIC_ID, get_host, get_unused_tcp_port, ) @@ -50,7 +52,7 @@ def setup_server(self): self.host = get_host() self.port = get_unused_tcp_port() self.connection_id = HTTPServerConnection.connection_id - self.protocol_id = PublicId.from_str("fetchai/http:0.3.0") + self.protocol_id = PublicId.from_str("fetchai/http:0.4.0") self.configuration = ConnectionConfig( host=self.host, @@ -78,6 +80,7 @@ def setup_client(self): ) self.client.loop = asyncio.get_event_loop() self.loop.run_until_complete(self.client.connect()) + self._client_dialogues = HttpDialogues("some_addr") def setup(self): """Set up test case.""" @@ -89,7 +92,7 @@ def _make_request( ) -> Envelope: """Make request envelope.""" request_http_message = HttpMessage( - dialogue_reference=("", ""), + dialogue_reference=self._client_dialogues.new_self_initiated_dialogue_reference(), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, @@ -99,10 +102,12 @@ def _make_request( version="", bodyy=b"", ) + request_http_message.counterparty = "receiver" + request_http_message.sender = "sender" request_envelope = Envelope( - to="receiver", - sender="sender", - protocol_id=HTTP_PROTOCOL_PUBLIC_ID, + to=request_http_message.counterparty, + sender=request_http_message.sender, + protocol_id=request_http_message.protocol_id, message=request_http_message, ) return request_envelope @@ -114,7 +119,7 @@ def _make_response( incoming_message = cast(HttpMessage, request_envelope.message) message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, - dialogue_reference=("", ""), + dialogue_reference=("1", ""), target=incoming_message.message_id, message_id=incoming_message.message_id + 1, version=incoming_message.version, @@ -123,10 +128,12 @@ def _make_response( status_text=status_text, bodyy=incoming_message.bodyy, ) + message.counterparty = incoming_message.counterparty + message.sender = request_envelope.to response_envelope = Envelope( - to=request_envelope.sender, - sender=request_envelope.to, - protocol_id=request_envelope.protocol_id, + to=message.counterparty, + sender=message.sender, + protocol_id=message.protocol_id, context=request_envelope.context, message=message, ) diff --git a/tests/test_packages/test_connections/test_ledger/test_contract_api.py b/tests/test_packages/test_connections/test_ledger/test_contract_api.py index 8eb6148b07..f42b1ab8c2 100644 --- a/tests/test_packages/test_connections/test_ledger/test_contract_api.py +++ b/tests/test_packages/test_connections/test_ledger/test_contract_api.py @@ -16,44 +16,28 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """This module contains the tests of the ledger API connection for the contract APIs.""" import asyncio -from pathlib import Path +import copy +import logging +import unittest.mock from typing import cast import pytest -from aea.configurations.constants import DEFAULT_LEDGER -from aea.connections.base import Connection, ConnectionStatus -from aea.crypto.registries import make_crypto -from aea.crypto.wallet import CryptoStore + from aea.helpers.transaction.base import RawMessage, RawTransaction, State -from aea.identity.base import Identity from aea.mail.base import Envelope +from aea.multiplexer import ConnectionStatus from packages.fetchai.connections.ledger.contract_dispatcher import ( - ContractApiDialogues, ContractApiRequestDispatcher, ) -from packages.fetchai.protocols.contract_api import ContractApiMessage - -from tests.conftest import ETHEREUM, ETHEREUM_ADDRESS_ONE, ROOT_DIR +from packages.fetchai.protocols.contract_api.dialogues import ContractApiDialogues +from packages.fetchai.protocols.contract_api.message import ContractApiMessage - -@pytest.fixture() -async def ledger_apis_connection(request): - """Create connection.""" - crypto = make_crypto(DEFAULT_LEDGER) - identity = Identity("name", crypto.address) - crypto_store = CryptoStore() - directory = Path(ROOT_DIR, "packages", "fetchai", "connections", "ledger") - connection = Connection.from_dir( - directory, identity=identity, crypto_store=crypto_store - ) - connection = cast(Connection, connection) - await connection.connect() - yield connection - await connection.disconnect() +from tests.conftest import ETHEREUM, ETHEREUM_ADDRESS_ONE @pytest.mark.integration @@ -61,13 +45,14 @@ async def ledger_apis_connection(request): @pytest.mark.asyncio async def test_erc1155_get_deploy_transaction(erc1155_contract, ledger_apis_connection): """Test get state with contract erc1155.""" + # TODO to fix address = ETHEREUM_ADDRESS_ONE - contract_api_dialogues = ContractApiDialogues() + contract_api_dialogues = ContractApiDialogues("agent_address") request = ContractApiMessage( performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({"deployer_address": address}), ) @@ -87,7 +72,10 @@ async def test_erc1155_get_deploy_transaction(erc1155_contract, ledger_apis_conn assert response is not None assert type(response.message) == ContractApiMessage - response_message = cast(ContractApiMessage, response.message) + response_message_orig = cast(ContractApiMessage, response.message) + response_message = copy.copy(response_message_orig) + response_message.is_incoming = True + response_message.counterparty = response_message_orig.sender assert ( response_message.performative == ContractApiMessage.Performative.RAW_TRANSACTION ), "Error: {}".format(response_message.message) @@ -106,12 +94,12 @@ async def test_erc1155_get_raw_transaction(erc1155_contract, ledger_apis_connect """Test get state with contract erc1155.""" address = ETHEREUM_ADDRESS_ONE contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" - contract_api_dialogues = ContractApiDialogues() + contract_api_dialogues = ContractApiDialogues("agent_address") request = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -134,10 +122,14 @@ async def test_erc1155_get_raw_transaction(erc1155_contract, ledger_apis_connect assert response is not None assert type(response.message) == ContractApiMessage - response_message = cast(ContractApiMessage, response.message) + response_message_orig = cast(ContractApiMessage, response.message) + response_message = copy.copy(response_message_orig) + response_message.is_incoming = True + response_message.counterparty = response_message_orig.sender assert ( response_message.performative == ContractApiMessage.Performative.RAW_TRANSACTION ), "Error: {}".format(response_message.message) + response_dialogue = contract_api_dialogues.update(response_message) assert response_dialogue == contract_api_dialogue assert type(response_message.raw_transaction) == RawTransaction @@ -153,12 +145,12 @@ async def test_erc1155_get_raw_message(erc1155_contract, ledger_apis_connection) """Test get state with contract erc1155.""" address = ETHEREUM_ADDRESS_ONE contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" - contract_api_dialogues = ContractApiDialogues() + contract_api_dialogues = ContractApiDialogues("agent_address") request = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=contract_address, callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( @@ -189,7 +181,10 @@ async def test_erc1155_get_raw_message(erc1155_contract, ledger_apis_connection) assert response is not None assert type(response.message) == ContractApiMessage - response_message = cast(ContractApiMessage, response.message) + response_message_orig = cast(ContractApiMessage, response.message) + response_message = copy.copy(response_message_orig) + response_message.is_incoming = True + response_message.counterparty = response_message_orig.sender assert ( response_message.performative == ContractApiMessage.Performative.RAW_MESSAGE ), "Error: {}".format(response_message.message) @@ -207,13 +202,13 @@ async def test_erc1155_get_state(erc1155_contract, ledger_apis_connection): """Test get state with contract erc1155.""" address = ETHEREUM_ADDRESS_ONE contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" - contract_api_dialogues = ContractApiDialogues() + contract_api_dialogues = ContractApiDialogues("agent_address") token_id = 1 request = ContractApiMessage( performative=ContractApiMessage.Performative.GET_STATE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( @@ -236,7 +231,10 @@ async def test_erc1155_get_state(erc1155_contract, ledger_apis_connection): assert response is not None assert type(response.message) == ContractApiMessage - response_message = cast(ContractApiMessage, response.message) + response_message_orig = cast(ContractApiMessage, response.message) + response_message = copy.copy(response_message_orig) + response_message.is_incoming = True + response_message.counterparty = response_message_orig.sender assert ( response_message.performative == ContractApiMessage.Performative.STATE ), "Error: {}".format(response_message.message) @@ -256,12 +254,12 @@ async def test_run_async(): def _raise(): raise Exception("Expected") - contract_api_dialogues = ContractApiDialogues() + contract_api_dialogues = ContractApiDialogues("agent_address") message = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.6.0", + contract_id="fetchai/erc1155:0.7.0", contract_address="test addr", callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -287,3 +285,243 @@ async def test_get_handler(): ContractApiRequestDispatcher(ConnectionStatus()).get_handler( ContractApiMessage.Performative.ERROR ) + + +@pytest.mark.integration +@pytest.mark.ledger +@pytest.mark.asyncio +async def test_callable_wrong_number_of_arguments_api_and_contract_address( + erc1155_contract, ledger_apis_connection +): + """ + Test a contract callable with wrong number of arguments. + + Test the case of either GET_STATE, GET_RAW_MESSAGE or GET_RAW_TRANSACTION. + """ + address = ETHEREUM_ADDRESS_ONE + contract_api_dialogues = ContractApiDialogues("agent_address") + token_id = 1 + contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" + request = ContractApiMessage( + performative=ContractApiMessage.Performative.GET_STATE, + dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + ledger_id=ETHEREUM, + contract_id="fetchai/erc1155:0.7.0", + contract_address=contract_address, + callable="get_balance", + kwargs=ContractApiMessage.Kwargs( + {"agent_address": address, "token_id": token_id} + ), + ) + request.counterparty = str(ledger_apis_connection.connection_id) + contract_api_dialogue = contract_api_dialogues.update(request) + assert contract_api_dialogue is not None + envelope = Envelope( + to=str(ledger_apis_connection.connection_id), + sender=address, + protocol_id=request.protocol_id, + message=request, + ) + + with unittest.mock.patch( + "inspect.getfullargspec", return_value=unittest.mock.MagicMock(args=[None]) + ): + with unittest.mock.patch.object( + ledger_apis_connection._logger, "error" + ) as mock_logger: + await ledger_apis_connection.send(envelope) + await asyncio.sleep(0.01) + response = await ledger_apis_connection.receive() + mock_logger.assert_any_call( + "Expected two or more positional arguments, got 1" + ) + assert ( + response.message.performative == ContractApiMessage.Performative.ERROR + ) + assert ( + response.message.message + == "Expected two or more positional arguments, got 1" + ) + + +@pytest.mark.integration +@pytest.mark.ledger +@pytest.mark.asyncio +async def test_callable_wrong_number_of_arguments_apis( + erc1155_contract, ledger_apis_connection +): + """ + Test a contract callable with wrong number of arguments. + + Test the case of either GET_DEPLOY_TRANSACTION. + """ + address = ETHEREUM_ADDRESS_ONE + contract_api_dialogues = ContractApiDialogues("agent_address") + request = ContractApiMessage( + performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, + dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + ledger_id=ETHEREUM, + contract_id="fetchai/erc1155:0.7.0", + callable="get_deploy_transaction", + kwargs=ContractApiMessage.Kwargs({}), + ) + request.counterparty = str(ledger_apis_connection.connection_id) + contract_api_dialogue = contract_api_dialogues.update(request) + assert contract_api_dialogue is not None + envelope = Envelope( + to=str(ledger_apis_connection.connection_id), + sender=address, + protocol_id=request.protocol_id, + message=request, + ) + + with unittest.mock.patch( + "inspect.getfullargspec", return_value=unittest.mock.MagicMock(args=[]) + ): + with unittest.mock.patch.object( + ledger_apis_connection._contract_dispatcher, "_call_stub", return_value=None + ): + with unittest.mock.patch.object( + ledger_apis_connection._contract_dispatcher.logger, "error" + ) as mock_logger: + await ledger_apis_connection.send(envelope) + await asyncio.sleep(0.01) + response = await ledger_apis_connection.receive() + mock_logger.assert_any_call( + "Expected one or more positional arguments, got 0" + ) + assert ( + response.message.performative + == ContractApiMessage.Performative.ERROR + ) + assert ( + response.message.message + == "Expected one or more positional arguments, got 0" + ) + + +@pytest.mark.integration +@pytest.mark.ledger +@pytest.mark.asyncio +async def test_callable_wrong_number_of_arguments_apis_method_call( + erc1155_contract, ledger_apis_connection, caplog +): + """ + Test a contract callable with wrong number of arguments. + + Test the case of either GET_DEPLOY_TRANSACTION. + """ + address = ETHEREUM_ADDRESS_ONE + contract_api_dialogues = ContractApiDialogues("agent_address") + request = ContractApiMessage( + performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, + dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + ledger_id=ETHEREUM, + contract_id="fetchai/erc1155:0.7.0", + callable="get_deploy_transaction", + kwargs=ContractApiMessage.Kwargs({}), + ) + request.counterparty = str(ledger_apis_connection.connection_id) + contract_api_dialogue = contract_api_dialogues.update(request) + assert contract_api_dialogue is not None + envelope = Envelope( + to=str(ledger_apis_connection.connection_id), + sender=address, + protocol_id=request.protocol_id, + message=request, + ) + + with unittest.mock.patch.object( + ledger_apis_connection._contract_dispatcher, "_call_stub", return_value=None + ): + with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.ledger"): + await ledger_apis_connection.send(envelope) + await asyncio.sleep(0.01) + assert ( + "An error occurred while processing the contract api request: 'get_deploy_transaction() missing 1 required positional argument: 'deployer_address''." + in caplog.text + ) + + +@pytest.mark.integration +@pytest.mark.ledger +@pytest.mark.asyncio +async def test_callable_generic_error(erc1155_contract, ledger_apis_connection): + """Test error messages when an exception is raised while processing the request.""" + address = ETHEREUM_ADDRESS_ONE + contract_api_dialogues = ContractApiDialogues("agent_address") + token_id = 1 + contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" + request = ContractApiMessage( + performative=ContractApiMessage.Performative.GET_STATE, + dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + ledger_id=ETHEREUM, + contract_id="fetchai/erc1155:0.7.0", + contract_address=contract_address, + callable="get_balance", + kwargs=ContractApiMessage.Kwargs( + {"agent_address": address, "token_id": token_id} + ), + ) + request.counterparty = str(ledger_apis_connection.connection_id) + contract_api_dialogue = contract_api_dialogues.update(request) + assert contract_api_dialogue is not None + envelope = Envelope( + to=str(ledger_apis_connection.connection_id), + sender=address, + protocol_id=request.protocol_id, + message=request, + ) + + with unittest.mock.patch( + "inspect.getfullargspec", side_effect=Exception("Generic error") + ): + with unittest.mock.patch.object( + ledger_apis_connection._logger, "error" + ) as mock_logger: + await ledger_apis_connection.send(envelope) + await asyncio.sleep(0.01) + response = await ledger_apis_connection.receive() + mock_logger.assert_any_call( + "An error occurred while processing the contract api request: 'Generic error'." + ) + assert ( + response.message.performative == ContractApiMessage.Performative.ERROR + ) + assert response.message.message == "Generic error" + + +@pytest.mark.integration +@pytest.mark.ledger +@pytest.mark.asyncio +async def test_callable_cannot_find(erc1155_contract, ledger_apis_connection, caplog): + """Test error messages when an exception is raised while processing the request.""" + address = ETHEREUM_ADDRESS_ONE + contract_api_dialogues = ContractApiDialogues("agent_address") + token_id = 1 + contract_address = "0x250A2aeb3eB84782e83365b4c42dbE3CDA9920e4" + request = ContractApiMessage( + performative=ContractApiMessage.Performative.GET_STATE, + dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + ledger_id=ETHEREUM, + contract_id="fetchai/erc1155:0.7.0", + contract_address=contract_address, + callable="unknown_callable", + kwargs=ContractApiMessage.Kwargs( + {"agent_address": address, "token_id": token_id} + ), + ) + request.counterparty = str(ledger_apis_connection.connection_id) + contract_api_dialogue = contract_api_dialogues.update(request) + assert contract_api_dialogue is not None + envelope = Envelope( + to=str(ledger_apis_connection.connection_id), + sender=address, + protocol_id=request.protocol_id, + message=request, + ) + + with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.ledger"): + await ledger_apis_connection.send(envelope) + await asyncio.sleep(0.01) + assert f"Cannot find {request.callable} in contract" in caplog.text diff --git a/tests/test_packages/test_connections/test_ledger/test_ledger_api.py b/tests/test_packages/test_connections/test_ledger/test_ledger_api.py index f67be8f881..bf11f6168d 100644 --- a/tests/test_packages/test_connections/test_ledger/test_ledger_api.py +++ b/tests/test_packages/test_connections/test_ledger/test_ledger_api.py @@ -16,20 +16,22 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + + """This module contains the tests of the ledger API connection module.""" import asyncio +import copy import logging -from pathlib import Path from typing import cast from unittest.mock import Mock, patch import pytest from aea.configurations.base import ProtocolId -from aea.configurations.constants import DEFAULT_LEDGER -from aea.connections.base import Connection, ConnectionStatus +from aea.connections.base import Connection, ConnectionStates +from aea.crypto.ledger_apis import LedgerApis from aea.crypto.registries import make_crypto, make_ledger_api -from aea.crypto.wallet import CryptoStore +from aea.helpers.async_utils import AsyncState from aea.helpers.transaction.base import ( RawTransaction, SignedTransaction, @@ -37,18 +39,15 @@ TransactionDigest, TransactionReceipt, ) -from aea.identity.base import Identity from aea.mail.base import Envelope from packages.fetchai.connections.ledger.connection import LedgerConnection -from packages.fetchai.connections.ledger.contract_dispatcher import ContractApiDialogues from packages.fetchai.connections.ledger.ledger_dispatcher import ( - LedgerApiDialogues, LedgerApiRequestDispatcher, ) +from packages.fetchai.protocols.ledger_api.dialogues import LedgerApiDialogues from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage - from tests.conftest import ( COSMOS, COSMOS_ADDRESS_ONE, @@ -60,7 +59,6 @@ FETCHAI, FETCHAI_ADDRESS_ONE, FETCHAI_TESTNET_CONFIG, - ROOT_DIR, ) logger = logging.getLogger(__name__) @@ -76,22 +74,6 @@ ) -@pytest.fixture() -async def ledger_apis_connection(request): - """Make a connection.""" - crypto = make_crypto(DEFAULT_LEDGER) - identity = Identity("name", crypto.address) - crypto_store = CryptoStore() - directory = Path(ROOT_DIR, "packages", "fetchai", "connections", "ledger") - connection = Connection.from_dir( - directory, identity=identity, crypto_store=crypto_store - ) - connection = cast(Connection, connection) - await connection.connect() - yield connection - await connection.disconnect() - - @pytest.mark.integration @pytest.mark.ledger @pytest.mark.asyncio @@ -102,7 +84,7 @@ async def test_get_balance( """Test get balance.""" import aea # noqa # to load registries - ledger_api_dialogues = LedgerApiDialogues() + ledger_api_dialogues = LedgerApiDialogues(address) request = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, dialogue_reference=ledger_api_dialogues.new_self_initiated_dialogue_reference(), @@ -126,7 +108,10 @@ async def test_get_balance( assert response is not None assert type(response.message) == LedgerApiMessage - response_msg = cast(LedgerApiMessage, response.message) + response_msg_orig = cast(LedgerApiMessage, response.message) + response_msg = copy.copy(response_msg_orig) + response_msg.is_incoming = True + response_msg.counterparty = response_msg_orig.sender response_dialogue = ledger_api_dialogues.update(response_msg) assert response_dialogue == ledger_api_dialogue assert response_msg.performative == LedgerApiMessage.Performative.BALANCE @@ -144,7 +129,7 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti crypto1 = make_crypto(ETHEREUM, private_key_path=ETHEREUM_PRIVATE_KEY_PATH) crypto2 = make_crypto(ETHEREUM) - ledger_api_dialogues = LedgerApiDialogues() + ledger_api_dialogues = LedgerApiDialogues(crypto1.address) amount = 40000 fee = 30000 @@ -179,7 +164,10 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti assert response is not None assert type(response.message) == LedgerApiMessage - response_message = cast(LedgerApiMessage, response.message) + response_msg_orig = cast(LedgerApiMessage, response.message) + response_message = copy.copy(response_msg_orig) + response_message.is_incoming = True + response_message.counterparty = response_msg_orig.sender assert ( response_message.performative == LedgerApiMessage.Performative.RAW_TRANSACTION ) @@ -188,18 +176,11 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti assert type(response_message.raw_transaction) == RawTransaction assert response_message.raw_transaction.ledger_id == request.terms.ledger_id - # raw_tx = api.get_transfer_transaction( - # sender_address=crypto1.address, - # destination_address=crypto2.address, - # amount=amount, - # tx_fee=fee, - # tx_nonce="", - # chain_id=3, - # ) - signed_transaction = crypto1.sign_transaction(response_message.raw_transaction.body) request = LedgerApiMessage( performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, + message_id=response_message.message_id + 1, + target=response_message.message_id, dialogue_reference=ledger_api_dialogue.dialogue_label.dialogue_reference, signed_transaction=SignedTransaction(ETHEREUM, signed_transaction), ) @@ -217,7 +198,10 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti assert response is not None assert type(response.message) == LedgerApiMessage - response_message = cast(LedgerApiMessage, response.message) + response_msg_orig = cast(LedgerApiMessage, response.message) + response_message = copy.copy(response_msg_orig) + response_message.is_incoming = True + response_message.counterparty = response_msg_orig.sender assert ( response_message.performative != LedgerApiMessage.Performative.ERROR ), f"Received error: {response_message.message}" @@ -238,6 +222,8 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti request = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, dialogue_reference=ledger_api_dialogue.dialogue_label.dialogue_reference, + message_id=response_message.message_id + 1, + target=response_message.message_id, transaction_digest=response_message.transaction_digest, ) request.counterparty = str(ledger_apis_connection.connection_id) @@ -254,7 +240,10 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti assert response is not None assert type(response.message) == LedgerApiMessage - response_message = cast(LedgerApiMessage, response.message) + response_msg_orig = cast(LedgerApiMessage, response.message) + response_message = copy.copy(response_msg_orig) + response_message.is_incoming = True + response_message.counterparty = response_msg_orig.sender assert ( response_message.performative == LedgerApiMessage.Performative.TRANSACTION_RECEIPT @@ -268,18 +257,10 @@ async def test_send_signed_transaction_ethereum(ledger_apis_connection: Connecti response_message.transaction_receipt.ledger_id == request.transaction_digest.ledger_id ) - - # # check that the transaction is settled (to update nonce!) - # is_settled = False - # attempts = 0 - # while not is_settled and attempts < 60: - # attempts += 1 - # tx_receipt = api.get_transaction_receipt( - # response_message.transaction_digest.body - # ) - # is_settled = api.is_transaction_settled(tx_receipt) - # await asyncio.sleep(4.0) - # assert is_settled, "Transaction not settled." + assert LedgerApis.is_transaction_settled( + response_message.transaction_receipt.ledger_id, + response_message.transaction_receipt.receipt, + ), "Transaction not settled." @pytest.mark.asyncio @@ -307,17 +288,16 @@ async def test_new_message_wait_flag(ledger_apis_connection: LedgerConnection): @pytest.mark.asyncio async def test_no_balance(): """Test no balance.""" - dispatcher = LedgerApiRequestDispatcher(ConnectionStatus()) + dispatcher = LedgerApiRequestDispatcher(AsyncState()) mock_api = Mock() - contract_api_dialogues = ContractApiDialogues() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_BALANCE, - dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), ledger_id=ETHEREUM, address="test", ) message.counterparty = "test" - dialogue = contract_api_dialogues.update(message) + dialogue = dispatcher.dialogues.update(message) mock_api.get_balance.return_value = None msg = dispatcher.get_balance(mock_api, message, dialogue) @@ -327,12 +307,11 @@ async def test_no_balance(): @pytest.mark.asyncio async def test_no_raw_tx(): """Test no raw tx returned.""" - dispatcher = LedgerApiRequestDispatcher(ConnectionStatus()) + dispatcher = LedgerApiRequestDispatcher(AsyncState()) mock_api = Mock() - contract_api_dialogues = ContractApiDialogues() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, - dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), terms=Terms( ledger_id=ETHEREUM, sender_address="1111", @@ -346,7 +325,7 @@ async def test_no_raw_tx(): ), ) message.counterparty = "test" - dialogue = contract_api_dialogues.update(message) + dialogue = dispatcher.dialogues.update(message) mock_api.get_transfer_transaction.return_value = None msg = dispatcher.get_raw_transaction(mock_api, message, dialogue) @@ -356,17 +335,16 @@ async def test_no_raw_tx(): @pytest.mark.asyncio async def test_attempts_get_transaction_receipt(): """Test retry and sleep.""" - dispatcher = LedgerApiRequestDispatcher(ConnectionStatus()) - dispatcher.connection_status.is_connected = True + dispatcher = LedgerApiRequestDispatcher(AsyncState(ConnectionStates.connected)) mock_api = Mock() - contract_api_dialogues = ContractApiDialogues() message = LedgerApiMessage( performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, - dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), + dialogue_reference=dispatcher.dialogues.new_self_initiated_dialogue_reference(), transaction_digest=TransactionDigest("asdad", "sdfdsf"), ) message.counterparty = "test" - dialogue = contract_api_dialogues.update(message) + dialogue = dispatcher.dialogues.update(message) + assert dialogue is not None mock_api.get_transaction.return_value = None mock_api.is_transaction_settled.return_value = True with patch.object(dispatcher, "MAX_ATTEMPTS", 2): diff --git a/tests/test_packages/test_connections/test_local/test_search_services.py b/tests/test_packages/test_connections/test_local/test_search_services.py index d288b21773..539a0ea8d2 100644 --- a/tests/test_packages/test_connections/test_local/test_search_services.py +++ b/tests/test_packages/test_connections/test_local/test_search_services.py @@ -17,7 +17,11 @@ # # ------------------------------------------------------------------------------ """This module contains the tests for the search feature of the local OEF node.""" +import asyncio +import copy import time +import unittest.mock +from typing import Optional, cast import pytest @@ -33,13 +37,74 @@ from aea.multiplexer import InBox, Multiplexer from aea.protocols.default.message import DefaultMessage -from packages.fetchai.connections.local.connection import LocalNode +from packages.fetchai.connections.local.connection import LocalNode, OEFLocalConnection +from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue, + OefSearchDialogues, +) from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from tests.common.mocks import AnyStringWith from tests.conftest import MAX_FLAKY_RERUNS, _make_local_connection -DEFAULT_OEF = "default_oef" + +class TestNoValidDialogue: + """Test that the search request returns an empty search result.""" + + @classmethod + def setup_class(cls): + """Set up the test.""" + cls.node = LocalNode() + cls.node.start() + + cls.address_1 = "address_1" + cls.connection = _make_local_connection(cls.address_1, cls.node,) + cls.multiplexer = Multiplexer([cls.connection]) + + cls.multiplexer.connect() + cls.dialogues = OefSearchDialogues(cls.address_1) + + @pytest.mark.asyncio + async def test_wrong_dialogue(self): + """Test that at the beginning, the search request returns an empty search result.""" + query = Query( + constraints=[Constraint("foo", ConstraintType("==", 1))], model=None + ) + + # build and send the request + search_services_request = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + message_id=2, + target=1, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), + query=query, + ) + search_services_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], self.dialogues.update(search_services_request) + ) + assert sending_dialogue is None + search_services_request.sender = self.address_1 + envelope = Envelope( + to=search_services_request.counterparty, + sender=search_services_request.sender, + protocol_id=search_services_request.protocol_id, + message=search_services_request, + ) + self.multiplexer.put(envelope) + with unittest.mock.patch.object(self.node.logger, "warning") as mock_logger: + await asyncio.sleep(0.1) + mock_logger.assert_any_call( + AnyStringWith("Could not create dialogue for message=") + ) + + @classmethod + def teardown_class(cls): + """Teardown the test.""" + cls.multiplexer.disconnect() + cls.node.stop() class TestEmptySearch: @@ -57,22 +122,27 @@ def setup_class(cls): ) cls.multiplexer.connect() + cls.dialogues = OefSearchDialogues(cls.address_1) def test_empty_search_result(self): """Test that at the beginning, the search request returns an empty search result.""" - request_id = 1 query = Query(constraints=[], model=None) # build and send the request search_services_request = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(request_id), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=query, ) + search_services_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], self.dialogues.update(search_services_request) + ) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=search_services_request.counterparty, + sender=search_services_request.sender, + protocol_id=search_services_request.protocol_id, message=search_services_request, ) self.multiplexer.put(envelope) @@ -80,9 +150,12 @@ def test_empty_search_result(self): # check the result response_envelope = self.multiplexer.get(block=True, timeout=2.0) assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.to == self.address_1 - assert response_envelope.sender == DEFAULT_OEF - search_result = response_envelope.message + search_result_orig = cast(OefSearchMessage, response_envelope.message) + search_result = copy.copy(search_result_orig) + search_result.is_incoming = True + search_result.counterparty = search_result_orig.sender + response_dialogue = self.dialogues.update(search_result) + assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == () @@ -110,7 +183,7 @@ def setup_class(cls): cls.multiplexer.connect() # register a service. - request_id = 1 + cls.dialogues = OefSearchDialogues(cls.address_1) cls.data_model = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], @@ -120,13 +193,18 @@ def setup_class(cls): ) register_service_request = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(request_id), ""), + dialogue_reference=cls.dialogues.new_self_initiated_dialogue_reference(), service_description=service_description, ) + register_service_request.counterparty = str(OEFLocalConnection.connection_id) + cls.sending_dialogue = cast( + Optional[OefSearchDialogue], cls.dialogues.update(register_service_request) + ) + assert cls.sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=cls.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=register_service_request.counterparty, + sender=register_service_request.sender, + protocol_id=register_service_request.protocol_id, message=register_service_request, ) cls.multiplexer.put(envelope) @@ -136,19 +214,23 @@ def setup_class(cls): ) # TODO: check reasons!. quite unstable test def test_not_empty_search_result(self): """Test that the search result contains one entry after a successful registration.""" - request_id = 1 query = Query(constraints=[], model=self.data_model) # build and send the request search_services_request = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(request_id), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=query, ) + search_services_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], self.dialogues.update(search_services_request) + ) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=search_services_request.counterparty, + sender=search_services_request.sender, + protocol_id=search_services_request.protocol_id, message=search_services_request, ) self.multiplexer.put(envelope) @@ -156,9 +238,12 @@ def test_not_empty_search_result(self): # check the result response_envelope = self.multiplexer.get(block=True, timeout=2.0) assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.to == self.address_1 - assert response_envelope.sender == DEFAULT_OEF - search_result = response_envelope.message + search_result_orig = cast(OefSearchMessage, response_envelope.message) + search_result = copy.copy(search_result_orig) + search_result.is_incoming = True + search_result.counterparty = search_result_orig.sender + response_dialogue = self.dialogues.update(search_result) + assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == (self.address_1,) @@ -188,6 +273,7 @@ def setup_class(cls): ) cls.multiplexer1.connect() cls.multiplexer2.connect() + cls.dialogues = OefSearchDialogues(cls.address_1) def test_unregister_service_result(self): """Test that at the beginning, the search request returns an empty search result.""" @@ -200,13 +286,16 @@ def test_unregister_service_result(self): ) msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(1), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), service_description=service_description, ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) @@ -214,19 +303,26 @@ def test_unregister_service_result(self): # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.sender == DEFAULT_OEF - result = response_envelope.message - assert result.performative == OefSearchMessage.Performative.OEF_ERROR + response_orig = cast(OefSearchMessage, response_envelope.message) + response = copy.copy(response_orig) + response.is_incoming = True + response.counterparty = response_orig.sender + response_dialogue = self.dialogues.update(response) + assert response_dialogue == sending_dialogue + assert response.performative == OefSearchMessage.Performative.OEF_ERROR msg = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(1), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), service_description=service_description, ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) @@ -234,34 +330,43 @@ def test_unregister_service_result(self): # Search for the registered service msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(1), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=Query([Constraint("foo", ConstraintType("==", 1))]), ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) - assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.sender == DEFAULT_OEF - result = response_envelope.message - assert result.performative == OefSearchMessage.Performative.SEARCH_RESULT - assert len(result.agents) == 1 + search_result_orig = cast(OefSearchMessage, response_envelope.message) + search_result = copy.copy(search_result_orig) + search_result.is_incoming = True + search_result.counterparty = search_result_orig.sender + response_dialogue = self.dialogues.update(search_result) + assert response_dialogue == sending_dialogue + assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT + assert len(search_result.agents) == 1 # unregister the service msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(1), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), service_description=service_description, ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) @@ -270,23 +375,29 @@ def test_unregister_service_result(self): # Search for the register agent msg = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(1), ""), + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=Query([Constraint("foo", ConstraintType("==", 1))]), ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) - assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.sender == DEFAULT_OEF - result = response_envelope.message - assert result.performative == OefSearchMessage.Performative.SEARCH_RESULT - assert result.agents == () + search_result_orig = cast(OefSearchMessage, response_envelope.message) + search_result = copy.copy(search_result_orig) + search_result.is_incoming = True + search_result.counterparty = search_result_orig.sender + response_dialogue = self.dialogues.update(search_result) + assert response_dialogue == sending_dialogue + assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT + assert search_result.agents == () @classmethod def teardown_class(cls): @@ -309,21 +420,23 @@ def setup_class(cls): cls.multiplexer1 = Multiplexer( [_make_local_connection(cls.address_1, cls.node,)] ) + cls.dialogues = FipaDialogues(cls.address_1) @pytest.mark.asyncio async def test_messages(self): """Test that at the beginning, the search request returns an empty search result.""" msg = FipaMessage( performative=FipaMessage.Performative.CFP, - dialogue_reference=(str(0), ""), - message_id=1, - target=0, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=Query([Constraint("something", ConstraintType(">", 1))]), ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[FipaDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=FipaMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) with pytest.raises(AEAConnectionError): @@ -332,15 +445,16 @@ async def test_messages(self): self.multiplexer1.connect() msg = FipaMessage( performative=FipaMessage.Performative.CFP, - dialogue_reference=(str(0), str(1)), - message_id=1, - target=0, + dialogue_reference=self.dialogues.new_self_initiated_dialogue_reference(), query=Query([Constraint("something", ConstraintType(">", 1))]), ) + msg.counterparty = "this_address_does_not_exist" + sending_dialogue = cast(Optional[FipaDialogue], self.dialogues.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to="this_address_does_not_exist", - sender=self.address_1, - protocol_id=FipaMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) self.multiplexer1.put(envelope) @@ -348,7 +462,7 @@ async def test_messages(self): # check the result response_envelope = self.multiplexer1.get(block=True, timeout=5.0) assert response_envelope.protocol_id == DefaultMessage.protocol_id - assert response_envelope.sender == DEFAULT_OEF + assert response_envelope.sender == str(OEFLocalConnection.connection_id) result = response_envelope.message assert result.performative == DefaultMessage.Performative.ERROR @@ -378,9 +492,10 @@ def setup_class(cls): ) cls.multiplexer1.connect() cls.multiplexer2.connect() + cls.dialogues1 = OefSearchDialogues(cls.address_1) + cls.dialogues2 = OefSearchDialogues(cls.address_2) # register 'multiplexer1' as a service 'foobar'. - request_id = 1 cls.data_model_foobar = DataModel( "foobar", attributes=[Attribute("foo", int, True), Attribute("bar", str, True)], @@ -390,13 +505,18 @@ def setup_class(cls): ) register_service_request = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(request_id), ""), + dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(), service_description=service_description, ) + register_service_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], cls.dialogues1.update(register_service_request) + ) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=cls.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=register_service_request.counterparty, + sender=register_service_request.sender, + protocol_id=register_service_request.protocol_id, message=register_service_request, ) cls.multiplexer1.put(envelope) @@ -413,13 +533,18 @@ def setup_class(cls): ) register_service_request = OefSearchMessage( performative=OefSearchMessage.Performative.REGISTER_SERVICE, - dialogue_reference=(str(request_id), ""), + dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(), service_description=service_description, ) + register_service_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], cls.dialogues2.update(register_service_request) + ) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=cls.address_2, - protocol_id=OefSearchMessage.protocol_id, + to=register_service_request.counterparty, + sender=register_service_request.sender, + protocol_id=register_service_request.protocol_id, message=register_service_request, ) cls.multiplexer2.put(envelope) @@ -434,45 +559,52 @@ def setup_class(cls): ) msg = OefSearchMessage( performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, - dialogue_reference=(str(1), ""), + dialogue_reference=cls.dialogues1.new_self_initiated_dialogue_reference(), service_description=service_description, ) + msg.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast(Optional[OefSearchDialogue], cls.dialogues1.update(msg)) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=cls.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) cls.multiplexer1.put(envelope) - @pytest.mark.flaky( - reruns=MAX_FLAKY_RERUNS - ) # TODO: check reasons!. quite unstable test + @pytest.mark.flaky(reruns=0) # TODO: check reasons!. quite unstable test def test_filtered_search_result(self): """Test that the search result contains only the entries matching the query.""" - request_id = 1 query = Query(constraints=[], model=self.data_model_barfoo) # build and send the request search_services_request = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, - dialogue_reference=(str(request_id), ""), + dialogue_reference=self.dialogues1.new_self_initiated_dialogue_reference(), query=query, ) + search_services_request.counterparty = str(OEFLocalConnection.connection_id) + sending_dialogue = cast( + Optional[OefSearchDialogue], self.dialogues1.update(search_services_request) + ) + assert sending_dialogue is not None envelope = Envelope( - to=DEFAULT_OEF, - sender=self.address_1, - protocol_id=OefSearchMessage.protocol_id, + to=search_services_request.counterparty, + sender=search_services_request.sender, + protocol_id=search_services_request.protocol_id, message=search_services_request, ) self.multiplexer1.put(envelope) # check the result response_envelope = InBox(self.multiplexer1).get(block=True, timeout=5.0) - assert response_envelope.protocol_id == OefSearchMessage.protocol_id - assert response_envelope.to == self.address_1 - assert response_envelope.sender == DEFAULT_OEF - search_result = response_envelope.message + search_result_orig = cast(OefSearchMessage, response_envelope.message) + search_result = copy.copy(search_result_orig) + search_result.is_incoming = True + search_result.counterparty = search_result_orig.sender + response_dialogue = self.dialogues1.update(search_result) + assert response_dialogue == sending_dialogue assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert search_result.agents == (self.address_2,) diff --git a/tests/test_packages/test_connections/test_oef/test_communication.py b/tests/test_packages/test_connections/test_oef/test_communication.py index 715b4b3699..b016337ab0 100644 --- a/tests/test_packages/test_connections/test_oef/test_communication.py +++ b/tests/test_packages/test_connections/test_oef/test_communication.py @@ -21,6 +21,7 @@ """This test module contains the tests for the OEF communication using an OEF.""" import asyncio +import copy import logging import sys import time @@ -68,8 +69,6 @@ _make_oef_connection, ) -DEFAULT_OEF = "default_oef" - logger = logging.getLogger(__name__) @@ -179,11 +178,12 @@ def test_search_services_with_query_without_model(self): dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), query=search_query_empty_model, ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(self.connection.connection_id) sending_dialogue = self.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, @@ -191,7 +191,10 @@ def test_search_services_with_query_without_model(self): ) envelope = self.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -215,11 +218,12 @@ def test_search_services_with_query_with_model(self): dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), query=search_query, ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(self.connection.connection_id) sending_dialogue = self.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, @@ -227,7 +231,10 @@ def test_search_services_with_query_with_model(self): ) envelope = self.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -258,18 +265,22 @@ def test_search_services_with_distance_query(self): dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), query=search_query, ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(self.connection.connection_id) sending_dialogue = self.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -308,14 +319,14 @@ def test_register_service(self): dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), service_description=desc, ) - oef_search_registration.counterparty = DEFAULT_OEF + oef_search_registration.counterparty = str(self.connection.connection_id) sending_dialogue_1 = self.oef_search_dialogues.update( oef_search_registration ) assert sending_dialogue_1 is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_registration, @@ -330,18 +341,22 @@ def test_register_service(self): [Constraint("bar", ConstraintType("==", 1))], model=foo_datamodel ), ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(self.connection.connection_id) sending_dialogue_2 = self.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue_2 is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, ) ) envelope = self.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -385,14 +400,14 @@ def setup_class(cls): dialogue_reference=cls.oef_search_dialogues.new_self_initiated_dialogue_reference(), service_description=cls.desc, ) - oef_search_registration.counterparty = DEFAULT_OEF + oef_search_registration.counterparty = str(cls.connection.connection_id) sending_dialogue_1 = cls.oef_search_dialogues.update( oef_search_registration ) assert sending_dialogue_1 is not None cls.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(cls.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_registration, @@ -409,18 +424,22 @@ def setup_class(cls): model=cls.foo_datamodel, ), ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(cls.connection.connection_id) sending_dialogue_2 = cls.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue_2 is not None cls.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(cls.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, ) ) envelope = cls.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = cls.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -444,14 +463,14 @@ def test_unregister_service(self): dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), service_description=self.desc, ) - oef_search_deregistration.counterparty = DEFAULT_OEF + oef_search_deregistration.counterparty = str(self.connection.connection_id) sending_dialogue_1 = self.oef_search_dialogues.update( oef_search_deregistration ) assert sending_dialogue_1 is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_deregistration, @@ -468,11 +487,12 @@ def test_unregister_service(self): model=self.foo_datamodel, ), ) - oef_search_request.counterparty = DEFAULT_OEF + oef_search_request.counterparty = str(self.connection.connection_id) sending_dialogue_2 = self.oef_search_dialogues.update(oef_search_request) + assert sending_dialogue_2 is not None self.multiplexer.put( Envelope( - to=DEFAULT_OEF, + to=str(self.connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=oef_search_request, @@ -480,7 +500,10 @@ def test_unregister_service(self): ) envelope = self.multiplexer.get(block=True, timeout=5.0) - oef_search_response = envelope.message + oef_search_response_original = envelope.message + oef_search_response = copy.copy(oef_search_response_original) + oef_search_response.is_incoming = True + oef_search_response.counterparty = oef_search_response_original.sender oef_search_dialogue = self.oef_search_dialogues.update(oef_search_response) assert ( oef_search_response.performative @@ -840,7 +863,7 @@ def test_on_oef_error(self): def test_send(self): """Test the send method.""" envelope = Envelope( - to=DEFAULT_OEF, + to=str(self.connection1.connection_id), sender="me", protocol_id=DefaultMessage.protocol_id, message=b"Hello", @@ -1009,7 +1032,7 @@ class TestSendWithOEF(UseOef): """Test other usecases with OEF.""" @pytest.mark.asyncio - async def test_send_oef_message(self, pytestconfig): + async def test_send_oef_message(self, pytestconfig, caplog): """Test the send oef message.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, oef_addr="127.0.0.1", oef_port=10000, @@ -1022,16 +1045,17 @@ async def test_send_oef_message(self, pytestconfig): dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, ) - msg.counterparty = DEFAULT_OEF + msg.counterparty = str(oef_connection.connection_id) sending_dialogue = oef_search_dialogues.update(msg) envelope = Envelope( - to=DEFAULT_OEF, + to=str(oef_connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=msg, ) - with pytest.raises(ValueError): + with caplog.at_level(logging.DEBUG, "aea.packages.fetchai.connections.oef"): await oef_connection.send(envelope) + assert "Could not create dialogue for message=" in caplog.text data_model = DataModel("foobar", attributes=[Attribute("foo", str, True)]) query = Query( @@ -1044,17 +1068,20 @@ async def test_send_oef_message(self, pytestconfig): dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), query=query, ) - msg.counterparty = DEFAULT_OEF + msg.counterparty = str(oef_connection.connection_id) sending_dialogue = oef_search_dialogues.update(msg) envelope = Envelope( - to=DEFAULT_OEF, + to=str(oef_connection.connection_id), sender=FETCHAI_ADDRESS_ONE, protocol_id=OefSearchMessage.protocol_id, message=msg, ) await oef_connection.send(envelope) envelope = await oef_connection.receive() - search_result = envelope.message + search_result_original = envelope.message + search_result = copy.copy(search_result_original) + search_result.counterparty = search_result_original.sender + search_result.is_incoming = True response_dialogue = oef_search_dialogues.update(search_result) assert search_result.performative == OefSearchMessage.Performative.SEARCH_RESULT assert sending_dialogue == response_dialogue @@ -1108,11 +1135,11 @@ async def test_connecting_twice_is_ok(self, pytestconfig): ) oef_connection.loop = asyncio.get_event_loop() - assert not oef_connection.connection_status.is_connected + assert not oef_connection.is_connected await oef_connection.connect() - assert oef_connection.connection_status.is_connected + assert oef_connection.is_connected await oef_connection.connect() - assert oef_connection.connection_status.is_connected + assert oef_connection.is_connected await oef_connection.disconnect() @@ -1121,7 +1148,7 @@ async def test_connecting_twice_is_ok(self, pytestconfig): @pytest.mark.skipif( sys.version_info < (3, 7), reason="Python version < 3.7 not supported by the OEF." ) -async def test_cannot_connect_to_oef(caplog): +async def test_cannot_connect_to_oef(): """Test the case when we can't connect to the OEF.""" oef_connection = _make_oef_connection( address=FETCHAI_ADDRESS_ONE, @@ -1129,13 +1156,15 @@ async def test_cannot_connect_to_oef(caplog): oef_port=61234, # use addr instead of hostname to avoid name resolution ) - with caplog.at_level(logging.DEBUG, logger="aea.packages.fetchai.connections.oef"): + with mock.patch.object(oef_connection.logger, "warning") as mock_logger: task = asyncio.ensure_future( oef_connection.connect(), loop=asyncio.get_event_loop() ) await asyncio.sleep(3.0) - assert "Cannot connect to OEFChannel. Retrying in 5 seconds..." in caplog.text + mock_logger.assert_any_call( + "Cannot connect to OEFChannel. Retrying in 5 seconds..." + ) await cancel_and_wait(task) await oef_connection.disconnect() diff --git a/tests/test_packages/test_connections/test_p2p_client/test_p2p_client.py b/tests/test_packages/test_connections/test_p2p_client/test_p2p_client.py index 1dbdb7dc73..5ef384bb40 100644 --- a/tests/test_packages/test_connections/test_p2p_client/test_p2p_client.py +++ b/tests/test_packages/test_connections/test_p2p_client/test_p2p_client.py @@ -82,7 +82,7 @@ async def test_disconnect(self): return_value={"status": "OK"}, ): await self.p2p_client_connection.disconnect() - assert self.p2p_client_connection.connection_status.is_connected is False + assert self.p2p_client_connection.is_connected is False @pytest.mark.asyncio @@ -117,7 +117,7 @@ async def test_p2p_receive(): return_value={"status": "OK"}, ): await p2p_connection.connect() - assert p2p_connection.connection_status.is_connected is True + assert p2p_connection.is_connected is True with mock.patch.object( fetch.p2p.api.http_calls.HTTPCalls, "get_messages", return_value=messages @@ -132,7 +132,7 @@ async def test_p2p_receive(): ): p2p_connection.channel._httpCall.get_messages = fake_get_messages_empty await p2p_connection.disconnect() - assert p2p_connection.connection_status.is_connected is False + assert p2p_connection.is_connected is False @pytest.mark.asyncio @@ -162,7 +162,7 @@ async def test_p2p_send(): return_value={"status": "OK"}, ): await p2p_client_connection.connect() - assert p2p_client_connection.connection_status.is_connected is True + assert p2p_client_connection.is_connected is True with mock.patch.object( fetch.p2p.api.http_calls.HTTPCalls, "get_messages", return_value=[] @@ -180,4 +180,4 @@ async def test_p2p_send(): fetch.p2p.api.http_calls.HTTPCalls, "unregister", return_value={"status": "OK"}, ): await p2p_client_connection.disconnect() - assert p2p_client_connection.connection_status.is_connected is False + assert p2p_client_connection.is_connected is False diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py index 63ad027400..5a73b2a0ca 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py @@ -38,8 +38,8 @@ class TestP2PLibp2pConnectionAEARunningDefaultConfigNode(AEATestCaseEmpty): @libp2p_log_on_failure def test_agent(self): - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") # for logging config_path = "vendor.fetchai.connections.p2p_libp2p.config" @@ -68,7 +68,7 @@ def teardown_class(cls): """Tear down the test""" cls.terminate_agents() - AEATestCaseEmpty.teardown_class() + super(TestP2PLibp2pConnectionAEARunningDefaultConfigNode, cls).teardown_class() @skip_test_windows @@ -77,7 +77,7 @@ class TestP2PLibp2pConnectionAEARunningFullNode(AEATestCaseEmpty): @libp2p_log_on_failure def test_agent(self): - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") # setup a full node: with public uri, relay service, and delegate service config_path = "vendor.fetchai.connections.p2p_libp2p.config" @@ -118,5 +118,4 @@ def test_agent(self): def teardown_class(cls): """Tear down the test""" cls.terminate_agents() - - AEATestCaseEmpty.teardown_class() + super(TestP2PLibp2pConnectionAEARunningFullNode, cls).teardown_class() diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_communication.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_communication.py index 376ef99293..8d78c93c97 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_communication.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_communication.py @@ -58,16 +58,16 @@ def setup_class(cls): @pytest.mark.asyncio async def test_p2plibp2pconnection_connect_disconnect(self): - assert self.connection.connection_status.is_connected is False + assert self.connection.is_connected is False try: await self.connection.connect() - assert self.connection.connection_status.is_connected is True + assert self.connection.is_connected is True except Exception as e: await self.connection.disconnect() raise e await self.connection.disconnect() - assert self.connection.connection_status.is_connected is False + assert self.connection.is_connected is False @classmethod def teardown_class(cls): @@ -109,8 +109,8 @@ def setup_class(cls): cls.multiplexer2.connect() def test_connection_is_established(self): - assert self.connection1.connection_status.is_connected is True - assert self.connection2.connection_status.is_connected is True + assert self.connection1.is_connected is True + assert self.connection2.is_connected is True def test_envelope_routed(self): addr_1 = self.connection1.node.address @@ -228,9 +228,9 @@ def setup_class(cls): muxer.connect() def test_connection_is_established(self): - assert self.connection_genesis.connection_status.is_connected is True + assert self.connection_genesis.is_connected is True for conn in self.connections: - assert conn.connection_status.is_connected is True + assert conn.is_connected is True def test_star_routing_connectivity(self): addrs = [conn.node.address for conn in self.connections] @@ -317,9 +317,9 @@ def setup_class(cls): cls.multiplexer2.connect() def test_connection_is_established(self): - assert self.relay.connection_status.is_connected is True - assert self.connection1.connection_status.is_connected is True - assert self.connection2.connection_status.is_connected is True + assert self.relay.is_connected is True + assert self.connection1.is_connected is True + assert self.connection2.is_connected is True def test_envelope_routed(self): addr_1 = self.connection1.node.address @@ -460,10 +460,10 @@ def setup_class(cls): muxer.connect() def test_connection_is_established(self): - assert self.connection_relay_1.connection_status.is_connected is True - assert self.connection_relay_2.connection_status.is_connected is True + assert self.connection_relay_1.is_connected is True + assert self.connection_relay_2.is_connected is True for conn in self.connections: - assert conn.connection_status.is_connected is True + assert conn.is_connected is True def test_star_routing_connectivity(self): addrs = [conn.node.address for conn in self.connections] diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py index ad774dc22a..53b8228fc3 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py @@ -113,9 +113,10 @@ def test_wrong_path(self): def test_timeout(self): self.connection.node._connection_timeout = 0 self.connection.node._connection_attempts = 2 + muxer = Multiplexer([self.connection]) with pytest.raises(Exception): - muxer = Multiplexer([self.connection]) muxer.connect() + muxer.disconnect() @classmethod def teardown_class(cls): diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py new file mode 100644 index 0000000000..ab1430bdba --- /dev/null +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py @@ -0,0 +1,210 @@ +# -*- 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 test module contains integration tests for P2PLibp2p connection.""" + +import os +import shutil +import tempfile +import time + +import pytest + +from aea.configurations.constants import DEFAULT_LEDGER +from aea.crypto.registries import make_crypto +from aea.mail.base import Envelope +from aea.multiplexer import Multiplexer +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer + +from tests.conftest import ( + PUBLIC_DHT_P2P_MADDR_1, + PUBLIC_DHT_P2P_MADDR_2, + _make_libp2p_connection, + libp2p_log_on_failure, + libp2p_log_on_failure_all, + skip_test_windows, +) + +DEFAULT_PORT = 10234 + + +@skip_test_windows +@pytest.mark.integration +@libp2p_log_on_failure_all +class TestLibp2pConnectionRelayPublicDHT: + """Test that public dht is working properly""" + + @classmethod + def setup_class(cls): + """Set the test up""" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + cls.log_files = [] + + def test_reachable(self): + public_nodes_maddrs = [PUBLIC_DHT_P2P_MADDR_1, PUBLIC_DHT_P2P_MADDR_2] + for maddr in public_nodes_maddrs: + connection = _make_libp2p_connection( + DEFAULT_PORT + 1, relay=False, entry_peers=[maddr] + ) + multiplexer = Multiplexer([connection]) + self.log_files.append(connection.node.log_file) + multiplexer.connect() + + assert connection.is_connected is True + multiplexer.disconnect() + + @classmethod + def teardown_class(cls): + """Tear down the test""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +@skip_test_windows +@libp2p_log_on_failure_all +class TestLibp2pConnectionRelayNodeRestart: + """Test that connection will reliably route envelope to destination in case of relay node restart within timeout""" + + @classmethod + @libp2p_log_on_failure + def setup_class(cls): + """Set the test up""" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + cls.log_files = [] + + cls.genesis = _make_libp2p_connection(DEFAULT_PORT + 1) + + cls.multiplexer_genesis = Multiplexer([cls.genesis]) + cls.log_files.append(cls.genesis.node.log_file) + cls.multiplexer_genesis.connect() + + genesis_peer = cls.genesis.node.multiaddrs[0] + + with open("node_key", "wb") as f: + make_crypto(DEFAULT_LEDGER).dump(f) + cls.relay_key_path = "node_key" + + cls.relay = _make_libp2p_connection( + port=DEFAULT_PORT + 2, + entry_peers=[genesis_peer], + node_key_file=cls.relay_key_path, + ) + cls.multiplexer_relay = Multiplexer([cls.relay]) + cls.log_files.append(cls.relay.node.log_file) + cls.multiplexer_relay.connect() + + relay_peer = cls.relay.node.multiaddrs[0] + + cls.connection = _make_libp2p_connection( + DEFAULT_PORT + 3, relay=False, entry_peers=[relay_peer] + ) + cls.multiplexer = Multiplexer([cls.connection]) + cls.log_files.append(cls.connection.node.log_file) + cls.multiplexer.connect() + + def test_connection_is_established(self): + assert self.relay.is_connected is True + assert self.connection.is_connected is True + + def test_envelope_routed_after_relay_restart(self): + addr_1 = self.connection.address + addr_2 = self.genesis.address + + msg = DefaultMessage( + dialogue_reference=("", ""), + message_id=1, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"hello", + ) + envelope = Envelope( + to=addr_2, + sender=addr_1, + protocol_id=DefaultMessage.protocol_id, + message=DefaultSerializer().encode(msg), + ) + + self.multiplexer.put(envelope) + delivered_envelope = self.multiplexer_genesis.get(block=True, timeout=20) + + assert delivered_envelope is not None + assert delivered_envelope.to == envelope.to + assert delivered_envelope.sender == envelope.sender + assert delivered_envelope.protocol_id == envelope.protocol_id + assert delivered_envelope.message == envelope.message + + self.multiplexer_relay.disconnect() + + msg = DefaultMessage( + dialogue_reference=("", ""), + message_id=1, + target=0, + performative=DefaultMessage.Performative.BYTES, + content=b"helloAfterRestart", + ) + envelope = Envelope( + to=addr_2, + sender=addr_1, + protocol_id=DefaultMessage.protocol_id, + message=DefaultSerializer().encode(msg), + ) + + self.multiplexer.put(envelope) + time.sleep(5) + + TestLibp2pConnectionRelayNodeRestart.relay = _make_libp2p_connection( + port=DEFAULT_PORT + 2, + entry_peers=[self.genesis.node.multiaddrs[0]], + node_key_file=self.relay_key_path, + ) + TestLibp2pConnectionRelayNodeRestart.multiplexer_relay = Multiplexer( + [self.relay] + ) + self.multiplexer_relay.connect() + + delivered_envelope = self.multiplexer_genesis.get(block=True, timeout=20) + + assert delivered_envelope is not None + assert delivered_envelope.to == envelope.to + assert delivered_envelope.sender == envelope.sender + assert delivered_envelope.protocol_id == envelope.protocol_id + assert delivered_envelope.message == envelope.message + + @classmethod + def teardown_class(cls): + """Tear down the test""" + cls.multiplexer.disconnect() + cls.multiplexer_relay.disconnect() + cls.multiplexer_genesis.disconnect() + + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass diff --git a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py index 3703113ece..bb5bffe21d 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py @@ -46,7 +46,7 @@ class TestP2PLibp2pClientConnectionAEARunning(AEATestCaseEmpty): @libp2p_log_on_failure def setup_class(cls): """Set up the test class.""" - AEATestCaseEmpty.setup_class() + super(TestP2PLibp2pClientConnectionAEARunning, cls).setup_class() cls.node_connection = _make_libp2p_connection( delegate_host=DEFAULT_HOST, @@ -59,10 +59,10 @@ def setup_class(cls): cls.node_multiplexer.connect() def test_node(self): - assert self.node_connection.connection_status.is_connected is True + assert self.node_connection.is_connected is True def test_connection(self): - self.add_item("connection", "fetchai/p2p_libp2p_client:0.4.0") + self.add_item("connection", "fetchai/p2p_libp2p_client:0.5.0") config_path = "vendor.fetchai.connections.p2p_libp2p_client.config" self.force_set_config( "{}.nodes".format(config_path), @@ -91,6 +91,6 @@ def teardown_class(cls): """Tear down the test""" cls.terminate_agents() - AEATestCaseEmpty.teardown_class() + super(TestP2PLibp2pClientConnectionAEARunning, cls).teardown_class() cls.node_multiplexer.disconnect() diff --git a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_communication.py b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_communication.py index 4e2ec63b29..6c976faf3c 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_communication.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_communication.py @@ -63,17 +63,17 @@ def setup_class(cls): @pytest.mark.asyncio async def test_libp2pclientconnection_connect_disconnect(self): - assert self.connection.connection_status.is_connected is False + assert self.connection.is_connected is False try: await self.connection_node.connect() await self.connection.connect() - assert self.connection.connection_status.is_connected is True + assert self.connection.is_connected is True except Exception as e: await self.connection.disconnect() raise e await self.connection.disconnect() - assert self.connection.connection_status.is_connected is False + assert self.connection.is_connected is False await self.connection_node.disconnect() @classmethod @@ -115,8 +115,8 @@ def setup_class(cls): cls.multiplexer_client_2.connect() def test_connection_is_established(self): - assert self.connection_client_1.connection_status.is_connected is True - assert self.connection_client_2.connection_status.is_connected is True + assert self.connection_client_1.is_connected is True + assert self.connection_client_2.is_connected is True def test_envelope_routed(self): addr_1 = self.connection_client_1.address @@ -276,10 +276,10 @@ def setup_class(cls): cls.multiplexer_client_2.connect() def test_connection_is_established(self): - assert self.connection_node_1.connection_status.is_connected is True - assert self.connection_node_2.connection_status.is_connected is True - assert self.connection_client_1.connection_status.is_connected is True - assert self.connection_client_2.connection_status.is_connected is True + assert self.connection_node_1.is_connected is True + assert self.connection_node_2.is_connected is True + assert self.connection_client_1.is_connected is True + assert self.connection_client_2.is_connected is True def test_envelope_routed(self): addr_1 = self.connection_client_1.address @@ -447,7 +447,7 @@ def setup_class(cls): def test_connection_is_established(self): for conn in self.connections: - assert conn.connection_status.is_connected is True + assert conn.is_connected is True def test_star_routing_connectivity(self): msg = DefaultMessage( diff --git a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_errors.py b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_errors.py index 6c881a73e0..a2e976eb84 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_errors.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_errors.py @@ -127,7 +127,7 @@ def setup_class(cls): cls.multiplexer_client.connect() def test_node_disconnected(self): - assert self.connection_client.connection_status.is_connected is True + assert self.connection_client.is_connected is True self.multiplexer_node.disconnect() self.multiplexer_client.disconnect() diff --git a/tests/test_packages/test_connections/test_p2p_stub/test_p2p_stub.py b/tests/test_packages/test_connections/test_p2p_stub/test_p2p_stub.py index a19e18dbdb..4d1a6820da 100644 --- a/tests/test_packages/test_connections/test_p2p_stub/test_p2p_stub.py +++ b/tests/test_packages/test_connections/test_p2p_stub/test_p2p_stub.py @@ -80,10 +80,10 @@ def setup(self): async def test_send(self): """Test that the connection receives what has been enqueued in the input file.""" await self.connection1.connect() - assert self.connection1.connection_status.is_connected + assert self.connection1.is_connected await self.connection2.connect() - assert self.connection2.connection_status.is_connected + assert self.connection2.is_connected envelope = make_test_envelope(to_="con2") await self.connection1.send(envelope) diff --git a/tests/test_packages/test_connections/test_soef/test_soef.py b/tests/test_packages/test_connections/test_soef/test_soef.py index 13a2918a20..8a5722c4ee 100644 --- a/tests/test_packages/test_connections/test_soef/test_soef.py +++ b/tests/test_packages/test_connections/test_soef/test_soef.py @@ -132,7 +132,7 @@ def setup(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, ) self.connection = SOEFConnection( @@ -214,19 +214,19 @@ async def test_remove_service_key(self): def test_connected(self): """Test connected==True.""" - assert self.connection.connection_status.is_connected + assert self.connection.is_connected @pytest.mark.asyncio async def test_disconnected(self): """Test disconnect.""" - assert self.connection.connection_status.is_connected + assert self.connection.is_connected with patch.object( self.connection.channel, "_request_text", make_async("Goodbye!"), ): await self.connection.disconnect() - assert not self.connection.connection_status.is_connected + assert not self.connection.is_connected @pytest.mark.asyncio async def test_register_service(self): @@ -302,9 +302,10 @@ async def test_bad_register_service(self): expected_envelope.message.performative == OefSearchMessage.Performative.OEF_ERROR ) - message = copy.deepcopy(expected_envelope.message) + orig = expected_envelope.message + message = copy.copy(orig) message.is_incoming = True # TODO: fix - message.counterparty = SOEFConnection.connection_id.latest # TODO; fix + message.counterparty = orig.sender # TODO; fix receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @@ -406,11 +407,17 @@ async def test_bad_performative(self, caplog): service_instance, data_model=models.AGENT_LOCATION_MODEL ) message = OefSearchMessage( - performative="oef_error", service_description=service_description, + performative="oef_error", + dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), + service_description=service_description, ) + message.counterparty = SOEFConnection.connection_id.latest + sending_dialogue = self.oef_search_dialogues.update(message) + assert sending_dialogue is None + message.sender = self.crypto.address envelope = Envelope( - to="soef", - sender=self.crypto.address, + to=message.counterparty, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -448,9 +455,10 @@ async def test_bad_search_query(self, caplog): assert expected_envelope message = expected_envelope.message assert message.performative == OefSearchMessage.Performative.OEF_ERROR - message = copy.deepcopy(expected_envelope.message) + orig = expected_envelope.message + message = copy.copy(orig) message.is_incoming = True # TODO: fix - message.counterparty = SOEFConnection.connection_id.latest # TODO; fix + message.counterparty = orig.sender # TODO; fix receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @@ -495,14 +503,17 @@ async def test_search(self): make_async(self.search_success_response), ): await self.connection.send(envelope) + expected_envelope = await asyncio.wait_for( + self.connection.receive(), timeout=1 + ) - expected_envelope = await asyncio.wait_for(self.connection.receive(), timeout=1) assert expected_envelope message = expected_envelope.message assert len(message.agents) >= 1 - message = copy.deepcopy(expected_envelope.message) + orig = expected_envelope.message + message = copy.copy(orig) message.is_incoming = True # TODO: fix - message.counterparty = SOEFConnection.connection_id.latest # TODO; fix + message.counterparty = orig.sender # TODO; fix receiving_dialogue = self.oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue @@ -526,14 +537,58 @@ async def test_find_around_me(self): closeness_query = Query( [close_to_my_service] + personality_filters + service_key_filters ) - message = OefSearchMessage( + + message_1 = OefSearchMessage( performative=OefSearchMessage.Performative.SEARCH_SERVICES, dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), query=closeness_query, ) - message.counterparty = SOEFConnection.connection_id.latest - sending_dialogue = self.oef_search_dialogues.update(message) + message_1.counterparty = SOEFConnection.connection_id.latest + sending_dialogue = self.oef_search_dialogues.update(message_1) + assert sending_dialogue is not None + + internal_msg_1 = copy.copy(message_1) + internal_msg_1.is_incoming = True + internal_msg_1.counterparty = message_1.sender + internal_dialogue_1 = self.connection.channel.oef_search_dialogues.update( + internal_msg_1 + ) + assert internal_dialogue_1 is not None + + message_2 = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), + query=closeness_query, + ) + message_2.counterparty = SOEFConnection.connection_id.latest + sending_dialogue = self.oef_search_dialogues.update(message_2) + assert sending_dialogue is not None + + internal_msg_2 = copy.copy(message_2) + internal_msg_2.is_incoming = True + internal_msg_2.counterparty = message_2.sender + internal_dialogue_2 = self.connection.channel.oef_search_dialogues.update( + internal_msg_2 + ) + assert internal_dialogue_2 is not None + + message_3 = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + dialogue_reference=self.oef_search_dialogues.new_self_initiated_dialogue_reference(), + query=closeness_query, + ) + message_3.counterparty = SOEFConnection.connection_id.latest + sending_dialogue = self.oef_search_dialogues.update(message_3) assert sending_dialogue is not None + + internal_msg_3 = copy.copy(message_3) + internal_msg_3.is_incoming = True + internal_msg_3.counterparty = message_3.sender + internal_dialogue_3 = self.connection.channel.oef_search_dialogues.update( + internal_msg_3 + ) + assert internal_dialogue_3 is not None + with patch.object( self.connection.channel, "_request_text", @@ -544,15 +599,15 @@ async def test_find_around_me(self): wrap_future(self.search_fail_response), ], ): - await self.connection.channel._find_around_me( - message, sending_dialogue, 1, {} + await self.connection.channel._find_around_me_handle_requet( + internal_msg_1, internal_dialogue_1, 1, {} ) - await self.connection.channel._find_around_me( - message, sending_dialogue, 1, {} + await self.connection.channel._find_around_me_handle_requet( + internal_msg_2, internal_dialogue_2, 1, {} ) with pytest.raises(SOEFException, match=r"`find_around_me` error: .*"): - await self.connection.channel._find_around_me( - message, sending_dialogue, 1, {} + await self.connection.channel._find_around_me_handle_requet( + internal_msg_3, internal_dialogue_3, 1, {} ) @pytest.mark.asyncio @@ -671,7 +726,7 @@ def test_chain_identifier_fail(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) @@ -689,7 +744,7 @@ def test_chain_identifier_ok(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) diff --git a/tests/test_packages/test_connections/test_soef/test_soef_integration.py b/tests/test_packages/test_connections/test_soef/test_soef_integration.py index f5a310a005..06150dafe6 100644 --- a/tests/test_packages/test_connections/test_soef/test_soef_integration.py +++ b/tests/test_packages/test_connections/test_soef/test_soef_integration.py @@ -66,7 +66,7 @@ def test_soef(): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.3.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.4.0")}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection(configuration=configuration, identity=identity,) @@ -95,7 +95,7 @@ def test_soef(): assert sending_dialogue is not None envelope = Envelope( to=message.counterparty, - sender=crypto.address, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -121,7 +121,7 @@ def test_soef(): assert sending_dialogue is not None envelope = Envelope( to=message.counterparty, - sender=crypto.address, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -143,7 +143,7 @@ def test_soef(): assert sending_dialogue is not None envelope = Envelope( to=message.counterparty, - sender=crypto.address, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -166,9 +166,9 @@ def test_soef(): message.counterparty = SOEFConnection.connection_id.latest sending_dialogue = oef_search_dialogues.update(message) assert sending_dialogue is not None - envelope = Envelope( + search_envelope = Envelope( to=message.counterparty, - sender=crypto.address, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -177,20 +177,42 @@ def test_soef(): radius, agent_location.latitude, agent_location.longitude, ) ) - multiplexer.put(envelope) + multiplexer.put(search_envelope) wait_for_condition(lambda: not multiplexer.in_queue.empty(), timeout=20) # check for search results envelope = multiplexer.get() - message = envelope.message - assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT - assert len(message.agents) >= 0 - message = copy.deepcopy(message) + orig_message = envelope.message + assert orig_message.performative == OefSearchMessage.Performative.SEARCH_RESULT + assert len(orig_message.agents) >= 0 + message = copy.copy(orig_message) message.is_incoming = True # TODO: fix - message.counterparty = SOEFConnection.connection_id.latest # TODO; fix + message.counterparty = orig_message.sender # TODO; fix receiving_dialogue = oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue + # double send to check issue with too many requests + message = OefSearchMessage( + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + dialogue_reference=oef_search_dialogues.new_self_initiated_dialogue_reference(), + query=closeness_query, + ) + message.counterparty = SOEFConnection.connection_id.latest + sending_dialogue = oef_search_dialogues.update(message) + assert sending_dialogue is not None + search_envelope = Envelope( + to=message.counterparty, + sender=message.sender, + protocol_id=message.protocol_id, + message=message, + ) + multiplexer.put(search_envelope) + wait_for_condition(lambda: not multiplexer.in_queue.empty(), timeout=20) + # check for search results + envelope = multiplexer.get() + message = envelope.message + assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT + # find agents near me with filter radius = 0.1 close_to_my_service = Constraint( @@ -221,7 +243,7 @@ def test_soef(): assert sending_dialogue is not None envelope = Envelope( to=message.counterparty, - sender=crypto.address, + sender=message.sender, protocol_id=message.protocol_id, message=message, ) @@ -235,12 +257,12 @@ def test_soef(): wait_for_condition(lambda: not multiplexer.in_queue.empty(), timeout=20) envelope = multiplexer.get() - message = envelope.message - assert message.performative == OefSearchMessage.Performative.SEARCH_RESULT - assert len(message.agents) >= 0 - message = copy.deepcopy(message) + orig_message = envelope.message + assert orig_message.performative == OefSearchMessage.Performative.SEARCH_RESULT + assert len(orig_message.agents) >= 0 + message = copy.copy(orig_message) message.is_incoming = True # TODO: fix - message.counterparty = SOEFConnection.connection_id.latest # TODO; fix + message.counterparty = orig_message.sender # TODO; fix receiving_dialogue = oef_search_dialogues.update(message) assert sending_dialogue == receiving_dialogue diff --git a/tests/test_packages/test_connections/test_tcp/test_base.py b/tests/test_packages/test_connections/test_tcp/test_base.py index abaa0bcd27..befe88aa87 100644 --- a/tests/test_packages/test_connections/test_tcp/test_base.py +++ b/tests/test_packages/test_connections/test_tcp/test_base.py @@ -20,7 +20,6 @@ """This module contains the tests for the TCP base module.""" import asyncio -import logging import unittest.mock from asyncio import CancelledError @@ -37,7 +36,7 @@ @pytest.mark.asyncio -async def test_connect_twice(caplog): +async def test_connect_twice(): """Test that connecting twice the tcp connection works correctly.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection("address", "127.0.0.1", port) @@ -47,17 +46,15 @@ async def test_connect_twice(caplog): await tcp_connection.connect() await asyncio.sleep(0.1) - with caplog.at_level( - logging.WARNING, "aea.packages.fetchai.connections.tcp.tcp_server" - ): + with unittest.mock.patch.object(tcp_connection.logger, "warning") as mock_logger: await tcp_connection.connect() - assert "Connection already set up." in caplog.text + mock_logger.assert_called_with("Connection already set up.") await tcp_connection.disconnect() @pytest.mark.asyncio -async def test_connect_raises_exception(caplog): +async def test_connect_raises_exception(): """Test the case that a connection attempt raises an exception.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection("address", "127.0.0.1", port) @@ -65,14 +62,12 @@ async def test_connect_raises_exception(caplog): loop = asyncio.get_event_loop() tcp_connection.loop = loop - with caplog.at_level( - logging.ERROR, "aea.packages.fetchai.connections.tcp.tcp_server" - ): + with unittest.mock.patch.object(tcp_connection.logger, "error") as mock_logger: with unittest.mock.patch.object( tcp_connection, "setup", side_effect=Exception("error during setup") ): await tcp_connection.connect() - assert "error during setup" in caplog.text + mock_logger.assert_called_with("error during setup") @pytest.mark.asyncio @@ -81,15 +76,13 @@ async def test_disconnect_when_already_disconnected(caplog): port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection("address", "127.0.0.1", port) - with caplog.at_level( - logging.WARNING, "aea.packages.fetchai.connections.tcp.tcp_server" - ): + with unittest.mock.patch.object(tcp_connection.logger, "warning") as mock_logger: await tcp_connection.disconnect() - assert "Connection already disconnected." in caplog.text + mock_logger.assert_called_with("Connection already disconnected.") @pytest.mark.asyncio -async def test_send_to_unknown_destination(caplog): +async def test_send_to_unknown_destination(): """Test that a message to an unknown destination logs an error.""" address = "address" port = get_unused_tcp_port() @@ -100,11 +93,11 @@ async def test_send_to_unknown_destination(caplog): protocol_id=DefaultMessage.protocol_id, message=b"", ) - with caplog.at_level( - logging.ERROR, "aea.packages.fetchai.connections.tcp.tcp_server" - ): + with unittest.mock.patch.object(tcp_connection.logger, "error") as mock_logger: await tcp_connection.send(envelope) - assert "[{}]: Cannot send envelope {}".format(address, envelope) in caplog.text + mock_logger.assert_called_with( + "[{}]: Cannot send envelope {}".format(address, envelope) + ) @pytest.mark.asyncio diff --git a/tests/test_packages/test_connections/test_tcp/test_communication.py b/tests/test_packages/test_connections/test_tcp/test_communication.py index 1a4502a039..980f769e45 100644 --- a/tests/test_packages/test_connections/test_tcp/test_communication.py +++ b/tests/test_packages/test_connections/test_tcp/test_communication.py @@ -20,7 +20,6 @@ """This module contains the tests for the TCP connection communication.""" import asyncio -import logging import struct import unittest.mock @@ -64,9 +63,9 @@ def setup_class(cls): cls.client_1_multiplexer = Multiplexer([cls.client_conn_1]) cls.client_2_multiplexer = Multiplexer([cls.client_conn_2]) - assert not cls.server_conn.connection_status.is_connected - assert not cls.client_conn_1.connection_status.is_connected - assert not cls.client_conn_2.connection_status.is_connected + assert not cls.server_conn.is_connected + assert not cls.client_conn_1.is_connected + assert not cls.client_conn_2.is_connected cls.server_multiplexer.connect() cls.client_1_multiplexer.connect() @@ -74,9 +73,9 @@ def setup_class(cls): def test_is_connected(self): """Test that the connection status are connected.""" - assert self.server_conn.connection_status.is_connected - assert self.client_conn_1.connection_status.is_connected - assert self.client_conn_2.connection_status.is_connected + assert self.server_conn.is_connected + assert self.client_conn_1.is_connected + assert self.client_conn_2.is_connected def test_communication_client_server(self): """Test that envelopes can be sent from a client to a server.""" @@ -156,7 +155,7 @@ class TestTCPClientConnection: """Test TCP Client code.""" @pytest.mark.asyncio - async def test_receive_cancelled(self, caplog): + async def test_receive_cancelled(self): """Test that cancelling a receive task works correctly.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection("address_server", "127.0.0.1", port,) @@ -165,21 +164,21 @@ async def test_receive_cancelled(self, caplog): await tcp_server.connect() await tcp_client.connect() - with caplog.at_level( - logging.DEBUG, "aea.packages.fetchai.connections.tcp.tcp_client" - ): + with unittest.mock.patch.object(tcp_client.logger, "debug") as mock_logger: task = asyncio.ensure_future(tcp_client.receive()) await asyncio.sleep(0.1) task.cancel() await asyncio.sleep(0.1) - assert "[{}] Read cancelled.".format("address_client") in caplog.text + mock_logger.assert_called_with( + "[{}] Read cancelled.".format("address_client") + ) assert task.result() is None await tcp_client.disconnect() await tcp_server.disconnect() @pytest.mark.asyncio - async def test_receive_raises_struct_error(self, caplog): + async def test_receive_raises_struct_error(self): """Test the case when a receive raises a struct error.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection("address_server", "127.0.0.1", port,) @@ -188,15 +187,13 @@ async def test_receive_raises_struct_error(self, caplog): await tcp_server.connect() await tcp_client.connect() - with caplog.at_level( - logging.DEBUG, "aea.packages.fetchai.connections.tcp.tcp_client" - ): + with unittest.mock.patch.object(tcp_client.logger, "debug") as mock_logger: with unittest.mock.patch.object( tcp_client, "_recv", side_effect=struct.error ): task = asyncio.ensure_future(tcp_client.receive()) await asyncio.sleep(0.1) - assert "Struct error: " in caplog.text + mock_logger.assert_called_with("Struct error: ") assert task.result() is None await tcp_client.disconnect() @@ -228,7 +225,7 @@ class TestTCPServerConnection: """Test TCP Server code.""" @pytest.mark.asyncio - async def test_receive_raises_exception(self, caplog): + async def test_receive_raises_exception(self): """Test the case when a receive raises a generic exception.""" port = get_unused_tcp_port() tcp_server = _make_tcp_server_connection("address_server", "127.0.0.1", port,) @@ -237,15 +234,16 @@ async def test_receive_raises_exception(self, caplog): await tcp_server.connect() await tcp_client.connect() await asyncio.sleep(0.1) - with caplog.at_level( - logging.DEBUG, "aea.packages.fetchai.connections.tcp.tcp_server" - ): + + with unittest.mock.patch.object(tcp_server.logger, "error") as mock_logger: with unittest.mock.patch( "asyncio.wait", side_effect=Exception("generic exception") ): result = await tcp_server.receive() assert result is None - assert "Error in the receiving loop: generic exception" in caplog.text + mock_logger.assert_any_call( + "Error in the receiving loop: generic exception" + ) await tcp_client.disconnect() await tcp_server.disconnect() diff --git a/tests/test_packages/test_connections/test_webhook/test_webhook.py b/tests/test_packages/test_connections/test_webhook/test_webhook.py index dd0da1537d..fa2a11fb38 100644 --- a/tests/test_packages/test_connections/test_webhook/test_webhook.py +++ b/tests/test_packages/test_connections/test_webhook/test_webhook.py @@ -19,10 +19,12 @@ """Tests for the webhook connection and channel.""" import asyncio +import copy import json import logging from traceback import print_exc from typing import cast +from unittest.mock import patch import aiohttp from aiohttp.client_reqrep import ClientResponse @@ -35,8 +37,10 @@ from packages.fetchai.connections.webhook.connection import WebhookConnection +from packages.fetchai.protocols.http.dialogues import HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage +from tests.common.mocks import RegexComparator from tests.conftest import ( get_host, get_unused_tcp_port, @@ -67,6 +71,7 @@ def setup(self): configuration=configuration, identity=self.identity, ) self.webhook_connection.loop = self.loop + self.dialogues = HttpDialogues(self.identity.address) async def test_initialization(self): """Test the initialisation of the class.""" @@ -76,16 +81,16 @@ async def test_initialization(self): async def test_connection(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() - assert self.webhook_connection.connection_status.is_connected is True + assert self.webhook_connection.is_connected is True @pytest.mark.asyncio async def test_disconnect(self): """Test the disconnect functionality of the webhook connection.""" await self.webhook_connection.connect() - assert self.webhook_connection.connection_status.is_connected is True + assert self.webhook_connection.is_connected is True await self.webhook_connection.disconnect() - assert self.webhook_connection.connection_status.is_connected is False + assert self.webhook_connection.is_connected is False def teardown(self): """Close connection after testing.""" @@ -99,14 +104,19 @@ def teardown(self): async def test_receive_post_ok(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() - assert self.webhook_connection.connection_status.is_connected is True + assert self.webhook_connection.is_connected is True payload = {"hello": "world"} call_task = self.loop.create_task(self.call_webhook("test_topic", json=payload)) envelope = await asyncio.wait_for(self.webhook_connection.receive(), timeout=10) assert envelope - message = cast(HttpMessage, envelope.message) + orig_message = cast(HttpMessage, envelope.message) + message = copy.copy(orig_message) + message.counterparty = orig_message.sender + message.is_incoming = True + dialogue = self.dialogues.update(message) + assert dialogue is not None assert message.method.upper() == "POST" assert message.bodyy.decode("utf-8") == json.dumps(payload) await call_task @@ -115,7 +125,7 @@ async def test_receive_post_ok(self): async def test_send(self): """Test the connect functionality of the webhook connection.""" await self.webhook_connection.connect() - assert self.webhook_connection.connection_status.is_connected is True + assert self.webhook_connection.is_connected is True http_message = HttpMessage( dialogue_reference=("", ""), @@ -131,10 +141,17 @@ async def test_send(self): envelope = Envelope( to="addr", sender="my_id", - protocol_id=PublicId.from_str("fetchai/http:0.3.0"), + protocol_id=PublicId.from_str("fetchai/http:0.4.0"), message=http_message, ) - await self.webhook_connection.send(envelope) + with patch.object(self.webhook_connection.logger, "warning") as mock_logger: + await self.webhook_connection.send(envelope) + await asyncio.sleep(0.01) + mock_logger.assert_any_call( + RegexComparator( + "Dropping envelope=.* as sending via the webhook is not possible!" + ) + ) async def call_webhook(self, topic: str, **kwargs) -> ClientResponse: """ diff --git a/tests/test_packages/test_protocols/test_fipa.py b/tests/test_packages/test_protocols/test_fipa.py index b34d4862c8..1a8d5be733 100644 --- a/tests/test_packages/test_protocols/test_fipa.py +++ b/tests/test_packages/test_protocols/test_fipa.py @@ -369,7 +369,9 @@ def setup_class(cls): def test_create_self_initiated(self): """Test the self initialisation of a dialogue.""" result = self.buyer_dialogues._create_self_initiated( - dialogue_opponent_addr=self.seller_addr, role=FipaDialogue.Role.SELLER, + dialogue_opponent_addr=self.seller_addr, + dialogue_reference=(str(0), ""), + role=FipaDialogue.Role.SELLER, ) assert isinstance(result, FipaDialogue) assert result.role == FipaDialogue.Role.SELLER, "The role must be seller." diff --git a/tests/test_packages/test_protocols/test_tac.py b/tests/test_packages/test_protocols/test_tac.py index 16d4a347b2..71a71ebfe6 100644 --- a/tests/test_packages/test_protocols/test_tac.py +++ b/tests/test_packages/test_protocols/test_tac.py @@ -34,16 +34,16 @@ def test_tac_message_instantiation(): assert TacMessage(performative=TacMessage.Performative.UNREGISTER) assert TacMessage( performative=TacMessage.Performative.TRANSACTION, - tx_id="some_id", - tx_sender_addr="some_address", - tx_counterparty_addr="some_other_address", + transaction_id="some_id", + ledger_id="some_ledger", + sender_address="some_address", + counterparty_address="some_other_address", amount_by_currency_id={"FET": 10}, - tx_sender_fee=10, - tx_counterparty_fee=10, + fee_by_currency_id={"FET": 1}, quantities_by_good_id={"123": 0, "1234": 10}, - tx_nonce=1, - tx_sender_signature="some_signature", - tx_counterparty_signature="some_other_signature", + nonce=1, + sender_signature="some_signature", + counterparty_signature="some_other_signature", ) assert TacMessage(performative=TacMessage.Performative.CANCELLED) assert TacMessage( @@ -52,7 +52,7 @@ def test_tac_message_instantiation(): exchange_params_by_currency_id={"FET": 10.0}, quantities_by_good_id={"123": 20, "1234": 15}, utility_params_by_good_id={"123": 30.0, "1234": 50.0}, - tx_fee=20, + fee_by_currency_id={"FET": 1}, agent_addr_to_name={"agent_1": "Agent one", "agent_2": "Agent two"}, currency_id_to_name={"FET": "currency_name"}, good_id_to_name={"123": "First good", "1234": "Second good"}, @@ -60,7 +60,7 @@ def test_tac_message_instantiation(): ) assert TacMessage( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, - tx_id="some_id", + transaction_id="some_id", amount_by_currency_id={"FET": 10}, quantities_by_good_id={"123": 20, "1234": 15}, ) @@ -90,16 +90,16 @@ def test_tac_serialization(): msg = TacMessage( performative=TacMessage.Performative.TRANSACTION, - tx_id="some_id", - tx_sender_addr="some_address", - tx_counterparty_addr="some_other_address", + ledger_id="some_ledger", + transaction_id="some_id", + sender_address="some_address", + counterparty_address="some_other_address", amount_by_currency_id={"FET": -10}, - tx_sender_fee=10, - tx_counterparty_fee=10, + fee_by_currency_id={"FET": 1}, quantities_by_good_id={"123": 0, "1234": 10}, - tx_nonce=1, - tx_sender_signature="some_signature", - tx_counterparty_signature="some_other_signature", + nonce="1", + sender_signature="some_signature", + counterparty_signature="some_other_signature", ) msg_bytes = TacMessage.serializer.encode(msg) actual_msg = TacMessage.serializer.decode(msg_bytes) @@ -118,7 +118,7 @@ def test_tac_serialization(): exchange_params_by_currency_id={"FET": 10.0}, quantities_by_good_id={"123": 20, "1234": 15}, utility_params_by_good_id={"123": 30.0, "1234": 50.0}, - tx_fee=20, + fee_by_currency_id={"FET": 1}, agent_addr_to_name={"agent_1": "Agent one", "agent_2": "Agent two"}, currency_id_to_name={"FET": "currency_name"}, good_id_to_name={"123": "First good", "1234": "Second good"}, @@ -131,7 +131,7 @@ def test_tac_serialization(): msg = TacMessage( performative=TacMessage.Performative.TRANSACTION_CONFIRMATION, - tx_id="some_id", + transaction_id="some_id", amount_by_currency_id={"FET": 10}, quantities_by_good_id={"123": 20, "1234": 15}, ) diff --git a/tests/test_packages/test_skills/test_carpark.py b/tests/test_packages/test_skills/test_carpark.py index 2ba59aaefb..9b6e4893ca 100644 --- a/tests/test_packages/test_skills/test_carpark.py +++ b/tests/test_packages/test_skills/test_carpark.py @@ -19,6 +19,8 @@ """This test module contains the integration test for the weather skills.""" +from random import uniform + import pytest from aea.test_tools.test_cases import AEATestCaseMany @@ -26,7 +28,7 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -48,17 +50,23 @@ def test_carpark(self): self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # Setup agent one self.set_agent_context(carpark_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/carpark_detection:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/carpark_detection:0.8.0") setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.is_ledger_tx" ) @@ -67,21 +75,30 @@ def test_carpark(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + + # replace location + setting_path = ( + "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) + self.force_set_config(setting_path, location) # Setup agent two self.set_agent_context(carpark_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/carpark_client:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/carpark_client:0.8.0") setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.is_ledger_tx" ) @@ -90,16 +107,24 @@ def test_carpark(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.carpark_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) carpark_aea_process = self.run_agent() @@ -195,64 +220,93 @@ def test_carpark(self): self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # Setup agent one self.set_agent_context(carpark_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/carpark_detection:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/carpark_detection:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/car_detector:0.8.0", carpark_aea_name + "fetchai/car_detector:0.9.0", carpark_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + + # replace location + setting_path = ( + "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) + self.force_set_config(setting_path, location) # Setup agent two self.set_agent_context(carpark_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/carpark_client:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/carpark_client:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/car_data_buyer:0.8.0", carpark_client_aea_name + "fetchai/car_data_buyer:0.9.0", carpark_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + # self.replace_private_key_in_file( + # FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + # ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.carpark_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) carpark_aea_process = self.run_agent() diff --git a/tests/test_packages/test_skills/test_echo.py b/tests/test_packages/test_skills/test_echo.py index e4ad5f9014..f7afc6e01d 100644 --- a/tests/test_packages/test_skills/test_echo.py +++ b/tests/test_packages/test_skills/test_echo.py @@ -34,7 +34,7 @@ class TestEchoSkill(AEATestCaseEmpty): @skip_test_windows def test_echo(self): """Run the echo skill sequence.""" - self.add_item("skill", "fetchai/echo:0.3.0") + self.add_item("skill", "fetchai/echo:0.4.0") process = self.run_agent() is_running = self.is_running(process) diff --git a/tests/test_packages/test_skills/test_erc1155.py b/tests/test_packages/test_skills/test_erc1155.py index 733a69e995..90b9ca1624 100644 --- a/tests/test_packages/test_skills/test_erc1155.py +++ b/tests/test_packages/test_skills/test_erc1155.py @@ -18,20 +18,29 @@ # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" +from random import uniform + import pytest -from aea.test_tools.test_cases import AEATestCaseMany, UseOef +from aea.test_tools.test_cases import AEATestCaseMany from tests.conftest import ( + COSMOS, + COSMOS_PRIVATE_KEY_FILE, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, ETHEREUM, ETHEREUM_PRIVATE_KEY_FILE, - FUNDED_ETH_PRIVATE_KEY_1, FUNDED_ETH_PRIVATE_KEY_2, + FUNDED_ETH_PRIVATE_KEY_3, MAX_FLAKY_RERUNS_ETH, + NON_FUNDED_COSMOS_PRIVATE_KEY_1, + NON_GENESIS_CONFIG, + wait_for_localhost_ports_to_close, ) -class TestERCSkillsEthereumLedger(AEATestCaseMany, UseOef): +@pytest.mark.integration +class TestERCSkillsEthereumLedger(AEATestCaseMany): """Test that erc1155 skills work.""" @pytest.mark.integration @@ -46,22 +55,30 @@ def test_generic(self): # add ethereum ledger in both configuration files default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/contract_api:0.1.0": "fetchai/ledger:0.2.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/contract_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one self.set_agent_context(deploy_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) - self.add_item("skill", "fetchai/erc1155_deploy:0.9.0") + self.add_item("skill", "fetchai/erc1155_deploy:0.10.0") diff = self.difference_to_fetched_agent( - "fetchai/erc1155_deployer:0.9.0", deploy_aea_name + "fetchai/erc1155_deployer:0.10.0", deploy_aea_name ) assert ( diff == [] @@ -70,25 +87,43 @@ def test_generic(self): self.generate_private_key(ETHEREUM) self.add_private_key(ETHEREUM, ETHEREUM_PRIVATE_KEY_FILE) self.replace_private_key_in_file( - FUNDED_ETH_PRIVATE_KEY_1, ETHEREUM_PRIVATE_KEY_FILE + FUNDED_ETH_PRIVATE_KEY_3, ETHEREUM_PRIVATE_KEY_FILE + ) + self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) + self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + self.replace_private_key_in_file( + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" + self.set_config(setting_path, "ethereum") # stdout = self.get_wealth(ETHEREUM) # if int(stdout) < 100000000000000000: # pytest.skip("The agent needs more funds for the test to pass.") self.run_install() + # replace location + setting_path = ( + "vendor.fetchai.skills.erc1155_deploy.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # add packages for agent two self.set_agent_context(client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) - self.add_item("skill", "fetchai/erc1155_client:0.8.0") + self.add_item("skill", "fetchai/erc1155_client:0.9.0") diff = self.difference_to_fetched_agent( - "fetchai/erc1155_client:0.9.0", client_aea_name + "fetchai/erc1155_client:0.10.0", client_aea_name ) assert ( diff == [] @@ -99,15 +134,46 @@ def test_generic(self): self.replace_private_key_in_file( FUNDED_ETH_PRIVATE_KEY_2, ETHEREUM_PRIVATE_KEY_FILE ) + self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) + self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) + setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" + self.set_config(setting_path, "ethereum") + setting_path = "vendor.fetchai.connections.p2p_libp2p.config" + self.force_set_config(setting_path, NON_GENESIS_CONFIG) # stdout = self.get_wealth(ETHEREUM) # if int(stdout) < 100000000000000000: # pytest.skip("The agent needs more funds for the test to pass.") self.run_install() + # replace location + setting_path = ( + "vendor.fetchai.skills.erc1155_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run agents self.set_agent_context(deploy_aea_name) deploy_aea_process = self.run_agent() + check_strings = ( + "Downloading golang dependencies. This may take a while...", + "Finished downloading golang dependencies.", + "Starting libp2p node...", + "Connecting to libp2p node...", + "Successfully connected to libp2p node!", + "My libp2p addresses:", + ) + missing_strings = self.missing_from_output( + deploy_aea_process, check_strings, timeout=240, is_terminating=False + ) + assert ( + missing_strings == [] + ), "Strings {} didn't appear in deploy_aea output.".format(missing_strings) + check_strings = ( "starting balance on ethereum ledger=", "received raw transaction=", @@ -117,8 +183,10 @@ def test_generic(self): "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", - "Requesting create batch transaction...", - "Requesting mint batch transaction...", + "requesting create batch transaction...", + "requesting mint batch transaction...", + "registering agent on SOEF.", + "registering service on SOEF.", ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=420, is_terminating=False @@ -131,9 +199,25 @@ def test_generic(self): client_aea_process = self.run_agent() check_strings = ( - "Sending PROPOSE to agent=", + "Downloading golang dependencies. This may take a while...", + "Finished downloading golang dependencies.", + "Starting libp2p node...", + "Connecting to libp2p node...", + "Successfully connected to libp2p node!", + "My libp2p addresses:", + ) + missing_strings = self.missing_from_output( + client_aea_process, check_strings, timeout=240, is_terminating=False + ) + assert ( + missing_strings == [] + ), "Strings {} didn't appear in client_aea output.".format(missing_strings) + + check_strings = ( + "received CFP from sender=", + "sending PROPOSE to agent=", "received ACCEPT_W_INFORM from sender=", - "Requesting single atomic swap transaction...", + "requesting single atomic swap transaction...", "received raw transaction=", "proposing the transaction to the decision maker. Waiting for confirmation ...", "transaction signing was successful.", @@ -141,7 +225,7 @@ def test_generic(self): "transaction was successfully submitted. Transaction digest=", "requesting transaction receipt.", "transaction was successfully settled. Transaction receipt=", - "Demo finished!", + "demo finished!", ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=360, is_terminating=False @@ -170,3 +254,4 @@ def test_generic(self): assert ( self.is_successfully_terminated() ), "Agents weren't successfully terminated." + wait_for_localhost_ports_to_close([9000, 9001]) diff --git a/tests/test_packages/test_skills/test_generic.py b/tests/test_packages/test_skills/test_generic.py index b5d6b33716..f44c07d10f 100644 --- a/tests/test_packages/test_skills/test_generic.py +++ b/tests/test_packages/test_skills/test_generic.py @@ -17,6 +17,9 @@ # # ------------------------------------------------------------------------------ """This test module contains the integration test for the generic buyer and seller skills.""" + +from random import uniform + import pytest from aea.test_tools.test_cases import AEATestCaseMany @@ -24,7 +27,7 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -46,17 +49,23 @@ def test_generic(self, pytestconfig): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/generic_seller:0.8.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/generic_seller:0.9.0") setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.is_ledger_tx" ) @@ -65,25 +74,34 @@ def test_generic(self, pytestconfig): self.force_set_config(setting_path, default_routing) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") + # replace location + setting_path = ( + "vendor.fetchai.skills.generic_seller.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # prepare buyer agent self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/generic_buyer:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/generic_buyer:0.8.0") setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.is_ledger_tx" ) @@ -92,13 +110,15 @@ def test_generic(self, pytestconfig): self.force_set_config(setting_path, default_routing) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) @@ -106,6 +126,12 @@ def test_generic(self, pytestconfig): setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") + # replace location + setting_path = ( + "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() @@ -198,53 +224,68 @@ def test_generic(self, pytestconfig): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # prepare seller agent self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/generic_seller:0.8.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/generic_seller:0.9.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/generic_seller:0.5.0", seller_aea_name + "fetchai/generic_seller:0.6.0", seller_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" self.set_config(setting_path, False, "bool") + # replace location + setting_path = ( + "vendor.fetchai.skills.generic_seller.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # prepare buyer agent self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/generic_buyer:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/generic_buyer:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/generic_buyer:0.5.0", buyer_aea_name + "fetchai/generic_buyer:0.6.0", buyer_aea_name ) assert ( diff == [] @@ -253,16 +294,27 @@ def test_generic(self, pytestconfig): setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" self.set_config(setting_path, False, "bool") - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run AEAs self.set_agent_context(seller_aea_name) seller_aea_process = self.run_agent() diff --git a/tests/test_packages/test_skills/test_gym.py b/tests/test_packages/test_skills/test_gym.py index 661a7261c1..f505cd4548 100644 --- a/tests/test_packages/test_skills/test_gym.py +++ b/tests/test_packages/test_skills/test_gym.py @@ -33,16 +33,16 @@ class TestGymSkill(AEATestCaseEmpty): @skip_test_windows def test_gym(self): """Run the gym skill sequence.""" - self.add_item("skill", "fetchai/gym:0.4.0") - self.add_item("connection", "fetchai/gym:0.4.0") + self.add_item("skill", "fetchai/gym:0.5.0") + self.add_item("connection", "fetchai/gym:0.5.0") self.run_install() # change default connection setting_path = "agent.default_connection" - self.set_config(setting_path, "fetchai/gym:0.4.0") + self.set_config(setting_path, "fetchai/gym:0.5.0") diff = self.difference_to_fetched_agent( - "fetchai/gym_aea:0.6.0", self.agent_name + "fetchai/gym_aea:0.7.0", self.agent_name ) assert ( diff == [] diff --git a/tests/test_packages/test_skills/test_http_echo.py b/tests/test_packages/test_skills/test_http_echo.py index 4a0c8fdf14..0e8ef1d48a 100644 --- a/tests/test_packages/test_skills/test_http_echo.py +++ b/tests/test_packages/test_skills/test_http_echo.py @@ -36,9 +36,9 @@ class TestHttpEchoSkill(AEATestCaseEmpty): @skip_test_windows def test_echo(self): """Run the echo skill sequence.""" - self.add_item("connection", "fetchai/http_server:0.5.0") - self.add_item("skill", "fetchai/http_echo:0.3.0") - self.set_config("agent.default_connection", "fetchai/http_server:0.5.0") + self.add_item("connection", "fetchai/http_server:0.6.0") + self.add_item("skill", "fetchai/http_echo:0.4.0") + self.set_config("agent.default_connection", "fetchai/http_server:0.6.0") self.set_config( "vendor.fetchai.connections.http_server.config.api_spec_path", API_SPEC_PATH ) diff --git a/tests/test_packages/test_skills/test_ml_skills.py b/tests/test_packages/test_skills/test_ml_skills.py index 6c86801cca..f18d3f6149 100644 --- a/tests/test_packages/test_skills/test_ml_skills.py +++ b/tests/test_packages/test_skills/test_ml_skills.py @@ -20,6 +20,7 @@ """This test module contains the integration test for the weather skills.""" import sys +from random import uniform import pytest @@ -28,7 +29,7 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -36,6 +37,15 @@ ) +def _is_not_tensorflow_installed(): + try: + import tensorflow # noqa + + return False + except ImportError: + return True + + @pytest.mark.integration class TestMLSkills(AEATestCaseMany): """Test that ml skills work.""" @@ -44,8 +54,7 @@ class TestMLSkills(AEATestCaseMany): reruns=MAX_FLAKY_RERUNS_INTEGRATION ) # cause possible network issues @pytest.mark.skipif( - sys.version_info >= (3, 8), - reason="cannot run on 3.8 as tensorflow not installable", + _is_not_tensorflow_installed(), reason="This test requires Tensorflow.", ) def test_ml_skills(self, pytestconfig): """Run the ml skills sequence.""" @@ -54,17 +63,23 @@ def test_ml_skills(self, pytestconfig): self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # prepare data provider agent self.set_agent_context(data_provider_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/ml_data_provider:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/ml_data_provider:0.8.0") setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.is_ledger_tx" ) @@ -73,21 +88,30 @@ def test_ml_skills(self, pytestconfig): self.force_set_config(setting_path, default_routing) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + + # replace location + setting_path = ( + "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) + self.force_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/ml_train:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/ml_train:0.8.0") setting_path = ( "vendor.fetchai.skills.ml_train.models.strategy.args.is_ledger_tx" ) @@ -96,16 +120,22 @@ def test_ml_skills(self, pytestconfig): self.force_set_config(setting_path, default_routing) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" + self.force_set_config(setting_path, location) + self.set_agent_context(data_provider_aea_name) data_provider_aea_process = self.run_agent() @@ -205,64 +235,88 @@ def test_ml_skills(self, pytestconfig): self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # prepare data provider agent self.set_agent_context(data_provider_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/ml_data_provider:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/ml_data_provider:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/ml_data_provider:0.8.0", data_provider_aea_name + "fetchai/ml_data_provider:0.9.0", data_provider_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + + # replace location + setting_path = ( + "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) + self.force_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/ml_train:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/ml_train:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/ml_model_trainer:0.8.0", model_trainer_aea_name + "fetchai/ml_model_trainer:0.9.0", model_trainer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" + self.force_set_config(setting_path, location) + self.set_agent_context(data_provider_aea_name) data_provider_aea_process = self.run_agent() diff --git a/tests/test_packages/test_skills/test_tac.py b/tests/test_packages/test_skills/test_tac.py index fba245eda2..dbf5c299b5 100644 --- a/tests/test_packages/test_skills/test_tac.py +++ b/tests/test_packages/test_skills/test_tac.py @@ -53,14 +53,14 @@ def test_tac(self): # prepare tac controller for test self.set_agent_context(tac_controller_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("skill", "fetchai/tac_control:0.3.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("skill", "fetchai/tac_control:0.4.0") self.set_config("agent.default_ledger", ETHEREUM) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_controller:0.5.0", tac_controller_name + "fetchai/tac_controller:0.6.0", tac_controller_name ) assert ( diff == [] @@ -69,14 +69,14 @@ def test_tac(self): # prepare agents for test for agent_name in (tac_aea_one, tac_aea_two): self.set_agent_context(agent_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("skill", "fetchai/tac_participation:0.4.0") - self.add_item("skill", "fetchai/tac_negotiation:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("skill", "fetchai/tac_participation:0.5.0") + self.add_item("skill", "fetchai/tac_negotiation:0.6.0") self.set_config("agent.default_ledger", ETHEREUM) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_participant:0.6.0", agent_name + "fetchai/tac_participant:0.7.0", agent_name ) assert ( diff == [] @@ -95,18 +95,18 @@ def test_tac(self): ) self.set_config(setting_path, start_time) tac_controller_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) # run two agents (participants) self.set_agent_context(tac_aea_one) tac_aea_one_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) self.set_agent_context(tac_aea_two) tac_aea_two_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) check_strings = ( @@ -179,9 +179,9 @@ def test_tac(self): # prepare tac controller for test self.set_agent_context(tac_controller_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("skill", "fetchai/tac_control_contract:0.4.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("skill", "fetchai/tac_control_contract:0.5.0") self.set_config("agent.default_ledger", ETHEREUM) # stdout = self.get_wealth(ETHEREUM) # if int(stdout) < 100000000000000000: @@ -189,7 +189,7 @@ def test_tac(self): self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_controller_contract:0.6.0", tac_controller_name + "fetchai/tac_controller_contract:0.7.0", tac_controller_name ) assert ( diff == [] @@ -207,10 +207,10 @@ def test_tac(self): (FUNDED_ETH_PRIVATE_KEY_2, FUNDED_ETH_PRIVATE_KEY_3), ): self.set_agent_context(agent_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("skill", "fetchai/tac_participation:0.4.0") - self.add_item("skill", "fetchai/tac_negotiation:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("skill", "fetchai/tac_participation:0.5.0") + self.add_item("skill", "fetchai/tac_negotiation:0.6.0") self.set_config("agent.default_ledger", ETHEREUM) self.set_config( "vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract", @@ -224,7 +224,7 @@ def test_tac(self): ) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_participant:0.6.0", agent_name + "fetchai/tac_participant:0.7.0", agent_name ) assert ( diff == [] @@ -244,7 +244,7 @@ def test_tac(self): setting_path = "vendor.fetchai.skills.tac_control_contract.models.parameters.args.start_time" self.set_config(setting_path, start_time) tac_controller_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) check_strings = ( @@ -264,12 +264,12 @@ def test_tac(self): # run two participants as well self.set_agent_context(tac_aea_one) tac_aea_one_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) self.set_agent_context(tac_aea_two) tac_aea_two_process = self.run_agent( - "--connections", "fetchai/p2p_libp2p:0.5.0" + "--connections", "fetchai/p2p_libp2p:0.6.0" ) check_strings = ( diff --git a/tests/test_packages/test_skills/test_thermometer.py b/tests/test_packages/test_skills/test_thermometer.py index 8ae7876803..d1d8923cde 100644 --- a/tests/test_packages/test_skills/test_thermometer.py +++ b/tests/test_packages/test_skills/test_thermometer.py @@ -17,6 +17,9 @@ # # ------------------------------------------------------------------------------ """This test module contains the integration test for the thermometer skills.""" + +from random import uniform + import pytest from aea.test_tools.test_cases import AEATestCaseMany @@ -24,7 +27,7 @@ from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -47,17 +50,23 @@ def test_thermometer(self): self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer:0.8.0") setting_path = ( "vendor.fetchai.skills.thermometer.models.strategy.args.is_ledger_tx" ) @@ -66,21 +75,28 @@ def test_thermometer(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) + # replace location + setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" + self.force_set_config(setting_path, location) + # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer_client:0.6.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer_client:0.7.0") setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.is_ledger_tx" ) @@ -89,16 +105,24 @@ def test_thermometer(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run AEAs self.set_agent_context(thermometer_aea_name) thermometer_aea_process = self.run_agent() @@ -198,64 +222,88 @@ def test_thermometer(self): self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/thermometer_aea:0.6.0", thermometer_aea_name + "fetchai/thermometer_aea:0.7.0", thermometer_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) + # replace location + setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" + self.force_set_config(setting_path, location) + # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/thermometer_client:0.6.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/thermometer_client:0.7.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/thermometer_client:0.6.0", thermometer_client_aea_name + "fetchai/thermometer_client:0.7.0", thermometer_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run AEAs self.set_agent_context(thermometer_aea_name) thermometer_aea_process = self.run_agent() diff --git a/tests/test_packages/test_skills/test_weather.py b/tests/test_packages/test_skills/test_weather.py index b6d1a3b1f9..a0b8d2b06e 100644 --- a/tests/test_packages/test_skills/test_weather.py +++ b/tests/test_packages/test_skills/test_weather.py @@ -17,15 +17,17 @@ # # ------------------------------------------------------------------------------ """This test module contains the integration test for the weather skills.""" -import pytest +from random import uniform + +import pytest from aea.test_tools.test_cases import AEATestCaseMany from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE, - FUNDED_COSMOS_PRIVATE_KEY_1, + COSMOS_PRIVATE_KEY_FILE_CONNECTION, MAX_FLAKY_RERUNS_INTEGRATION, NON_FUNDED_COSMOS_PRIVATE_KEY_1, NON_GENESIS_CONFIG, @@ -47,18 +49,24 @@ def test_weather(self): self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # prepare agent one (weather station) self.set_agent_context(weather_station_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/weather_station:0.7.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/weather_station:0.8.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") dotted_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx" ) @@ -67,22 +75,31 @@ def test_weather(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) + # replace location + setting_path = ( + "vendor.fetchai.skills.weather_station.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # prepare agent two (weather client) self.set_agent_context(weather_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/weather_client:0.6.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/weather_client:0.7.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") dotted_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.is_ledger_tx" ) @@ -91,16 +108,24 @@ def test_weather(self): self.force_set_config(setting_path, default_routing) self.run_install() - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.weather_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + # run agents self.set_agent_context(weather_station_aea_name) weather_station_process = self.run_agent() @@ -192,64 +217,90 @@ def test_weather(self): self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { - "fetchai/ledger_api:0.1.0": "fetchai/ledger:0.2.0", - "fetchai/oef_search:0.3.0": "fetchai/soef:0.5.0", + "fetchai/ledger_api:0.2.0": "fetchai/ledger:0.3.0", + "fetchai/oef_search:0.4.0": "fetchai/soef:0.6.0", + } + + # generate random location + location = { + "latitude": round(uniform(-90, 90), 2), # nosec + "longitude": round(uniform(-180, 180), 2), # nosec } # add packages for agent one self.set_agent_context(weather_station_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/weather_station:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/weather_station:0.8.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/weather_station:0.8.0", weather_station_aea_name + "fetchai/weather_station:0.9.0", weather_station_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add non-funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True + ) self.replace_private_key_in_file( - NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION + ) + + # replace location + setting_path = ( + "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) + self.force_set_config(setting_path, location) # add packages for agent two self.set_agent_context(weather_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/soef:0.5.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.5.0") - self.add_item("connection", "fetchai/ledger:0.2.0") - self.add_item("skill", "fetchai/weather_client:0.6.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/soef:0.6.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.6.0") + self.add_item("connection", "fetchai/ledger:0.3.0") + self.add_item("skill", "fetchai/weather_client:0.7.0") setting_path = "agent.default_routing" self.force_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/weather_client:0.8.0", weather_client_aea_name + "fetchai/weather_client:0.9.0", weather_client_aea_name ) assert ( diff == [] ), "Difference between created and fetched project for files={}".format(diff) - # add funded key + # add keys self.generate_private_key(COSMOS) + self.generate_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION) self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE) - self.add_private_key(COSMOS, COSMOS_PRIVATE_KEY_FILE, connection=True) - self.replace_private_key_in_file( - FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE + self.add_private_key( + COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, connection=True ) + + # fund key + self.generate_wealth(COSMOS) + + # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" self.force_set_config(setting_path, NON_GENESIS_CONFIG) + # replace location + setting_path = ( + "vendor.fetchai.skills.weather_client.models.strategy.args.location" + ) + self.force_set_config(setting_path, location) + self.set_agent_context(weather_station_aea_name) weather_station_process = self.run_agent() diff --git a/tests/test_protocols/test_base.py b/tests/test_protocols/test_base.py index 1f57cbb92d..e983a62cbb 100644 --- a/tests/test_protocols/test_base.py +++ b/tests/test_protocols/test_base.py @@ -32,6 +32,25 @@ from tests.conftest import UNKNOWN_PROTOCOL_PUBLIC_ID +class TestMessageProperties: + """Test that the base serializations work.""" + + @classmethod + def setup_class(cls): + cls.body = {"body_1": "1", "body_2": "2"} + cls.kwarg = 1 + cls.message = Message(cls.body, kwarg=cls.kwarg) + + def test_message_properties(self): + for key, value in self.body.items(): + assert self.message.get(key) == value + assert self.message.get("kwarg") == self.kwarg + assert not self.message.has_sender + assert not self.message.has_counterparty + assert not self.message.has_to + assert not self.message.is_incoming + + class TestBaseSerializations: """Test that the base serializations work.""" diff --git a/tests/test_protocols/test_generator.py b/tests/test_protocols/test_generator.py deleted file mode 100644 index ae8d6a3b33..0000000000 --- a/tests/test_protocols/test_generator.py +++ /dev/null @@ -1,571 +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 protocol generator.""" - -import inspect -import logging -import os -import shutil -import tempfile -import time -from pathlib import Path -from threading import Thread -from typing import Optional -from unittest import TestCase, mock - -import pytest - -from aea.aea_builder import AEABuilder -from aea.configurations.base import ( - ComponentType, - ProtocolId, - ProtocolSpecificationParseError, - PublicId, - SkillConfig, -) -from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE -from aea.crypto.helpers import create_private_key -from aea.mail.base import Envelope -from aea.protocols.base import Message -from aea.protocols.generator.base import ( - ProtocolGenerator, - _union_sub_type_to_protobuf_variable_name, -) -from aea.protocols.generator.common import check_prerequisites -from aea.protocols.generator.extract_specification import ( - _specification_type_to_python_type, -) -from aea.skills.base import Handler, Skill, SkillContext -from aea.test_tools.test_cases import UseOef - -from tests.conftest import CliRunner, ROOT_DIR -from tests.data.generator.t_protocol.message import ( # type: ignore - TProtocolMessage, -) - - -logger = logging.getLogger("aea") -logging.basicConfig(level=logging.INFO) - -CUR_PATH = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore -HOST = "127.0.0.1" -PORT = 10000 - - -class TestEndToEndGenerator(UseOef): - """Test that the generating a protocol works correctly in correct preconditions.""" - - @classmethod - def setup_class(cls): - """Set the test up.""" - cls.runner = CliRunner() - cls.cwd = os.getcwd() - cls.t = tempfile.mkdtemp() - os.chdir(cls.t) - cls.private_key_path_1 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_1") - cls.private_key_path_2 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_2") - create_private_key(DEFAULT_LEDGER, cls.private_key_path_1) - create_private_key(DEFAULT_LEDGER, cls.private_key_path_2) - - def test_compare_latest_generator_output_with_test_protocol(self): - """Test that the "t_protocol" test protocol matches with what the latest generator generates based on the specification.""" - # Skip if prerequisite applications are not installed - try: - check_prerequisites() - except FileNotFoundError: - pytest.skip( - "Some prerequisite applications are not installed. Skipping this test." - ) - - # Specification - # protocol_name = "t_protocol" - path_to_specification = os.path.join( - ROOT_DIR, "tests", "data", "sample_specification.yaml" - ) - path_to_generated_protocol = self.t - # path_to_original_protocol = os.path.join( - # ROOT_DIR, "tests", "data", "generator", protocol_name - # ) - path_to_package = "tests.data.generator." - - # Generate the protocol - protocol_generator = ProtocolGenerator( - path_to_specification, - path_to_generated_protocol, - path_to_protocol_package=path_to_package, - ) - protocol_generator.generate() - - # # compare __init__.py - # init_file_generated = Path(self.t, protocol_name, "__init__.py") - # init_file_original = Path(path_to_original_protocol, "__init__.py",) - # assert filecmp.cmp(init_file_generated, init_file_original) - - # # compare protocol.yaml - # protocol_yaml_file_generated = Path(self.t, protocol_name, "protocol.yaml") - # protocol_yaml_file_original = Path(path_to_original_protocol, "protocol.yaml",) - # assert filecmp.cmp(protocol_yaml_file_generated, protocol_yaml_file_original) - - # # compare message.py - # message_file_generated = Path(self.t, protocol_name, "message.py") - # message_file_original = Path(path_to_original_protocol, "message.py",) - # assert filecmp.cmp(message_file_generated, message_file_original) - - # # compare serialization.py - # serialization_file_generated = Path(self.t, protocol_name, "serialization.py") - # serialization_file_original = Path( - # path_to_original_protocol, "serialization.py", - # ) - # assert filecmp.cmp(serialization_file_generated, serialization_file_original) - - # # compare .proto - # proto_file_generated = Path( - # self.t, protocol_name, "{}.proto".format(protocol_name) - # ) - # proto_file_original = Path( - # path_to_original_protocol, "{}.proto".format(protocol_name), - # ) - # assert filecmp.cmp(proto_file_generated, proto_file_original) - - # # compare _pb2.py - # pb2_file_generated = Path( - # self.t, protocol_name, "{}_pb2.py".format(protocol_name) - # ) - # with open(ROOT_DIR + "/x_pb2.py", "w") as fp: - # fp.write(pb2_file_generated.read_text()) - # pb2_file_original = Path( - # path_to_original_protocol, "{}_pb2.py".format(protocol_name), - # ) - # assert filecmp.cmp(pb2_file_generated, pb2_file_original) - assert True - - def test_generated_protocol_serialisation_ct(self): - """Test that a generated protocol's serialisation + deserialisation work correctly.""" - # create a message with pt content - some_dict = {1: True, 2: False, 3: True, 4: False} - data_model = TProtocolMessage.DataModel( - bytes_field=b"some bytes", - int_field=42, - float_field=42.7, - bool_field=True, - str_field="some string", - set_field={1, 2, 3, 4, 5}, - list_field=["some string 1", "some string 2"], - dict_field=some_dict, - ) - message = TProtocolMessage( - message_id=1, - dialogue_reference=(str(0), ""), - target=0, - performative=TProtocolMessage.Performative.PERFORMATIVE_CT, - content_ct=data_model, - ) - - # serialise the message - encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) - - # deserialise the message - decoded_message = TProtocolMessage.serializer.decode(encoded_message_in_bytes) - - # Compare the original message with the serialised+deserialised message - assert decoded_message.message_id == message.message_id - assert decoded_message.dialogue_reference == message.dialogue_reference - assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] - assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] - assert decoded_message.target == message.target - assert decoded_message.performative == message.performative - assert decoded_message.content_ct == message.content_ct - - def test_generated_protocol_serialisation_pt(self): - """Test that a generated protocol's serialisation + deserialisation work correctly.""" - # create a message with pt content - message = TProtocolMessage( - message_id=1, - dialogue_reference=(str(0), ""), - target=0, - performative=TProtocolMessage.Performative.PERFORMATIVE_PT, - content_bytes=b"some bytes", - content_int=42, - content_float=42.7, - content_bool=True, - content_str="some string", - ) - - # serialise the message - encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) - - # deserialise the message - decoded_message = TProtocolMessage.serializer.decode(encoded_message_in_bytes) - - # Compare the original message with the serialised+deserialised message - assert decoded_message.message_id == message.message_id - assert decoded_message.dialogue_reference == message.dialogue_reference - assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] - assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] - assert decoded_message.target == message.target - assert decoded_message.performative == message.performative - assert decoded_message.content_bytes == message.content_bytes - assert decoded_message.content_int == message.content_int - # floats do not seem to lose some precision when serialised then deserialised using protobuf - # assert decoded_message.content_float == message.content_float - assert decoded_message.content_bool == message.content_bool - assert decoded_message.content_str == message.content_str - - def test_generated_protocol_end_to_end(self): - """Test that a generated protocol could be used in exchanging messages between two agents.""" - agent_name_1 = "my_aea_1" - agent_name_2 = "my_aea_2" - builder_1 = AEABuilder() - builder_1.set_name(agent_name_1) - builder_1.add_private_key(DEFAULT_LEDGER, self.private_key_path_1) - builder_1.set_default_ledger(DEFAULT_LEDGER) - builder_1.set_default_connection(PublicId.from_str("fetchai/oef:0.6.0")) - builder_1.add_protocol( - Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") - ) - builder_1.add_protocol( - Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") - ) - builder_1.add_component( - ComponentType.PROTOCOL, - Path(ROOT_DIR, "tests", "data", "generator", "t_protocol"), - skip_consistency_check=True, - ) - builder_1.add_connection( - Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") - ) - - builder_2 = AEABuilder() - builder_2.set_name(agent_name_2) - builder_2.add_private_key(DEFAULT_LEDGER, self.private_key_path_2) - builder_2.set_default_ledger(DEFAULT_LEDGER) - builder_2.add_protocol( - Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") - ) - builder_2.add_protocol( - Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") - ) - builder_2.set_default_connection(PublicId.from_str("fetchai/oef:0.6.0")) - builder_2.add_component( - ComponentType.PROTOCOL, - Path(ROOT_DIR, "tests", "data", "generator", "t_protocol"), - skip_consistency_check=True, - ) - builder_2.add_connection( - Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") - ) - - # create AEAs - aea_1 = builder_1.build(connection_ids=[PublicId.from_str("fetchai/oef:0.6.0")]) - aea_2 = builder_2.build(connection_ids=[PublicId.from_str("fetchai/oef:0.6.0")]) - - # message 1 - message = TProtocolMessage( - message_id=1, - dialogue_reference=(str(0), ""), - target=0, - performative=TProtocolMessage.Performative.PERFORMATIVE_PT, - content_bytes=b"some bytes", - content_int=42, - content_float=42.7, - content_bool=True, - content_str="some string", - ) - message.counterparty = aea_2.identity.address - envelope = Envelope( - to=aea_2.identity.address, - sender=aea_1.identity.address, - protocol_id=TProtocolMessage.protocol_id, - message=message, - ) - - # message 2 - message_2 = TProtocolMessage( - message_id=2, - dialogue_reference=(str(0), ""), - target=1, - performative=TProtocolMessage.Performative.PERFORMATIVE_PT, - content_bytes=b"some other bytes", - content_int=43, - content_float=43.7, - content_bool=False, - content_str="some other string", - ) - message_2.counterparty = aea_1.identity.address - - # add handlers to AEA resources] - skill_context_1 = SkillContext(aea_1.context) - skill_1 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_1) - skill_context_1._skill = skill_1 - - agent_1_handler = Agent1Handler( - skill_context=skill_context_1, name="fake_handler_1" - ) - aea_1.resources._handler_registry.register( - ( - PublicId.from_str("fetchai/fake_skill:0.1.0"), - TProtocolMessage.protocol_id, - ), - agent_1_handler, - ) - skill_context_2 = SkillContext(aea_2.context) - skill_2 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_2) - skill_context_2._skill = skill_2 - - agent_2_handler = Agent2Handler( - message=message_2, skill_context=skill_context_2, name="fake_handler_2", - ) - aea_2.resources._handler_registry.register( - ( - PublicId.from_str("fetchai/fake_skill:0.1.0"), - TProtocolMessage.protocol_id, - ), - agent_2_handler, - ) - - # Start threads - t_1 = Thread(target=aea_1.start) - t_2 = Thread(target=aea_2.start) - try: - t_1.start() - t_2.start() - time.sleep(1.0) - aea_1.outbox.put(envelope) - time.sleep(5.0) - assert ( - agent_2_handler.handled_message.message_id == message.message_id - ), "Message from Agent 1 to 2: message ids do not match" - assert ( - agent_2_handler.handled_message.dialogue_reference - == message.dialogue_reference - ), "Message from Agent 1 to 2: dialogue references do not match" - assert ( - agent_2_handler.handled_message.dialogue_reference[0] - == message.dialogue_reference[0] - ), "Message from Agent 1 to 2: dialogue reference[0]s do not match" - assert ( - agent_2_handler.handled_message.dialogue_reference[1] - == message.dialogue_reference[1] - ), "Message from Agent 1 to 2: dialogue reference[1]s do not match" - assert ( - agent_2_handler.handled_message.target == message.target - ), "Message from Agent 1 to 2: targets do not match" - assert ( - agent_2_handler.handled_message.performative == message.performative - ), "Message from Agent 1 to 2: performatives do not match" - assert ( - agent_2_handler.handled_message.content_bytes == message.content_bytes - ), "Message from Agent 1 to 2: content_bytes do not match" - assert ( - agent_2_handler.handled_message.content_int == message.content_int - ), "Message from Agent 1 to 2: content_int do not match" - # floats do not seem to lose some precision when serialised then deserialised using protobuf - # assert agent_2_handler.handled_message.content_float == message.content_float, "Message from Agent 1 to 2: content_float do not match" - assert ( - agent_2_handler.handled_message.content_bool == message.content_bool - ), "Message from Agent 1 to 2: content_bool do not match" - assert ( - agent_2_handler.handled_message.content_str == message.content_str - ), "Message from Agent 1 to 2: content_str do not match" - - assert ( - agent_1_handler.handled_message.message_id == message_2.message_id - ), "Message from Agent 1 to 2: dialogue references do not match" - assert ( - agent_1_handler.handled_message.dialogue_reference - == message_2.dialogue_reference - ), "Message from Agent 2 to 1: dialogue references do not match" - assert ( - agent_1_handler.handled_message.dialogue_reference[0] - == message_2.dialogue_reference[0] - ), "Message from Agent 2 to 1: dialogue reference[0]s do not match" - assert ( - agent_1_handler.handled_message.dialogue_reference[1] - == message_2.dialogue_reference[1] - ), "Message from Agent 2 to 1: dialogue reference[1]s do not match" - assert ( - agent_1_handler.handled_message.target == message_2.target - ), "Message from Agent 2 to 1: targets do not match" - assert ( - agent_1_handler.handled_message.performative == message_2.performative - ), "Message from Agent 2 to 1: performatives do not match" - assert ( - agent_1_handler.handled_message.content_bytes == message_2.content_bytes - ), "Message from Agent 2 to 1: content_bytes do not match" - assert ( - agent_1_handler.handled_message.content_int == message_2.content_int - ), "Message from Agent 2 to 1: content_int do not match" - # floats do not seem to lose some precision when serialised then deserialised using protobuf - # assert agent_1_handler.handled_message.content_float == message_2.content_float, "Message from Agent 2 to 1: content_float do not match" - assert ( - agent_1_handler.handled_message.content_bool == message_2.content_bool - ), "Message from Agent 2 to 1: content_bool do not match" - assert ( - agent_1_handler.handled_message.content_str == message_2.content_str - ), "Message from Agent 2 to 1: content_str do not match" - time.sleep(2.0) - finally: - aea_1.stop() - aea_2.stop() - t_1.join() - t_2.join() - - @classmethod - def teardown_class(cls): - """Tear the test down.""" - os.chdir(cls.cwd) - try: - shutil.rmtree(cls.t) - except (OSError, IOError): - pass - - -class SpecificationTypeToPythonTypeTestCase(TestCase): - """Test case for _specification_type_to_python_type method.""" - - def test__specification_type_to_python_type_unsupported_type(self): - """Test _specification_type_to_python_type method unsupported type.""" - with self.assertRaises(ProtocolSpecificationParseError): - _specification_type_to_python_type("unsupported_type") - - -@mock.patch( - "aea.protocols.generator.common._get_sub_types_of_compositional_types", - return_value=[1, 2], -) -class UnionSubTypeToProtobufVariableNameTestCase(TestCase): - """Test case for _union_sub_type_to_protobuf_variable_name method.""" - - def test__union_sub_type_to_protobuf_variable_name_tuple(self, mock): - """Test _union_sub_type_to_protobuf_variable_name method tuple.""" - pytest.skip() - _union_sub_type_to_protobuf_variable_name("content_name", "Tuple[str, ...]") - mock.assert_called_once() - - -class ProtocolGeneratorTestCase(TestCase): - """Test case for ProtocolGenerator class.""" - - def setUp(self): - protocol_specification = mock.Mock() - protocol_specification.name = "name" - - # @mock.patch( - # "aea.protocols.generator.common._get_sub_types_of_compositional_types", - # return_value=["some"], - # ) - # def test__includes_custom_type_positive(self, *mocks): - # """Test _includes_custom_type method positive result.""" - # content_type = "pt:union[pt:str]" - # result = not _is_composition_type_with_custom_type(content_type) - # self.assertTrue(result) - # - # content_type = "pt:optional[pt:str]" - # result = not _is_composition_type_with_custom_type(content_type) - # self.assertTrue(result) - - # @mock.patch("aea.protocols.generator._get_indent_str") - # @mock.patch( - # "aea.protocols.generator._get_sub_types_of_compositional_types", - # return_value=["Tuple", "FrozenSet"], - # ) - # def test__check_content_type_str_tuple(self, *mocks): - # """Test _check_content_type_str method tuple.""" - # no_of_indents = 1 - # content_name = "name" - # content_type = ( - # "Union[str, Dict[str, int], FrozenSet[DataModel, int], Dict[str, float]]" - # ) - # self.protocol_generator._check_content_type_str( - # no_of_indents, content_name, content_type - # ) - # # TODO: finish this test - - -class Agent1Handler(Handler): - """The handler for agent 1.""" - - SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[ProtocolId] - - def __init__(self, **kwargs): - """Initialize the handler.""" - super().__init__(**kwargs) - self.kwargs = kwargs - self.handled_message = None - - def setup(self) -> None: - """Implement the setup for the handler.""" - pass - - def handle(self, message: Message) -> None: - """ - Implement the reaction to a message. - - :param message: the message - :return: None - """ - self.handled_message = message - - def teardown(self) -> None: - """ - Implement the handler teardown. - - :return: None - """ - - -class Agent2Handler(Handler): - """The handler for agent 2.""" - - SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[ProtocolId] - - def __init__(self, message, **kwargs): - """Initialize the handler.""" - print("inside handler's initialisation method for agent 2") - super().__init__(**kwargs) - self.kwargs = kwargs - self.handled_message = None - self.message_2 = message - - def setup(self) -> None: - """Implement the setup for the handler.""" - pass - - def handle(self, message: Message) -> None: - """ - Implement the reaction to a message. - - :param message: the message - :return: None - """ - self.handled_message = message - envelope = Envelope( - to=message.counterparty, - sender=self.context.agent_address, - protocol_id=TProtocolMessage.protocol_id, - message=self.message_2, - ) - self.context.outbox.put(envelope) - - def teardown(self) -> None: - """ - Implement the handler teardown. - - :return: None - """ diff --git a/tests/test_protocols/test_generator/__init__.py b/tests/test_protocols/test_generator/__init__.py new file mode 100644 index 0000000000..82e1017c65 --- /dev/null +++ b/tests/test_protocols/test_generator/__init__.py @@ -0,0 +1,20 @@ +# -*- 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 test generator module contains the tests of the AEA protocol generator.""" diff --git a/tests/test_protocols/test_generator/common.py b/tests/test_protocols/test_generator/common.py new file mode 100644 index 0000000000..012c56c1e1 --- /dev/null +++ b/tests/test_protocols/test_generator/common.py @@ -0,0 +1,34 @@ +# -*- 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 utility code for the test_generator modules.""" +import os + +from tests.conftest import ROOT_DIR + +T_PROTOCOL_NAME = "t_protocol" +PATH_TO_T_PROTOCOL_SPECIFICATION = os.path.join( + ROOT_DIR, "tests", "data", "sample_specification.yaml" +) +PATH_TO_T_PROTOCOL = os.path.join( + ROOT_DIR, "tests", "data", "generator", T_PROTOCOL_NAME +) + + +def black_is_not_installed(*args, **kwargs): + return not args[0] == "black" diff --git a/tests/test_protocols/test_generator/test_common.py b/tests/test_protocols/test_generator/test_common.py new file mode 100644 index 0000000000..73b28368fc --- /dev/null +++ b/tests/test_protocols/test_generator/test_common.py @@ -0,0 +1,438 @@ +# -*- 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 generator/common.py module.""" +import logging +import os +import shutil +import tempfile +from pathlib import Path +from subprocess import CalledProcessError # nosec +from unittest import TestCase, mock + +from aea.protocols.generator.common import ( + _camel_case_to_snake_case, + _create_protocol_file, + _get_sub_types_of_compositional_types, + _has_matched_brackets, + _includes_custom_type, + _match_brackets, + _python_pt_or_ct_type_to_proto_type, + _to_camel_case, + _union_sub_type_to_protobuf_variable_name, + check_prerequisites, + check_protobuf_using_protoc, + is_installed, + load_protocol_specification, + try_run_black_formatting, + try_run_protoc, +) + +from tests.test_protocols.test_generator.common import ( + PATH_TO_T_PROTOCOL_SPECIFICATION, + T_PROTOCOL_NAME, +) + +logger = logging.getLogger("aea") +logging.basicConfig(level=logging.INFO) + + +def black_is_not_installed_side_effect(*args, **kwargs): + return not args[0] == "black" + + +def protoc_is_not_installed_side_effect(*args, **kwargs): + return not args[0] == "protoc" + + +class TestCommon(TestCase): + """Test for generator/common.py.""" + + @classmethod + def setup_class(cls): + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + def test_to_camel_case(self): + """Test the '_to_camel_case' method.""" + input_text_1 = "this_is_a_snake_case_text" + expected_1 = "ThisIsASnakeCaseText" + output_1 = _to_camel_case(input_text_1) + assert output_1 == expected_1 + + input_text_2 = "This_is_a_Snake_Case_text" + expected_2 = "ThisIsASnakeCaseText" + output_2 = _to_camel_case(input_text_2) + assert output_2 == expected_2 + + def test_camel_case_to_snake_case(self): + """Test the '_camel_case_to_snake_case' method.""" + input_text_1 = "ThisIsASnakeCaseText" + expected_1 = "this_is_a_snake_case_text" + output_1 = _camel_case_to_snake_case(input_text_1) + assert output_1 == expected_1 + + def test_match_brackets(self,): + """Positive test the '_match_brackets' method.""" + text_1 = "[so[met[hi]]ng]" + assert _match_brackets(text_1, 0) == 14 + assert _match_brackets(text_1, 3) == 11 + assert _match_brackets(text_1, 7) == 10 + + text_2 = "[]]som[]et[hi[ng][sf]" + index_2 = 4 + with self.assertRaises(SyntaxError) as cm: + _match_brackets(text_2, index_2) + self.assertEqual( + str(cm.exception), + "Index {} in 'text' is not an open bracket '['. It is {}".format( + index_2, text_2[index_2], + ), + ) + + index_3 = 2 + with self.assertRaises(SyntaxError) as cm: + _match_brackets(text_2, index_3) + self.assertEqual( + str(cm.exception), + "Index {} in 'text' is not an open bracket '['. It is {}".format( + index_3, text_2[index_3], + ), + ) + + index_4 = 10 + with self.assertRaises(SyntaxError) as cm: + _match_brackets(text_2, index_4) + self.assertEqual( + str(cm.exception), + "No matching closing bracket ']' for the opening bracket '[' at {} " + + str(index_4), + ) + + def test_has_matched_brackets(self,): + """Positive test the '_has_matched_brackets' method.""" + valid_text_1 = "[so[met[hi]]ng]" + assert _has_matched_brackets(valid_text_1) is True + + valid_text_2 = "[[][[]]]" + assert _has_matched_brackets(valid_text_2) is True + + valid_text_3 = "[[[[[[[]]]]]]]" + assert _has_matched_brackets(valid_text_3) is True + + invalid_text_1 = "[]]som[]et[hi[ng][sf]" + assert _has_matched_brackets(invalid_text_1) is False + + invalid_text_2 = "[]][][[][]" + assert _has_matched_brackets(invalid_text_2) is False + + invalid_text_3 = "[]]" + assert _has_matched_brackets(invalid_text_3) is False + + invalid_text_4 = "[[]" + assert _has_matched_brackets(invalid_text_4) is False + + def test_get_sub_types_of_compositional_types_positive(self,): + """Positive test the '_get_sub_types_of_compositional_types' method.""" + composition_type_1 = "pt:set[pt:int, integer, bool]" + expected_1 = ("pt:int", "integer", "bool") + assert _get_sub_types_of_compositional_types(composition_type_1) == expected_1 + + composition_type_2 = "FrozenSet[something, anotherthing]" + expected_2 = ("something", "anotherthing") + assert _get_sub_types_of_compositional_types(composition_type_2) == expected_2 + + composition_type_3 = "pt:list[pt:str]" + expected_3 = ("pt:str",) + assert _get_sub_types_of_compositional_types(composition_type_3) == expected_3 + + composition_type_4 = "Tuple[bytes, ...]" + expected_4 = ("bytes",) + assert _get_sub_types_of_compositional_types(composition_type_4) == expected_4 + + composition_type_5 = "pt:dict[pt:int, pt:int]" + expected_5 = ("pt:int", "pt:int") + assert _get_sub_types_of_compositional_types(composition_type_5) == expected_5 + + composition_type_6 = "Dict[bool, float]" + expected_6 = ("bool", "float") + assert _get_sub_types_of_compositional_types(composition_type_6) == expected_6 + + composition_type_7 = "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]" + expected_7 = ( + "ct:DataModel", + "pt:bytes", + "pt:int", + "pt:bool", + "pt:float", + "pt:str", + "pt:set[pt:int]", + "pt:list[pt:bool]", + "pt:dict[pt:str,pt:str]", + ) + assert _get_sub_types_of_compositional_types(composition_type_7) == expected_7 + + composition_type_8 = "Union[int, Tuple[bool, ...]]" + expected_8 = ("int", "Tuple[bool, ...]") + assert _get_sub_types_of_compositional_types(composition_type_8) == expected_8 + + composition_type_9 = ( + "Union[DataModel, FrozenSet[int], Tuple[bool, ...], bytes, Dict[bool,float], int, " + "FrozenSet[bool], Dict[int, str], Tuple[str, ...], bool, float, str, Dict[str, str]]" + ) + expected_9 = ( + "DataModel", + "FrozenSet[int]", + "Tuple[bool, ...]", + "bytes", + "Dict[bool,float]", + "int", + "FrozenSet[bool]", + "Dict[int, str]", + "Tuple[str, ...]", + "bool", + "float", + "str", + "Dict[str, str]", + ) + assert _get_sub_types_of_compositional_types(composition_type_9) == expected_9 + + composition_type_10 = "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" + expected_10 = ( + "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]", + ) + assert _get_sub_types_of_compositional_types(composition_type_10) == expected_10 + + composition_type_11 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str,str]]]" + expected_11 = ( + "Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str,str]]", + ) + assert _get_sub_types_of_compositional_types(composition_type_11) == expected_11 + + def test_get_sub_types_of_compositional_types_negative(self,): + """Negative test the '_get_sub_types_of_compositional_types' method""" + composition_type_1 = "pt:int" + with self.assertRaises(SyntaxError) as cm: + _get_sub_types_of_compositional_types(composition_type_1) + self.assertEqual( + str(cm.exception), + "{} is not a valid compositional type.".format(composition_type_1), + ) + + composition_type_2 = "pt:int[pt:DataModel]" + with self.assertRaises(SyntaxError) as cm: + _get_sub_types_of_compositional_types(composition_type_2) + self.assertEqual( + str(cm.exception), + "{} is not a valid compositional type.".format(composition_type_2), + ) + + composition_type_3 = "pt:dict[pt:set[int, pt:list[pt:bool]]" + with self.assertRaises(SyntaxError) as cm: + _get_sub_types_of_compositional_types(composition_type_3) + self.assertEqual( + str(cm.exception), + "Bad formatting. No matching close bracket ']' for the open bracket at pt:set[", + ) + + def test_union_sub_type_to_protobuf_variable_name(self,): + """Test the '_union_sub_type_to_protobuf_variable_name' method""" + content_name = "proposal" + + content_type_1 = "FrozenSet[int]" + assert ( + _union_sub_type_to_protobuf_variable_name(content_name, content_type_1) + == "proposal_type_set_of_int" + ) + + content_type_2 = "Tuple[str, ...]" + assert ( + _union_sub_type_to_protobuf_variable_name(content_name, content_type_2) + == "proposal_type_list_of_str" + ) + + content_type_3 = "Dict[bool, float]" + assert ( + _union_sub_type_to_protobuf_variable_name(content_name, content_type_3) + == "proposal_type_dict_of_bool_float" + ) + + content_type_4 = "int" + assert ( + _union_sub_type_to_protobuf_variable_name(content_name, content_type_4) + == "proposal_type_int" + ) + + content_type_5 = "DataModel" + assert ( + _union_sub_type_to_protobuf_variable_name(content_name, content_type_5) + == "proposal_type_DataModel" + ) + + def test_python_pt_or_ct_type_to_proto_type(self,): + """Test the '_python_pt_or_ct_type_to_proto_type' method""" + content_type_bytes = "bytes" + assert _python_pt_or_ct_type_to_proto_type(content_type_bytes) == "bytes" + + content_type_int = "int" + assert _python_pt_or_ct_type_to_proto_type(content_type_int) == "int32" + + content_type_float = "float" + assert _python_pt_or_ct_type_to_proto_type(content_type_float) == "float" + + content_type_bool = "bool" + assert _python_pt_or_ct_type_to_proto_type(content_type_bool) == "bool" + + content_type_str = "str" + assert _python_pt_or_ct_type_to_proto_type(content_type_str) == "string" + + content_type_ct = "Query" + assert _python_pt_or_ct_type_to_proto_type(content_type_ct) == "Query" + + def test_includes_custom_type(self,): + """Test the '_includes_custom_type' method""" + content_type_includes_1 = "Optional[DataModel]" + assert _includes_custom_type(content_type_includes_1) is True + + content_type_includes_2 = "Union[int, DataModel]" + assert _includes_custom_type(content_type_includes_2) is True + + content_type_includes_3 = "Optional[Union[int, float, DataModel, Query, float]]" + assert _includes_custom_type(content_type_includes_3) is True + + content_type_not_includes_1 = "Optional[int]" + assert _includes_custom_type(content_type_not_includes_1) is False + + content_type_not_includes_2 = "Union[int, float, str]" + assert _includes_custom_type(content_type_not_includes_2) is False + + content_type_not_includes_3 = ( + "Optional[Union[int, float, FrozenSet[int], Tuple[bool, ...], float]]" + ) + assert _includes_custom_type(content_type_not_includes_3) is False + + @mock.patch("shutil.which", return_value="some string") + def test_is_installed_positive(self, mocked_shutil_which): + """Positive test for the 'is_installed' method""" + assert is_installed("some_programme") is True + + @mock.patch("shutil.which", return_value=None) + def test_is_installed_negative(self, mocked_shutil_which): + """Negative test for the 'is_installed' method: programme is not installed""" + assert is_installed("some_programme") is False + + @mock.patch("aea.protocols.generator.common.is_installed", return_value=True) + def test_check_prerequisites_positive(self, mocked_is_installed): + """Positive test for the 'check_prerequisites' method""" + try: + check_prerequisites() + except FileNotFoundError: + self.assertTrue(False) + + @mock.patch( + "aea.protocols.generator.common.is_installed", + side_effect=black_is_not_installed_side_effect, + ) + def test_check_prerequisites_negative_black_is_not_installed( + self, mocked_is_installed + ): + """Negative test for the 'check_prerequisites' method: black isn't installed""" + with self.assertRaises(FileNotFoundError): + check_prerequisites() + + @mock.patch( + "aea.protocols.generator.common.is_installed", + side_effect=protoc_is_not_installed_side_effect, + ) + def test_check_prerequisites_negative_protoc_is_not_installed( + self, mocked_is_installed + ): + """Negative test for the 'check_prerequisites' method: protoc isn't installed""" + with self.assertRaises(FileNotFoundError): + check_prerequisites() + + def test_load_protocol_specification(self,): + """Test the 'load_protocol_specification' method""" + spec = load_protocol_specification(PATH_TO_T_PROTOCOL_SPECIFICATION) + assert spec.name == T_PROTOCOL_NAME + assert spec.version == "0.1.0" + assert spec.author == "fetchai" + assert spec.license == "Apache-2.0" + assert spec.aea_version == ">=0.5.0, <0.6.0" + assert spec.description == "A protocol for testing purposes." + assert spec.speech_acts is not None + assert spec.protobuf_snippets is not None and spec.protobuf_snippets != "" + + def test_create_protocol_file(self,): + """Test the '_create_protocol_file' method""" + file_name = "temp_file" + file_content = "this is a temporary file" + + _create_protocol_file(self.t, file_name, file_content) + path_to_the_file = os.path.join(self.t, file_name) + + assert Path(path_to_the_file).exists() + assert Path(path_to_the_file).read_text() == file_content + + @mock.patch("subprocess.run") + def test_try_run_black_formatting(self, mocked_subprocess): + """Test the 'try_run_black_formatting' method""" + try_run_black_formatting("some_path") + mocked_subprocess.assert_called_once() + + @mock.patch("subprocess.run") + def test_try_run_protoc(self, mocked_subprocess): + """Test the 'try_run_protoc' method""" + try_run_protoc("some_path", "some_name") + mocked_subprocess.assert_called_once() + + @mock.patch("aea.protocols.generator.common.try_run_protoc") + def test_check_protobuf_using_protoc_positive(self, mocked_try_run_protoc): + """Positive test for the 'check_protobuf_using_protoc' method""" + protocol_name = "protocol_name" + file_name = protocol_name + "_pb2.py" + + new_file = open(os.path.join(self.t, file_name), "w+") + new_file.close() + result, msg = check_protobuf_using_protoc(self.t, protocol_name) + + assert not Path(self.t, file_name).exists() + assert result is True + assert msg == "protobuf file is valid" + + @mock.patch( + "subprocess.run", + side_effect=CalledProcessError( + 1, "some_command", stderr="name.proto:12:45: some_protoc_error\n" + ), + ) + def test_check_protobuf_using_protoc_nagative(self, mocked_subprocess): + """Negative test for the 'check_protobuf_using_protoc' method: protoc has some errors""" + result, msg = check_protobuf_using_protoc("some_path", "name") + assert result is False + assert msg == "some_protoc_error" + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass diff --git a/tests/test_protocols/test_generator/test_end_to_end.py b/tests/test_protocols/test_generator/test_end_to_end.py new file mode 100644 index 0000000000..3f78f5f33f --- /dev/null +++ b/tests/test_protocols/test_generator/test_end_to_end.py @@ -0,0 +1,372 @@ +# -*- 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 end to end tests for the protocol generator.""" +import logging +import os +import shutil +import tempfile +import time +from pathlib import Path +from threading import Thread +from typing import Optional + +from aea.aea_builder import AEABuilder +from aea.configurations.base import ( + ComponentType, + ProtocolId, + PublicId, + SkillConfig, +) +from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE +from aea.crypto.helpers import create_private_key +from aea.helpers.dialogue.base import DialogueLabel +from aea.protocols.base import Message +from aea.skills.base import Handler, Skill, SkillContext +from aea.test_tools.test_cases import UseOef + +from tests.conftest import ROOT_DIR +from tests.data.generator.t_protocol.dialogues import TProtocolDialogue +from tests.data.generator.t_protocol.message import ( # type: ignore + TProtocolMessage, +) +from tests.test_protocols.test_generator.common import PATH_TO_T_PROTOCOL + +logger = logging.getLogger("aea") +logging.basicConfig(level=logging.INFO) + + +class TestEndToEndGenerator(UseOef): + """ + Test that the generating a protocol works correctly in correct preconditions. + + Note: Types involving Floats seem to lose some precision when serialised then deserialised using protobuf. + So tests for these types are commented out throughout for now. + """ + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + cls.private_key_path_1 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_1") + cls.private_key_path_2 = os.path.join(cls.t, DEFAULT_PRIVATE_KEY_FILE + "_2") + create_private_key(DEFAULT_LEDGER, cls.private_key_path_1) + create_private_key(DEFAULT_LEDGER, cls.private_key_path_2) + + def test_generated_protocol_end_to_end(self): + """Test that a generated protocol could be used in exchanging messages between two agents.""" + agent_name_1 = "my_aea_1" + agent_name_2 = "my_aea_2" + builder_1 = AEABuilder() + builder_1.set_name(agent_name_1) + builder_1.add_private_key(DEFAULT_LEDGER, self.private_key_path_1) + builder_1.set_default_ledger(DEFAULT_LEDGER) + builder_1.set_default_connection(PublicId.from_str("fetchai/oef:0.7.0")) + builder_1.add_protocol( + Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") + ) + builder_1.add_protocol( + Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") + ) + builder_1.add_component( + ComponentType.PROTOCOL, + Path(PATH_TO_T_PROTOCOL), + skip_consistency_check=True, + ) + builder_1.add_connection( + Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") + ) + + builder_2 = AEABuilder() + builder_2.set_name(agent_name_2) + builder_2.add_private_key(DEFAULT_LEDGER, self.private_key_path_2) + builder_2.set_default_ledger(DEFAULT_LEDGER) + builder_2.add_protocol( + Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") + ) + builder_2.add_protocol( + Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") + ) + builder_2.set_default_connection(PublicId.from_str("fetchai/oef:0.7.0")) + builder_2.add_component( + ComponentType.PROTOCOL, + Path(PATH_TO_T_PROTOCOL), + skip_consistency_check=True, + ) + builder_2.add_connection( + Path(ROOT_DIR, "packages", "fetchai", "connections", "oef") + ) + + # create AEAs + aea_1 = builder_1.build(connection_ids=[PublicId.from_str("fetchai/oef:0.7.0")]) + aea_2 = builder_2.build(connection_ids=[PublicId.from_str("fetchai/oef:0.7.0")]) + + # dialogues + dialogue_label_1 = DialogueLabel( + (str(1), ""), aea_2.identity.address, aea_1.identity.address + ) + aea_1_dialogue = TProtocolDialogue( + dialogue_label_1, aea_1.identity.address, TProtocolDialogue.Role.ROLE_1 + ) + dialogue_label_2 = DialogueLabel( + (str(1), str(1)), aea_1.identity.address, aea_1.identity.address + ) + aea_2_dialogue = TProtocolDialogue( + dialogue_label_2, aea_2.identity.address, TProtocolDialogue.Role.ROLE_2 + ) + + # message 1 + message_1 = TProtocolMessage( + message_id=1, + dialogue_reference=(str(1), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_PT, + content_bytes=b"some bytes", + content_int=42, + content_float=42.7, + content_bool=True, + content_str="some string", + ) + message_1.counterparty = aea_2.identity.address + message_1.is_incoming = False + + # message 2 + message_2 = TProtocolMessage( + message_id=2, + dialogue_reference=(str(1), str(1)), + target=1, + performative=TProtocolMessage.Performative.PERFORMATIVE_PT, + content_bytes=b"some other bytes", + content_int=43, + content_float=43.7, + content_bool=False, + content_str="some other string", + ) + message_2.counterparty = aea_1.identity.address + message_2.is_incoming = False + + # add handlers to AEA resources + skill_context_1 = SkillContext(aea_1.context) + skill_1 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_1) + skill_context_1._skill = skill_1 + + agent_1_handler = Agent1Handler( + skill_context=skill_context_1, + name="fake_handler_1", + dialogue=aea_1_dialogue, + ) + aea_1.resources._handler_registry.register( + ( + PublicId.from_str("fetchai/fake_skill:0.1.0"), + TProtocolMessage.protocol_id, + ), + agent_1_handler, + ) + skill_context_2 = SkillContext(aea_2.context) + skill_2 = Skill(SkillConfig("fake_skill", "fetchai", "0.1.0"), skill_context_2) + skill_context_2._skill = skill_2 + + agent_2_handler = Agent2Handler( + message=message_2, + dialogue=aea_2_dialogue, + skill_context=skill_context_2, + name="fake_handler_2", + ) + aea_2.resources._handler_registry.register( + ( + PublicId.from_str("fetchai/fake_skill:0.1.0"), + TProtocolMessage.protocol_id, + ), + agent_2_handler, + ) + + # Start threads + t_1 = Thread(target=aea_1.start) + t_2 = Thread(target=aea_2.start) + try: + t_1.start() + t_2.start() + time.sleep(1.0) + aea_1_dialogue.update(message_1) + aea_1.outbox.put_message(message_1) + time.sleep(5.0) + assert ( + agent_2_handler.handled_message.message_id == message_1.message_id + ), "Message from Agent 1 to 2: message ids do not match" + assert ( + agent_2_handler.handled_message.dialogue_reference + == message_1.dialogue_reference + ), "Message from Agent 1 to 2: dialogue references do not match" + assert ( + agent_2_handler.handled_message.dialogue_reference[0] + == message_1.dialogue_reference[0] + ), "Message from Agent 1 to 2: dialogue reference[0]s do not match" + assert ( + agent_2_handler.handled_message.dialogue_reference[1] + == message_1.dialogue_reference[1] + ), "Message from Agent 1 to 2: dialogue reference[1]s do not match" + assert ( + agent_2_handler.handled_message.target == message_1.target + ), "Message from Agent 1 to 2: targets do not match" + assert ( + agent_2_handler.handled_message.performative == message_1.performative + ), "Message from Agent 1 to 2: performatives do not match" + assert ( + agent_2_handler.handled_message.content_bytes == message_1.content_bytes + ), "Message from Agent 1 to 2: content_bytes do not match" + assert ( + agent_2_handler.handled_message.content_int == message_1.content_int + ), "Message from Agent 1 to 2: content_int do not match" + # assert ( + # agent_2_handler.handled_message.content_float == message_1.content_float + # ), "Message from Agent 1 to 2: content_float do not match" + assert ( + agent_2_handler.handled_message.content_bool == message_1.content_bool + ), "Message from Agent 1 to 2: content_bool do not match" + assert ( + agent_2_handler.handled_message.content_str == message_1.content_str + ), "Message from Agent 1 to 2: content_str do not match" + + assert ( + agent_1_handler.handled_message.message_id == message_2.message_id + ), "Message from Agent 1 to 2: dialogue references do not match" + assert ( + agent_1_handler.handled_message.dialogue_reference + == message_2.dialogue_reference + ), "Message from Agent 2 to 1: dialogue references do not match" + assert ( + agent_1_handler.handled_message.dialogue_reference[0] + == message_2.dialogue_reference[0] + ), "Message from Agent 2 to 1: dialogue reference[0]s do not match" + assert ( + agent_1_handler.handled_message.dialogue_reference[1] + == message_2.dialogue_reference[1] + ), "Message from Agent 2 to 1: dialogue reference[1]s do not match" + assert ( + agent_1_handler.handled_message.target == message_2.target + ), "Message from Agent 2 to 1: targets do not match" + assert ( + agent_1_handler.handled_message.performative == message_2.performative + ), "Message from Agent 2 to 1: performatives do not match" + assert ( + agent_1_handler.handled_message.content_bytes == message_2.content_bytes + ), "Message from Agent 2 to 1: content_bytes do not match" + assert ( + agent_1_handler.handled_message.content_int == message_2.content_int + ), "Message from Agent 2 to 1: content_int do not match" + # assert ( + # agent_1_handler.handled_message.content_float == message_2.content_float + # ), "Message from Agent 2 to 1: content_float do not match" + assert ( + agent_1_handler.handled_message.content_bool == message_2.content_bool + ), "Message from Agent 2 to 1: content_bool do not match" + assert ( + agent_1_handler.handled_message.content_str == message_2.content_str + ), "Message from Agent 2 to 1: content_str do not match" + time.sleep(2.0) + finally: + aea_1.stop() + aea_2.stop() + t_1.join() + t_2.join() + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +class Agent1Handler(Handler): + """The handler for agent 1.""" + + SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[ProtocolId] + + def __init__(self, dialogue: TProtocolDialogue, **kwargs): + """Initialize the handler.""" + super().__init__(**kwargs) + self.kwargs = kwargs + self.handled_message = None # type: Optional[Message] + self.dialogue = dialogue + + def setup(self) -> None: + """Implement the setup for the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + self.dialogue.update(message) + self.handled_message = message + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + + +class Agent2Handler(Handler): + """The handler for agent 2.""" + + SUPPORTED_PROTOCOL = TProtocolMessage.protocol_id # type: Optional[ProtocolId] + + def __init__( + self, message: TProtocolMessage, dialogue: TProtocolDialogue, **kwargs + ): + """Initialize the handler.""" + print("inside handler's initialisation method for agent 2") + super().__init__(**kwargs) + self.kwargs = kwargs + self.handled_message = None # type: Optional[Message] + self.message_2 = message + self.dialogue = dialogue + + def setup(self) -> None: + """Implement the setup for the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + self.dialogue.update(message) + self.handled_message = message + message.counterparty = self.dialogue.dialogue_label.dialogue_opponent_addr + self.dialogue.update(self.message_2) + self.context.outbox.put_message(self.message_2) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ diff --git a/tests/test_protocols/test_generator/test_extract_specification.py b/tests/test_protocols/test_generator/test_extract_specification.py new file mode 100644 index 0000000000..6687057b40 --- /dev/null +++ b/tests/test_protocols/test_generator/test_extract_specification.py @@ -0,0 +1,530 @@ +# -*- 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 generator/extract_specification.py module.""" +import logging +import os +import shutil +import tempfile +from unittest import TestCase, mock + +from aea.configurations.base import ProtocolSpecificationParseError +from aea.protocols.generator.common import load_protocol_specification +from aea.protocols.generator.extract_specification import ( + PythonicProtocolSpecification, + _ct_specification_type_to_python_type, + _mt_specification_type_to_python_type, + _optional_specification_type_to_python_type, + _pct_specification_type_to_python_type, + _pmt_specification_type_to_python_type, + _pt_specification_type_to_python_type, + _specification_type_to_python_type, + extract, +) + +from tests.test_protocols.test_generator.common import PATH_TO_T_PROTOCOL_SPECIFICATION + +logger = logging.getLogger("aea") +logging.basicConfig(level=logging.INFO) + + +class TestExtractSpecification(TestCase): + """Test for generator/extract_specification.py.""" + + @classmethod + def setup_class(cls): + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + def test_ct_specification_type_to_python_type(self): + """Test the '_ct_specification_type_to_python_type' method.""" + specification_type_1 = "ct:DataModel" + expected_1 = "DataModel" + assert _ct_specification_type_to_python_type(specification_type_1) == expected_1 + + specification_type_2 = "ct:Query" + expected_2 = "Query" + assert _ct_specification_type_to_python_type(specification_type_2) == expected_2 + + def test_pt_specification_type_to_python_type(self): + """Test the '_pt_specification_type_to_python_type' method.""" + specification_type_1 = "pt:bytes" + expected_1 = "bytes" + assert _pt_specification_type_to_python_type(specification_type_1) == expected_1 + + specification_type_2 = "pt:int" + expected_2 = "int" + assert _pt_specification_type_to_python_type(specification_type_2) == expected_2 + + specification_type_3 = "pt:float" + expected_3 = "float" + assert _pt_specification_type_to_python_type(specification_type_3) == expected_3 + + specification_type_4 = "pt:bool" + expected_4 = "bool" + assert _pt_specification_type_to_python_type(specification_type_4) == expected_4 + + specification_type_5 = "pt:str" + expected_5 = "str" + assert _pt_specification_type_to_python_type(specification_type_5) == expected_5 + + def test_pct_specification_type_to_python_type(self): + """Test the '_pct_specification_type_to_python_type' method.""" + specification_type_1 = "pt:set[pt:bytes]" + expected_1 = "FrozenSet[bytes]" + assert ( + _pct_specification_type_to_python_type(specification_type_1) == expected_1 + ) + + specification_type_2 = "pt:set[pt:int]" + expected_2 = "FrozenSet[int]" + assert ( + _pct_specification_type_to_python_type(specification_type_2) == expected_2 + ) + + specification_type_3 = "pt:set[pt:float]" + expected_3 = "FrozenSet[float]" + assert ( + _pct_specification_type_to_python_type(specification_type_3) == expected_3 + ) + + specification_type_4 = "pt:set[pt:bool]" + expected_4 = "FrozenSet[bool]" + assert ( + _pct_specification_type_to_python_type(specification_type_4) == expected_4 + ) + + specification_type_5 = "pt:set[pt:str]" + expected_5 = "FrozenSet[str]" + assert ( + _pct_specification_type_to_python_type(specification_type_5) == expected_5 + ) + + specification_type_6 = "pt:list[pt:bytes]" + expected_6 = "Tuple[bytes, ...]" + assert ( + _pct_specification_type_to_python_type(specification_type_6) == expected_6 + ) + + specification_type_7 = "pt:list[pt:int]" + expected_7 = "Tuple[int, ...]" + assert ( + _pct_specification_type_to_python_type(specification_type_7) == expected_7 + ) + + specification_type_8 = "pt:list[pt:float]" + expected_8 = "Tuple[float, ...]" + assert ( + _pct_specification_type_to_python_type(specification_type_8) == expected_8 + ) + + specification_type_9 = "pt:list[pt:bool]" + expected_9 = "Tuple[bool, ...]" + assert ( + _pct_specification_type_to_python_type(specification_type_9) == expected_9 + ) + + specification_type_10 = "pt:list[pt:str]" + expected_10 = "Tuple[str, ...]" + assert ( + _pct_specification_type_to_python_type(specification_type_10) == expected_10 + ) + + def test_pmt_specification_type_to_python_type(self): + """Test the '_pmt_specification_type_to_python_type' method.""" + specification_type_1 = "pt:dict[pt:int, pt:bytes]" + expected_1 = "Dict[int, bytes]" + assert ( + _pmt_specification_type_to_python_type(specification_type_1) == expected_1 + ) + + specification_type_2 = "pt:dict[pt:int, pt:int]" + expected_2 = "Dict[int, int]" + assert ( + _pmt_specification_type_to_python_type(specification_type_2) == expected_2 + ) + + specification_type_3 = "pt:dict[pt:int, pt:float]" + expected_3 = "Dict[int, float]" + assert ( + _pmt_specification_type_to_python_type(specification_type_3) == expected_3 + ) + + specification_type_4 = "pt:dict[pt:int, pt:bool]" + expected_4 = "Dict[int, bool]" + assert ( + _pmt_specification_type_to_python_type(specification_type_4) == expected_4 + ) + + specification_type_5 = "pt:dict[pt:int, pt:str]" + expected_5 = "Dict[int, str]" + assert ( + _pmt_specification_type_to_python_type(specification_type_5) == expected_5 + ) + + specification_type_6 = "pt:dict[pt:bool, pt:bytes]" + expected_6 = "Dict[bool, bytes]" + assert ( + _pmt_specification_type_to_python_type(specification_type_6) == expected_6 + ) + + specification_type_7 = "pt:dict[pt:bool, pt:int]" + expected_7 = "Dict[bool, int]" + assert ( + _pmt_specification_type_to_python_type(specification_type_7) == expected_7 + ) + + specification_type_8 = "pt:dict[pt:bool, pt:float]" + expected_8 = "Dict[bool, float]" + assert ( + _pmt_specification_type_to_python_type(specification_type_8) == expected_8 + ) + + specification_type_9 = "pt:dict[pt:bool, pt:bool]" + expected_9 = "Dict[bool, bool]" + assert ( + _pmt_specification_type_to_python_type(specification_type_9) == expected_9 + ) + + specification_type_10 = "pt:dict[pt:bool, pt:str]" + expected_10 = "Dict[bool, str]" + assert ( + _pmt_specification_type_to_python_type(specification_type_10) == expected_10 + ) + + specification_type_11 = "pt:dict[pt:str, pt:bytes]" + expected_11 = "Dict[str, bytes]" + assert ( + _pmt_specification_type_to_python_type(specification_type_11) == expected_11 + ) + + specification_type_12 = "pt:dict[pt:str, pt:int]" + expected_12 = "Dict[str, int]" + assert ( + _pmt_specification_type_to_python_type(specification_type_12) == expected_12 + ) + + specification_type_13 = "pt:dict[pt:str, pt:float]" + expected_13 = "Dict[str, float]" + assert ( + _pmt_specification_type_to_python_type(specification_type_13) == expected_13 + ) + + specification_type_14 = "pt:dict[pt:str, pt:bool]" + expected_14 = "Dict[str, bool]" + assert ( + _pmt_specification_type_to_python_type(specification_type_14) == expected_14 + ) + + specification_type_15 = "pt:dict[pt:str, pt:str]" + expected_15 = "Dict[str, str]" + assert ( + _pmt_specification_type_to_python_type(specification_type_15) == expected_15 + ) + + def test_mt_specification_type_to_python_type(self): + """Test the '_mt_specification_type_to_python_type' method.""" + specification_type_1 = "pt:union[pt:int, pt:bytes]" + expected_1 = "Union[int, bytes]" + assert _mt_specification_type_to_python_type(specification_type_1) == expected_1 + + specification_type_2 = "pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], pt:list[pt:bool], pt:dict[pt:str,pt:str]]" + expected_2 = "Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]" + assert _mt_specification_type_to_python_type(specification_type_2) == expected_2 + + specification_type_3 = ( + "pt:union[ct:DataModel, pt:set[pt:int], pt:list[pt:bool], pt:bytes, pt:dict[pt:bool,pt:float], pt:int, " + "pt:set[pt:bool], pt:dict[pt:int, pt:str], pt:list[pt:str], pt:bool, pt:float, pt:str, pt:dict[pt:str, pt:str]]" + ) + expected_3 = ( + "Union[DataModel, FrozenSet[int], Tuple[bool, ...], bytes, Dict[bool, float], int, " + "FrozenSet[bool], Dict[int, str], Tuple[str, ...], bool, float, str, Dict[str, str]]" + ) + assert _mt_specification_type_to_python_type(specification_type_3) == expected_3 + + def test_optional_specification_type_to_python_type(self): + """Test the '_optional_specification_type_to_python_type' method.""" + specification_type_1 = ( + "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " + "pt:list[pt:bool], pt:dict[pt:str, pt:str]]]" + ) + expected_1 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" + assert ( + _optional_specification_type_to_python_type(specification_type_1) + == expected_1 + ) + + specification_type_2 = ( + "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " + "pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" + ) + expected_2 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" + assert ( + _optional_specification_type_to_python_type(specification_type_2) + == expected_2 + ) + + specification_type_3 = "pt:optional[ct:DataModel]" + expected_3 = "Optional[DataModel]" + assert ( + _optional_specification_type_to_python_type(specification_type_3) + == expected_3 + ) + + def test_specification_type_to_python_type(self): + """Test the '_specification_type_to_python_type' method.""" + specification_type_1 = "ct:DataModel" + expected_1 = "DataModel" + assert _specification_type_to_python_type(specification_type_1) == expected_1 + + specification_type_2 = "pt:bytes" + expected_2 = "bytes" + assert _specification_type_to_python_type(specification_type_2) == expected_2 + + specification_type_3 = "pt:set[pt:int]" + expected_3 = "FrozenSet[int]" + assert _specification_type_to_python_type(specification_type_3) == expected_3 + + specification_type_4 = "pt:list[pt:float]" + expected_4 = "Tuple[float, ...]" + assert _specification_type_to_python_type(specification_type_4) == expected_4 + + specification_type_5 = "pt:dict[pt:bool, pt:str]" + expected_5 = "Dict[bool, str]" + assert _specification_type_to_python_type(specification_type_5) == expected_5 + + specification_type_6 = "pt:union[pt:int, pt:bytes]" + expected_6 = "Union[int, bytes]" + assert _specification_type_to_python_type(specification_type_6) == expected_6 + + specification_type_7 = ( + "pt:optional[pt:union[ct:DataModel, pt:bytes, pt:int, pt:bool, pt:float, pt:str, pt:set[pt:int], " + "pt:list[pt:bool], pt:dict[pt:str,pt:str]]]" + ) + expected_7 = "Optional[Union[DataModel, bytes, int, bool, float, str, FrozenSet[int], Tuple[bool, ...], Dict[str, str]]]" + assert _specification_type_to_python_type(specification_type_7) == expected_7 + + specification_type_8 = "wrong_type" + with self.assertRaises(ProtocolSpecificationParseError) as cm: + _specification_type_to_python_type(specification_type_8) + self.assertEqual( + str(cm.exception), "Unsupported type: '{}'".format(specification_type_8) + ) + + specification_type_9 = "pt:integer" + with self.assertRaises(ProtocolSpecificationParseError) as cm: + _specification_type_to_python_type(specification_type_9) + self.assertEqual( + str(cm.exception), "Unsupported type: '{}'".format(specification_type_9) + ) + + specification_type_10 = "pt: list" + with self.assertRaises(ProtocolSpecificationParseError) as cm: + _specification_type_to_python_type(specification_type_10) + self.assertEqual( + str(cm.exception), "Unsupported type: '{}'".format(specification_type_10) + ) + + specification_type_11 = "pt:list[wrong_sub_type]" + with self.assertRaises(ProtocolSpecificationParseError) as cm: + _specification_type_to_python_type(specification_type_11) + self.assertEqual(str(cm.exception), "Unsupported type: 'wrong_sub_type'") + + def test_pythonic_protocol_specification_class(self): + """Test the 'PythonicProtocolSpecification' class.""" + spec = PythonicProtocolSpecification() + assert spec.speech_acts == dict() + assert spec.all_performatives == list() + assert spec.all_unique_contents == dict() + assert spec.all_custom_types == list() + assert spec.custom_custom_types == dict() + assert spec.initial_performatives == list() + assert spec.reply == dict() + assert spec.terminal_performatives == list() + assert spec.roles == list() + assert spec.end_states == list() + assert spec.typing_imports == { + "Set": True, + "Tuple": True, + "cast": True, + "FrozenSet": False, + "Dict": False, + "Union": False, + "Optional": False, + } + + def test_extract_positive(self): + """Positive test the 'extract' method.""" + protocol_specification = load_protocol_specification( + PATH_TO_T_PROTOCOL_SPECIFICATION + ) + spec = extract(protocol_specification) + + assert spec.speech_acts == { + "performative_ct": {"content_ct": "DataModel"}, + "performative_pt": { + "content_bytes": "bytes", + "content_int": "int", + "content_float": "float", + "content_bool": "bool", + "content_str": "str", + }, + "performative_pct": { + "content_set_bytes": "FrozenSet[bytes]", + "content_set_int": "FrozenSet[int]", + "content_set_float": "FrozenSet[float]", + "content_set_bool": "FrozenSet[bool]", + "content_set_str": "FrozenSet[str]", + "content_list_bytes": "Tuple[bytes, ...]", + "content_list_int": "Tuple[int, ...]", + "content_list_float": "Tuple[float, ...]", + "content_list_bool": "Tuple[bool, ...]", + "content_list_str": "Tuple[str, ...]", + }, + "performative_pmt": { + "content_dict_int_bytes": "Dict[int, bytes]", + "content_dict_int_int": "Dict[int, int]", + "content_dict_int_float": "Dict[int, float]", + "content_dict_int_bool": "Dict[int, bool]", + "content_dict_int_str": "Dict[int, str]", + "content_dict_bool_bytes": "Dict[bool, bytes]", + "content_dict_bool_int": "Dict[bool, int]", + "content_dict_bool_float": "Dict[bool, float]", + "content_dict_bool_bool": "Dict[bool, bool]", + "content_dict_bool_str": "Dict[bool, str]", + "content_dict_str_bytes": "Dict[str, bytes]", + "content_dict_str_int": "Dict[str, int]", + "content_dict_str_float": "Dict[str, float]", + "content_dict_str_bool": "Dict[str, bool]", + "content_dict_str_str": "Dict[str, str]", + }, + "performative_mt": { + "content_union_1": "Union[DataModel, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int]]", + "content_union_2": "Union[FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes]]", + }, + "performative_o": { + "content_o_ct": "Optional[DataModel]", + "content_o_bool": "Optional[bool]", + "content_o_set_int": "Optional[FrozenSet[int]]", + "content_o_list_bytes": "Optional[Tuple[bytes, ...]]", + "content_o_dict_str_int": "Optional[Dict[str, int]]", + }, + "performative_empty_contents": {}, + } + assert spec.all_performatives == [ + "performative_ct", + "performative_empty_contents", + "performative_mt", + "performative_o", + "performative_pct", + "performative_pmt", + "performative_pt", + ] + assert spec.all_unique_contents == { + "content_ct": "DataModel", + "content_bytes": "bytes", + "content_int": "int", + "content_float": "float", + "content_bool": "bool", + "content_str": "str", + "content_set_bytes": "FrozenSet[bytes]", + "content_set_int": "FrozenSet[int]", + "content_set_float": "FrozenSet[float]", + "content_set_bool": "FrozenSet[bool]", + "content_set_str": "FrozenSet[str]", + "content_list_bytes": "Tuple[bytes, ...]", + "content_list_int": "Tuple[int, ...]", + "content_list_float": "Tuple[float, ...]", + "content_list_bool": "Tuple[bool, ...]", + "content_list_str": "Tuple[str, ...]", + "content_dict_int_bytes": "Dict[int, bytes]", + "content_dict_int_int": "Dict[int, int]", + "content_dict_int_float": "Dict[int, float]", + "content_dict_int_bool": "Dict[int, bool]", + "content_dict_int_str": "Dict[int, str]", + "content_dict_bool_bytes": "Dict[bool, bytes]", + "content_dict_bool_int": "Dict[bool, int]", + "content_dict_bool_float": "Dict[bool, float]", + "content_dict_bool_bool": "Dict[bool, bool]", + "content_dict_bool_str": "Dict[bool, str]", + "content_dict_str_bytes": "Dict[str, bytes]", + "content_dict_str_int": "Dict[str, int]", + "content_dict_str_float": "Dict[str, float]", + "content_dict_str_bool": "Dict[str, bool]", + "content_dict_str_str": "Dict[str, str]", + "content_union_1": "Union[DataModel, bytes, int, float, bool, str, FrozenSet[int], Tuple[bool, ...], Dict[str, int]]", + "content_union_2": "Union[FrozenSet[bytes], FrozenSet[int], FrozenSet[str], Tuple[float, ...], Tuple[bool, ...], Tuple[bytes, ...], Dict[str, int], Dict[int, float], Dict[bool, bytes]]", + "content_o_ct": "Optional[DataModel]", + "content_o_bool": "Optional[bool]", + "content_o_set_int": "Optional[FrozenSet[int]]", + "content_o_list_bytes": "Optional[Tuple[bytes, ...]]", + "content_o_dict_str_int": "Optional[Dict[str, int]]", + } + assert spec.all_custom_types == ["DataModel"] + assert spec.custom_custom_types == {"DataModel": "CustomDataModel"} + assert spec.initial_performatives == ["PERFORMATIVE_CT", "PERFORMATIVE_PT"] + assert spec.reply == { + "performative_ct": ["performative_pct"], + "performative_pt": ["performative_pmt"], + "performative_pct": ["performative_mt", "performative_o"], + "performative_pmt": ["performative_mt", "performative_o"], + "performative_mt": [], + "performative_o": [], + "performative_empty_contents": ["performative_empty_contents"], + } + assert spec.terminal_performatives == [ + "PERFORMATIVE_MT", + "PERFORMATIVE_O", + "PERFORMATIVE_EMPTY_CONTENTS", + ] + assert spec.roles == ["role_1", "role_2"] + assert spec.end_states == ["end_state_1", "end_state_2", "end_state_3"] + assert spec.typing_imports == { + "Set": True, + "Tuple": True, + "cast": True, + "FrozenSet": True, + "Dict": True, + "Union": True, + "Optional": True, + } + + @mock.patch( + "aea.protocols.generator.extract_specification.validate", + return_value=(False, "Some error!"), + ) + def test_extract_negative_invalid_specification(self, mocked_validate): + """Negative test the 'extract' method: invalid protocol specification""" + protocol_specification = load_protocol_specification( + PATH_TO_T_PROTOCOL_SPECIFICATION + ) + + with self.assertRaises(ProtocolSpecificationParseError) as cm: + extract(protocol_specification) + expected_msg = "Some error!" + assert str(cm.exception) == expected_msg + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass diff --git a/tests/test_protocols/test_generator/test_generator.py b/tests/test_protocols/test_generator/test_generator.py new file mode 100644 index 0000000000..7cf30dba19 --- /dev/null +++ b/tests/test_protocols/test_generator/test_generator.py @@ -0,0 +1,1273 @@ +# -*- 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 miscellaneous tests for the protocol generator.""" +import filecmp +import logging +import os +import shutil +import tempfile +from pathlib import Path +from typing import cast +from unittest import TestCase, mock + +import pytest + +from aea.configurations.base import ( + ProtocolSpecification, + ProtocolSpecificationParseError, +) +from aea.protocols.generator.base import ProtocolGenerator + +from tests.conftest import ROOT_DIR, skip_test_windows +from tests.data.generator.t_protocol.message import ( # type: ignore + TProtocolMessage, +) +from tests.test_protocols.test_generator.common import ( + PATH_TO_T_PROTOCOL, + PATH_TO_T_PROTOCOL_SPECIFICATION, + T_PROTOCOL_NAME, +) + + +logger = logging.getLogger("aea") +logging.basicConfig(level=logging.INFO) + + +@skip_test_windows +class TestCompareLatestGeneratorOutputWithTestProtocol: + """Test that the "t_protocol" test protocol matches with the latest generator output based on its specification.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + filecmp.clear_cache() + + def test_compare_latest_generator_output_with_test_protocol(self): + """ + Test that the "t_protocol" test protocol matches with the latest generator output based on its specification. + + Note: + - custom_types.py files are not compared as the generated one is only a template. + - protocol.yaml files are consequently not compared either because the different + custom_types.py files makes their IPFS hashes different. + """ + path_to_generated_protocol = self.t + dotted_path_to_package_for_imports = "tests.data.generator." + + # Generate the protocol + try: + protocol_generator = ProtocolGenerator( + path_to_protocol_specification=PATH_TO_T_PROTOCOL_SPECIFICATION, + output_path=path_to_generated_protocol, + dotted_path_to_protocol_package=dotted_path_to_package_for_imports, + ) + protocol_generator.generate() + except Exception as e: + pytest.skip( + "Something went wrong when generating the protocol. The exception:" + + str(e) + ) + + # compare __init__.py + init_file_generated = Path(self.t, T_PROTOCOL_NAME, "__init__.py") + init_file_original = Path(PATH_TO_T_PROTOCOL, "__init__.py",) + assert filecmp.cmp(init_file_generated, init_file_original) + + # compare message.py + message_file_generated = Path(self.t, T_PROTOCOL_NAME, "message.py") + message_file_original = Path(PATH_TO_T_PROTOCOL, "message.py",) + assert filecmp.cmp(message_file_generated, message_file_original) + + # compare serialization.py + serialization_file_generated = Path(self.t, T_PROTOCOL_NAME, "serialization.py") + serialization_file_original = Path(PATH_TO_T_PROTOCOL, "serialization.py",) + assert filecmp.cmp(serialization_file_generated, serialization_file_original) + + # compare dialogues.py + dialogue_file_generated = Path(self.t, T_PROTOCOL_NAME, "dialogues.py") + dialogue_file_original = Path(PATH_TO_T_PROTOCOL, "dialogues.py",) + assert filecmp.cmp(dialogue_file_generated, dialogue_file_original) + + # compare .proto + proto_file_generated = Path( + self.t, T_PROTOCOL_NAME, "{}.proto".format(T_PROTOCOL_NAME) + ) + proto_file_original = Path( + PATH_TO_T_PROTOCOL, "{}.proto".format(T_PROTOCOL_NAME), + ) + assert filecmp.cmp(proto_file_generated, proto_file_original, shallow=False) + + # compare _pb2.py + # ToDo Fails in CI. Investigate! + # pb2_file_generated = Path( + # self.t, T_PROTOCOL_NAME, "{}_pb2.py".format(T_PROTOCOL_NAME) + # ) + # pb2_file_original = Path( + # PATH_TO_T_PROTOCOL, "{}_pb2.py".format(T_PROTOCOL_NAME), + # ) + # assert filecmp.cmp(pb2_file_generated, pb2_file_original) + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +@skip_test_windows +class TestCompareLatestGeneratorOutputWithTestProtocolWithNoCustomTypes: + """Test that the "t_protocol" test protocol matches with the latest generator output based on its specification.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + filecmp.clear_cache() + + def test_compare_latest_generator_output_with_test_protocol(self): + """ + Test that the "t_protocol" test protocol matches with the latest generator output based on its specification. + + Note: + - custom_types.py files are not compared as the generated one is only a template. + - protocol.yaml files are consequently not compared either because the different + custom_types.py files makes their IPFS hashes different. + """ + + protocol_name = "t_protocol_no_ct" + path_to_protocol_specification_with_no_custom_types = os.path.join( + ROOT_DIR, "tests", "data", "sample_specification_no_custom_types.yaml" + ) + path_to_generated_protocol = self.t + dotted_path_to_package_for_imports = "tests.data.generator." + path_to_protocol = os.path.join( + ROOT_DIR, "tests", "data", "generator", protocol_name + ) + + # Generate the protocol + try: + protocol_generator = ProtocolGenerator( + path_to_protocol_specification=path_to_protocol_specification_with_no_custom_types, + output_path=path_to_generated_protocol, + dotted_path_to_protocol_package=dotted_path_to_package_for_imports, + ) + protocol_generator.generate() + except Exception as e: + pytest.skip( + "Something went wrong when generating the protocol. The exception:" + + str(e) + ) + + # compare __init__.py + init_file_generated = Path(self.t, protocol_name, "__init__.py") + init_file_original = Path(path_to_protocol, "__init__.py",) + assert filecmp.cmp(init_file_generated, init_file_original) + + # compare message.py + message_file_generated = Path(self.t, protocol_name, "message.py") + message_file_original = Path(path_to_protocol, "message.py",) + assert filecmp.cmp(message_file_generated, message_file_original) + + # compare serialization.py + serialization_file_generated = Path(self.t, protocol_name, "serialization.py") + serialization_file_original = Path(path_to_protocol, "serialization.py",) + assert filecmp.cmp(serialization_file_generated, serialization_file_original) + + # compare dialogues.py + dialogue_file_generated = Path(self.t, protocol_name, "dialogues.py") + dialogue_file_original = Path(path_to_protocol, "dialogues.py",) + assert filecmp.cmp(dialogue_file_generated, dialogue_file_original) + + # compare .proto + proto_file_generated = Path( + self.t, protocol_name, "{}.proto".format(protocol_name) + ) + proto_file_original = Path(path_to_protocol, "{}.proto".format(protocol_name),) + assert filecmp.cmp(proto_file_generated, proto_file_original) + + # compare _pb2.py + # ToDo Fails in CI. Investigate! + # pb2_file_generated = Path( + # self.t, protocol_name, "{}_pb2.py".format(protocol_name) + # ) + # pb2_file_original = Path( + # path_to_protocol, "{}_pb2.py".format(protocol_name), + # ) + # assert filecmp.cmp(pb2_file_generated, pb2_file_original) + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +class TestSerialisations: + """ + Test that the generating a protocol works correctly in correct preconditions. + + Note: Types involving Floats seem to lose some precision when serialised then deserialised using protobuf. + So tests for these types are commented out throughout for now. + """ + + def test_generated_protocol_serialisation_ct(self): + """Test serialisation and deserialisation of a message involving a ct type.""" + some_dict = {1: True, 2: False, 3: True, 4: False} + data_model = TProtocolMessage.DataModel( + bytes_field=b"some bytes", + int_field=42, + float_field=42.7, + bool_field=True, + str_field="some string", + set_field={1, 2, 3, 4, 5}, + list_field=["some string 1", "some string 2"], + dict_field=some_dict, + ) + message = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_CT, + content_ct=data_model, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message.message_id + assert decoded_message.dialogue_reference == message.dialogue_reference + assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] + assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] + assert decoded_message.target == message.target + assert decoded_message.performative == message.performative + assert decoded_message.content_ct == message.content_ct + + def test_generated_protocol_serialisation_pt(self): + """Test serialisation and deserialisation of a message involving a pt type.""" + message = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_PT, + content_bytes=b"some bytes", + content_int=42, + content_float=42.7, + content_bool=True, + content_str="some string", + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message.message_id + assert decoded_message.dialogue_reference == message.dialogue_reference + assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] + assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] + assert decoded_message.target == message.target + assert decoded_message.performative == message.performative + assert decoded_message.content_bytes == message.content_bytes + assert decoded_message.content_int == message.content_int + # assert decoded_message.content_float == message.content_float + assert decoded_message.content_bool == message.content_bool + assert decoded_message.content_str == message.content_str + + def test_generated_protocol_serialisation_pct(self): + """Test serialisation and deserialisation of a message involving a pct type.""" + message = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_PCT, + content_set_bytes=frozenset([b"byte 1", b"byte 2", b"byte 3"]), + content_set_int=frozenset([1, 2, 3]), + content_set_float=frozenset([1.2, 2.3, 3.4]), + content_set_bool=frozenset([True, False, False, True]), + content_set_str=frozenset(["string1", "string2", "string3"]), + content_list_bytes=(b"byte 4", b"byte 5", b"byte 6"), + content_list_int=(4, 5, 6), + content_list_float=(4.5, 5.6, 6.7), + content_list_bool=(False, True, False, False), + content_list_str=("string4", "string5", "string6"), + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message.message_id + assert decoded_message.dialogue_reference == message.dialogue_reference + assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] + assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] + assert decoded_message.target == message.target + assert decoded_message.performative == message.performative + assert decoded_message.content_set_bytes == message.content_set_bytes + assert decoded_message.content_set_int == message.content_set_int + # assert decoded_message.content_set_float == message.content_set_float + assert decoded_message.content_set_bool == message.content_set_bool + assert decoded_message.content_set_str == message.content_set_str + assert decoded_message.content_list_bytes == message.content_list_bytes + assert decoded_message.content_list_int == message.content_list_int + # assert decoded_message.content_list_float == message.content_list_float + assert decoded_message.content_list_bool == message.content_list_bool + assert decoded_message.content_list_str == message.content_list_str + + def test_generated_protocol_serialisation_pmt(self): + """Test serialisation and deserialisation of a message involving a pmt type.""" + message = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_PMT, + content_dict_int_bytes={1: b"bytes1", 2: b"bytes2", 3: b"bytes3"}, + content_dict_int_int={1: 2, 2: 3, 3: 4}, + content_dict_int_float={1: 3.4, 2: 4.7, 3: 4.6}, + content_dict_int_bool={1: True, 2: True, 3: False}, + content_dict_int_str={1: "string1", 2: "string2", 3: "string3"}, + content_dict_bool_bytes={True: b"bytes1", False: b"bytes2"}, + content_dict_bool_int={True: 5, False: 7}, + content_dict_bool_float={True: 5.4, False: 4.6}, + content_dict_bool_bool={True: False, False: False}, + content_dict_bool_str={True: "string1", False: "string2"}, + content_dict_str_bytes={ + "string1": b"bytes1", + "string2": b"bytes2", + "string3": b"bytes3", + }, + content_dict_str_int={"string1": 2, "string2": 3, "string3": 4}, + content_dict_str_float={"string1": 3.4, "string2": 4.7, "string3": 4.6}, + content_dict_str_bool={"string1": True, "string2": True, "string3": False}, + content_dict_str_str={ + "string1": "string4", + "string2": "string5", + "string3": "string6", + }, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message.message_id + assert decoded_message.dialogue_reference == message.dialogue_reference + assert decoded_message.dialogue_reference[0] == message.dialogue_reference[0] + assert decoded_message.dialogue_reference[1] == message.dialogue_reference[1] + assert decoded_message.target == message.target + assert decoded_message.performative == message.performative + assert decoded_message.content_dict_int_bytes == message.content_dict_int_bytes + assert decoded_message.content_dict_int_int == message.content_dict_int_int + # assert decoded_message.content_dict_int_float == message.content_dict_int_float + assert decoded_message.content_dict_int_bool == message.content_dict_int_bool + assert decoded_message.content_dict_int_str == message.content_dict_int_str + assert ( + decoded_message.content_dict_bool_bytes == message.content_dict_bool_bytes + ) + assert decoded_message.content_dict_bool_int == message.content_dict_bool_int + # assert decoded_message.content_dict_bool_float == message.content_dict_bool_float + assert decoded_message.content_dict_bool_bool == message.content_dict_bool_bool + assert decoded_message.content_dict_bool_str == message.content_dict_bool_str + assert decoded_message.content_dict_str_bytes == message.content_dict_str_bytes + assert decoded_message.content_dict_str_int == message.content_dict_str_int + # assert decoded_message.content_dict_str_float == message.content_dict_str_float + assert decoded_message.content_dict_str_bool == message.content_dict_str_bool + assert decoded_message.content_dict_str_str == message.content_dict_str_str + + def test_generated_protocol_serialisation_mt(self): + """Test serialisation and deserialisation of a message involving an mt type.""" + pytest.skip( + "Currently, union type is not properly implemented in the generator." + ) + some_dict = {1: True, 2: False, 3: True, 4: False} + data_model = TProtocolMessage.DataModel( + bytes_field=b"some bytes", + int_field=42, + float_field=42.7, + bool_field=True, + str_field="some string", + set_field={1, 2, 3, 4, 5}, + list_field=["some string 1", "some string 2"], + dict_field=some_dict, + ) + message_ct = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=data_model, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_ct) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_ct.message_id + assert decoded_message.dialogue_reference == message_ct.dialogue_reference + assert decoded_message.dialogue_reference[0] == message_ct.dialogue_reference[0] + assert decoded_message.dialogue_reference[1] == message_ct.dialogue_reference[1] + assert decoded_message.target == message_ct.target + assert decoded_message.performative == message_ct.performative + assert decoded_message.content_union_1 == message_ct.content_union_1 + + ##################### + + message_pt_bytes = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=b"some bytes", + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_bytes) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_pt_bytes.message_id + assert decoded_message.dialogue_reference == message_pt_bytes.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_pt_bytes.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_pt_bytes.dialogue_reference[1] + ) + assert decoded_message.target == message_pt_bytes.target + assert decoded_message.performative == message_pt_bytes.performative + assert decoded_message.content_union_1 == message_pt_bytes.content_union_1 + + ##################### + + message_pt_int = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=3453, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_int) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_pt_int.message_id + assert decoded_message.dialogue_reference == message_pt_int.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_pt_int.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_pt_int.dialogue_reference[1] + ) + assert decoded_message.target == message_pt_int.target + assert decoded_message.performative == message_pt_int.performative + assert decoded_message.content_union_1 == message_pt_int.content_union_1 + + ##################### + + message_pt_float = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=34.64, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_float) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_pt_float.message_id + assert decoded_message.dialogue_reference == message_pt_float.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_pt_float.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_pt_float.dialogue_reference[1] + ) + assert decoded_message.target == message_pt_float.target + assert decoded_message.performative == message_pt_float.performative + assert decoded_message.content_union_1 == message_pt_float.content_union_1 + + ##################### + + message_pt_bool = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=True, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_bool) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_pt_bool.message_id + assert decoded_message.dialogue_reference == message_pt_bool.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_pt_bool.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_pt_bool.dialogue_reference[1] + ) + assert decoded_message.target == message_pt_bool.target + assert decoded_message.performative == message_pt_bool.performative + assert decoded_message.content_union_1 == message_pt_bool.content_union_1 + + ##################### + + message_pt_str = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1="some string", + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_pt_str) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_pt_str.message_id + assert decoded_message.dialogue_reference == message_pt_str.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_pt_str.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_pt_str.dialogue_reference[1] + ) + assert decoded_message.target == message_pt_str.target + assert decoded_message.performative == message_pt_str.performative + assert decoded_message.content_union_1 == message_pt_str.content_union_1 + + ##################### + + message_set_int = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=frozenset([1, 2, 3]), + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_set_int) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_set_int.message_id + assert decoded_message.dialogue_reference == message_set_int.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_set_int.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_set_int.dialogue_reference[1] + ) + assert decoded_message.target == message_set_int.target + assert decoded_message.performative == message_set_int.performative + assert decoded_message.content_union_1 == message_set_int.content_union_1 + + ##################### + + message_list_bool = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1=(True, False, False, True, True), + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_list_bool) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_list_bool.message_id + assert ( + decoded_message.dialogue_reference == message_list_bool.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_list_bool.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_list_bool.dialogue_reference[1] + ) + assert decoded_message.target == message_list_bool.target + assert decoded_message.performative == message_list_bool.performative + assert decoded_message.content_union_1 == message_list_bool.content_union_1 + + ##################### + + message_dict_str_int = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_MT, + content_union_1={"string1": 2, "string2": 3, "string3": 4}, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_dict_str_int + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_dict_str_int.message_id + assert ( + decoded_message.dialogue_reference + == message_dict_str_int.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_dict_str_int.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_dict_str_int.dialogue_reference[1] + ) + assert decoded_message.target == message_dict_str_int.target + assert decoded_message.performative == message_dict_str_int.performative + assert decoded_message.content_union_1 == message_dict_str_int.content_union_1 + + def test_generated_protocol_serialisation_o(self): + """Test serialisation and deserialisation of a message involving an optional type.""" + some_dict = {1: True, 2: False, 3: True, 4: False} + data_model = TProtocolMessage.DataModel( + bytes_field=b"some bytes", + int_field=42, + float_field=42.7, + bool_field=True, + str_field="some string", + set_field={1, 2, 3, 4, 5}, + list_field=["some string 1", "some string 2"], + dict_field=some_dict, + ) + message_o_ct_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + content_o_ct=data_model, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode(message_o_ct_set) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_ct_set.message_id + assert decoded_message.dialogue_reference == message_o_ct_set.dialogue_reference + assert ( + decoded_message.dialogue_reference[0] + == message_o_ct_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_ct_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_ct_set.target + assert decoded_message.performative == message_o_ct_set.performative + assert decoded_message.content_o_ct == message_o_ct_set.content_o_ct + + ##################### + + message_o_ct_not_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_ct_not_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_ct_not_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_ct_not_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_ct_not_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_ct_not_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_ct_not_set.target + assert decoded_message.performative == message_o_ct_not_set.performative + assert decoded_message.content_o_ct == message_o_ct_not_set.content_o_ct + + ##################### + + message_o_bool_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + content_o_bool=True, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_bool_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_bool_set.message_id + assert ( + decoded_message.dialogue_reference == message_o_bool_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_bool_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_bool_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_bool_set.target + assert decoded_message.performative == message_o_bool_set.performative + assert decoded_message.content_o_ct == message_o_bool_set.content_o_ct + + ##################### + + message_o_bool_not_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_bool_not_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_bool_not_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_bool_not_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_bool_not_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_bool_not_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_bool_not_set.target + assert decoded_message.performative == message_o_bool_not_set.performative + assert decoded_message.content_o_bool == message_o_bool_not_set.content_o_bool + + ##################### + + message_o_set_int_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + content_o_set_int=frozenset([1, 2, 3]), + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_set_int_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_set_int_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_set_int_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_set_int_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_set_int_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_set_int_set.target + assert decoded_message.performative == message_o_set_int_set.performative + assert ( + decoded_message.content_o_set_int == message_o_set_int_set.content_o_set_int + ) + + ##################### + + message_o_set_int_not_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_set_int_not_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_set_int_not_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_set_int_not_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_set_int_not_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_set_int_not_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_set_int_not_set.target + assert decoded_message.performative == message_o_set_int_not_set.performative + assert ( + decoded_message.content_o_set_int + == message_o_set_int_not_set.content_o_set_int + ) + + ##################### + + message_o_list_bytes_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + content_o_list_bytes=(b"bytes1", b"bytes2", b"bytes3"), + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_list_bytes_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_list_bytes_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_list_bytes_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_list_bytes_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_list_bytes_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_list_bytes_set.target + assert decoded_message.performative == message_o_list_bytes_set.performative + assert ( + decoded_message.content_o_list_bytes + == message_o_list_bytes_set.content_o_list_bytes + ) + + ##################### + + message_o_list_bytes_not_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_list_bytes_not_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_list_bytes_not_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_list_bytes_not_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_list_bytes_not_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_list_bytes_not_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_list_bytes_not_set.target + assert decoded_message.performative == message_o_list_bytes_not_set.performative + assert ( + decoded_message.content_o_list_bytes + == message_o_list_bytes_not_set.content_o_list_bytes + ) + + ##################### + + message_o_dict_str_int_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + content_o_dict_str_int={"string1": 2, "string2": 3, "string3": 4}, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_dict_str_int_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_dict_str_int_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_dict_str_int_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_dict_str_int_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_dict_str_int_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_dict_str_int_set.target + assert decoded_message.performative == message_o_dict_str_int_set.performative + assert ( + decoded_message.content_o_list_bytes + == message_o_dict_str_int_set.content_o_list_bytes + ) + + ##################### + + message_o_dict_str_int_not_set = TProtocolMessage( + message_id=1, + dialogue_reference=(str(0), ""), + target=0, + performative=TProtocolMessage.Performative.PERFORMATIVE_O, + ) + + encoded_message_in_bytes = TProtocolMessage.serializer.encode( + message_o_dict_str_int_not_set + ) + decoded_message = cast( + TProtocolMessage, + TProtocolMessage.serializer.decode(encoded_message_in_bytes), + ) + + assert decoded_message.message_id == message_o_dict_str_int_not_set.message_id + assert ( + decoded_message.dialogue_reference + == message_o_dict_str_int_not_set.dialogue_reference + ) + assert ( + decoded_message.dialogue_reference[0] + == message_o_dict_str_int_not_set.dialogue_reference[0] + ) + assert ( + decoded_message.dialogue_reference[1] + == message_o_dict_str_int_not_set.dialogue_reference[1] + ) + assert decoded_message.target == message_o_dict_str_int_not_set.target + assert ( + decoded_message.performative == message_o_dict_str_int_not_set.performative + ) + assert ( + decoded_message.content_o_list_bytes + == message_o_dict_str_int_not_set.content_o_list_bytes + ) + + +class ProtocolGeneratorTestCase(TestCase): + """Test for generator/base.py.""" + + @classmethod + def setup_class(cls): + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + os.chdir(cls.t) + + @mock.patch( + "aea.protocols.generator.base.check_prerequisites", + side_effect=FileNotFoundError("Some error!"), + ) + def test_init_negative_no_prerequisits(self, mocked_check_prerequisites): + """Negative test for the '__init__' method: check_prerequisites fails.""" + with self.assertRaises(FileNotFoundError) as cm: + ProtocolGenerator(PATH_TO_T_PROTOCOL, self.t) + expected_msg = "Some error!" + assert str(cm.exception) == expected_msg + + @mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + side_effect=ValueError("Some error!"), + ) + def test_init_negative_loading_specification_fails(self, mocked_load): + """Negative test for the '__init__' method: loading the specification fails.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + with self.assertRaises(ValueError) as cm: + ProtocolGenerator(PATH_TO_T_PROTOCOL, self.t) + expected_msg = "Some error!" + assert str(cm.exception) == expected_msg + + @mock.patch( + "aea.protocols.generator.base.extract", + side_effect=ProtocolSpecificationParseError("Some error!"), + ) + def test_init_negative_extracting_specification_fails(self, mocked_extract): + """Negative test for the '__init__' method: extracting the specification fails.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + p_spec_mock = mock.MagicMock(ProtocolSpecification) + p_spec_mock.name = "some_name" + p_spec_mock.author = "some_author" + with mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + return_value=p_spec_mock, + ): + with self.assertRaises(ProtocolSpecificationParseError) as cm: + ProtocolGenerator( + "some_path_to_protocol_specification", "some_path_to_output" + ) + expected_msg = "Some error!" + assert str(cm.exception) == expected_msg + + def test_change_indent_negative_set_indent_to_negative_value(self): + """Negative test for the '_change_indent' method: setting indent level to negative value.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + p_spec_mock = mock.MagicMock(ProtocolSpecification) + p_spec_mock.name = "some_name" + p_spec_mock.author = "some_author" + with mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + return_value=p_spec_mock, + ): + with mock.patch("aea.protocols.generator.base.extract"): + protocol_generator = ProtocolGenerator( + "some_path_to_protocol_specification", "some_path_to_output" + ) + with self.assertRaises(ValueError) as cm: + protocol_generator._change_indent(-1, "s") + expected_msg = "Error: setting indent to be a negative number." + assert str(cm.exception) == expected_msg + + def test_change_indent_negative_decreasing_more_spaces_than_available(self): + """Negative test for the '_change_indent' method: decreasing more spaces than available.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + p_spec_mock = mock.MagicMock(ProtocolSpecification) + p_spec_mock.name = "some_name" + p_spec_mock.author = "some_author" + with mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + return_value=p_spec_mock, + ): + with mock.patch("aea.protocols.generator.base.extract"): + protocol_generator = ProtocolGenerator( + "some_path_to_protocol_specification", "some_path_to_output" + ) + protocol_generator.indent = " " + with self.assertRaises(ValueError) as cm: + protocol_generator._change_indent(-2) + expected_msg = ( + "Not enough spaces in the 'indent' variable to remove." + ) + assert str(cm.exception) == expected_msg + + def test_import_from_custom_types_module_no_custom_types(self): + """Test the '_import_from_custom_types_module' method: no custom types.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + p_spec_mock = mock.MagicMock(ProtocolSpecification) + p_spec_mock.name = "some_name" + p_spec_mock.author = "some_author" + with mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + return_value=p_spec_mock, + ): + with mock.patch("aea.protocols.generator.base.extract"): + protocol_generator = ProtocolGenerator( + "some_path_to_protocol_specification", "some_path_to_output" + ) + protocol_generator.spec.all_custom_types = [] + assert protocol_generator._import_from_custom_types_module() == "" + + def test_protocol_buffer_schema_str(self): + """Negative test for the '_protocol_buffer_schema_str' method: 1 line protobuf snippet.""" + with mock.patch("aea.protocols.generator.base.check_prerequisites"): + p_spec_mock = mock.MagicMock(ProtocolSpecification) + p_spec_mock.name = "some_name" + p_spec_mock.author = "some_author" + with mock.patch( + "aea.protocols.generator.base.load_protocol_specification", + return_value=p_spec_mock, + ): + with mock.patch("aea.protocols.generator.base.extract"): + protocol_generator = ProtocolGenerator( + "some_path_to_protocol_specification", "some_path_to_output" + ) + protocol_generator.spec.all_custom_types = ["SomeCustomType"] + protocol_generator.protocol_specification.protobuf_snippets = { + "ct:SomeCustomType": "bytes description = 1;" + } + proto_buff_schema_str = ( + protocol_generator._protocol_buffer_schema_str() + ) + print(proto_buff_schema_str) + expected = ( + 'syntax = "proto3";\n\n' + "package fetch.aea.SomeName;\n\n" + "message SomeNameMessage{\n\n" + " // Custom Types\n" + " message SomeCustomType{\n" + " bytes description = 1; }\n\n\n" + " // Performatives and contents\n\n" + " // Standard SomeNameMessage fields\n" + " int32 message_id = 1;\n" + " string dialogue_starter_reference = 2;\n" + " string dialogue_responder_reference = 3;\n" + " int32 target = 4;\n" + " oneof performative{\n" + " }\n" + "}\n" + ) + assert proto_buff_schema_str == expected + + def test_generate_protobuf_only_mode_positive(self): + """Positive test for the 'generate_protobuf_only_mode' method.""" + protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) + protocol_generator.generate_protobuf_only_mode() + path_to_protobuf_file = os.path.join( + self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" + ) + assert Path(path_to_protobuf_file).exists() + + @mock.patch( + "aea.protocols.generator.base.check_protobuf_using_protoc", + return_value=(False, "Some error!"), + ) + def test_generate_protobuf_only_mode_negative(self, mocked_check_protobuf): + """Negative test for the 'generate_protobuf_only_mode' method: protobuf schema file is invalid""" + protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) + with self.assertRaises(SyntaxError) as cm: + protocol_generator.generate_protobuf_only_mode() + expected_msg = "Error in the protocol buffer schema code:\n" + "Some error!" + assert str(cm.exception) == expected_msg + + path_to_protobuf_file = os.path.join( + self.t, T_PROTOCOL_NAME, T_PROTOCOL_NAME + ".proto" + ) + assert not Path(path_to_protobuf_file).exists() + + @mock.patch( + "aea.protocols.generator.base.ProtocolGenerator.generate_protobuf_only_mode" + ) + @mock.patch("aea.protocols.generator.base.ProtocolGenerator.generate_full_mode") + def test_generate_1(self, mocked_full_mode, mocked_protobuf_mode): + """Test the 'generate' method: protobuf_only mode""" + protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) + protocol_generator.generate(protobuf_only=True) + mocked_protobuf_mode.assert_called_once() + mocked_full_mode.assert_not_called() + + @mock.patch( + "aea.protocols.generator.base.ProtocolGenerator.generate_protobuf_only_mode" + ) + @mock.patch("aea.protocols.generator.base.ProtocolGenerator.generate_full_mode") + def test_generate_2(self, mocked_full_mode, mocked_protobuf_mode): + """Test the 'generate' method: full mode""" + protocol_generator = ProtocolGenerator(PATH_TO_T_PROTOCOL_SPECIFICATION, self.t) + protocol_generator.generate(protobuf_only=False) + mocked_protobuf_mode.assert_not_called() + mocked_full_mode.assert_called_once() + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass diff --git a/tests/test_protocols/test_generator/test_validate.py b/tests/test_protocols/test_generator/test_validate.py new file mode 100644 index 0000000000..eeeda771f3 --- /dev/null +++ b/tests/test_protocols/test_generator/test_validate.py @@ -0,0 +1,1595 @@ +# -*- 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 generator/validate.py module.""" +import logging +from unittest import TestCase, mock + +from aea.configurations.base import CRUDCollection, SpeechActContentConfig +from aea.protocols.generator.validate import ( + CONTENT_NAME_REGEX_PATTERN, + END_STATE_REGEX_PATTERN, + PERFORMATIVE_REGEX_PATTERN, + ROLE_REGEX_PATTERN, + _has_brackets, + _is_reserved_name, + _is_valid_content_type_format, + _is_valid_ct, + _is_valid_dict, + _is_valid_list, + _is_valid_optional, + _is_valid_pt, + _is_valid_regex, + _is_valid_set, + _is_valid_union, + _validate_content_name, + _validate_content_type, + _validate_dialogue_section, + _validate_end_states, + _validate_initiation, + _validate_performatives, + _validate_protocol_buffer_schema_code_snippets, + _validate_reply, + _validate_roles, + _validate_speech_acts_section, + _validate_termination, + validate, +) + +logger = logging.getLogger("aea") +logging.basicConfig(level=logging.INFO) + + +class TestValidate(TestCase): + """Test for generator/validate.py.""" + + def test_is_reserved_name(self): + """Test for the '_is_reserved_name' method.""" + invalid_content_name_1 = "body" + assert _is_reserved_name(invalid_content_name_1) is True + + invalid_content_name_2 = "message_id" + assert _is_reserved_name(invalid_content_name_2) is True + + invalid_content_name_3 = "dialogue_reference" + assert _is_reserved_name(invalid_content_name_3) is True + + invalid_content_name_4 = "target" + assert _is_reserved_name(invalid_content_name_4) is True + + invalid_content_name_5 = "performative" + assert _is_reserved_name(invalid_content_name_5) is True + + valid_content_nam_1 = "content_name" + assert _is_reserved_name(valid_content_nam_1) is False + + valid_content_name_2 = "query" + assert _is_reserved_name(valid_content_name_2) is False + + valid_content_name_3 = "ThiSiSAConTEnT234" + assert _is_reserved_name(valid_content_name_3) is False + + def test_is_valid_regex(self): + """Test for the '_is_valid_regex' method.""" + regex_1 = "^[0-9][a-zA-Z0-9]*[A-Z]$" + + valid_text_1 = "53453hKb35nDkG" + assert _is_valid_regex(regex_1, valid_text_1) is True + + invalid_text_1 = "hKbnDkG" + assert _is_valid_regex(regex_1, invalid_text_1) is False + + invalid_text_2 = "4f nkG" + assert _is_valid_regex(regex_1, invalid_text_2) is False + + def test_has_brackets(self): + """Test for the '_has_brackets' method.""" + valid_content_type_1 = "pt:set[pt:int]" + assert _has_brackets(valid_content_type_1) is True + + valid_content_type_2 = "pt:union[hskdjf-8768&^${]hsdkjhfk]" + assert _has_brackets(valid_content_type_2) is True + + valid_content_type_3 = "pt:optional[[]]" + assert _has_brackets(valid_content_type_3) is True + + ################################################### + + invalid_content_type_1 = "ct:set[pt:int]" + with self.assertRaises(SyntaxError) as cm: + _has_brackets(invalid_content_type_1) + self.assertEqual( + str(cm.exception), "Content type must be a compositional type!" + ) + + invalid_content_type_2 = "pt:tuple[pt:float]" + with self.assertRaises(SyntaxError) as cm: + _has_brackets(invalid_content_type_2) + self.assertEqual( + str(cm.exception), "Content type must be a compositional type!" + ) + + invalid_content_type_3 = "pt:optinal[pt:bool]" + with self.assertRaises(SyntaxError) as cm: + _has_brackets(invalid_content_type_3) + self.assertEqual( + str(cm.exception), "Content type must be a compositional type!" + ) + + ################################################### + + invalid_content_type_4 = "pt:optional{}" + assert _has_brackets(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:set[]7657" + assert _has_brackets(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:union [pt:int, pt:bool]" + assert _has_brackets(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:dict[pt:int, pt:bool] " + assert _has_brackets(invalid_content_type_7) is False + + def test_is_valid_ct(self): + """Test for the '_is_valid_ct' method.""" + valid_content_type_1 = "ct:DataModel" + assert _is_valid_ct(valid_content_type_1) is True + + valid_content_type_2 = "ct:ThisIsACustomContent" + assert _is_valid_ct(valid_content_type_2) is True + + valid_content_type_3 = "ct:Query" + assert _is_valid_ct(valid_content_type_3) is True + + valid_content_type_4 = " ct:Proposal " + assert _is_valid_ct(valid_content_type_4) is True + + valid_content_type_5 = "ct:DSA" + assert _is_valid_ct(valid_content_type_5) is True + + valid_content_type_6 = "ct:DataF" + assert _is_valid_ct(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "ct:data" + assert _is_valid_ct(invalid_content_type_1) is False + + invalid_content_type_2 = "Model" + assert _is_valid_ct(invalid_content_type_2) is False + + invalid_content_type_3 = "ct: DataModel" + assert _is_valid_ct(invalid_content_type_3) is False + + invalid_content_type_4 = "ct:E3" + assert _is_valid_ct(invalid_content_type_4) is False + + def test_is_valid_pt(self): + """Test for the '_is_valid_pt' method.""" + valid_content_type_1 = "pt:bytes" + assert _is_valid_pt(valid_content_type_1) is True + + valid_content_type_2 = "pt:int" + assert _is_valid_pt(valid_content_type_2) is True + + valid_content_type_3 = "pt:float" + assert _is_valid_pt(valid_content_type_3) is True + + valid_content_type_4 = "pt:bool" + assert _is_valid_pt(valid_content_type_4) is True + + valid_content_type_5 = "pt:str" + assert _is_valid_pt(valid_content_type_5) is True + + valid_content_type_6 = " pt:int " + assert _is_valid_pt(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "pt:integer" + assert _is_valid_pt(invalid_content_type_1) is False + + invalid_content_type_2 = "bool" + assert _is_valid_pt(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: str" + assert _is_valid_pt(invalid_content_type_3) is False + + invalid_content_type_4 = "pt;float" + assert _is_valid_pt(invalid_content_type_4) is False + + def test_is_valid_set(self): + """Test for the '_is_valid_set' method.""" + valid_content_type_1 = "pt:set[pt:bytes]" + assert _is_valid_set(valid_content_type_1) is True + + valid_content_type_2 = "pt:set[pt:int]" + assert _is_valid_set(valid_content_type_2) is True + + valid_content_type_3 = "pt:set[pt:float]" + assert _is_valid_set(valid_content_type_3) is True + + valid_content_type_4 = "pt:set[pt:bool]" + assert _is_valid_set(valid_content_type_4) is True + + valid_content_type_5 = "pt:set[pt:str]" + assert _is_valid_set(valid_content_type_5) is True + + valid_content_type_6 = " pt:set[ pt:int ] " + assert _is_valid_set(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "pt:frozenset[pt:int]" + assert _is_valid_set(invalid_content_type_1) is False + + invalid_content_type_2 = "set[pt:int]" + assert _is_valid_set(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: set[pt:int]" + assert _is_valid_set(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:set[integer]" + assert _is_valid_set(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:set[int]" + assert _is_valid_set(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:set{int]" + assert _is_valid_set(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:set[pt:int, pt:str]" + assert _is_valid_set(invalid_content_type_7) is False + + invalid_content_type_8 = "pt:set[]" + assert _is_valid_set(invalid_content_type_8) is False + + invalid_content_type_9 = "pt:set[pt:list[pt:int, pt:list[pt:bool]]" + assert _is_valid_set(invalid_content_type_9) is False + + invalid_content_type_10 = "pt:set" + assert _is_valid_set(invalid_content_type_10) is False + + def test_is_valid_list(self): + """Test for the '_is_valid_list' method.""" + valid_content_type_1 = "pt:list[pt:bytes]" + assert _is_valid_list(valid_content_type_1) is True + + valid_content_type_2 = "pt:list[pt:int]" + assert _is_valid_list(valid_content_type_2) is True + + valid_content_type_3 = "pt:list[pt:float]" + assert _is_valid_list(valid_content_type_3) is True + + valid_content_type_4 = "pt:list[pt:bool]" + assert _is_valid_list(valid_content_type_4) is True + + valid_content_type_5 = "pt:list[pt:str]" + assert _is_valid_list(valid_content_type_5) is True + + valid_content_type_6 = " pt:list[ pt:bool ] " + assert _is_valid_list(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "pt:tuple[pt:bytes]" + assert _is_valid_list(invalid_content_type_1) is False + + invalid_content_type_2 = "list[pt:bool]" + assert _is_valid_list(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: list[pt:float]" + assert _is_valid_list(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:list[string]" + assert _is_valid_list(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:list[bool]" + assert _is_valid_list(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:list[bytes" + assert _is_valid_list(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:list[pt:float, pt:bool]" + assert _is_valid_list(invalid_content_type_7) is False + + invalid_content_type_8 = "pt:list[]" + assert _is_valid_list(invalid_content_type_8) is False + + invalid_content_type_9 = "pt:list[pt:set[pt:bool, pt:set[pt:str]]" + assert _is_valid_list(invalid_content_type_9) is False + + invalid_content_type_10 = "pt:list" + assert _is_valid_list(invalid_content_type_10) is False + + def test_is_valid_dict(self): + """Test for the '_is_valid_dict' method.""" + valid_content_type_1 = "pt:dict[pt:bytes, pt:int]" + assert _is_valid_dict(valid_content_type_1) is True + + valid_content_type_2 = "pt:dict[pt:int, pt:int]" + assert _is_valid_dict(valid_content_type_2) is True + + valid_content_type_3 = "pt:dict[pt:float, pt:str]" + assert _is_valid_dict(valid_content_type_3) is True + + valid_content_type_4 = "pt:dict[pt:bool, pt:str]" + assert _is_valid_dict(valid_content_type_4) is True + + valid_content_type_5 = "pt:dict[pt:bool,pt:float]" + assert _is_valid_dict(valid_content_type_5) is True + + valid_content_type_6 = " pt:dict[ pt:bytes , pt:int ] " + assert _is_valid_dict(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "pt:map[pt:bool, pt:str]" + assert _is_valid_dict(invalid_content_type_1) is False + + invalid_content_type_2 = "dict[pt:int, pt:float]" + assert _is_valid_dict(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: dict[pt:bytes, pt:bool]" + assert _is_valid_dict(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:dict[float, pt:str]" + assert _is_valid_dict(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" + assert _is_valid_dict(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:dict(pt:boolean, pt:int" + assert _is_valid_dict(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:dict[pt:boolean]" + assert _is_valid_dict(invalid_content_type_7) is False + + invalid_content_type_8 = "pt:dict[]" + assert _is_valid_dict(invalid_content_type_8) is False + + invalid_content_type_9 = "pt:dict[pt:str, pt:float, pt:int, pt:bytes]" + assert _is_valid_dict(invalid_content_type_9) is False + + invalid_content_type_10 = "pt:dict[pt:set[pt:bool, pt:str]" + assert _is_valid_dict(invalid_content_type_10) is False + + invalid_content_type_11 = "pt:dict" + assert _is_valid_dict(invalid_content_type_11) is False + + def test_is_valid_union(self): + """Test for the '_is_valid_union' method.""" + valid_content_type_1 = ( + "pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:bytes], " + "pt:set[pt:int], pt:set[pt:float], pt:set[pt:bool], pt:set[pt:str], " + "pt:list[pt:bytes], pt:list[pt:int], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:str], " + "pt:dict[pt:bytes, pt:bytes], pt:dict[ pt:bytes , pt:int ] , pt:dict[pt:bytes, pt:float], pt:dict[pt:bytes, pt:bool], pt:dict[pt:bytes, pt:str], " + "pt:dict[pt:int, pt:bytes], pt:dict[pt:int, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:int, pt:bool], pt:dict[pt:int, pt:str], " + "pt:dict[pt:float, pt:bytes], pt:dict[pt:float, pt:int], pt:dict[pt:float, pt:float], pt:dict[pt:float, pt:bool], pt:dict[pt:float, pt:str], " + "pt:dict[pt:bool, pt:bytes], pt:dict[pt:bool, pt:int], pt:dict[pt:bool,pt:float], pt:dict[pt:bool, pt:bool], pt:dict[pt:bool, pt:str], " + "pt:dict[pt:str, pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:str,pt:float], pt:dict[pt:str, pt:bool], pt:dict[pt:str, pt:str]]" + ) + assert _is_valid_union(valid_content_type_1) is True + + valid_content_type_2 = "pt:union[pt:bytes, pt:set[pt:int]]" + assert _is_valid_union(valid_content_type_2) is True + + valid_content_type_3 = "pt:union[pt:float, pt:bool]" + assert _is_valid_union(valid_content_type_3) is True + + valid_content_type_4 = "pt:union[pt:set[pt:int], pt:set[pt:float]]" + assert _is_valid_union(valid_content_type_4) is True + + valid_content_type_5 = "pt:union[pt:bool,pt:bytes]" + assert _is_valid_union(valid_content_type_5) is True + + valid_content_type_6 = " pt:union[ pt:bytes , pt:set[ pt:int ] ] " + assert _is_valid_union(valid_content_type_6) is True + + ################################################### + + invalid_content_type_1 = "pt:onion[pt:bool, pt:str]" + assert _is_valid_union(invalid_content_type_1) is False + + invalid_content_type_2 = "union[pt:int, pt:float]" + assert _is_valid_union(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: union[pt:set[pt:int], pt:bool]" + assert _is_valid_union(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:union[float, pt:str" + assert _is_valid_union(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:union[pt:int, pt:dict[pt:str, pt:bool]" + assert _is_valid_union(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" + assert _is_valid_union(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:union[pt:boolean]" + assert _is_valid_union(invalid_content_type_7) is False + + invalid_content_type_8 = "pt:union[]" + assert _is_valid_union(invalid_content_type_8) is False + + invalid_content_type_9 = "pt:union[pt:str, pt:int, pt:str]" + assert _is_valid_union(invalid_content_type_9) is False + + invalid_content_type_10 = "pt:union[pt:set[pt:integer], pt:float]" + assert _is_valid_union(invalid_content_type_10) is False + + invalid_content_type_11 = ( + "pt:union[pt:dict[pt:set[pt:bool]], pt:list[pt:set[pt:str]]]" + ) + assert _is_valid_union(invalid_content_type_11) is False + + invalid_content_type_12 = "pt:union" + assert _is_valid_union(invalid_content_type_12) is False + + def test_is_valid_optional(self): + """Test for the '_is_valid_optional' method.""" + valid_content_type_1 = ( + "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:bytes], " + "pt:set[pt:int], pt:set[pt:float], pt:set[pt:bool], pt:set[pt:str], " + "pt:list[pt:bytes], pt:list[pt:int], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:str], " + "pt:dict[pt:bytes, pt:bytes], pt:dict[ pt:bytes , pt:int ] , pt:dict[pt:bytes, pt:float], pt:dict[pt:bytes, pt:bool], pt:dict[pt:bytes, pt:str], " + "pt:dict[pt:int, pt:bytes], pt:dict[pt:int, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:int, pt:bool], pt:dict[pt:int, pt:str], " + "pt:dict[pt:float, pt:bytes], pt:dict[pt:float, pt:int], pt:dict[pt:float, pt:float], pt:dict[pt:float, pt:bool], pt:dict[pt:float, pt:str], " + "pt:dict[pt:bool, pt:bytes], pt:dict[pt:bool, pt:int], pt:dict[pt:bool,pt:float], pt:dict[pt:bool, pt:bool], pt:dict[pt:bool, pt:str], " + "pt:dict[pt:str, pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:str,pt:float], pt:dict[pt:str, pt:bool], pt:dict[pt:str, pt:str]]]" + ) + assert _is_valid_optional(valid_content_type_1) is True + + valid_content_type_2 = "pt:optional[pt:union[pt:bytes, pt:set[pt:int]]]" + assert _is_valid_optional(valid_content_type_2) is True + + valid_content_type_3 = "pt:optional[pt:bytes]" + assert _is_valid_optional(valid_content_type_3) is True + + valid_content_type_4 = "pt:optional[pt:int]" + assert _is_valid_optional(valid_content_type_4) is True + + valid_content_type_5 = "pt:optional[pt:float]" + assert _is_valid_optional(valid_content_type_5) is True + + valid_content_type_6 = "pt:optional[pt:bool]" + assert _is_valid_optional(valid_content_type_6) is True + + valid_content_type_7 = "pt:optional[pt:str]" + assert _is_valid_optional(valid_content_type_7) is True + + valid_content_type_8 = "pt:optional[pt:set[pt:bytes]]" + assert _is_valid_optional(valid_content_type_8) is True + + valid_content_type_9 = "pt:optional[pt:list[pt:int]]" + assert _is_valid_optional(valid_content_type_9) is True + + valid_content_type_10 = ( + " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " + ) + assert _is_valid_optional(valid_content_type_10) is True + + ################################################### + + invalid_content_type_1 = "pt:optinal[pt:bytes]" + assert _is_valid_optional(invalid_content_type_1) is False + + invalid_content_type_2 = "optional[pt:int]" + assert _is_valid_optional(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: optional[pt:float]" + assert _is_valid_optional(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:optional[bool]" + assert _is_valid_optional(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:optional[pt:str" + assert _is_valid_optional(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:optional{pt:set[pt:int]]" + assert _is_valid_optional(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:optional[pt:string]" + assert _is_valid_optional(invalid_content_type_7) is False + + invalid_content_type_8 = "pt:optional[]" + assert _is_valid_optional(invalid_content_type_8) is False + + invalid_content_type_9 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" + assert _is_valid_optional(invalid_content_type_9) is False + + invalid_content_type_10 = "pt:optional[pt:list[pt:boolean]]" + assert _is_valid_optional(invalid_content_type_10) is False + + invalid_content_type_11 = "pt:optional[pt:dict[pt:set[pt:int]]]" + assert _is_valid_optional(invalid_content_type_11) is False + + invalid_content_type_12 = "pt:optional" + assert _is_valid_optional(invalid_content_type_12) is False + + def test_is_valid_content_type_format(self): + """Test for the '_is_valid_content_type_format' method.""" + valid_content_type_1 = "ct:DataModel" + assert _is_valid_content_type_format(valid_content_type_1) is True + + valid_content_type_2 = "pt:int" + assert _is_valid_content_type_format(valid_content_type_2) is True + + valid_content_type_3 = "pt:set[pt:float]" + assert _is_valid_content_type_format(valid_content_type_3) is True + + valid_content_type_4 = "pt:list[pt:bool]" + assert _is_valid_content_type_format(valid_content_type_4) is True + + valid_content_type_5 = "pt:dict[pt:bool,pt:float]" + assert _is_valid_content_type_format(valid_content_type_5) is True + + valid_content_type_6 = ( + "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:bytes], " + "pt:set[pt:int], pt:set[pt:float], pt:set[pt:bool], pt:set[pt:str], " + "pt:list[pt:bytes], pt:list[pt:int], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:str], " + "pt:dict[pt:bytes, pt:bytes], pt:dict[ pt:bytes , pt:int ] , pt:dict[pt:bytes, pt:float], pt:dict[pt:bytes, pt:bool], pt:dict[pt:bytes, pt:str], " + "pt:dict[pt:int, pt:bytes], pt:dict[pt:int, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:int, pt:bool], pt:dict[pt:int, pt:str], " + "pt:dict[pt:float, pt:bytes], pt:dict[pt:float, pt:int], pt:dict[pt:float, pt:float], pt:dict[pt:float, pt:bool], pt:dict[pt:float, pt:str], " + "pt:dict[pt:bool, pt:bytes], pt:dict[pt:bool, pt:int], pt:dict[pt:bool,pt:float], pt:dict[pt:bool, pt:bool], pt:dict[pt:bool, pt:str], " + "pt:dict[pt:str, pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:str,pt:float], pt:dict[pt:str, pt:bool], pt:dict[pt:str, pt:str]]]" + ) + assert _is_valid_content_type_format(valid_content_type_6) is True + + valid_content_type_7 = ( + " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " + ) + assert _is_valid_content_type_format(valid_content_type_7) is True + + ################################################### + + invalid_content_type_1 = "ct:data" + assert _is_valid_content_type_format(invalid_content_type_1) is False + + invalid_content_type_2 = "bool" + assert _is_valid_content_type_format(invalid_content_type_2) is False + + invalid_content_type_3 = "pt: set[pt:int]" + assert _is_valid_content_type_format(invalid_content_type_3) is False + + invalid_content_type_4 = "pt:list[string]" + assert _is_valid_content_type_format(invalid_content_type_4) is False + + invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" + assert _is_valid_content_type_format(invalid_content_type_5) is False + + invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" + assert _is_valid_content_type_format(invalid_content_type_6) is False + + invalid_content_type_7 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" + assert _is_valid_content_type_format(invalid_content_type_7) is False + + def test_validate_performatives(self): + """Test for the '_validate_performatives' method.""" + valid_content_type_1 = "offer" + valid_result_1, valid_msg_1 = _validate_performatives(valid_content_type_1) + assert valid_result_1 is True + assert valid_msg_1 == "Performative '{}' is valid.".format(valid_content_type_1) + + valid_content_type_2 = "send_HTTP_message" + valid_result_2, valid_msg_2 = _validate_performatives(valid_content_type_2) + assert valid_result_2 is True + assert valid_msg_2 == "Performative '{}' is valid.".format(valid_content_type_2) + + valid_content_type_3 = "request_2PL" + valid_result_3, valid_msg_3 = _validate_performatives(valid_content_type_3) + assert valid_result_3 is True + assert valid_msg_3 == "Performative '{}' is valid.".format(valid_content_type_3) + + valid_content_type_4 = "argue" + valid_result_4, valid_msg_4 = _validate_performatives(valid_content_type_4) + assert valid_result_4 is True + assert valid_msg_4 == "Performative '{}' is valid.".format(valid_content_type_4) + + ################################################### + + invalid_content_type_1 = "_offer" + invalid_result_1, invalid_msg_1 = _validate_performatives( + invalid_content_type_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_content_type_1, PERFORMATIVE_REGEX_PATTERN + ) + ) + + invalid_content_type_2 = "request_" + invalid_result_2, invalid_msg_2 = _validate_performatives( + invalid_content_type_2 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_content_type_2, PERFORMATIVE_REGEX_PATTERN + ) + ) + + invalid_content_type_3 = "_query_" + invalid_result_3, invalid_msg_3 = _validate_performatives( + invalid_content_type_3 + ) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_content_type_3, PERFORMATIVE_REGEX_PATTERN + ) + ) + + invalid_content_type_4 = "$end" + invalid_result_4, invalid_msg_4 = _validate_performatives( + invalid_content_type_4 + ) + assert invalid_result_4 is False + assert ( + invalid_msg_4 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_content_type_4, PERFORMATIVE_REGEX_PATTERN + ) + ) + + invalid_content_type_5 = "create()" + invalid_result_5, invalid_msg_5 = _validate_performatives( + invalid_content_type_5 + ) + assert invalid_result_5 is False + assert ( + invalid_msg_5 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_content_type_5, PERFORMATIVE_REGEX_PATTERN + ) + ) + + invalid_content_type_6 = "body" + invalid_result_6, invalid_msg_6 = _validate_performatives( + invalid_content_type_6 + ) + assert invalid_result_6 is False + assert ( + invalid_msg_6 + == "Invalid name for performative '{}'. This name is reserved.".format( + invalid_content_type_6, + ) + ) + + invalid_content_type_7 = "message_id" + invalid_result_7, invalid_msg_7 = _validate_performatives( + invalid_content_type_7 + ) + assert invalid_result_7 is False + assert ( + invalid_msg_6 + == "Invalid name for performative '{}'. This name is reserved.".format( + invalid_content_type_6, + ) + ) + + def test_validate_content_name(self): + """Test for the '_validate_content_name' method.""" + performative = "some_performative" + + valid_content_type_1 = "content" + valid_result_1, valid_msg_1 = _validate_content_name( + valid_content_type_1, performative + ) + assert valid_result_1 is True + assert valid_msg_1 == "Content name '{}' of performative '{}' is valid.".format( + valid_content_type_1, performative + ) + + valid_content_type_2 = "HTTP_msg_name" + valid_result_2, valid_msg_2 = _validate_content_name( + valid_content_type_2, performative + ) + assert valid_result_2 is True + assert valid_msg_2 == "Content name '{}' of performative '{}' is valid.".format( + valid_content_type_2, performative + ) + + valid_content_type_3 = "number_of_3PLs" + valid_result_3, valid_msg_3 = _validate_content_name( + valid_content_type_3, performative + ) + assert valid_result_3 is True + assert valid_msg_3 == "Content name '{}' of performative '{}' is valid.".format( + valid_content_type_3, performative + ) + + valid_content_type_4 = "model" + valid_result_4, valid_msg_4 = _validate_content_name( + valid_content_type_4, performative + ) + assert valid_result_4 is True + assert valid_msg_4 == "Content name '{}' of performative '{}' is valid.".format( + valid_content_type_4, performative + ) + + ################################################### + + invalid_content_type_1 = "_content" + invalid_result_1, invalid_msg_1 = _validate_content_name( + invalid_content_type_1, performative + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( + invalid_content_type_1, performative, CONTENT_NAME_REGEX_PATTERN + ) + ) + + invalid_content_type_2 = "content_" + invalid_result_2, invalid_msg_2 = _validate_content_name( + invalid_content_type_2, performative + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( + invalid_content_type_2, performative, CONTENT_NAME_REGEX_PATTERN + ) + ) + + invalid_content_type_3 = "_content_" + invalid_result_3, invalid_msg_3 = _validate_content_name( + invalid_content_type_3, performative + ) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( + invalid_content_type_3, performative, CONTENT_NAME_REGEX_PATTERN + ) + ) + + invalid_content_type_4 = "con^en^" + invalid_result_4, invalid_msg_4 = _validate_content_name( + invalid_content_type_4, performative + ) + assert invalid_result_4 is False + assert ( + invalid_msg_4 + == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( + invalid_content_type_4, performative, CONTENT_NAME_REGEX_PATTERN + ) + ) + + invalid_content_type_5 = "some_content()" + invalid_result_5, invalid_msg_5 = _validate_content_name( + invalid_content_type_5, performative + ) + assert invalid_result_5 is False + assert ( + invalid_msg_5 + == "Invalid name for content '{}' of performative '{}'. Content names must match the following regular expression: {} ".format( + invalid_content_type_5, performative, CONTENT_NAME_REGEX_PATTERN + ) + ) + + invalid_content_type_6 = "target" + invalid_result_6, invalid_msg_6 = _validate_content_name( + invalid_content_type_6, performative + ) + assert invalid_result_6 is False + assert ( + invalid_msg_6 + == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( + invalid_content_type_6, performative, + ) + ) + + invalid_content_type_7 = "performative" + invalid_result_7, invalid_msg_7 = _validate_content_name( + invalid_content_type_7, performative + ) + assert invalid_result_7 is False + assert ( + invalid_msg_7 + == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( + invalid_content_type_7, performative, + ) + ) + + def test_validate_content_type(self): + """Test for the '_validate_content_type' method.""" + performative = "some_performative" + content_name = "some_content_name" + + valid_content_type_1 = "ct:DataModel" + valid_result_1, valid_msg_1 = _validate_content_type( + valid_content_type_1, content_name, performative + ) + assert valid_result_1 is True + assert ( + valid_msg_1 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_2 = "pt:int" + valid_result_2, valid_msg_2 = _validate_content_type( + valid_content_type_2, content_name, performative + ) + assert valid_result_2 is True + assert ( + valid_msg_2 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_3 = "pt:set[pt:float]" + valid_result_3, valid_msg_3 = _validate_content_type( + valid_content_type_3, content_name, performative + ) + assert valid_result_3 is True + assert ( + valid_msg_3 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_4 = "pt:list[pt:bool]" + valid_result_4, valid_msg_4 = _validate_content_type( + valid_content_type_4, content_name, performative + ) + assert valid_result_4 is True + assert ( + valid_msg_4 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_5 = "pt:dict[pt:bool,pt:float]" + valid_result_5, valid_msg_5 = _validate_content_type( + valid_content_type_5, content_name, performative + ) + assert valid_result_5 is True + assert ( + valid_msg_5 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_6 = ( + "pt:optional[pt:union[pt:bytes, pt:int, pt:float, pt:bool, pt:str, pt:set[pt:bytes], " + "pt:set[pt:int], pt:set[pt:float], pt:set[pt:bool], pt:set[pt:str], " + "pt:list[pt:bytes], pt:list[pt:int], pt:list[pt:float], pt:list[pt:bool], pt:list[pt:str], " + "pt:dict[pt:bytes, pt:bytes], pt:dict[ pt:bytes , pt:int ] , pt:dict[pt:bytes, pt:float], pt:dict[pt:bytes, pt:bool], pt:dict[pt:bytes, pt:str], " + "pt:dict[pt:int, pt:bytes], pt:dict[pt:int, pt:int], pt:dict[pt:int, pt:float], pt:dict[pt:int, pt:bool], pt:dict[pt:int, pt:str], " + "pt:dict[pt:float, pt:bytes], pt:dict[pt:float, pt:int], pt:dict[pt:float, pt:float], pt:dict[pt:float, pt:bool], pt:dict[pt:float, pt:str], " + "pt:dict[pt:bool, pt:bytes], pt:dict[pt:bool, pt:int], pt:dict[pt:bool,pt:float], pt:dict[pt:bool, pt:bool], pt:dict[pt:bool, pt:str], " + "pt:dict[pt:str, pt:bytes], pt:dict[pt:str, pt:int], pt:dict[pt:str,pt:float], pt:dict[pt:str, pt:bool], pt:dict[pt:str, pt:str]]]" + ) + valid_result_6, valid_msg_6 = _validate_content_type( + valid_content_type_6, content_name, performative + ) + assert valid_result_6 is True + assert ( + valid_msg_6 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + valid_content_type_7 = ( + " pt:optional[ pt:dict[ pt:float , pt:bool ] ] " + ) + valid_result_7, valid_msg_7 = _validate_content_type( + valid_content_type_7, content_name, performative + ) + assert valid_result_7 is True + assert ( + valid_msg_7 + == "Type of content '{}' of performative '{}' is valid.".format( + content_name, performative + ) + ) + + ################################################### + + invalid_content_type_1 = "ct:data" + invalid_result_1, invalid_msg_1 = _validate_content_type( + invalid_content_type_1, content_name, performative + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_2 = "bool" + invalid_result_2, invalid_msg_2 = _validate_content_type( + invalid_content_type_2, content_name, performative + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_3 = "pt: set[pt:int]" + invalid_result_3, invalid_msg_3 = _validate_content_type( + invalid_content_type_3, content_name, performative + ) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_4 = "pt:list[string]" + invalid_result_4, invalid_msg_4 = _validate_content_type( + invalid_content_type_4, content_name, performative + ) + assert invalid_result_4 is False + assert ( + invalid_msg_4 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_5 = "pt:dict[pt:bool, pt:integer]" + invalid_result_5, invalid_msg_5 = _validate_content_type( + invalid_content_type_5, content_name, performative + ) + assert invalid_result_5 is False + assert ( + invalid_msg_5 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_6 = "pt:union{pt:boolean, pt:int]" + invalid_result_6, invalid_msg_6 = _validate_content_type( + invalid_content_type_6, content_name, performative + ) + assert invalid_result_6 is False + assert ( + invalid_msg_6 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + invalid_content_type_7 = "pt:optional[pt:str, pt:int, pt:list[pt:bool]]" + invalid_result_7, invalid_msg_7 = _validate_content_type( + invalid_content_type_7, content_name, performative + ) + assert invalid_result_7 is False + assert ( + invalid_msg_7 + == "Invalid type for content '{}' of performative '{}'. See documentation for the correct format of specification types.".format( + content_name, performative, + ) + ) + + @mock.patch("aea.configurations.base.ProtocolSpecification",) + def test_validate_speech_acts_section(self, mocked_spec): + """Test for the '_validate_speech_acts_section' method.""" + valid_speech_act_content_config_1 = SpeechActContentConfig( + content_1="ct:CustomType", content_2="pt:int" + ) + valid_speech_act_content_config_2 = SpeechActContentConfig( + content_3="ct:DataModel" + ) + valid_speech_act_content_config_3 = SpeechActContentConfig() + + speech_act_1 = CRUDCollection() + speech_act_1.create("perm_1", valid_speech_act_content_config_1) + speech_act_1.create("perm_2", valid_speech_act_content_config_2) + speech_act_1.create("perm_3", valid_speech_act_content_config_3) + + mocked_spec.speech_acts = speech_act_1 + + ( + valid_result_1, + valid_msg_1, + valid_all_per_1, + valid_all_content_1, + ) = _validate_speech_acts_section(mocked_spec) + assert valid_result_1 is True + assert valid_msg_1 == "Speech-acts are valid." + assert valid_all_per_1 == {"perm_1", "perm_2", "perm_3"} + assert valid_all_content_1 == {"ct:CustomType", "ct:DataModel"} + + ################################################### + + speech_act_3 = CRUDCollection() + invalid_perm = "_query_" + speech_act_3.create(invalid_perm, valid_speech_act_content_config_1) + + mocked_spec.speech_acts = speech_act_3 + + ( + invalid_result_1, + invalid_msg_1, + invalid_all_per_1, + invalid_all_content_1, + ) = _validate_speech_acts_section(mocked_spec) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Invalid name for performative '{}'. Performative names must match the following regular expression: {} ".format( + invalid_perm, PERFORMATIVE_REGEX_PATTERN + ) + ) + assert invalid_all_per_1 is None + assert invalid_all_content_1 is None + + invalid_speech_act_content_config_1 = SpeechActContentConfig(target="pt:int") + speech_act_4 = CRUDCollection() + valid_perm = "perm_1" + speech_act_4.create(valid_perm, invalid_speech_act_content_config_1) + + mocked_spec.speech_acts = speech_act_4 + + ( + invalid_result_2, + invalid_msg_2, + invalid_all_per_2, + invalid_all_content_2, + ) = _validate_speech_acts_section(mocked_spec) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Invalid name for content '{}' of performative '{}'. This name is reserved.".format( + "target", valid_perm, + ) + ) + assert invalid_all_per_2 is None + assert invalid_all_content_2 is None + + invalid_speech_act_content_config_2 = SpeechActContentConfig( + conten_name_1="pt: set[pt:int]" + ) + speech_act_5 = CRUDCollection() + valid_perm = "perm_1" + speech_act_5.create(valid_perm, invalid_speech_act_content_config_2) + + mocked_spec.speech_acts = speech_act_5 + + ( + invalid_result_3, + invalid_msg_3, + invalid_all_per_3, + invalid_all_content_3, + ) = _validate_speech_acts_section(mocked_spec) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Invalid type for content 'conten_name_1' of performative '{}'. See documentation for the correct format of specification types.".format( + valid_perm, + ) + ) + assert invalid_all_per_3 is None + assert invalid_all_content_3 is None + + speech_act_6 = CRUDCollection() + mocked_spec.speech_acts = speech_act_6 + + ( + invalid_result_4, + invalid_msg_4, + invalid_all_per_4, + invalid_all_content_4, + ) = _validate_speech_acts_section(mocked_spec) + assert invalid_result_4 is False + assert invalid_msg_4 == "Speech-acts cannot be empty!" + assert invalid_all_per_4 is None + assert invalid_all_content_4 is None + + @mock.patch("aea.configurations.base.ProtocolSpecification",) + def test_validate_protocol_buffer_schema_code_snippets(self, mocked_spec): + """Test for the '_validate_protocol_buffer_schema_code_snippets' method.""" + valid_protobuf_snippet_1 = { + "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;\nbool bool_field = 4;\nstring str_field = 5;\nrepeated int32 set_field = 6;\nrepeated string list_field = 7;\nmap dict_field = 8;\n" + } + valid_all_content_1 = {"ct:DataModel"} + mocked_spec.protobuf_snippets = valid_protobuf_snippet_1 + + valid_result_1, valid_msg_1, = _validate_protocol_buffer_schema_code_snippets( + mocked_spec, valid_all_content_1 + ) + assert valid_result_1 is True + assert valid_msg_1 == "Protobuf code snippet section is valid." + + valid_protobuf_snippet_2 = {} + valid_all_content_2 = set() + mocked_spec.protobuf_snippets = valid_protobuf_snippet_2 + + valid_result_2, valid_msg_2, = _validate_protocol_buffer_schema_code_snippets( + mocked_spec, valid_all_content_2 + ) + assert valid_result_2 is True + assert valid_msg_2 == "Protobuf code snippet section is valid." + + ################################################### + + invalid_protobuf_snippet_1 = { + "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;\nbool bool_field = 4;\nstring str_field = 5;", + "ct:Query": "bytes bytes_field = 1;", + } + invalid_all_content_1 = {"ct:DataModel"} + mocked_spec.protobuf_snippets = invalid_protobuf_snippet_1 + + ( + invalid_result_1, + invalid_msg_1, + ) = _validate_protocol_buffer_schema_code_snippets( + mocked_spec, invalid_all_content_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Extra protobuf code snippet provided. Type 'ct:Query' is not used anywhere in your protocol definition." + ) + + invalid_protobuf_snippet_2 = { + "ct:DataModel": "bytes bytes_field = 1;\nint32 int_field = 2;\nfloat float_field = 3;", + } + invalid_all_content_2 = {"ct:DataModel", "ct:Frame"} + mocked_spec.protobuf_snippets = invalid_protobuf_snippet_2 + + ( + invalid_result_2, + invalid_msg_2, + ) = _validate_protocol_buffer_schema_code_snippets( + mocked_spec, invalid_all_content_2 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "No protobuf code snippet is provided for the following custom types: {}".format( + {"ct:Frame"}, + ) + ) + + def test_validate_initiation(self): + """Test for the '_validate_initiation' method.""" + valid_initiation_1 = ["perm_1", "perm_2"] + valid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4"} + valid_result_1, valid_msg_1 = _validate_initiation( + valid_initiation_1, valid_performatives_set_1 + ) + assert valid_result_1 is True + assert valid_msg_1 == "Initial messages are valid." + + ################################################### + + invalid_initiation_1 = [] + invalid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4"} + invalid_result_1, invalid_msg_1 = _validate_initiation( + invalid_initiation_1, invalid_performatives_set_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "At least one initial performative for this dialogue must be specified." + ) + + invalid_initiation_2 = ["perm_5"] + invalid_performatives_set_2 = {"perm_1", "perm_2", "perm_3", "perm_4"} + invalid_result_2, invalid_msg_2 = _validate_initiation( + invalid_initiation_2, invalid_performatives_set_2 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Performative 'perm_5' specified in \"initiation\" is not defined in the protocol's speech-acts." + ) + + def test_validate_reply(self): + """Test for the '_validate_reply' method.""" + valid_reply_1 = { + "performative_ct": ["performative_pct"], + "performative_pt": ["performative_pmt"], + "performative_pct": ["performative_mt", "performative_o"], + "performative_pmt": ["performative_mt", "performative_o"], + "performative_mt": [], + "performative_o": [], + "performative_empty_contents": ["performative_empty_contents"], + } + valid_performatives_set_1 = { + "performative_ct", + "performative_pt", + "performative_pct", + "performative_pmt", + "performative_mt", + "performative_o", + "performative_empty_contents", + } + + valid_result_1, valid_msg_1, = _validate_reply( + valid_reply_1, valid_performatives_set_1 + ) + assert valid_result_1 is True + assert valid_msg_1 == "Reply structure is valid." + + ################################################### + + invalid_reply_1 = { + "perm_1": ["perm_2"], + "perm_2": ["perm_3"], + "perm_3": ["perm_4"], + "perm_4": [], + } + invalid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4", "perm_5"} + + invalid_result_1, invalid_msg_1, = _validate_reply( + invalid_reply_1, invalid_performatives_set_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "No reply is provided for the following performatives: {}".format( + {"perm_5"}, + ) + ) + + invalid_reply_2 = { + "perm_1": ["perm_2"], + "perm_2": ["perm_3"], + "perm_3": ["perm_4"], + "perm_4": ["perm_5"], + "perm_5": [], + } + invalid_performatives_set_2 = {"perm_1", "perm_2", "perm_3", "perm_4"} + + invalid_result_2, invalid_msg_2, = _validate_reply( + invalid_reply_2, invalid_performatives_set_2 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Performative 'perm_5' specified in \"reply\" is not defined in the protocol's speech-acts." + ) + + def test_validate_termination(self): + """Test for the '_validate_termination' method.""" + valid_termination_1 = ["perm_4", "perm_3"] + valid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4"} + valid_result_1, valid_msg_1 = _validate_termination( + valid_termination_1, valid_performatives_set_1 + ) + assert valid_result_1 is True + assert valid_msg_1 == "Terminal messages are valid." + + ################################################### + + invalid_termination_1 = [] + invalid_performatives_set_1 = {"perm_1", "perm_2", "perm_3", "perm_4"} + invalid_result_1, invalid_msg_1 = _validate_termination( + invalid_termination_1, invalid_performatives_set_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "At least one terminal performative for this dialogue must be specified." + ) + + invalid_termination_2 = ["perm_5"] + invalid_performatives_set_2 = {"perm_1", "perm_2", "perm_3", "perm_4"} + invalid_result_2, invalid_msg_2 = _validate_termination( + invalid_termination_2, invalid_performatives_set_2 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Performative 'perm_5' specified in \"termination\" is not defined in the protocol's speech-acts." + ) + + def test_validate_roles(self): + """Test for the '_validate_roles' method.""" + valid_roles_1 = {"role_1", "role_2"} + valid_result_1, valid_msg_1 = _validate_roles(valid_roles_1) + assert valid_result_1 is True + assert valid_msg_1 == "Dialogue roles are valid." + + valid_roles_2 = {"role_1"} + valid_result_2, valid_msg_2 = _validate_roles(valid_roles_2) + assert valid_result_2 is True + assert valid_msg_2 == "Dialogue roles are valid." + + ################################################### + + invalid_roles_1 = set() + invalid_result_1, invalid_msg_1 = _validate_roles(invalid_roles_1) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "There must be either 1 or 2 roles defined in this dialogue. Found 0" + ) + + invalid_roles_2 = {"role_1", "role_2", "role_3"} + invalid_result_2, invalid_msg_2 = _validate_roles(invalid_roles_2) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "There must be either 1 or 2 roles defined in this dialogue. Found 3" + ) + + invalid_roles_3 = {"_agent_"} + invalid_result_3, invalid_msg_3 = _validate_roles(invalid_roles_3) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Invalid name for role '_agent_'. Role names must match the following regular expression: {} ".format( + ROLE_REGEX_PATTERN + ) + ) + + def test_validate_end_states(self): + """Test for the '_validate_end_states' method.""" + valid_end_states_1 = ["end_state_1", "end_state_2"] + valid_result_1, valid_msg_1 = _validate_end_states(valid_end_states_1) + assert valid_result_1 is True + assert valid_msg_1 == "Dialogue end_states are valid." + + valid_end_states_2 = [] + valid_result_2, valid_msg_2 = _validate_end_states(valid_end_states_2) + assert valid_result_2 is True + assert valid_msg_2 == "Dialogue end_states are valid." + + ################################################### + + invalid_end_states_1 = ["_end_state_1"] + invalid_result_1, invalid_msg_1 = _validate_end_states(invalid_end_states_1) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Invalid name for end_state '_end_state_1'. End_state names must match the following regular expression: {} ".format( + END_STATE_REGEX_PATTERN + ) + ) + + invalid_end_states_2 = ["end_$tate_1"] + invalid_result_2, invalid_msg_2 = _validate_end_states(invalid_end_states_2) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "Invalid name for end_state 'end_$tate_1'. End_state names must match the following regular expression: {} ".format( + END_STATE_REGEX_PATTERN + ) + ) + + @mock.patch("aea.configurations.base.ProtocolSpecification",) + def test_validate_dialogue_section(self, mocked_spec): + """Test for the '_validate_dialogue_section' method.""" + valid_dialogue_config_1 = { + "initiation": ["performative_ct", "performative_pt"], + "reply": { + "performative_ct": ["performative_pct"], + "performative_pt": ["performative_pmt"], + "performative_pct": ["performative_mt", "performative_o"], + "performative_pmt": ["performative_mt", "performative_o"], + "performative_mt": [], + "performative_o": [], + "performative_empty_contents": ["performative_empty_contents"], + }, + "termination": [ + "performative_mt", + "performative_o", + "performative_empty_contents", + ], + "roles": {"role_1": None, "role_2": None}, + "end_states": ["end_state_1", "end_state_2", "end_state_3"], + } + valid_performatives_set_1 = { + "performative_ct", + "performative_pt", + "performative_pct", + "performative_pmt", + "performative_mt", + "performative_o", + "performative_empty_contents", + } + mocked_spec.dialogue_config = valid_dialogue_config_1 + + valid_result_1, valid_msg_1, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert valid_result_1 is True + assert valid_msg_1 == "Dialogue section of the protocol specification is valid." + + ################################################### + + invalid_dialogue_config_1 = valid_dialogue_config_1.copy() + invalid_dialogue_config_1["initiation"] = ["new_performative"] + + mocked_spec.dialogue_config = invalid_dialogue_config_1 + + invalid_result_1, invalid_msg_1, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert invalid_result_1 is False + assert ( + invalid_msg_1 + == "Performative 'new_performative' specified in \"initiation\" is not defined in the protocol's speech-acts." + ) + + invalid_dialogue_config_2 = valid_dialogue_config_1.copy() + invalid_dialogue_config_2["reply"] = { + "performative_ct": ["performative_pct"], + "performative_pt": ["performative_pmt"], + "performative_pct": ["performative_mt", "performative_o"], + "performative_pmt": ["performative_mt", "performative_o"], + "performative_mt": [], + "performative_o": [], + } + + mocked_spec.dialogue_config = invalid_dialogue_config_2 + + invalid_result_2, invalid_msg_2, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert invalid_result_2 is False + assert ( + invalid_msg_2 + == "No reply is provided for the following performatives: {}".format( + {"performative_empty_contents"}, + ) + ) + + invalid_dialogue_config_3 = valid_dialogue_config_1.copy() + invalid_dialogue_config_3["termination"] = ["new_performative"] + + mocked_spec.dialogue_config = invalid_dialogue_config_3 + + invalid_result_3, invalid_msg_3, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert invalid_result_3 is False + assert ( + invalid_msg_3 + == "Performative 'new_performative' specified in \"termination\" is not defined in the protocol's speech-acts." + ) + + invalid_dialogue_config_4 = valid_dialogue_config_1.copy() + invalid_dialogue_config_4["roles"] = { + "role_1": None, + "role_2": None, + "role_3": None, + } + + mocked_spec.dialogue_config = invalid_dialogue_config_4 + + invalid_result_4, invalid_msg_4, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert invalid_result_4 is False + assert ( + invalid_msg_4 + == "There must be either 1 or 2 roles defined in this dialogue. Found 3" + ) + + invalid_dialogue_config_5 = valid_dialogue_config_1.copy() + invalid_dialogue_config_5["end_states"] = ["end_$tate_1"] + + mocked_spec.dialogue_config = invalid_dialogue_config_5 + + invalid_result_5, invalid_msg_5, = _validate_dialogue_section( + mocked_spec, valid_performatives_set_1 + ) + assert invalid_result_5 is False + assert ( + invalid_msg_5 + == "Invalid name for end_state 'end_$tate_1'. End_state names must match the following regular expression: {} ".format( + END_STATE_REGEX_PATTERN + ) + ) + + @mock.patch("aea.configurations.base.ProtocolSpecification") + @mock.patch( + "aea.protocols.generator.validate._validate_speech_acts_section", + return_value=tuple([True, "Speech_acts are correct!", set(), set()]), + ) + @mock.patch( + "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", + return_value=tuple([True, "Protobuf snippets are correct!"]), + ) + @mock.patch( + "aea.protocols.generator.validate._validate_dialogue_section", + return_value=tuple([True, "Dialogue section is correct!"]), + ) + def test_validate_positive( + self, + mocked_spec, + macked_validate_speech_acts, + macked_validate_protobuf, + macked_validate_dialogue, + ): + """Positive test for the 'validate' method: invalid dialogue section.""" + valid_result_1, valid_msg_1, = validate(mocked_spec) + assert valid_result_1 is True + assert valid_msg_1 == "Protocol specification is valid." + + @mock.patch("aea.configurations.base.ProtocolSpecification") + @mock.patch( + "aea.protocols.generator.validate._validate_speech_acts_section", + return_value=tuple([False, "Some error on speech_acts.", None, None]), + ) + def test_validate_negative_invalid_speech_acts( + self, mocked_spec, macked_validate_speech_acts + ): + """Negative test for the 'validate' method: invalid speech_acts.""" + invalid_result_1, invalid_msg_1, = validate(mocked_spec) + assert invalid_result_1 is False + assert invalid_msg_1 == "Some error on speech_acts." + + @mock.patch("aea.configurations.base.ProtocolSpecification") + @mock.patch( + "aea.protocols.generator.validate._validate_speech_acts_section", + return_value=tuple([True, "Speech_acts are correct!", set(), set()]), + ) + @mock.patch( + "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", + return_value=tuple([False, "Some error on protobuf snippets."]), + ) + def test_validate_negative_invalid_protobuf_snippets( + self, mocked_spec, macked_validate_speech_acts, macked_validate_protobuf + ): + """Negative test for the 'validate' method: invalid protobuf snippets.""" + invalid_result_1, invalid_msg_1, = validate(mocked_spec) + assert invalid_result_1 is False + assert invalid_msg_1 == "Some error on protobuf snippets." + + @mock.patch("aea.configurations.base.ProtocolSpecification") + @mock.patch( + "aea.protocols.generator.validate._validate_speech_acts_section", + return_value=tuple([True, "Speech_acts are correct!", set(), set()]), + ) + @mock.patch( + "aea.protocols.generator.validate._validate_protocol_buffer_schema_code_snippets", + return_value=tuple([True, "Protobuf snippets are correct!"]), + ) + @mock.patch( + "aea.protocols.generator.validate._validate_dialogue_section", + return_value=tuple([False, "Some error on dialogue section."]), + ) + def test_validate_negative_invalid_dialogue_section( + self, + mocked_spec, + macked_validate_speech_acts, + macked_validate_protobuf, + macked_validate_dialogue, + ): + """Negative test for the 'validate' method: invalid dialogue section.""" + invalid_result_1, invalid_msg_1, = validate(mocked_spec) + assert invalid_result_1 is False + assert invalid_msg_1 == "Some error on dialogue section." diff --git a/tests/test_registries/test_base.py b/tests/test_registries/test_base.py index 1b5ea7ed80..e27399d787 100644 --- a/tests/test_registries/test_base.py +++ b/tests/test_registries/test_base.py @@ -80,7 +80,7 @@ def setup_class(cls): cls.registry = AgentComponentRegistry() cls.registry.register(contract.component_id, cast(Contract, contract)) cls.expected_contract_ids = { - PublicId.from_str("fetchai/erc1155:0.6.0"), + PublicId.from_str("fetchai/erc1155:0.7.0"), } def test_fetch_all(self): @@ -91,14 +91,14 @@ def test_fetch_all(self): def test_fetch(self): """Test that the `fetch` method works as expected.""" - contract_id = PublicId.from_str("fetchai/erc1155:0.6.0") + contract_id = PublicId.from_str("fetchai/erc1155:0.7.0") contract = self.registry.fetch(ComponentId(ComponentType.CONTRACT, contract_id)) assert isinstance(contract, Contract) assert contract.id == contract_id def test_unregister(self): """Test that the 'unregister' method works as expected.""" - contract_id_removed = PublicId.from_str("fetchai/erc1155:0.6.0") + contract_id_removed = PublicId.from_str("fetchai/erc1155:0.7.0") component_id = ComponentId(ComponentType.CONTRACT, contract_id_removed) contract_removed = self.registry.fetch(component_id) self.registry.unregister(contract_removed.component_id) @@ -153,7 +153,7 @@ def setup_class(cls): cls.expected_protocol_ids = { DEFAULT_PROTOCOL, - PublicId.from_str("fetchai/fipa:0.4.0"), + PublicId.from_str("fetchai/fipa:0.5.0"), } def test_fetch_all(self): @@ -244,7 +244,7 @@ def setup_class(cls): cls.error_skill_public_id = DEFAULT_SKILL cls.dummy_skill_public_id = PublicId.from_str("dummy_author/dummy:0.1.0") - cls.contract_public_id = PublicId.from_str("fetchai/erc1155:0.6.0") + cls.contract_public_id = PublicId.from_str("fetchai/erc1155:0.7.0") def test_unregister_handler(self): """Test that the unregister of handlers work correctly.""" @@ -390,24 +390,6 @@ def test_add_component_raises_error(self): with pytest.raises(ValueError): self.resources.add_component(a_component) - def test_inject_contracts_unknown_contract(self): - """Test inject contracts when there is a missing contract.""" - public_id = PublicId.from_str("author/name:0.1.0") - mock_skill = MagicMock(**{"config.contracts": {public_id}}) - with pytest.raises( - ValueError, match=f"Missing contract for contract id {public_id}" - ): - self.resources.inject_contracts(mock_skill) - - def test_inject_contracts(self): - """Test inject contracts.""" - with unittest.mock.patch.object( - self.resources._component_registry, "fetch", return_value=object() - ): - public_id = PublicId.from_str("author/name:0.1.0") - mock_skill = MagicMock(**{"config.contracts": {public_id}}) - self.resources.inject_contracts(mock_skill) - def test_register_behaviour_with_already_existing_skill_id(self): """Test that registering a behaviour with an already existing skill id behaves as expected.""" # this should raise an error, since the 'dummy" skill already has a behaviour named "dummy" @@ -535,11 +517,13 @@ def test_handle_internal_messages(self): """Test that the internal messages are handled.""" t = SigningMessage( performative=SigningMessage.Performative.SIGNED_TRANSACTION, - skill_callback_ids=[str(PublicId("dummy_author", "dummy", "0.1.0"))], + skill_callback_ids=(str(PublicId("dummy_author", "dummy", "0.1.0")),), skill_callback_info={}, - crypto_id="ledger_id", + # crypto_id="ledger_id", signed_transaction=SignedTransaction("ledger_id", "tx"), ) + t.counterparty = "skill" + t.sender = "decision_maker" self.aea.decision_maker.message_out_queue.put(t) self.aea._filter.handle_internal_messages() @@ -667,6 +651,30 @@ def setup_class(cls): """Set up the tests.""" cls.registry = HandlerRegistry() + def test_register_and_unregister_dynamically(self): + """Test register when protocol id is None.""" + assert len(self.registry._dynamically_added) == 0 + self.registry.register( + (PublicId.from_str("author/name:0.1.0"), "name"), + MagicMock(SUPPORTED_PROTOCOL="author/protocol:0.1.0"), + is_dynamically_added=True, + ) + assert len(self.registry._dynamically_added) == 1 + self.registry.unregister((PublicId.from_str("author/name:0.1.0"), "name"),) + assert len(self.registry._dynamically_added) == 0 + + def test_register_and_teardown_dynamically(self): + """Test register when protocol id is None.""" + assert len(self.registry._dynamically_added) == 0 + self.registry.register( + (PublicId.from_str("author/name:0.1.0"), "name"), + MagicMock(SUPPORTED_PROTOCOL="author/protocol:0.1.0"), + is_dynamically_added=True, + ) + assert len(self.registry._dynamically_added) == 1 + self.registry.teardown() + assert len(self.registry._dynamically_added) == 0 + def test_register_when_protocol_id_is_none(self): """Test register when protocol id is None.""" with pytest.raises( diff --git a/tests/test_registries/test_filter.py b/tests/test_registries/test_filter.py index bfbd93323a..ff5ed6cf5a 100644 --- a/tests/test_registries/test_filter.py +++ b/tests/test_registries/test_filter.py @@ -29,6 +29,7 @@ from aea.skills.base import Skill from tests.data.dummy_skill.behaviours import DummyBehaviour +from tests.data.dummy_skill.handlers import DummyHandler class TestFilter: @@ -142,6 +143,52 @@ def test_handle_internal_message_new_behaviours_with_error(self): # restore previous state self.resources.component_registry.unregister(skill.component_id) + def test_handle_internal_message_new_handlers(self): + """Test handle internal message when there are new handlers to register.""" + skill = Skill( + SkillConfig("name", "author", "0.1.0"), + handlers={}, + behaviours={}, + models={}, + ) + self.resources.add_skill(skill) + new_handler = DummyHandler(name="dummy2", skill_context=skill.skill_context) + skill.skill_context.new_handlers.put(new_handler) + self.filter.handle_internal_messages() + + assert self.decision_make_queue.empty() + assert len(self.resources.handler_registry.fetch_all()) == 1 + # restore previous state + self.resources.remove_skill(skill.public_id) + assert len(self.resources.handler_registry.fetch_all()) == 0 + + def test_handle_internal_message_new_handlers_with_error(self): + """Test handle internal message when an error happens while registering a new handler.""" + skill = Skill( + SkillConfig("name", "author", "0.1.0"), + handlers={}, + behaviours={}, + models={}, + ) + self.resources.add_skill(skill) + new_handler = DummyHandler(name="dummy2", skill_context=skill.skill_context) + with unittest.mock.patch.object( + self.resources.handler_registry, "register", side_effect=ValueError + ): + with unittest.mock.patch.object( + aea.registries.filter.logger, "warning" + ) as mock_logger_warning: + skill.skill_context.new_handlers.put(new_handler) + self.filter.handle_internal_messages() + + mock_logger_warning.assert_called_with( + "Error when trying to add a new handler: " + ) + assert self.decision_make_queue.empty() + assert len(self.resources.handler_registry.fetch_all()) == 0 + # restore previous state + self.resources.component_registry.unregister(skill.component_id) + def test_handle_signing_message(self): """Test the handling of a signing message.""" public_id = "author/non_existing_skill:0.1.0" diff --git a/tests/test_skills/test_base.py b/tests/test_skills/test_base.py index 89f86b1170..13105caffa 100644 --- a/tests/test_skills/test_base.py +++ b/tests/test_skills/test_base.py @@ -17,25 +17,43 @@ # # ------------------------------------------------------------------------------ -"""This module contains the tests for the base classes for the skills.""" +"""This module contains the tests for the base classes for the skills.""" +import unittest.mock +from pathlib import Path from queue import Queue +from types import SimpleNamespace from unittest import TestCase, mock -from unittest.mock import Mock +from unittest.mock import MagicMock, Mock +import pytest + +import aea from aea.aea import AEA -from aea.connections.base import ConnectionStatus +from aea.configurations.base import PublicId, SkillComponentConfiguration from aea.crypto.wallet import Wallet from aea.decision_maker.default import GoalPursuitReadiness, OwnershipState, Preferences +from aea.exceptions import AEAException from aea.identity.base import Identity +from aea.multiplexer import ConnectionStatus from aea.registries.resources import Resources -from aea.skills.base import SkillComponent, SkillContext +from aea.skills.base import ( + Behaviour, + Handler, + Model, + Skill, + SkillComponent, + SkillContext, + _check_duplicate_classes, + _print_warning_message_for_non_declared_skill_components, +) from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_PATH, ETHEREUM, ETHEREUM_PRIVATE_KEY_PATH, + ROOT_DIR, _make_dummy_connection, ) @@ -55,7 +73,9 @@ def setup_class(cls): ) cls.my_aea = AEA(cls.identity, cls.wallet, resources=Resources()) cls.my_aea.resources.add_connection(cls.connection) - cls.skill_context = SkillContext(cls.my_aea.context) + cls.skill_context = SkillContext( + cls.my_aea.context, skill=MagicMock(contracts={}) + ) def test_agent_name(self): """Test the agent's name.""" @@ -101,6 +121,44 @@ def test_message_in_queue(self): """Test the 'message_in_queue' property.""" assert isinstance(self.skill_context.message_in_queue, Queue) + def test_logger_setter(self): + """Test the logger setter.""" + logger = self.skill_context.logger + self.skill_context._logger = None + self.skill_context.logger = logger + assert self.skill_context.logger == logger + + def test_agent_context_setter(self): + """Test the agent context setter.""" + agent_context = self.skill_context._agent_context + self.skill_context.set_agent_context(agent_context) + assert self.skill_context.agent_name == agent_context.agent_name + assert self.skill_context.agent_address == agent_context.address + assert self.skill_context.agent_addresses == agent_context.addresses + + def test_is_active_property(self): + """Test is_active property getter.""" + assert self.skill_context.is_active is True + + def test_new_behaviours_queue(self): + """Test 'new_behaviours_queue' property getter.""" + assert isinstance(self.skill_context.new_behaviours, Queue) + + def test_new_handlers_queue(self): + """Test 'new_behaviours_queue' property getter.""" + assert isinstance(self.skill_context.new_handlers, Queue) + + def test_search_service_address(self): + """Test 'search_service_address' property getter.""" + assert ( + self.skill_context.search_service_address + == self.my_aea.context.search_service_address + ) + + def test_namespace(self): + """Test the 'namespace' property getter.""" + assert isinstance(self.skill_context.namespace, SimpleNamespace) + @classmethod def teardown_class(cls): """Test teardown.""" @@ -125,7 +183,7 @@ def test_skill_id_positive(self): obj._skill.config.public_id = "public_id" obj.skill_id - @mock.patch("aea.skills.base.logger.debug") + @mock.patch("aea.skills.base._default_logger.debug") @mock.patch("aea.skills.base.SkillContext.skill_id") def test_is_active_positive(self, skill_id_mock, debug_mock): """Test is_active setter positive result""" @@ -217,3 +275,189 @@ def test_config_positive(self): configuration=Mock(args={}), skill_context="ctx", name="name" ) component.config + + def test_kwargs_not_empty(self): + """Test the case when there are some kwargs not-empty""" + kwargs = dict(foo="bar") + component_name = "component_name" + skill_context = SkillContext() + with mock.patch.object(skill_context.logger, "warning") as mock_logger: + self.TestComponent(component_name, skill_context, **kwargs) + mock_logger.assert_any_call( + f"The kwargs={kwargs} passed to {component_name} have not been set!" + ) + + +def test_load_skill(): + """Test the loading of a skill.""" + agent_context = MagicMock() + skill = Skill.from_dir( + Path(ROOT_DIR, "tests", "data", "dummy_skill"), agent_context=agent_context + ) + assert isinstance(skill, Skill) + + +def test_behaviour(): + """Test behaviour initialization.""" + + class CustomBehaviour(Behaviour): + def setup(self) -> None: + pass + + def teardown(self) -> None: + pass + + def act(self) -> None: + pass + + behaviour = CustomBehaviour("behaviour", skill_context=MagicMock()) + + # test getters (default values) + assert behaviour.tick_interval == 0.001 + assert behaviour.start_at is None + assert behaviour.is_done() is False + + +def test_behaviour_parse_module_without_configs(): + """call Behaviour.parse_module without configurations.""" + assert Behaviour.parse_module(MagicMock(), {}, MagicMock()) == {} + + +def test_behaviour_parse_module_missing_class(): + """Test Behaviour.parse_module when a class is missing.""" + skill_context = SkillContext( + skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) + ) + dummy_behaviours_path = Path( + ROOT_DIR, "tests", "data", "dummy_skill", "behaviours.py" + ) + with unittest.mock.patch.object( + aea.skills.base._default_logger, "warning" + ) as mock_logger_warning: + behaviours_by_id = Behaviour.parse_module( + dummy_behaviours_path, + { + "dummy_behaviour": SkillComponentConfiguration("DummyBehaviour"), + "unknown_behaviour": SkillComponentConfiguration("UnknownBehaviour"), + }, + skill_context, + ) + mock_logger_warning.assert_called_with( + "Behaviour 'UnknownBehaviour' cannot be found." + ) + assert "dummy_behaviour" in behaviours_by_id + + +def test_handler_parse_module_without_configs(): + """call Handler.parse_module without configurations.""" + assert Handler.parse_module(MagicMock(), {}, MagicMock()) == {} + + +def test_handler_parse_module_missing_class(): + """Test Handler.parse_module when a class is missing.""" + skill_context = SkillContext( + skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) + ) + dummy_handlers_path = Path(ROOT_DIR, "tests", "data", "dummy_skill", "handlers.py") + with unittest.mock.patch.object( + aea.skills.base._default_logger, "warning" + ) as mock_logger_warning: + behaviours_by_id = Handler.parse_module( + dummy_handlers_path, + { + "dummy_handler": SkillComponentConfiguration("DummyHandler"), + "unknown_handelr": SkillComponentConfiguration("UnknownHandler"), + }, + skill_context, + ) + mock_logger_warning.assert_called_with( + "Handler 'UnknownHandler' cannot be found." + ) + assert "dummy_handler" in behaviours_by_id + + +def test_model_parse_module_without_configs(): + """call Model.parse_module without configurations.""" + assert Model.parse_module(MagicMock(), {}, MagicMock()) == {} + + +def test_model_parse_module_missing_class(): + """Test Model.parse_module when a class is missing.""" + skill_context = SkillContext( + skill=MagicMock(skill_id=PublicId.from_str("author/name:0.1.0")) + ) + dummy_models_path = Path(ROOT_DIR, "tests", "data", "dummy_skill") + with unittest.mock.patch.object( + aea.skills.base._default_logger, "warning" + ) as mock_logger_warning: + models_by_id = Model.parse_module( + dummy_models_path, + { + "dummy_model": SkillComponentConfiguration("DummyModel"), + "unknown_model": SkillComponentConfiguration("UnknownModel"), + }, + skill_context, + ) + mock_logger_warning.assert_called_with("Model 'UnknownModel' cannot be found.") + assert "dummy_model" in models_by_id + + +def test_check_duplicate_classes(): + """Test check duplicate classes.""" + with pytest.raises( + AEAException, + match="Model 'ModelClass' present both in path_1 and path_2. Remove one of them.", + ): + _check_duplicate_classes( + [ + ("ModelClass", MagicMock(__module__="path_1")), + ("ModelClass", MagicMock(__module__="path_2")), + ] + ) + + +def test_print_warning_message_for_non_declared_skill_components(): + """Test the helper function '_print_warning_message_for_non_declared_skill_components'.""" + with unittest.mock.patch.object( + aea.skills.base._default_logger, "warning" + ) as mock_logger_warning: + _print_warning_message_for_non_declared_skill_components( + SkillContext(), + {"unknown_class_1", "unknown_class_2"}, + set(), + "type", + "path", + ) + mock_logger_warning.assert_any_call( + "Class unknown_class_1 of type type found but not declared in the configuration file path." + ) + mock_logger_warning.assert_any_call( + "Class unknown_class_2 of type type found but not declared in the configuration file path." + ) + + +class TestSkill: + """Test skill attributes.""" + + @classmethod + def setup_class(cls): + """Set the tests up.""" + cls.skill = Skill.from_dir( + Path(ROOT_DIR, "tests", "data", "dummy_skill"), + MagicMock(agent_name="agent_name"), + ) + + def test_logger(self): + """Test the logger getter.""" + self.skill.logger + + def test_logger_setter_raises_error(self): + """Test that the logger setter raises error.""" + with pytest.raises(ValueError, match="Cannot set logger to a skill component."): + logger = self.skill.logger + self.skill.logger = logger + + def test_skill_context(self): + """Test the skill context getter.""" + context = self.skill.skill_context + assert isinstance(context, SkillContext) diff --git a/tests/test_skills/test_error.py b/tests/test_skills/test_error.py index 404ceedf29..38bdae7f44 100644 --- a/tests/test_skills/test_error.py +++ b/tests/test_skills/test_error.py @@ -21,13 +21,15 @@ import logging import os +import unittest.mock from threading import Thread from aea.aea import AEA +from aea.configurations.base import PublicId from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE from aea.crypto.wallet import Wallet from aea.identity.base import Identity -from aea.mail.base import Envelope +from aea.mail.base import Envelope, EnvelopeContext, URI from aea.multiplexer import InBox, Multiplexer from aea.protocols.default.message import DefaultMessage from aea.registries.resources import Resources @@ -167,10 +169,11 @@ def test_error_unsupported_skill(self): performative=FipaMessage.Performative.ACCEPT, ) msg.counterparty = self.address + msg.sender = self.address envelope = Envelope( - to=self.address, - sender=self.address, - protocol_id=DefaultMessage.protocol_id, + to=msg.counterparty, + sender=msg.sender, + protocol_id=msg.protocol_id, message=msg, ) @@ -183,6 +186,26 @@ def test_error_unsupported_skill(self): assert msg.performative == DefaultMessage.Performative.ERROR assert msg.error_code == DefaultMessage.ErrorCode.UNSUPPORTED_SKILL + def test_error_unsupported_skill_when_skill_id_is_none(self): + """Test the 'send_unsupported_skill' when the skill id in the envelope is None.""" + skill_id = PublicId.from_str("author/skill:0.1.0") + protocol_id = PublicId.from_str("author/name:0.1.0") + envelope = Envelope( + to="", + sender="", + protocol_id=protocol_id, + message=b"", + context=EnvelopeContext(uri=URI(skill_id.to_uri_path)), + ) + with unittest.mock.patch.object(self.skill_context.outbox, "put_message"): + with unittest.mock.patch.object( + self.skill_context._logger, "warning" + ) as mock_logger_warning: + self.my_error_handler.send_unsupported_skill(envelope) + mock_logger_warning.assert_called_with( + f"Cannot handle envelope: no active handler registered for the protocol_id='{protocol_id}' and skill_id='{skill_id}'." + ) + def teardown(self): """Teardown method.""" self.my_aea.stop() diff --git a/tests/test_skills/test_scaffold.py b/tests/test_skills/test_scaffold.py new file mode 100644 index 0000000000..588f31662a --- /dev/null +++ b/tests/test_skills/test_scaffold.py @@ -0,0 +1,85 @@ +# -*- 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 scaffold skill.""" +from unittest.mock import MagicMock + +import pytest + +from aea.skills.base import SkillContext +from aea.skills.scaffold.behaviours import MyScaffoldBehaviour +from aea.skills.scaffold.handlers import MyScaffoldHandler +from aea.skills.scaffold.my_model import MyModel + + +class TestScaffoldHandler: + """Tests for the scaffold handler.""" + + @classmethod + def setup_class(cls): + """Set up the tests.""" + cls.handler = MyScaffoldHandler("handler", SkillContext()) + + def test_supported_protocol(self): + """Test that the supported protocol is None.""" + assert self.handler.SUPPORTED_PROTOCOL is None + + def test_setup(self): + """Test that the setup method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.handler.setup() + + def test_handle(self): + """Test that the handle method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.handler.handle(MagicMock()) + + def test_teardown(self): + """Test that the teardown method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.handler.teardown() + + +class TestScaffoldBehaviour: + """Tests for the scaffold behaviour.""" + + @classmethod + def setup_class(cls): + """Set up the tests.""" + cls.behaviour = MyScaffoldBehaviour("behaviour", SkillContext()) + + def test_setup(self): + """Test that the setup method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.behaviour.setup() + + def test_handle(self): + """Test that the handle method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.behaviour.act() + + def test_teardown(self): + """Test that the teardown method raises 'NotImplementedError'.""" + with pytest.raises(NotImplementedError): + self.behaviour.teardown() + + +def test_model_initialization(): + """Test scaffold model initialization.""" + MyModel("model", SkillContext()) diff --git a/tests/test_skills/test_tasks.py b/tests/test_skills/test_tasks.py index 79bcc4049a..4308e14740 100644 --- a/tests/test_skills/test_tasks.py +++ b/tests/test_skills/test_tasks.py @@ -47,15 +47,15 @@ def test_call_already_executed(self): with self.assertRaises(ValueError): obj() - @mock.patch("aea.skills.tasks.logger.debug") - def test_call_exception_while_executing(self, debug_mock): + def test_call_exception_while_executing(self): """Test call obj exception raised while executing.""" obj = Task() - obj.setup = mock.Mock() - obj.execute = _raise_exception - obj.teardown = mock.Mock() - obj() - debug_mock.assert_called_once() + with mock.patch.object(obj.logger, "debug") as debug_mock: + obj.setup = mock.Mock() + obj.execute = _raise_exception + obj.teardown = mock.Mock() + obj() + debug_mock.assert_called_once() def test_is_executed_positive(self): """Test is_executed property positive result.""" @@ -93,20 +93,31 @@ def test_nb_workers_positive(self): obj = TaskManager() obj.nb_workers - @mock.patch("aea.skills.tasks.logger.debug") - def test_stop_already_stopped(self, debug_mock): + def test_stop_already_stopped(self): """Test stop method already stopped.""" obj = TaskManager() - obj.stop() - debug_mock.assert_called_once() + with mock.patch.object(obj.logger, "debug") as debug_mock: + obj.stop() + debug_mock.assert_called_once() - @mock.patch("aea.skills.tasks.logger.debug") - def test_start_already_started(self, debug_mock): + def test_start_already_started(self): """Test start method already started.""" obj = TaskManager() - obj._stopped = False - obj.start() - debug_mock.assert_called_once() + with mock.patch.object(obj.logger, "debug") as debug_mock: + obj._stopped = False + obj.start() + debug_mock.assert_called_once() + obj.stop() + + def test_start_lazy_pool_start(self): + """Test start method with lazy pool start.""" + obj = TaskManager(is_lazy_pool_start=False) + with mock.patch.object(obj.logger, "debug") as debug_mock: + obj.start() + obj._stopped = True + obj.start() + debug_mock.assert_called_with("Pool was already started!") + obj.start() def test_enqueue_task_stopped(self): """Test enqueue_task method manager stopped.""" diff --git a/tests/test_test_tools/test_click_testing.py b/tests/test_test_tools/test_click_testing.py new file mode 100644 index 0000000000..f020666c2a --- /dev/null +++ b/tests/test_test_tools/test_click_testing.py @@ -0,0 +1,90 @@ +# -*- 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 test for aea.test_tools.""" +from unittest.mock import patch + +import pytest + +from aea.cli.core import cli +from aea.test_tools.click_testing import CliRunner + + +def test_invoke(): + """Test runner invoke method.""" + cli_runner = CliRunner() + + result = cli_runner.invoke(cli, ["--help"]) + assert ( + "Command-line tool for setting up an Autonomous Economic Agent" in result.output + ) + + result = cli_runner.invoke(cli, "--help") + assert ( + "Command-line tool for setting up an Autonomous Economic Agent" in result.output + ) + + +def test_invoke_error(): + """Test runner invoke method raises an error.""" + cli_runner = CliRunner() + + with patch.object(cli, "main", side_effect=SystemExit(1)): + result = cli_runner.invoke(cli, ["--help"]) + assert result.exit_code == 1 + + with patch.object(cli, "main", side_effect=SystemExit(object())): + result = cli_runner.invoke(cli, ["--help"]) + assert result.exit_code == 1 + + +def test_catch_exception(): + """Test runner invoke method raises an exception and its propogated.""" + cli_runner = CliRunner() + + # True + with patch.object(cli, "main", side_effect=ValueError("expected")): + result = cli_runner.invoke(cli, ["--help"]) + assert result.exit_code == 1 + + # False + + with pytest.raises(ValueError, match="expected"): + with patch.object(cli, "main", side_effect=ValueError("expected")): + result = cli_runner.invoke(cli, ["--help"], catch_exceptions=False) + + +def test_mix_std_err_False(): + """Test stderr and stdout not mixed.""" + cli_runner = CliRunner(mix_stderr=False) + + result = cli_runner.invoke(cli, "-v DEBUG run") + assert result.exit_code == 1 + # check for access, no exception should be raised + result.stderr + + +def test_mix_std_err_True(): + """Test stderr and stdout are mixed.""" + cli_runner = CliRunner(mix_stderr=True) + + result = cli_runner.invoke(cli, "-v DEBUG run") + assert result.exit_code == 1 + + with pytest.raises(ValueError): + result.stderr diff --git a/tests/test_test_tools/test_testcases.py b/tests/test_test_tools/test_testcases.py new file mode 100644 index 0000000000..900c92c1de --- /dev/null +++ b/tests/test_test_tools/test_testcases.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. +# +# ------------------------------------------------------------------------------ +"""This module contains a test for aea.test_tools.test_cases.""" + +import os +from pathlib import Path + + +from aea.test_tools.test_cases import AEATestCase, AEATestCaseEmpty + + +class TestGenericCases(AEATestCaseEmpty): + """Tests test for generic cases of AEATestCases.""" + + def test_disable_aea_logging(self): + """Call logging disable.""" + self.disable_aea_logging() + + def test_start_subprocess(self): + """Start a python subprocess and check output.""" + proc = self.start_subprocess("-c", "print('hi')") + proc.wait(10) + + assert "hi" in self.stdout[proc.pid] + + def test_start_thread(self): + """Start and join thread for python code.""" + called = False + + def fn(): + nonlocal called + called = True + + thread = self.start_thread(fn) + thread.join(10) + assert called + + def test_fetch_and_delete(self): + """Fetch and delete agent from repo.""" + agent_name = "some_agent_for_tests" + self.fetch_agent("fetchai/my_first_aea:0.8.0", agent_name) + assert os.path.exists(agent_name) + self.delete_agents(agent_name) + assert not os.path.exists(agent_name) + + def test_diff(self): + """Test difference_to_fetched_agent.""" + agent_name = "some_agent_for_tests2" + self.fetch_agent("fetchai/my_first_aea:0.8.0", agent_name) + self.run_cli_command( + "config", "set", "agent.default_ledger", "test_ledger", cwd=agent_name + ) + result = self.run_cli_command( + "config", "get", "agent.default_ledger", cwd=agent_name + ) + assert b"test_ledger" in result.stdout_bytes + diff = self.difference_to_fetched_agent( + "fetchai/my_first_aea:0.8.0", agent_name + ) + assert diff + assert "test_ledger" in diff[1] + + def test_no_diff(self): + """Test no difference for two aea configs.""" + agent_name = "some_agent_for_tests3" + self.fetch_agent("fetchai/my_first_aea:0.8.0", agent_name) + diff = self.difference_to_fetched_agent( + "fetchai/my_first_aea:0.8.0", agent_name + ) + assert not diff + + def test_terminate_subprocesses(self): + """Start and terminate long running python subprocess.""" + proc = self.start_subprocess("-c", "import time; time.sleep(10)") + assert proc.returncode is None + self._terminate_subprocesses() + assert proc.returncode is not None + + def test_miss_from_output(self): + """Test subprocess output missing output.""" + proc = self.start_subprocess("-c", "print('hi')") + assert len(self.missing_from_output(proc, ["hi"], timeout=5)) == 0 + assert "HI" in self.missing_from_output(proc, ["HI"], timeout=5) + + def test_replace_file_content(self): + """Replace content of the file with another one.""" + file1 = "file1.txt" + file2 = "file2.txt" + + with open(file1, "w") as f: + f.write("hi") + + with open(file2, "w") as f: + f.write("world") + + self.replace_file_content(Path(file1), Path(file2)) + + with open(file2, "r") as f: + assert f.read() == "hi" + + +class TestAddAndRejectComponent(AEATestCaseEmpty): + """Test add/reject components.""" + + def test_add_and_eject(self): + """Test add/reject components.""" + result = self.add_item("skill", "fetchai/echo:0.4.0", local=True) + assert result.exit_code == 0 + + result = self.eject_item("skill", "fetchai/echo:0.4.0") + assert result.exit_code == 0 + + +class TestGenerateAndAddKey(AEATestCaseEmpty): + """Test generate and add private key.""" + + def test_generate_and_add_key(self): + """Test generate and add private key.""" + result = self.generate_private_key("cosmos") + assert result.exit_code == 0 + result = self.add_private_key( + "cosmos", "cosmos_private_key.txt", connection=True + ) + assert result.exit_code == 0 + result = self.add_private_key("cosmos", "cosmos_private_key.txt") + assert result.exit_code == 0 + + +class TestGetWealth(AEATestCaseEmpty): + """Test get_wealth.""" + + def test_get_wealth(self): + """Test get_wealth.""" + # just call it, network related and quite unstable + self.get_wealth("cosmos") + + +class TestAEA(AEATestCase): + """Test agent test set from path.""" + + path_to_aea = Path("tests") / "data" / "dummy_aea" + + def test_agent_set(self): + """Test agent test set from path.""" + result = self.run_cli_command( + "config", "get", "agent.agent_name", cwd=self.path_to_aea + ) + assert b"Agent0" in result.stdout_bytes + + def test_scaffold_and_fingerprint(self): + """Test component scaffold and fingerprint.""" + result = self.scaffold_item("skill", "skill1") + assert result.exit_code == 0 + + result = self.fingerprint_item("skill", "fetchai/skill1:0.1.0") + assert result.exit_code == 0 diff --git a/tox.ini b/tox.ini index e01ceb8f5d..5e30a3256e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,55 +1,29 @@ +; By default, testenvs are configured to: +; - don't skip dist (skipsdist = False) +; - don't skip the package installation (skip_install = False) +; - don't use source installation (usedevelop = False) +; where one of those steps is not necessary for the test, +; we set the associated flag (e.g. for linting we don't need +; the package installation). [tox] -envlist = bandit-main, bandit-tests, black, black-check, copyright_check, docs, flake8, liccheck, mypy, py3.8, py3.7, py3.6 -skipsdist = False -ignore_basepython_conflict = True +envlist = bandit, black, black-check, copyright_check, docs, flake8, liccheck, mypy, py{3.6,3.7,3.8} -[testenv:py3.8] -basepython = python3.8 -passenv = * -deps = - Cython - pytest==5.3.5 - pytest-cov==2.8.1 - pytest-asyncio==0.10.0 - pytest-randomly==3.2.1 - docker - colorlog==4.1.0 - defusedxml==0.6.0 - oef==0.8.1 - gym==0.15.6 - numpy==1.18.1 - vyper==0.1.0b12 - openapi-core==0.13.2 - openapi-spec-validator==0.2.8 - black==19.10b0 - mistune==2.0.0a4 - aiohttp==3.6.2 - SQLAlchemy==1.3.16 - pynacl==1.3.0 - pexpect==4.8.0 - pytest-rerunfailures==9.0 - -commands = - pip install git+https://github.com/pytoolz/cytoolz.git#egg=cytoolz==0.10.1.dev0 - pip install .[all] - pip install -i https://test.pypi.org/simple/ fetch-p2p-api==0.0.2 - pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections tests/ --cov-report=html --cov-report=xml --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections {posargs} - -[testenv:py3.7] -basepython = python3.7 +[testenv] +basepython = python3 +whitelist_externals = /bin/sh passenv = * +extras = all deps = pytest==5.3.5 pytest-cov==2.8.1 pytest-asyncio==0.10.0 pytest-randomly==3.2.1 - docker + docker==4.2.0 colorlog==4.1.0 defusedxml==0.6.0 oef==0.8.1 gym==0.15.6 numpy==1.18.1 - tensorflow==1.14.0 vyper==0.1.0b12 openapi-core==0.13.2 openapi-spec-validator==0.2.8 @@ -61,72 +35,63 @@ deps = pexpect==4.8.0 pytest-rerunfailures==9.0 -commands = - pip install .[all] - pip install -i https://test.pypi.org/simple/ fetch-p2p-api==0.0.2 - pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections tests/ --cov-report=html --cov-report=xml --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections {posargs} +commands = pip install -i https://test.pypi.org/simple/ fetch-p2p-api==0.0.2 + pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections tests/ --cov-report=html --cov-report=xml --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections {posargs} [testenv:py3.6] basepython = python3.6 -passenv = * -deps = - pytest==5.3.5 - pytest-cov==2.8.1 - pytest-asyncio==0.10.0 - pytest-randomly==3.2.1 - docker - colorlog==4.1.0 - defusedxml==0.6.0 - oef==0.8.1 - gym==0.15.6 - numpy==1.18.1 - tensorflow==1.14.0 - vyper==0.1.0b12 - openapi-core==0.13.2 - openapi-spec-validator==0.2.8 - black==19.10b0 - mistune==2.0.0a4 - aiohttp==3.6.2 - SQLAlchemy==1.3.16 - pynacl==1.3.0 - pexpect==4.8.0 - pytest-rerunfailures==9.0 +[testenv:py3.7] +basepython = python3.7 -commands = - pip install .[all] - pip install -i https://test.pypi.org/simple/ fetch-p2p-api==0.0.2 - pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections tests/ --cov-report=html --cov-report=xml --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections {posargs} +[testenv:py3.8] +basepython = python3.8 [testenv:bandit] +skipsdist = True +skip_install = True deps = bandit==1.6.2 commands = bandit -s B101 -r aea benchmark examples packages scripts tests [testenv:black] +skipsdist = True +skip_install = True deps = black==19.10b0 commands = black aea benchmark examples packages scripts tests [testenv:black-check] +skipsdist = True +skip_install = True deps = black==19.10b0 commands = black aea benchmark examples packages scripts tests --check --verbose [testenv:copyright_check] +skipsdist = True +skip_install = True +deps = commands = {toxinidir}/scripts/check_copyright_notice.py --directory {toxinidir} [testenv:hash_check] -deps = python-dotenv +skipsdist = True +usedevelop = True +deps = ipfshttpclient==0.4.12 commands = {toxinidir}/scripts/generate_ipfs_hashes.py --check {posargs} [testenv:package_version_checks] -deps = python-dotenv +skipsdist = True +usedevelop = True +deps = commands = {toxinidir}/scripts/check_package_versions_in_docs.py [testenv:package_dependencies_checks] -deps = python-dotenv +skipsdist = True +usedevelop = True +deps = commands = {toxinidir}/scripts/check_package_dependencies.py - [testenv:docs] +skipsdist = True +skip_install = True description = Build the documentation. deps = markdown==3.2.1 mkdocs==1.1 @@ -137,6 +102,8 @@ commands = pip3 install git+https://github.com/pugong/mkdocs-mermaid-plugin.git# mkdocs build --clean [testenv:docs-serve] +skipsdist = True +skip_install = True description = Run a development server for working on documentation. deps = markdown==3.2.1 mkdocs==1.1 @@ -149,6 +116,8 @@ commands = pip3 install git+https://github.com/pugong/mkdocs-mermaid-plugin.git# mkdocs serve -a localhost:8080 [testenv:flake8] +skipsdist = True +skip_install = True deps = flake8==3.7.9 flake8-bugbear==20.1.4 flake8-docstrings==1.5.0 @@ -157,22 +126,28 @@ deps = flake8==3.7.9 commands = flake8 aea benchmark examples packages scripts tests [testenv:liccheck] +skipsdist = True +usedevelop = True deps = liccheck==0.4.3 -commands = pip install ".[all]" - {toxinidir}/scripts/freeze_dependencies.py -o {envtmpdir}/requirements.txt +commands = {toxinidir}/scripts/freeze_dependencies.py -o {envtmpdir}/requirements.txt liccheck -s strategy.ini -r {envtmpdir}/requirements.txt -l PARANOID [testenv:mypy] +skipsdist = True +skip_install = True deps = mypy==0.761 aiohttp==3.6.2 + packaging==20.4 commands = mypy aea benchmark examples packages scripts tests [testenv:pylint] +skipsdist = True deps = pylint==2.5.2 pytest==5.3.5 -commands = pip install .[all] - pylint aea benchmark examples packages scripts --disable=E1136 +commands = sh -c "pylint aea benchmark packages scripts examples/* --disable=E1136" [testenv:safety] +skipsdist = True +skip_install = True deps = safety==1.8.5 commands = safety check -i 37524 -i 38038 -i 37776 -i 38039