From 389c58c0c173211450ccdb9b348d607d046e5a46 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 20:27:53 +0300 Subject: [PATCH 01/39] feat: add mech-client to the dependencies This requires a python version >=3.10, therefore, I had to remove support for the previous versions. --- poetry.lock | 171 +++++++++++++++++++++++-------------------------- pyproject.toml | 3 +- 2 files changed, 83 insertions(+), 91 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7e03f40a..a7412190 100644 --- a/poetry.lock +++ b/poetry.lock @@ -101,6 +101,18 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "base58" version = "2.1.1" @@ -1302,7 +1314,6 @@ files = [ [package.dependencies] click = ">=8.0" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.0" Jinja2 = ">=3.0" Werkzeug = ">=2.0" @@ -1336,14 +1347,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.91.0" +version = "2.92.0" description = "Google API Client Library for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-python-client-2.91.0.tar.gz", hash = "sha256:d9385ad6e7f95eecd40f7c81e3abfe4b6ad3a84f2c16bcdb66fb7b8dd814ed56"}, - {file = "google_api_python_client-2.91.0-py2.py3-none-any.whl", hash = "sha256:6959d21d4b20c0f65c69662ca7b6a8a02fc08f3e7f72d70b28ae3e6e3a5f9ab2"}, + {file = "google-api-python-client-2.92.0.tar.gz", hash = "sha256:f38a6e106a7417719715506d36f0a233ec253335e422bda311352866a86c4187"}, + {file = "google_api_python_client-2.92.0-py2.py3-none-any.whl", hash = "sha256:e0b74ed5fa9bdb07a66fb030d3f4cae550ed1c07e23600d86450d3c3c5efae51"}, ] [package.dependencies] @@ -1414,6 +1425,45 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4 [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] +[[package]] +name = "gql" +version = "3.4.1" +description = "GraphQL client for Python" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "gql-3.4.1-py2.py3-none-any.whl", hash = "sha256:315624ca0f4d571ef149d455033ebd35e45c1a13f18a059596aeddcea99135cf"}, + {file = "gql-3.4.1.tar.gz", hash = "sha256:11dc5d8715a827f2c2899593439a4f36449db4f0eafa5b1ea63948f8a2f8c545"}, +] + +[package.dependencies] +backoff = ">=1.11.1,<3.0" +graphql-core = ">=3.2,<3.3" +yarl = ">=1.6,<2.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.7.1,<3.9.0)"] +all = ["aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +botocore = ["botocore (>=1.21,<2)"] +dev = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "black (==22.3.0)", "botocore (>=1.21,<2)", "check-manifest (>=0.42,<1)", "flake8 (==3.8.1)", "isort (==4.3.21)", "mock (==4.0.2)", "mypy (==0.910)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "sphinx (>=3.0.0,<4)", "sphinx-argparse (==0.2.5)", "sphinx-rtd-theme (>=0.4,<1)", "types-aiofiles", "types-mock", "types-requests", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +requests = ["requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)"] +test = ["aiofiles", "aiohttp (>=3.7.1,<3.9.0)", "botocore (>=1.21,<2)", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "requests (>=2.26,<3)", "requests-toolbelt (>=0.9.1,<1)", "urllib3 (>=1.26,<2)", "vcrpy (==4.0.2)", "websockets (>=10,<11)", "websockets (>=9,<10)"] +test-no-transport = ["aiofiles", "mock (==4.0.2)", "parse (==1.15.0)", "pytest (==6.2.5)", "pytest-asyncio (==0.16.0)", "pytest-console-scripts (==1.3.1)", "pytest-cov (==3.0.0)", "vcrpy (==4.0.2)"] +websockets = ["websockets (>=10,<11)", "websockets (>=9,<10)"] + +[[package]] +name = "graphql-core" +version = "3.2.3" +description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." +category = "main" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "graphql-core-3.2.3.tar.gz", hash = "sha256:06d2aad0ac723e35b1cb47885d3e5c45e956a53bc1b209a9fc5369007fe46676"}, + {file = "graphql_core-3.2.3-py3-none-any.whl", hash = "sha256:5766780452bd5ec8ba133f8bf287dc92713e3868ddd83aee4faab9fc3e303dc3"}, +] + [[package]] name = "grpcio" version = "1.43.0" @@ -1552,26 +1602,6 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -1807,6 +1837,27 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "mech-client" +version = "0.2.1" +description = "Basic client to interact with a mech" +category = "main" +optional = false +python-versions = ">=3.10,<4.0" +files = [ + {file = "mech_client-0.2.1-py3-none-any.whl", hash = "sha256:f50a758a94e92ed09fba30ac3b278b31eb2f098f7573a9abce3c2db6830c8a90"}, + {file = "mech_client-0.2.1.tar.gz", hash = "sha256:a7e2193e664a2c4af2bfe1209d84f23e7ce35362218b7cd59ef616bfa5229e0d"}, +] + +[package.dependencies] +asn1crypto = ">=1.4.0,<1.5.0" +gql = ">=3.4.1" +open-aea = {version = "1.35.0", extras = ["cli"]} +open-aea-cli-ipfs = "1.35.0" +open-aea-ledger-cosmos = "1.35.0" +open-aea-ledger-ethereum = "1.35.0" +websocket-client = ">=0.32.0,<1" + [[package]] name = "morphys" version = "1.0" @@ -1944,44 +1995,6 @@ files = [ {file = "netaddr-0.8.0.tar.gz", hash = "sha256:d6cc57c7a07b1d9d2e917aa8b36ae8ce61c35ba3fcd1b83ca31c5a0ee2b5a243"}, ] -[[package]] -name = "numpy" -version = "1.24.4" -description = "Fundamental package for array computing in Python" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, -] - [[package]] name = "numpy" version = "1.25.0" @@ -2035,8 +2048,8 @@ files = [ [package.dependencies] base58 = ">=1.0.3,<3.0.0" -click = {version = "8.0.2", optional = true, markers = "extra == \"all\""} -coverage = {version = ">=6.4.4,<8.0.0", optional = true, markers = "extra == \"all\""} +click = {version = "8.0.2", optional = true, markers = "extra == \"cli\""} +coverage = {version = ">=6.4.4,<8.0.0", optional = true, markers = "extra == \"cli\""} ecdsa = ">=0.15,<0.17.0" jsonschema = ">=3.0.0,<4.0.0" morphys = ">=1.0" @@ -2045,7 +2058,7 @@ protobuf = ">=3.19.0,<4.0.0" py-multibase = ">=1.0.0" py-multicodec = ">=0.2.0" pymultihash = "0.8.2" -pytest = {version = ">=7.0.0,<7.3.0", optional = true, markers = "extra == \"all\""} +pytest = {version = ">=7.0.0,<7.3.0", optional = true, markers = "extra == \"cli\""} python-dotenv = ">=0.14.0,<0.18.0" pyyaml = ">=4.2b1,<6.0" requests = ">=2.22.0,<3.0.0" @@ -2209,12 +2222,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.17.3", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""}, - {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, -] +numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""} python-dateutil = ">=2.7.3" pytz = ">=2017.3" @@ -2735,7 +2743,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" [[package]] @@ -3456,23 +3463,7 @@ files = [ idna = ">=2.0" multidict = ">=4.0" -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "2.0" -python-versions = "<3.11, >=3.8" -content-hash = "25bb044bb01a4defbd16557242c92586ba1dd5592523fdde3c5dc9ff58a167e6" +python-versions = ">=3.10, <3.11" +content-hash = "a1ff96b6540040b94e05122810f9e4ad0085c4dba288696d594e84d2938eea3d" diff --git a/pyproject.toml b/pyproject.toml index 1aad5967..0406358c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Environment :: Console", "Environment :: Web Environment", "Dev include = "packages" [tool.poetry.dependencies] -python = "<3.11, >=3.8" +python = ">=3.10, <3.11" open-autonomy = "==0.10.7" requests = "==2.28.2" py-multibase = "==1.0.3" @@ -27,6 +27,7 @@ open-aea-ledger-ethereum = "==1.35.0" open-aea-ledger-cosmos = "*" protobuf = "<=3.20.1,>=3.19" hypothesis = "==6.80.0" +mech-client = "==0.2.1" open-aea-test-autonomy = "==0.10.7" web3 = "==5.31.4" ipfshttpclient = "==0.8.0a2" From 196e463ee31e309ef75585f25bc221648a309767 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 20:28:45 +0300 Subject: [PATCH 02/39] fix: add missing `http` protocol to the market manager abci --- packages/valory/skills/market_manager_abci/skill.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/valory/skills/market_manager_abci/skill.yaml b/packages/valory/skills/market_manager_abci/skill.yaml index 26e922fd..f1b58577 100644 --- a/packages/valory/skills/market_manager_abci/skill.yaml +++ b/packages/valory/skills/market_manager_abci/skill.yaml @@ -10,7 +10,8 @@ fingerprint: fingerprint_ignore_patterns: [] connections: [] contracts: [] -protocols: [] +protocols: +- valory/http:1.0.0:bafybeifyoio7nlh5zzyn5yz7krkou56l22to3cwg7gw5v5o3vxwklibhty skills: - valory/abstract_round_abci:0.1.0:bafybeiac62ennpw54gns2quk4g3yoaili2mb72nj6c52czobz5dcwj4mwi behaviours: From 64ceb66bf1bd9d3872d95dfea998e723fdee297c Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 20:30:36 +0300 Subject: [PATCH 03/39] feat: create functionality to decide for the bet's choice --- .../skills/decision_maker_abci/README.md | 5 + .../skills/decision_maker_abci/__init__.py | 25 +++ .../skills/decision_maker_abci/behaviours.py | 124 +++++++++++++++ .../skills/decision_maker_abci/dialogues.py | 91 +++++++++++ .../skills/decision_maker_abci/handlers.py | 51 ++++++ .../skills/decision_maker_abci/models.py | 52 +++++++ .../skills/decision_maker_abci/payloads.py | 32 ++++ .../skills/decision_maker_abci/rounds.py | 102 ++++++++++++ .../skills/decision_maker_abci/skill.yaml | 145 ++++++++++++++++++ .../skills/decision_maker_abci/tasks.py | 89 +++++++++++ 10 files changed, 716 insertions(+) create mode 100644 packages/valory/skills/decision_maker_abci/README.md create mode 100644 packages/valory/skills/decision_maker_abci/__init__.py create mode 100644 packages/valory/skills/decision_maker_abci/behaviours.py create mode 100644 packages/valory/skills/decision_maker_abci/dialogues.py create mode 100644 packages/valory/skills/decision_maker_abci/handlers.py create mode 100644 packages/valory/skills/decision_maker_abci/models.py create mode 100644 packages/valory/skills/decision_maker_abci/payloads.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds.py create mode 100644 packages/valory/skills/decision_maker_abci/skill.yaml create mode 100644 packages/valory/skills/decision_maker_abci/tasks.py diff --git a/packages/valory/skills/decision_maker_abci/README.md b/packages/valory/skills/decision_maker_abci/README.md new file mode 100644 index 00000000..93307c4d --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/README.md @@ -0,0 +1,5 @@ +# DecisionMaker abci + +## Description + +This module contains the ABCI decision-making skill for an AEA. diff --git a/packages/valory/skills/decision_maker_abci/__init__.py b/packages/valory/skills/decision_maker_abci/__init__.py new file mode 100644 index 00000000..17b61f59 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 decision maker skill for the trader.""" + +from aea.configurations.base import PublicId + + +PUBLIC_ID = PublicId.from_str("valory/decision_maker_abci:0.1.0") diff --git a/packages/valory/skills/decision_maker_abci/behaviours.py b/packages/valory/skills/decision_maker_abci/behaviours.py new file mode 100644 index 00000000..c8df5b69 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 behaviours for the 'decision_maker_abci' skill.""" + +from multiprocessing.pool import AsyncResult +from typing import Set, Type, cast, Any, Optional, Generator, Tuple + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload +from packages.valory.skills.abstract_round_abci.behaviours import ( + AbstractRoundBehaviour, + BaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.rounds import ( + AgentDecisionMakerAbciApp, + DecisionMakerRound, SynchronizedData +) +from packages.valory.skills.decision_maker_abci.tasks import MechInteractionTask, MechInteractionResponse + + +class DecisionMakerBehaviour(BaseBehaviour): + """A round in which the agents decide which answer they are going to choose for the next bet.""" + + matching_round = DecisionMakerRound + + def __init__(self, **kwargs: Any) -> None: + """Initialize Behaviour.""" + super().__init__(**kwargs) + self._async_result: Optional[AsyncResult] = None + + @property + def params(self) -> DecisionMakerParams: + """Return the params.""" + return cast(DecisionMakerParams, self.context.params) + + @property + def synchronized_data(self) -> SynchronizedData: + """Return the synchronized data.""" + return cast(SynchronizedData, super().synchronized_data) + + def setup(self) -> None: + """Setup behaviour.""" + mech_task = MechInteractionTask() + sampled_bet = self.synchronized_data.sampled_bet.title + task_kwargs = dict( + question=sampled_bet.title, + yes=sampled_bet.yes, + no=sampled_bet.no, + agent_id=self.params.mech_agent_id, + tool=self.params.mech_tool, + private_key_path=self.context.private_key_paths.read("ethereum") + ) + task_id = self.context.task_manager.enqueue_task( + mech_task, kwargs=task_kwargs + ) + self._async_result = self.context.task_manager.get_task_result(task_id) + + def _get_decision(self) -> Generator[None, None, Optional[Tuple[bool, float]]]: + """Get the vote and it's confidence.""" + self._async_result = cast(AsyncResult, self._async_result) + if not self._async_result.ready(): + self.context.logger.debug("The decision making task is not finished yet.") + yield from self.sleep(self.params.sleep_time) + return None + + # Get the decision from the task. + mech_response = cast(MechInteractionResponse, self._async_result.get()) + self.context.logger.info( + f"Decision has been received:\n{mech_response}" + ) + + if mech_response.prediction is None: + return False, 0 + + return mech_response.prediction.vote, mech_response.prediction.confidence + + def finish_behaviour(self, payload: BaseTxPayload) -> Generator: + """Finish the behaviour.""" + with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): + yield from self.send_a2a_transaction(payload) + yield from self.wait_until_round_end() + + self.set_done() + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + decision = yield from self._get_decision() + if decision is None: + return + + vote, confidence = decision + payload = DecisionMakerPayload(self.context.agent_address, vote, confidence) + + yield from self.finish_behaviour(payload) + + +class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): + """This behaviour manages the consensus stages for the decision making.""" + + initial_behaviour_cls = DecisionMakerBehaviour + abci_app_cls = AgentDecisionMakerAbciApp + behaviours: Set[Type[BaseBehaviour]] = { + DecisionMakerBehaviour, # type: ignore + } diff --git a/packages/valory/skills/decision_maker_abci/dialogues.py b/packages/valory/skills/decision_maker_abci/dialogues.py new file mode 100644 index 00000000..e985dc86 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/dialogues.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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.""" + +from packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogue as BaseAbciDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + AbciDialogues as BaseAbciDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogue as BaseContractApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + ContractApiDialogues as BaseContractApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogue as BaseHttpDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + HttpDialogues as BaseHttpDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogue as BaseIpfsDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + IpfsDialogues as BaseIpfsDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogue as BaseLedgerApiDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + LedgerApiDialogues as BaseLedgerApiDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogue as BaseSigningDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + SigningDialogues as BaseSigningDialogues, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogue as BaseTendermintDialogue, +) +from packages.valory.skills.abstract_round_abci.dialogues import ( + TendermintDialogues as BaseTendermintDialogues, +) + + +AbciDialogue = BaseAbciDialogue +AbciDialogues = BaseAbciDialogues + + +HttpDialogue = BaseHttpDialogue +HttpDialogues = BaseHttpDialogues + + +SigningDialogue = BaseSigningDialogue +SigningDialogues = BaseSigningDialogues + + +LedgerApiDialogue = BaseLedgerApiDialogue +LedgerApiDialogues = BaseLedgerApiDialogues + + +ContractApiDialogue = BaseContractApiDialogue +ContractApiDialogues = BaseContractApiDialogues + + +TendermintDialogue = BaseTendermintDialogue +TendermintDialogues = BaseTendermintDialogues + + +IpfsDialogue = BaseIpfsDialogue +IpfsDialogues = BaseIpfsDialogues diff --git a/packages/valory/skills/decision_maker_abci/handlers.py b/packages/valory/skills/decision_maker_abci/handlers.py new file mode 100644 index 00000000..060a2155 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/handlers.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 handler for the 'decision_maker_abci' skill.""" + +from packages.valory.skills.abstract_round_abci.handlers import ( + ABCIRoundHandler as BaseABCIRoundHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + ContractApiHandler as BaseContractApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + HttpHandler as BaseHttpHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + IpfsHandler as BaseIpfsHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + LedgerApiHandler as BaseLedgerApiHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + SigningHandler as BaseSigningHandler, +) +from packages.valory.skills.abstract_round_abci.handlers import ( + TendermintHandler as BaseTendermintHandler, +) + + +ABCIHandler = BaseABCIRoundHandler +HttpHandler = BaseHttpHandler +SigningHandler = BaseSigningHandler +LedgerApiHandler = BaseLedgerApiHandler +ContractApiHandler = BaseContractApiHandler +TendermintHandler = BaseTendermintHandler +IpfsHandler = BaseIpfsHandler diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py new file mode 100644 index 00000000..9d03191e --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 models for the skill.""" + +from typing import Any + +from packages.valory.skills.abstract_round_abci.models import BaseParams +from packages.valory.skills.abstract_round_abci.models import ( + BenchmarkTool as BaseBenchmarkTool, +) +from packages.valory.skills.abstract_round_abci.models import Requests as BaseRequests +from packages.valory.skills.abstract_round_abci.models import ( + SharedState as BaseSharedState, +) +from packages.valory.skills.decision_maker_abci.rounds import AgentDecisionMakerAbciApp + + +Requests = BaseRequests +BenchmarkTool = BaseBenchmarkTool + + +class SharedState(BaseSharedState): + """Keep the current shared state of the skill.""" + + abci_app_cls = AgentDecisionMakerAbciApp + + +class DecisionMakerParams(BaseParams): + """Decision maker's parameters.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Initialize the parameters' object.""" + self.mech_agent_id: int = self._ensure("mech_agent_id", kwargs, int) + self.mech_tool: int = self._ensure("mech_tool", kwargs, str) + super().__init__(*args, **kwargs) diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py new file mode 100644 index 00000000..9e811de6 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 transaction payloads for the decision maker.""" + +from dataclasses import dataclass + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload + + +@dataclass(frozen=True) +class DecisionMakerPayload(BaseTxPayload): + """Represents a transaction payload for the decision-making.""" + + vote: bool + confidence: float diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py new file mode 100644 index 00000000..926a88dd --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 rounds for the decision-making.""" + +from enum import Enum +from typing import Dict, Set + +from packages.valory.skills.abstract_round_abci.base import ( + AbciApp, + AbciAppTransitionFunction, + AppState, + DegenerateRound, + CollectDifferentUntilThresholdRound, +) +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.market_manager_abci.bets import Bet +from packages.valory.skills.market_manager_abci.rounds import SynchronizedData as BaseSynchronizedData + + +class Event(Enum): + """Event enumeration for the price estimation demo.""" + + DONE = "done" + ROUND_TIMEOUT = "round_timeout" + NO_MAJORITY = "no_majority" + + +class SynchronizedData(BaseSynchronizedData): + """Class to represent the synchronized data. + + This data is replicated by the tendermint application. + """ + + @property + def sampled_bet(self) -> Bet: + """Get the sampled bet.""" + + +class DecisionMakerRound(CollectDifferentUntilThresholdRound): + """A round in which the agents decide on the bet's answer.""" + + payload_class = DecisionMakerPayload + synchronized_data_class = BaseSynchronizedData + + +class FinishedDecisionMakerRound(DegenerateRound): + """A round representing that decision-making has finished""" + + +class AgentDecisionMakerAbciApp(AbciApp[Event]): + """AgentDecisionMakerAbciApp + + Initial round: DecisionMakerRound + + Initial states: {DecisionMakerRound} + + Transition states: + + Final states: {FinishedDecisionMakerRound} + + Timeouts: + round timeout: 30.0 + """ + + initial_round_cls: AppState = DecisionMakerRound + initial_states: Set[AppState] = {DecisionMakerRound} + transition_function: AbciAppTransitionFunction = { + DecisionMakerRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.NO_MAJORITY: DecisionMakerRound, + }, + FinishedDecisionMakerRound: {}, + } + final_states: Set[AppState] = { + FinishedDecisionMakerRound, + } + event_to_timeout: Dict[Event, float] = { + Event.ROUND_TIMEOUT: 30.0, + } + db_pre_conditions: Dict[AppState, Set[str]] = { + DecisionMakerRound: set(), + } + db_post_conditions: Dict[AppState, Set[str]] = { + FinishedDecisionMakerRound: set(), + } diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml new file mode 100644 index 00000000..3160e763 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -0,0 +1,145 @@ +name: decision_maker_abci +author: valory +version: 0.1.0 +type: skill +description: ABCI application for common apps. +license: Apache-2.0 +aea_version: '>=1.0.0, <2.0.0' +fingerprint: + README.md: bafybeieztbubb6yn5umyt5ulknvb2xxppz5ecxaosxqsaejnrcrrwfu2ji + __init__.py: bafybeigqj2uodavhrygpqn6iah3ljp53z54c5fxyh5ykgkxuhh5lof6pda + behaviours.py: bafybeihtnjhozgnfasa4weq4tdoj74z5n7bkoqquh6h6eb2ykckmyamxny + dialogues.py: bafybeicm4bqedlyytfo4icqqbyolo36j2hk7pqh32d3zc5yqg75bt4demm + fsm_specification.yaml: bafybeicx5eutgr4lin7mhwr73xhanuzwdmps7pfoy5f2k7gfxmuec4qbyu + handlers.py: bafybeifby6yecei2d7jvxbqrc3tpyemb7xdb4ood2kny5dqja26qnxrf24 + models.py: bafybeifkfjsfkjy2x32cbuoewxujfgpcl3wk3fji6kq27ofr2zcfe7l5oe + payloads.py: bafybeiacrixfazch2a5ydj7jfk2pnvlxwkygqlwzkfmdeldrj4fqgwyyzm + rounds.py: bafybeicaewfjtvn4tg3te2glocojmd2gzqkjznv2ruyeev22yrw6ed3wu4 + tests/__init__.py: bafybeiab2s4vkmbz5bc4wggcclapdbp65bosv4en5zaazk5dwmldojpqja + tests/test_behaviours.py: bafybeiffgw2msg466lrtebl6cqa3736iuis2juf33a6awj7wzrcn5yxi6u + tests/test_dialogues.py: bafybeibeqnpzuzgcfb6yz76htslwsbbpenihswbp7j3qdyq42yswjq25l4 + tests/test_handlers.py: bafybeifpnwaktxckbvclklo6flkm5zqs7apmb33ffs4jrmunoykjbl5lni + tests/test_models.py: bafybeiewxl7nio5av2aukql2u7hlhodzdvjjneleba32abr42xeirrycb4 + tests/test_payloads.py: bafybeifik6ek75ughyd4y6t2lchlmjadkzbrz4hsb332k6ul4pwhlo2oga + tests/test_rounds.py: bafybeiefwk4quzkdn7m43rlahdhoe53xtzm4itlznjh26vdt5567oblgdm +fingerprint_ignore_patterns: [] +connections: [] +contracts: +- valory/gnosis_safe:0.1.0:bafybeif5fdwoxq5mscrurtuimadmtctyxxeeui45u4g6leqobzls7bsl3u +protocols: +- valory/contract_api:1.0.0:bafybeidv6wxpjyb2sdyibnmmum45et4zcla6tl63bnol6ztyoqvpl4spmy +skills: +- valory/abstract_round_abci:0.1.0:bafybeiac62ennpw54gns2quk4g3yoaili2mb72nj6c52czobz5dcwj4mwi +- valory/market_manager_abci:0.1.0:bafybeiunkownhashhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +behaviours: + main: + args: {} + class_name: AgentDecisionMakerRoundBehaviour +handlers: + abci: + args: {} + class_name: ABCIHandler + contract_api: + args: {} + class_name: ContractApiHandler + http: + args: {} + class_name: HttpHandler + ipfs: + args: {} + class_name: IpfsHandler + ledger_api: + args: {} + class_name: LedgerApiHandler + signing: + args: {} + class_name: SigningHandler + tendermint: + args: {} + class_name: TendermintHandler +models: + abci_dialogues: + args: {} + class_name: AbciDialogues + benchmark_tool: + args: + log_dir: /logs + class_name: BenchmarkTool + contract_api_dialogues: + args: {} + class_name: ContractApiDialogues + http_dialogues: + args: {} + class_name: HttpDialogues + ipfs_dialogues: + args: {} + class_name: IpfsDialogues + ledger_api_dialogues: + args: {} + class_name: LedgerApiDialogues + params: + args: + cleanup_history_depth: 1 + cleanup_history_depth_current: null + drand_public_key: 868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31 + genesis_config: + genesis_time: '2022-05-20T16:00:21.735122717Z' + chain_id: chain-c4daS1 + consensus_params: + block: + max_bytes: '22020096' + max_gas: '-1' + time_iota_ms: '1000' + evidence: + max_age_num_blocks: '100000' + max_age_duration: '172800000000000' + max_bytes: '1048576' + validator: + pub_key_types: + - ed25519 + version: {} + voting_power: '10' + keeper_timeout: 30.0 + max_attempts: 10 + max_healthcheck: 120 + on_chain_service_id: null + request_retry_delay: 1.0 + request_timeout: 10.0 + reset_pause_duration: 10 + reset_tendermint_after: 2 + retry_attempts: 400 + retry_timeout: 3 + round_timeout_seconds: 30.0 + service_id: decision_maker + service_registry_address: null + setup: + all_participants: + - '0x0000000000000000000000000000000000000000' + safe_contract_address: '0x0000000000000000000000000000000000000000' + consensus_threshold: null + share_tm_config_on_startup: false + sleep_time: 1 + tendermint_check_sleep_delay: 3 + tendermint_com_url: http://localhost:8080 + tendermint_max_retries: 5 + tendermint_p2p_url: localhost:26656 + tendermint_url: http://localhost:26657 + tx_timeout: 10.0 + use_termination: false + mech_agent_id: 3 + mech_tool: prediction-online + class_name: DecisionMakerParams + requests: + args: {} + class_name: Requests + signing_dialogues: + args: {} + class_name: SigningDialogues + state: + args: {} + class_name: SharedState + tendermint_dialogues: + args: {} + class_name: TendermintDialogues +dependencies: {} +is_abstract: true diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py new file mode 100644 index 00000000..723b8385 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Contains the background tasks of the decision maker skill.""" + +from dataclasses import dataclass +from string import Template +from typing import Optional + +from aea.skills.tasks import Task +from mech_client.interact import interact + +BET_PROMPT = Template( + """ + With the given question "${question}" + and the `yes` option represented by ${yes} + and the `no` option represented by ${no}, + what are the respective probabilities of `p_yes` and `p_no` occurring? + """ +) + + +@dataclass +class PredictionResponse: + """A response of a prediction.""" + + p_yes: float + p_no: float + confidence: float + info_utility: float + + def __post_init__(self): + """Runs checks on whether the current prediction response is valid or not.""" + # all the fields are probabilities + probabilities = (getattr(self, field) for field in self.__annotations__) + if ( + any(not (0 <= prob <= 1) for prob in probabilities) + or self.p_yes + self.p_no != 1 + ): + raise ValueError("Invalid prediction response initialization.") + + +@dataclass +class MechInteractionResponse: + """A structure for the response of a mech interaction task.""" + + prediction: Optional[PredictionResponse] = None + message: str = "Success" + + +class MechInteractionTask(Task): + """Perform an interaction with a mech.""" + + def execute( + self, + question: str, + yes: str, + no: str, + agent_id: int, + tool: str, + private_key_path: str, + ) -> MechInteractionResponse: + """Execute the task.""" + prompt = BET_PROMPT.substitute(question=question, yes=yes, no=no) + res = interact(prompt, agent_id, tool, private_key_path) + + try: + prediction = PredictionResponse(**res) + except (ValueError, TypeError): + error_msg = f"The response's format was unexpected: {res}" + return MechInteractionResponse(message=error_msg) + else: + return MechInteractionResponse(prediction) From b18834ae684271d0338eb7b2d5a4376d93c909fc Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:40:37 +0300 Subject: [PATCH 04/39] refactor: move behaviours to package --- .../behaviours/__init__.py | 20 +++++ .../decision_maker.py} | 77 ++++++------------- .../behaviours/round_behaviour.py | 41 ++++++++++ 3 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 packages/valory/skills/decision_maker_abci/behaviours/__init__.py rename packages/valory/skills/decision_maker_abci/{behaviours.py => behaviours/decision_maker.py} (59%) create mode 100644 packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py diff --git a/packages/valory/skills/decision_maker_abci/behaviours/__init__.py b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py new file mode 100644 index 00000000..0f37235e --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 behaviours for the 'decision_maker_abci' skill.""" diff --git a/packages/valory/skills/decision_maker_abci/behaviours.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py similarity index 59% rename from packages/valory/skills/decision_maker_abci/behaviours.py rename to packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index c8df5b69..a3263a16 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -1,40 +1,23 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2023 Valory AG -# -# 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 behaviours for the 'decision_maker_abci' skill.""" - from multiprocessing.pool import AsyncResult -from typing import Set, Type, cast, Any, Optional, Generator, Tuple +from string import Template +from typing import Any, Optional, cast, Generator, Tuple from packages.valory.skills.abstract_round_abci.base import BaseTxPayload -from packages.valory.skills.abstract_round_abci.behaviours import ( - AbstractRoundBehaviour, - BaseBehaviour, -) +from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds import ( - AgentDecisionMakerAbciApp, - DecisionMakerRound, SynchronizedData -) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerRound, SynchronizedData from packages.valory.skills.decision_maker_abci.tasks import MechInteractionTask, MechInteractionResponse +BET_PROMPT = Template( + """ + With the given question "${question}" + and the `yes` option represented by ${yes} + and the `no` option represented by ${no}, + what are the respective probabilities of `p_yes` and `p_no` occurring? + """ +) + class DecisionMakerBehaviour(BaseBehaviour): """A round in which the agents decide which answer they are going to choose for the next bet.""" @@ -59,21 +42,20 @@ def synchronized_data(self) -> SynchronizedData: def setup(self) -> None: """Setup behaviour.""" mech_task = MechInteractionTask() - sampled_bet = self.synchronized_data.sampled_bet.title + sampled_bet = self.synchronized_data.sampled_bet + prompt_params = dict( + question=sampled_bet.title, yes=sampled_bet.yes, no=sampled_bet.no + ) task_kwargs = dict( - question=sampled_bet.title, - yes=sampled_bet.yes, - no=sampled_bet.no, + prompt=BET_PROMPT.substitute(prompt_params), agent_id=self.params.mech_agent_id, tool=self.params.mech_tool, - private_key_path=self.context.private_key_paths.read("ethereum") - ) - task_id = self.context.task_manager.enqueue_task( - mech_task, kwargs=task_kwargs + private_key_path=self.context.private_key_paths.read("ethereum"), ) + task_id = self.context.task_manager.enqueue_task(mech_task, kwargs=task_kwargs) self._async_result = self.context.task_manager.get_task_result(task_id) - def _get_decision(self) -> Generator[None, None, Optional[Tuple[bool, float]]]: + def _get_decision(self) -> Generator[None, None, Optional[Tuple[Optional[int], float]]]: """Get the vote and it's confidence.""" self._async_result = cast(AsyncResult, self._async_result) if not self._async_result.ready(): @@ -83,12 +65,11 @@ def _get_decision(self) -> Generator[None, None, Optional[Tuple[bool, float]]]: # Get the decision from the task. mech_response = cast(MechInteractionResponse, self._async_result.get()) - self.context.logger.info( - f"Decision has been received:\n{mech_response}" - ) + self.context.logger.info(f"Decision has been received:\n{mech_response}") if mech_response.prediction is None: - return False, 0 + self.context.logger.info(f"There was an error on the mech response: {mech_response.error}") + return None, -1 return mech_response.prediction.vote, mech_response.prediction.confidence @@ -112,13 +93,3 @@ def async_act(self) -> Generator: payload = DecisionMakerPayload(self.context.agent_address, vote, confidence) yield from self.finish_behaviour(payload) - - -class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): - """This behaviour manages the consensus stages for the decision making.""" - - initial_behaviour_cls = DecisionMakerBehaviour - abci_app_cls = AgentDecisionMakerAbciApp - behaviours: Set[Type[BaseBehaviour]] = { - DecisionMakerBehaviour, # type: ignore - } diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py new file mode 100644 index 00000000..4d4928b3 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 round behaviour for the 'decision_maker_abci' skill.""" + +from typing import Set, Type + +from packages.valory.skills.abstract_round_abci.behaviours import ( + AbstractRoundBehaviour, + BaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.behaviours.decision_maker import DecisionMakerBehaviour +from packages.valory.skills.decision_maker_abci.rounds import ( + AgentDecisionMakerAbciApp, +) + + +class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): + """This behaviour manages the consensus stages for the decision-making.""" + + initial_behaviour_cls = DecisionMakerBehaviour + abci_app_cls = AgentDecisionMakerAbciApp + behaviours: Set[Type[BaseBehaviour]] = { + DecisionMakerBehaviour, # type: ignore + } From 83d9713952e2c81b066ca0d4f9c78cc9f8baafae Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:41:13 +0300 Subject: [PATCH 05/39] refactor: bets include the market --- .../skills/market_manager_abci/behaviours.py | 19 ++++++++----------- .../valory/skills/market_manager_abci/bets.py | 1 + .../skills/market_manager_abci/rounds.py | 4 ++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index 0fea9186..1c1a07dd 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -20,7 +20,7 @@ """This module contains the behaviours for the MarketManager skill.""" import json -from typing import Any, Dict, Generator, List, Optional, Set, Type +from typing import Any, Generator, List, Optional, Set, Type, Iterator from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.behaviours import AbstractRoundBehaviour @@ -45,7 +45,7 @@ def __init__(self, **kwargs: Any) -> None: """Initialize `UpdateBetsBehaviour`.""" super().__init__(**kwargs) # list of bets mapped to prediction markets - self.bets: Dict[str, List[Bet]] = {} + self.bets: List[Bet] = [] @property def serialized_bets(self) -> Optional[str]: @@ -57,7 +57,7 @@ def serialized_bets(self) -> Optional[str]: @property def bets_ids(self) -> List[str]: """Get the ids of the already existing bets.""" - return [bet.id for bets in self.bets.values() for bet in bets] + return [bet.id for bet in self.bets] def is_valid_bet(self, bet: Bet) -> bool: """Return if a bet is valid or not.""" @@ -67,18 +67,15 @@ def is_valid_bet(self, bet: Bet) -> bool: ) @property - def valid_local_bets(self) -> Dict[str, List[Bet]]: + def valid_local_bets(self) -> Iterator[Bet]: """Get the valid already existing bets.""" - return { - market: list(filter(self.is_valid_bet, bets)) - for market, bets in self.synchronized_data.bets.items() - } + return filter(self.is_valid_bet, self.synchronized_data.bets) def _update_bets( self, ) -> Generator: """Fetch the questions from all the prediction markets and update the local copy of the bets.""" - self.bets = self.valid_local_bets + self.bets = list(self.valid_local_bets) existing_ids = self.bets_ids while True: @@ -89,11 +86,11 @@ def _update_bets( bets_market_chunk = yield from self._fetch_bets() if bets_market_chunk is not None: bets_updates = ( - Bet(**bet) + Bet(**bet, market=self._current_market) for bet in bets_market_chunk if bet.id not in existing_ids ) - self.bets[self._current_market].extend(bets_updates) + self.bets.extend(bets_updates) if self._fetch_status != FetchStatus.SUCCESS: self.bets = {} diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 7f9e23f5..1924b633 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -41,6 +41,7 @@ class Bet: """A bet's structure.""" id: str + market: str title: str creator: str fee: int diff --git a/packages/valory/skills/market_manager_abci/rounds.py b/packages/valory/skills/market_manager_abci/rounds.py index e8ff47f6..560e95ac 100644 --- a/packages/valory/skills/market_manager_abci/rounds.py +++ b/packages/valory/skills/market_manager_abci/rounds.py @@ -61,12 +61,12 @@ def _get_deserialized(self, key: str) -> DeserializedCollection: return CollectionRound.deserialize_collection(serialized) @property - def bets(self) -> Dict[str, List[Bet]]: + def bets(self) -> List[Bet]: """Get the most voted bets.""" serialized_bets = str(self.db.get("bets", "")) if serialized_bets == "": - return {} + return [] return json.loads(serialized_bets, cls=BetsDecoder) From a92767226d500c8cfc8fb255c1e59c74f77d0c9b Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:42:29 +0300 Subject: [PATCH 06/39] refactor: the mech task accepts the prompt as an input --- .../skills/decision_maker_abci/tasks.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index 723b8385..281ad4e0 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -20,21 +20,11 @@ """Contains the background tasks of the decision maker skill.""" from dataclasses import dataclass -from string import Template -from typing import Optional +from typing import Optional, Any from aea.skills.tasks import Task from mech_client.interact import interact -BET_PROMPT = Template( - """ - With the given question "${question}" - and the `yes` option represented by ${yes} - and the `no` option represented by ${no}, - what are the respective probabilities of `p_yes` and `p_no` occurring? - """ -) - @dataclass class PredictionResponse: @@ -69,16 +59,11 @@ class MechInteractionTask(Task): def execute( self, - question: str, - yes: str, - no: str, - agent_id: int, - tool: str, - private_key_path: str, + *args: Any, + **kwargs: Any, ) -> MechInteractionResponse: """Execute the task.""" - prompt = BET_PROMPT.substitute(question=question, yes=yes, no=no) - res = interact(prompt, agent_id, tool, private_key_path) + res = interact(*args, **kwargs) try: prediction = PredictionResponse(**res) From a341a788e96e60e8ebb4677b25d467d40a4ad8fe Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:44:36 +0300 Subject: [PATCH 07/39] refactor: rename `message` to `error` --- packages/valory/skills/decision_maker_abci/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index 281ad4e0..5b5395d5 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -51,7 +51,7 @@ class MechInteractionResponse: """A structure for the response of a mech interaction task.""" prediction: Optional[PredictionResponse] = None - message: str = "Success" + error: str = "Unknown" class MechInteractionTask(Task): @@ -69,6 +69,6 @@ def execute( prediction = PredictionResponse(**res) except (ValueError, TypeError): error_msg = f"The response's format was unexpected: {res}" - return MechInteractionResponse(message=error_msg) + return MechInteractionResponse(error=error_msg) else: return MechInteractionResponse(prediction) From 3dd7d4e7eaf47c46e870cd685e0094e387b150cc Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:44:47 +0300 Subject: [PATCH 08/39] feat: add `vote` property --- packages/valory/skills/decision_maker_abci/tasks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index 5b5395d5..8628f19f 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -45,6 +45,13 @@ def __post_init__(self): ): raise ValueError("Invalid prediction response initialization.") + @property + def vote(self) -> Optional[int]: + """Return the vote. `0` represents "yes" and `1` represents "no".""" + if self.p_no != self.p_yes: + return bool(self.p_no > self.p_yes) + return None + @dataclass class MechInteractionResponse: From 50169756c2119adcc1c081920250596e0b6b70c4 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:47:57 +0300 Subject: [PATCH 09/39] refactor: `vote` and `confidence` can be `None` The following scenarios are possible: 1. both are `None` -> error on mech response 2. vote is `None` -> there was a tie, i.e., no decision can be made --- .../skills/decision_maker_abci/behaviours/decision_maker.py | 4 ++-- packages/valory/skills/decision_maker_abci/payloads.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index a3263a16..23578275 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -55,7 +55,7 @@ def setup(self) -> None: task_id = self.context.task_manager.enqueue_task(mech_task, kwargs=task_kwargs) self._async_result = self.context.task_manager.get_task_result(task_id) - def _get_decision(self) -> Generator[None, None, Optional[Tuple[Optional[int], float]]]: + def _get_decision(self) -> Generator[None, None, Optional[Tuple[Optional[int], Optional[float]]]]: """Get the vote and it's confidence.""" self._async_result = cast(AsyncResult, self._async_result) if not self._async_result.ready(): @@ -69,7 +69,7 @@ def _get_decision(self) -> Generator[None, None, Optional[Tuple[Optional[int], f if mech_response.prediction is None: self.context.logger.info(f"There was an error on the mech response: {mech_response.error}") - return None, -1 + return None, None return mech_response.prediction.vote, mech_response.prediction.confidence diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 9e811de6..74c47c6c 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -20,6 +20,7 @@ """This module contains the transaction payloads for the decision maker.""" from dataclasses import dataclass +from typing import Optional from packages.valory.skills.abstract_round_abci.base import BaseTxPayload @@ -28,5 +29,5 @@ class DecisionMakerPayload(BaseTxPayload): """Represents a transaction payload for the decision-making.""" - vote: bool - confidence: float + vote: Optional[int] + confidence: Optional[float] From 7c38c5e13b234610790ac54592d4e5141627154a Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:48:27 +0300 Subject: [PATCH 10/39] feat: implement yes and no properties for the bet --- packages/valory/skills/market_manager_abci/bets.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 1924b633..b49286b8 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -61,6 +61,20 @@ def __post_init__(self) -> None: if isinstance(self.status, int): super().__setattr__("status", BetStatus(self.status)) + @property + def yes(self) -> str: + """Return the "yes" outcome.""" + if not self.outcomes: + return "yes" + return self.outcomes[0] + + @property + def no(self) -> str: + """Return the "no" outcome.""" + if self.outcomes is None or len(self.outcomes) < 2: + return "no" + return self.outcomes[1] + class BetsEncoder(json.JSONEncoder): """JSON encoder for bets.""" From e17519afaba69245bf1066306ef89a9a2ae56ef4 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Thu, 6 Jul 2023 21:51:16 +0300 Subject: [PATCH 11/39] fix: decision maker is a collect same until threshold round --- packages/valory/skills/decision_maker_abci/rounds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 926a88dd..15d0e277 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -27,7 +27,7 @@ AbciAppTransitionFunction, AppState, DegenerateRound, - CollectDifferentUntilThresholdRound, + CollectSameUntilThresholdRound, ) from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload from packages.valory.skills.market_manager_abci.bets import Bet @@ -53,7 +53,7 @@ def sampled_bet(self) -> Bet: """Get the sampled bet.""" -class DecisionMakerRound(CollectDifferentUntilThresholdRound): +class DecisionMakerRound(CollectSameUntilThresholdRound): """A round in which the agents decide on the bet's answer.""" payload_class = DecisionMakerPayload From f0ab8c89278f8aadb661f0ec892dfecbad178b6d Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 10:21:40 +0300 Subject: [PATCH 12/39] refactor: sanitize outcomes & do not keep bets without any --- packages/valory/skills/market_manager_abci/behaviours.py | 2 +- packages/valory/skills/market_manager_abci/bets.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index 1c1a07dd..cd0540dc 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -88,7 +88,7 @@ def _update_bets( bets_updates = ( Bet(**bet, market=self._current_market) for bet in bets_market_chunk - if bet.id not in existing_ids + if bet.id not in existing_ids and bet.outcomes is not None ) self.bets.extend(bets_updates) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index b49286b8..382722bd 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -55,7 +55,13 @@ class Bet: def __post_init__(self) -> None: """Post initialization to adjust the values.""" - if self.outcomes == "null": + if ( + self.outcomes == "null" + or len(self.outcomes) + != len(self.outcomeTokenAmounts) + != len(self.outcomeTokenMarginalPrices) + != self.outcomeSlotCount + ): self.outcomes = None if isinstance(self.status, int): From e1aa3036fd54e3ca3e2a81463ca38e643d2ecca3 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 10:22:36 +0300 Subject: [PATCH 13/39] fix: avoid `StopIteration` and return `None` as a default --- .../valory/skills/market_manager_abci/graph_tooling/requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py index 32f39a02..65a95776 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py @@ -94,7 +94,7 @@ def current_subgraph(self) -> ApiSpecs: def _prepare_bets_fetching(self) -> bool: """Prepare for fetching a bet.""" if self._fetch_status in (FetchStatus.SUCCESS, FetchStatus.NONE): - res = next(self._creators_iterator) + res = next(self._creators_iterator, None) if res is None: return False self._current_market, self._current_creators = next(self._creators_iterator) From 4a57995838c7b85b07b2e085d06428187dde9ffd Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 10:24:13 +0300 Subject: [PATCH 14/39] style: correct type hint --- .../valory/skills/market_manager_abci/graph_tooling/requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py index 65a95776..3cec017c 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py @@ -147,7 +147,7 @@ def _handle_response( self._fetch_status = FetchStatus.SUCCESS return value - def _fetch_bets(self) -> Generator[None, None, Optional[dict]]: + def _fetch_bets(self) -> Generator[None, None, Optional[list]]: """Fetch questions from the current subgraph, for the current creators.""" self._fetch_status = FetchStatus.IN_PROGRESS From 4dd48477be3a7f27c14254882c494d0c0ddbc30f Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 10:25:22 +0300 Subject: [PATCH 15/39] refactor: move the response key into the api spec --- .../market_manager_abci/graph_tooling/requests.py | 12 ++---------- .../valory/skills/market_manager_abci/skill.yaml | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py index 3cec017c..0be77e59 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py @@ -109,14 +109,12 @@ def _handle_response( self, res: Optional[Dict], res_context: str, - keys: Tuple[Union[str, int], ...], sleep_on_fail: bool = True, ) -> Generator[None, None, Optional[Any]]: """Handle a response from a subgraph. :param res: the response to handle. :param res_context: the context of the current response. - :param keys: keys to get the information from the response. :param sleep_on_fail: whether we want to sleep if we fail to get the response's result. :return: the response's result, using the given keys. `None` if response is `None` (has failed). :yield: None @@ -136,16 +134,11 @@ def _handle_response( yield from self.sleep(sleep_time) return None - value = res[keys[0]] - if len(keys) > 1: - for key in keys[1:]: - value = value[key] - - self.context.logger.info(f"Retrieved {res_context}: {value}.") + self.context.logger.info(f"Retrieved {res_context}: {res}.") self._call_failed = False self.current_subgraph.reset_retries() self._fetch_status = FetchStatus.SUCCESS - return value + return res def _fetch_bets(self) -> Generator[None, None, Optional[list]]: """Fetch questions from the current subgraph, for the current creators.""" @@ -167,7 +160,6 @@ def _fetch_bets(self) -> Generator[None, None, Optional[list]]: bets = yield from self._handle_response( res, res_context="questions", - keys=("fixedProductMarketMakers",), ) return bets diff --git a/packages/valory/skills/market_manager_abci/skill.yaml b/packages/valory/skills/market_manager_abci/skill.yaml index f1b58577..3363b960 100644 --- a/packages/valory/skills/market_manager_abci/skill.yaml +++ b/packages/valory/skills/market_manager_abci/skill.yaml @@ -137,7 +137,7 @@ models: Content-Type: application/json method: POST parameters: { } - response_key: data + response_key: data:fixedProductMarketMakers response_type: dict retries: 5 url: https://api.thegraph.com/subgraphs/name/protofire/omen-xdai From af675b2409d498384e78ab08f810af25c55c3ed5 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 10:28:24 +0300 Subject: [PATCH 16/39] refactor: raise if the outcome is non-binary --- .../valory/skills/market_manager_abci/bets.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 382722bd..5ed75f9c 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -26,6 +26,9 @@ from typing import Any, Dict, List, Optional, Union +BINARY_N_SLOTS = 2 + + class BetStatus(Enum): """A bet's status.""" @@ -67,19 +70,22 @@ def __post_init__(self) -> None: if isinstance(self.status, int): super().__setattr__("status", BetStatus(self.status)) + def _get_binary_outcome(self, no: bool) -> str: + """Get an outcome only if it is binary.""" + if self.outcomeSlotCount == BINARY_N_SLOTS: + return self.outcomes[int(no)] + outcome = "no" if no else "yes" + raise ValueError(f"A '{outcome}' outcome is only available for binary questions.") + @property def yes(self) -> str: """Return the "yes" outcome.""" - if not self.outcomes: - return "yes" - return self.outcomes[0] + return self._get_binary_outcome(False) @property def no(self) -> str: """Return the "no" outcome.""" - if self.outcomes is None or len(self.outcomes) < 2: - return "no" - return self.outcomes[1] + return self._get_binary_outcome(True) class BetsEncoder(json.JSONEncoder): From f248c0afb7a64db06ccb2c52c379bcc6063afaf2 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:32:07 +0300 Subject: [PATCH 17/39] fix: bets is a list --- packages/valory/skills/market_manager_abci/behaviours.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index cd0540dc..408e86c3 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -93,7 +93,7 @@ def _update_bets( self.bets.extend(bets_updates) if self._fetch_status != FetchStatus.SUCCESS: - self.bets = {} + self.bets = [] def async_act(self) -> Generator: """Do the action.""" From aa378a742995d533eff3e47cde359d13e9047220 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:32:34 +0300 Subject: [PATCH 18/39] feat: improve the `outcomes` logic --- .../valory/skills/market_manager_abci/bets.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 5ed75f9c..5b9073fb 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -59,7 +59,8 @@ class Bet: def __post_init__(self) -> None: """Post initialization to adjust the values.""" if ( - self.outcomes == "null" + self.outcomes is None + or self.outcomes == "null" or len(self.outcomes) != len(self.outcomeTokenAmounts) != len(self.outcomeTokenMarginalPrices) @@ -70,12 +71,25 @@ def __post_init__(self) -> None: if isinstance(self.status, int): super().__setattr__("status", BetStatus(self.status)) + def get_outcome(self, index: int) -> str: + """Get an outcome given its index.""" + if self.outcomes is None: + raise ValueError(f"Bet {self} has an incorrect outcomes list of `None`.") + try: + return self.outcomes[index] + except KeyError as exc: + error = f"Cannot get outcome with index {index} from {self.outcomes}" + raise ValueError(error) from exc + def _get_binary_outcome(self, no: bool) -> str: """Get an outcome only if it is binary.""" if self.outcomeSlotCount == BINARY_N_SLOTS: - return self.outcomes[int(no)] - outcome = "no" if no else "yes" - raise ValueError(f"A '{outcome}' outcome is only available for binary questions.") + return self.get_outcome(int(no)) + requested_outcome = "no" if no else "yes" + error = ( + f"A {requested_outcome!r} outcome is only available for binary questions." + ) + raise ValueError(error) @property def yes(self) -> str: From 9099c7bb581bd099bf4f89789c8f3a6f638f28fd Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:35:35 +0300 Subject: [PATCH 19/39] feat: finish the decision-making core logic The `_is_profitable` method is pending implementation. --- .../behaviours/decision_maker.py | 21 ++++- .../skills/decision_maker_abci/payloads.py | 2 + .../skills/decision_maker_abci/rounds.py | 84 +++++++++++++++++-- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index 23578275..c8a7c2c5 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -39,8 +39,16 @@ def synchronized_data(self) -> SynchronizedData: """Return the synchronized data.""" return cast(SynchronizedData, super().synchronized_data) + @property + def n_slots_unsupported(self) -> bool: + """Whether the behaviour supports the current number of slots as it currently only supports binary decisions.""" + return self.params.slot_count != SUPPORTED_N_SLOTS + def setup(self) -> None: """Setup behaviour.""" + if self.n_slots_unsupported: + return + mech_task = MechInteractionTask() sampled_bet = self.synchronized_data.sampled_bet prompt_params = dict( @@ -55,9 +63,13 @@ def setup(self) -> None: task_id = self.context.task_manager.enqueue_task(mech_task, kwargs=task_kwargs) self._async_result = self.context.task_manager.get_task_result(task_id) - def _get_decision(self) -> Generator[None, None, Optional[Tuple[Optional[int], Optional[float]]]]: + def _get_decision( + self, + ) -> Generator[None, None, Optional[Tuple[Optional[int], Optional[float]]]]: """Get the vote and it's confidence.""" - self._async_result = cast(AsyncResult, self._async_result) + if self._async_result is None: + return None, None + if not self._async_result.ready(): self.context.logger.debug("The decision making task is not finished yet.") yield from self.sleep(self.params.sleep_time) @@ -90,6 +102,9 @@ def async_act(self) -> Generator: return vote, confidence = decision - payload = DecisionMakerPayload(self.context.agent_address, vote, confidence) + is_profitable = self._is_profitable(vote, confidence) + payload = DecisionMakerPayload( + self.context.agent_address, self.n_slots_unsupported, is_profitable, vote, confidence + ) yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 74c47c6c..787c6272 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -29,5 +29,7 @@ class DecisionMakerPayload(BaseTxPayload): """Represents a transaction payload for the decision-making.""" + unsupported: bool + is_profitable: bool vote: Optional[int] confidence: Optional[float] diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 15d0e277..1c76cf6e 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -20,24 +20,32 @@ """This module contains the rounds for the decision-making.""" from enum import Enum -from typing import Dict, Set +from typing import Dict, Optional, Set, Tuple, cast from packages.valory.skills.abstract_round_abci.base import ( AbciApp, AbciAppTransitionFunction, AppState, - DegenerateRound, CollectSameUntilThresholdRound, + DegenerateRound, + DeserializedCollection, + get_name, ) from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload from packages.valory.skills.market_manager_abci.bets import Bet -from packages.valory.skills.market_manager_abci.rounds import SynchronizedData as BaseSynchronizedData +from packages.valory.skills.market_manager_abci.rounds import ( + SynchronizedData as BaseSynchronizedData, +) class Event(Enum): """Event enumeration for the price estimation demo.""" DONE = "done" + MECH_RESPONSE_ERROR = "mech_response_error" + NON_BINARY = "non_binary" + TIE = "tie" + UNPROFITABLE = "unprofitable" ROUND_TIMEOUT = "round_timeout" NO_MAJORITY = "no_majority" @@ -51,6 +59,33 @@ class SynchronizedData(BaseSynchronizedData): @property def sampled_bet(self) -> Bet: """Get the sampled bet.""" + raise NotImplementedError + + @property + def non_binary(self) -> bool: + """Get whether the question is non-binary.""" + return bool(self.db.get_strict("non_binary")) + + @property + def vote(self) -> str: + """Get the bet's vote.""" + vote = self.db.get_strict("vote") + return self.sampled_bet.get_outcome(vote) + + @property + def confidence(self) -> float: + """Get the vote's confidence.""" + return float(self.db.get_strict("confidence")) + + @property + def is_profitable(self) -> bool: + """Get whether the current vote is profitable or not.""" + return bool(self.db.get_strict("is_profitable")) + + @property + def participant_to_decision(self) -> DeserializedCollection: + """Get the participants to decision-making.""" + return self._get_deserialized("participant_to_decision") class DecisionMakerRound(CollectSameUntilThresholdRound): @@ -59,13 +94,46 @@ class DecisionMakerRound(CollectSameUntilThresholdRound): payload_class = DecisionMakerPayload synchronized_data_class = BaseSynchronizedData + done_event = Event.DONE + none_event = Event.MECH_RESPONSE_ERROR + no_majority_event = Event.NO_MAJORITY + selection_key = ( + get_name(SynchronizedData.non_binary), + get_name(SynchronizedData.vote), + get_name(SynchronizedData.confidence), + get_name(SynchronizedData.is_profitable), + ) + collection_key = get_name(SynchronizedData.participant_to_decision) + + def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: + """Process the end of the block.""" + res = super().end_block() + if res is None: + return None + + synced_data, event = cast(Tuple[SynchronizedData, Enum], res) + if event == Event.DONE and synced_data.non_binary: + return synced_data, Event.NON_BINARY + + if event == Event.DONE and synced_data.vote is None: + return synced_data, Event.TIE + + if event == Event.DONE and not synced_data.is_profitable: + return synced_data, Event.UNPROFITABLE + + return synced_data, event + class FinishedDecisionMakerRound(DegenerateRound): - """A round representing that decision-making has finished""" + """A round representing that decision-making has finished.""" + + +class ImpossibleRound(DegenerateRound): + """A round representing that decision-making is impossible with the given parametrization.""" -class AgentDecisionMakerAbciApp(AbciApp[Event]): - """AgentDecisionMakerAbciApp +class DecisionMakerAbciApp(AbciApp[Event]): + """DecisionMakerAbciApp Initial round: DecisionMakerRound @@ -84,7 +152,11 @@ class AgentDecisionMakerAbciApp(AbciApp[Event]): transition_function: AbciAppTransitionFunction = { DecisionMakerRound: { Event.DONE: FinishedDecisionMakerRound, + Event.MECH_RESPONSE_ERROR: FinishedDecisionMakerRound, # TODO blacklist and go back to sampling a bet Event.NO_MAJORITY: DecisionMakerRound, + Event.NON_BINARY: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.TIE: FinishedDecisionMakerRound, # TODO blacklist and go back to sampling a bet + Event.UNPROFITABLE: FinishedDecisionMakerRound, # TODO blacklist the sampled bet for duration set in config }, FinishedDecisionMakerRound: {}, } From 14b0da00da3075ecde554adb224437bf55cd877f Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:36:28 +0300 Subject: [PATCH 20/39] chore: add decision maker to the generators and the checks --- Makefile | 1 + tox.ini | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b7a3b621..f4907db9 100644 --- a/Makefile +++ b/Makefile @@ -96,6 +96,7 @@ all-checks: clean format code-checks security generators common-checks-1 common- .PHONY: fix-abci-app-specs fix-abci-app-specs: autonomy analyse fsm-specs --update --app-class MarketManagerAbciApp --package packages/valory/skills/market_manager_abci + autonomy analyse fsm-specs --update --app-class DecisionMakerAbciApp --package packages/valory/skills/decision_maker_abci echo "Successfully validated abcis!" protolint_install: diff --git a/tox.ini b/tox.ini index 3ba7c9c9..1bcda0ab 100644 --- a/tox.ini +++ b/tox.ini @@ -58,7 +58,7 @@ setenv = PYTHONPATH={env:PWD:%CD%} PACKAGES_PATHS = packages/valory SKILLS_PATHS = {env:PACKAGES_PATHS}/skills - SERVICE_SPECIFIC_PACKAGES = {env:SKILLS_PATHS}/market_manager_abci + SERVICE_SPECIFIC_PACKAGES = {env:SKILLS_PATHS}/market_manager_abci {env:SKILLS_PATHS}/decision_maker_abci [testenv:bandit] skipsdist = True @@ -334,6 +334,9 @@ ignore_missing_imports = True [mypy-aea_cli_ipfs.*] ignore_missing_imports = True +[mypy-mech_client.*] +ignore_missing_imports = True + [mypy-py_eth_sig_utils.*] ignore_missing_imports = True From 02088416d0143b0d6fff2d51b434b568f64bc052 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:38:03 +0300 Subject: [PATCH 21/39] refactor: fail early in case of unsupported number of slots --- packages/valory/skills/market_manager_abci/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/valory/skills/market_manager_abci/models.py b/packages/valory/skills/market_manager_abci/models.py index 8f0c4211..0d8fa0b2 100644 --- a/packages/valory/skills/market_manager_abci/models.py +++ b/packages/valory/skills/market_manager_abci/models.py @@ -37,6 +37,9 @@ BenchmarkTool = BaseBenchmarkTool +SUPPORTED_N_SLOTS = 2 + + class SharedState(BaseSharedState): """Keep the current shared state of the skill.""" @@ -57,6 +60,12 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "creator_per_subgraph", kwargs, Dict[str, List[str]] ) self.slot_count: int = self._ensure("slot_count", kwargs, int) + + if self.slot_count != SUPPORTED_N_SLOTS: + raise ValueError( + f"Only a slot_count `2` is currently supported. `{self.slot_count}` was found in the configuration." + ) + self.opening_margin: int = self._ensure("opening_margin", kwargs, int) self.languages: List[str] = self._ensure("languages", kwargs, List[str]) super().__init__(*args, **kwargs) From 41b53aa07848fb25c90012941775dfad6828eed4 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:38:36 +0300 Subject: [PATCH 22/39] fix: correct the return value for the vote --- packages/valory/skills/decision_maker_abci/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index 8628f19f..c6dce5e4 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -49,7 +49,7 @@ def __post_init__(self): def vote(self) -> Optional[int]: """Return the vote. `0` represents "yes" and `1` represents "no".""" if self.p_no != self.p_yes: - return bool(self.p_no > self.p_yes) + return int(self.p_no > self.p_yes) return None From 93a96ef3967a6eb70a7e85df3ec2f5408b9dd6ae Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 12:38:46 +0300 Subject: [PATCH 23/39] chore: linting --- .../behaviours/__init__.py | 2 +- .../behaviours/decision_maker.py | 51 ++++++++++++++++--- .../behaviours/round_behaviour.py | 8 +-- .../skills/decision_maker_abci/models.py | 8 +-- .../skills/decision_maker_abci/tasks.py | 4 +- .../skills/market_manager_abci/behaviours.py | 2 +- .../graph_tooling/requests.py | 2 +- 7 files changed, 57 insertions(+), 20 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/__init__.py b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py index 0f37235e..c0db5476 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/__init__.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/__init__.py @@ -17,4 +17,4 @@ # # ------------------------------------------------------------------------------ -"""This module contains the behaviours for the 'decision_maker_abci' skill.""" +"""This package contains the behaviours for the 'decision_maker_abci' skill.""" diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index c8a7c2c5..eeaa8ca7 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -1,19 +1,48 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 behaviours for the 'decision_maker_abci' skill.""" + from multiprocessing.pool import AsyncResult from string import Template -from typing import Any, Optional, cast, Generator, Tuple +from typing import Any, Generator, Optional, Tuple, cast from packages.valory.skills.abstract_round_abci.base import BaseTxPayload from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerRound, SynchronizedData -from packages.valory.skills.decision_maker_abci.tasks import MechInteractionTask, MechInteractionResponse +from packages.valory.skills.decision_maker_abci.rounds import ( + DecisionMakerRound, + SynchronizedData, +) +from packages.valory.skills.decision_maker_abci.tasks import ( + MechInteractionResponse, + MechInteractionTask, +) +from packages.valory.skills.market_manager_abci.models import SUPPORTED_N_SLOTS + BET_PROMPT = Template( """ - With the given question "${question}" - and the `yes` option represented by ${yes} - and the `no` option represented by ${no}, + With the given question "${question}" + and the `yes` option represented by ${yes} + and the `no` option represented by ${no}, what are the respective probabilities of `p_yes` and `p_no` occurring? """ ) @@ -80,7 +109,9 @@ def _get_decision( self.context.logger.info(f"Decision has been received:\n{mech_response}") if mech_response.prediction is None: - self.context.logger.info(f"There was an error on the mech response: {mech_response.error}") + self.context.logger.info( + f"There was an error on the mech response: {mech_response.error}" + ) return None, None return mech_response.prediction.vote, mech_response.prediction.confidence @@ -104,7 +135,11 @@ def async_act(self) -> Generator: vote, confidence = decision is_profitable = self._is_profitable(vote, confidence) payload = DecisionMakerPayload( - self.context.agent_address, self.n_slots_unsupported, is_profitable, vote, confidence + self.context.agent_address, + self.n_slots_unsupported, + is_profitable, + vote, + confidence, ) yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py index 4d4928b3..df7b9ab9 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -25,17 +25,17 @@ AbstractRoundBehaviour, BaseBehaviour, ) -from packages.valory.skills.decision_maker_abci.behaviours.decision_maker import DecisionMakerBehaviour -from packages.valory.skills.decision_maker_abci.rounds import ( - AgentDecisionMakerAbciApp, +from packages.valory.skills.decision_maker_abci.behaviours.decision_maker import ( + DecisionMakerBehaviour, ) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): """This behaviour manages the consensus stages for the decision-making.""" initial_behaviour_cls = DecisionMakerBehaviour - abci_app_cls = AgentDecisionMakerAbciApp + abci_app_cls = DecisionMakerAbciApp behaviours: Set[Type[BaseBehaviour]] = { DecisionMakerBehaviour, # type: ignore } diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index 9d03191e..b8f7241a 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -21,7 +21,6 @@ from typing import Any -from packages.valory.skills.abstract_round_abci.models import BaseParams from packages.valory.skills.abstract_round_abci.models import ( BenchmarkTool as BaseBenchmarkTool, ) @@ -29,7 +28,10 @@ from packages.valory.skills.abstract_round_abci.models import ( SharedState as BaseSharedState, ) -from packages.valory.skills.decision_maker_abci.rounds import AgentDecisionMakerAbciApp +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp +from packages.valory.skills.market_manager_abci.models import ( + MarketManagerParams as BaseParams, +) Requests = BaseRequests @@ -39,7 +41,7 @@ class SharedState(BaseSharedState): """Keep the current shared state of the skill.""" - abci_app_cls = AgentDecisionMakerAbciApp + abci_app_cls = DecisionMakerAbciApp class DecisionMakerParams(BaseParams): diff --git a/packages/valory/skills/decision_maker_abci/tasks.py b/packages/valory/skills/decision_maker_abci/tasks.py index c6dce5e4..61428a47 100644 --- a/packages/valory/skills/decision_maker_abci/tasks.py +++ b/packages/valory/skills/decision_maker_abci/tasks.py @@ -20,7 +20,7 @@ """Contains the background tasks of the decision maker skill.""" from dataclasses import dataclass -from typing import Optional, Any +from typing import Any, Optional from aea.skills.tasks import Task from mech_client.interact import interact @@ -35,7 +35,7 @@ class PredictionResponse: confidence: float info_utility: float - def __post_init__(self): + def __post_init__(self) -> None: """Runs checks on whether the current prediction response is valid or not.""" # all the fields are probabilities probabilities = (getattr(self, field) for field in self.__annotations__) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index 408e86c3..9072823b 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -20,7 +20,7 @@ """This module contains the behaviours for the MarketManager skill.""" import json -from typing import Any, Generator, List, Optional, Set, Type, Iterator +from typing import Any, Generator, Iterator, List, Optional, Set, Type from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.behaviours import AbstractRoundBehaviour diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py index 0be77e59..9f2b664e 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/requests.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/requests.py @@ -20,7 +20,7 @@ import json from abc import ABC from enum import Enum, auto -from typing import Any, Dict, Generator, Iterator, List, Optional, Tuple, Union, cast +from typing import Any, Dict, Generator, Iterator, List, Optional, Tuple, cast from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.models import ApiSpecs From 50dc1cbbeb6affb60ab29ca35d31d7f1ae96eb2e Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:03:32 +0300 Subject: [PATCH 24/39] feat: implement check for profitability of a bet --- .../behaviours/decision_maker.py | 10 ++++++++++ .../valory/skills/decision_maker_abci/models.py | 13 ++++++++++++- .../valory/skills/decision_maker_abci/skill.yaml | 13 +++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index eeaa8ca7..2a4d1c85 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -116,6 +116,16 @@ def _get_decision( return mech_response.prediction.vote, mech_response.prediction.confidence + def _is_profitable(self, vote: Optional[int], confidence: Optional[float]) -> bool: + """Whether the decision is profitable or not.""" + if vote is None or confidence is None: + return False + + bet_amount = self.params.get_bet_amount(confidence) + fee = self.synchronized_data.sampled_bet.fee + bet_threshold = self.params.bet_threshold + return bet_amount - fee >= bet_threshold + def finish_behaviour(self, payload: BaseTxPayload) -> Generator: """Finish the behaviour.""" with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index b8f7241a..e1118778 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -19,7 +19,7 @@ """This module contains the models for the skill.""" -from typing import Any +from typing import Any, Dict from packages.valory.skills.abstract_round_abci.models import ( BenchmarkTool as BaseBenchmarkTool, @@ -51,4 +51,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: """Initialize the parameters' object.""" self.mech_agent_id: int = self._ensure("mech_agent_id", kwargs, int) self.mech_tool: int = self._ensure("mech_tool", kwargs, str) + # this is a mapping from the confidence of a bet's choice to the amount we are willing to bet + self.bet_amount_per_threshold: Dict[float, int] = self._ensure( + "bet_amount_per_threshold", kwargs, Dict[float, int] + ) + # the threshold amount in WEI starting from which we are willing to place a bet + self.bet_threshold: int = self._ensure("bet_threshold", kwargs, str) super().__init__(*args, **kwargs) + + def get_bet_amount(self, confidence: float) -> int: + """Get the bet amount given a prediction's confidence.""" + threshold = round(confidence, 1) + return self.bet_amount_per_threshold[threshold] diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml index 3160e763..84304b2e 100644 --- a/packages/valory/skills/decision_maker_abci/skill.yaml +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -128,6 +128,19 @@ models: use_termination: false mech_agent_id: 3 mech_tool: prediction-online + bet_amount_per_threshold: + 0: 0 + 0.1: 0 + 0.2: 0 + 0.3: 0 + 0.4: 0 + 0.5: 0 + 0.6: 0 + 0.7: 0 + 0.8: 0 + 0.9: 0 + 1: 0 + bet_threshold: 100000000000000000 class_name: DecisionMakerParams requests: args: {} From dcb41eac58594632384b794f774c7eceab6b15d4 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:04:37 +0300 Subject: [PATCH 25/39] fix: private key file's path `private_key_paths.read("ethereum")` would only make sense for `AgentConfig`, which is not accessible via the context. --- .../skills/decision_maker_abci/behaviours/decision_maker.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index 2a4d1c85..30423251 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -20,9 +20,12 @@ """This module contains the behaviours for the 'decision_maker_abci' skill.""" from multiprocessing.pool import AsyncResult +from pathlib import Path from string import Template from typing import Any, Generator, Optional, Tuple, cast +from mech_client.interact import PRIVATE_KEY_FILE_PATH + from packages.valory.skills.abstract_round_abci.base import BaseTxPayload from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams @@ -87,7 +90,7 @@ def setup(self) -> None: prompt=BET_PROMPT.substitute(prompt_params), agent_id=self.params.mech_agent_id, tool=self.params.mech_tool, - private_key_path=self.context.private_key_paths.read("ethereum"), + private_key_path=str(Path(self.context.data_dir) / PRIVATE_KEY_FILE_PATH), ) task_id = self.context.task_manager.enqueue_task(mech_task, kwargs=task_kwargs) self._async_result = self.context.task_manager.get_task_result(task_id) From a1011137a6d56bac4970aa93f90adc3ede59a04c Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:06:08 +0300 Subject: [PATCH 26/39] refactor: no need to define 2 constants for the same thing --- .../skills/decision_maker_abci/behaviours/decision_maker.py | 4 ++-- packages/valory/skills/market_manager_abci/models.py | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index 30423251..b0a73ced 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -38,7 +38,7 @@ MechInteractionResponse, MechInteractionTask, ) -from packages.valory.skills.market_manager_abci.models import SUPPORTED_N_SLOTS +from packages.valory.skills.market_manager_abci.bets import BINARY_N_SLOTS BET_PROMPT = Template( @@ -74,7 +74,7 @@ def synchronized_data(self) -> SynchronizedData: @property def n_slots_unsupported(self) -> bool: """Whether the behaviour supports the current number of slots as it currently only supports binary decisions.""" - return self.params.slot_count != SUPPORTED_N_SLOTS + return self.params.slot_count != BINARY_N_SLOTS def setup(self) -> None: """Setup behaviour.""" diff --git a/packages/valory/skills/market_manager_abci/models.py b/packages/valory/skills/market_manager_abci/models.py index 0d8fa0b2..ee4797bb 100644 --- a/packages/valory/skills/market_manager_abci/models.py +++ b/packages/valory/skills/market_manager_abci/models.py @@ -30,6 +30,7 @@ from packages.valory.skills.abstract_round_abci.models import ( SharedState as BaseSharedState, ) +from packages.valory.skills.market_manager_abci.bets import BINARY_N_SLOTS from packages.valory.skills.market_manager_abci.rounds import MarketManagerAbciApp @@ -37,9 +38,6 @@ BenchmarkTool = BaseBenchmarkTool -SUPPORTED_N_SLOTS = 2 - - class SharedState(BaseSharedState): """Keep the current shared state of the skill.""" @@ -61,7 +59,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ) self.slot_count: int = self._ensure("slot_count", kwargs, int) - if self.slot_count != SUPPORTED_N_SLOTS: + if self.slot_count != BINARY_N_SLOTS: raise ValueError( f"Only a slot_count `2` is currently supported. `{self.slot_count}` was found in the configuration." ) From e3859b3f07a9fd11488d43d1df9cf36f460db535 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:06:28 +0300 Subject: [PATCH 27/39] chore: bump `mech-client` --- poetry.lock | 14 +++++++------- pyproject.toml | 2 +- tox.ini | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index a7412190..e0eac89d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1839,14 +1839,14 @@ files = [ [[package]] name = "mech-client" -version = "0.2.1" +version = "0.2.2" description = "Basic client to interact with a mech" category = "main" optional = false python-versions = ">=3.10,<4.0" files = [ - {file = "mech_client-0.2.1-py3-none-any.whl", hash = "sha256:f50a758a94e92ed09fba30ac3b278b31eb2f098f7573a9abce3c2db6830c8a90"}, - {file = "mech_client-0.2.1.tar.gz", hash = "sha256:a7e2193e664a2c4af2bfe1209d84f23e7ce35362218b7cd59ef616bfa5229e0d"}, + {file = "mech_client-0.2.2-py3-none-any.whl", hash = "sha256:deaafea15d1dee0e4ed56124a325dbf27bb65c826c6ae0c6093cf718ad51dbe0"}, + {file = "mech_client-0.2.2.tar.gz", hash = "sha256:2704a9f50e8da467942f9e4ef2dcfb5d05c58d2a9fe2f460da2af78b87c471f8"}, ] [package.dependencies] @@ -2267,14 +2267,14 @@ six = ">=1.9.0" [[package]] name = "platformdirs" -version = "3.8.0" +version = "3.8.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, - {file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, + {file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"}, + {file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"}, ] [package.extras] @@ -3466,4 +3466,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10, <3.11" -content-hash = "a1ff96b6540040b94e05122810f9e4ad0085c4dba288696d594e84d2938eea3d" +content-hash = "9b90524176d2146d43ae1306618b4800b462913d469cb1b12f95eaf5d661de03" diff --git a/pyproject.toml b/pyproject.toml index 0406358c..591d3928 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ open-aea-ledger-ethereum = "==1.35.0" open-aea-ledger-cosmos = "*" protobuf = "<=3.20.1,>=3.19" hypothesis = "==6.80.0" -mech-client = "==0.2.1" +mech-client = "==0.2.2" open-aea-test-autonomy = "==0.10.7" web3 = "==5.31.4" ipfshttpclient = "==0.8.0a2" diff --git a/tox.ini b/tox.ini index 1bcda0ab..a2b34b49 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,7 @@ deps = typing_extensions>=3.10.0.2 websocket_client<1,>=0.32.0 toml==0.10.2 + mech-client==0.2.2 [testenv] basepython = python3 From e32db39ba052517ce76331fda8e949b40a7ffacf Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:14:30 +0300 Subject: [PATCH 28/39] refactor: extract reusable behaviour methods to base class --- .../decision_maker_abci/behaviours/base.py | 50 +++++++++++++++++++ .../behaviours/decision_maker.py | 31 ++---------- 2 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 packages/valory/skills/decision_maker_abci/behaviours/base.py diff --git a/packages/valory/skills/decision_maker_abci/behaviours/base.py b/packages/valory/skills/decision_maker_abci/behaviours/base.py new file mode 100644 index 00000000..c96ac5cf --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/base.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the base behaviour for the 'decision_maker_abci' skill.""" + +from abc import ABC +from typing import Generator, cast + +from packages.valory.skills.abstract_round_abci.base import BaseTxPayload +from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour +from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams +from packages.valory.skills.decision_maker_abci.rounds import SynchronizedData + + +class DecisionMakerBaseBehaviour(BaseBehaviour, ABC): + """Represents the base class for the decision-making FSM behaviour.""" + + @property + def params(self) -> DecisionMakerParams: + """Return the params.""" + return cast(DecisionMakerParams, self.context.params) + + @property + def synchronized_data(self) -> SynchronizedData: + """Return the synchronized data.""" + return cast(SynchronizedData, super().synchronized_data) + + def finish_behaviour(self, payload: BaseTxPayload) -> Generator: + """Finish the behaviour.""" + with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): + yield from self.send_a2a_transaction(payload) + yield from self.wait_until_round_end() + + self.set_done() diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index b0a73ced..a2af6465 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -26,14 +26,11 @@ from mech_client.interact import PRIVATE_KEY_FILE_PATH -from packages.valory.skills.abstract_round_abci.base import BaseTxPayload -from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour -from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams -from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds import ( - DecisionMakerRound, - SynchronizedData, +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, ) +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerRound from packages.valory.skills.decision_maker_abci.tasks import ( MechInteractionResponse, MechInteractionTask, @@ -51,7 +48,7 @@ ) -class DecisionMakerBehaviour(BaseBehaviour): +class DecisionMakerBehaviour(DecisionMakerBaseBehaviour): """A round in which the agents decide which answer they are going to choose for the next bet.""" matching_round = DecisionMakerRound @@ -61,16 +58,6 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self._async_result: Optional[AsyncResult] = None - @property - def params(self) -> DecisionMakerParams: - """Return the params.""" - return cast(DecisionMakerParams, self.context.params) - - @property - def synchronized_data(self) -> SynchronizedData: - """Return the synchronized data.""" - return cast(SynchronizedData, super().synchronized_data) - @property def n_slots_unsupported(self) -> bool: """Whether the behaviour supports the current number of slots as it currently only supports binary decisions.""" @@ -129,14 +116,6 @@ def _is_profitable(self, vote: Optional[int], confidence: Optional[float]) -> bo bet_threshold = self.params.bet_threshold return bet_amount - fee >= bet_threshold - def finish_behaviour(self, payload: BaseTxPayload) -> Generator: - """Finish the behaviour.""" - with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): - yield from self.send_a2a_transaction(payload) - yield from self.wait_until_round_end() - - self.set_done() - def async_act(self) -> Generator: """Do the action.""" From 6998ca1d3d29c01b632f0faa1d01eb83a9bb9c17 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 14:15:47 +0300 Subject: [PATCH 29/39] docs: improve module's docstring --- .../skills/decision_maker_abci/behaviours/decision_maker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index a2af6465..f1816703 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -17,7 +17,7 @@ # # ------------------------------------------------------------------------------ -"""This module contains the behaviours for the 'decision_maker_abci' skill.""" +"""This module contains the behaviour for the decision-making of the skill.""" from multiprocessing.pool import AsyncResult from pathlib import Path @@ -49,7 +49,7 @@ class DecisionMakerBehaviour(DecisionMakerBaseBehaviour): - """A round in which the agents decide which answer they are going to choose for the next bet.""" + """A behaviour in which the agents decide which answer they are going to choose for the next bet.""" matching_round = DecisionMakerRound From 05bdcdb03ff113b0b1474810a06a0068625a92d2 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 16:54:59 +0300 Subject: [PATCH 30/39] refactor: move serialization method in the bets' structures --- .../skills/market_manager_abci/behaviours.py | 18 +++++++----------- .../valory/skills/market_manager_abci/bets.py | 7 +++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/behaviours.py b/packages/valory/skills/market_manager_abci/behaviours.py index 9072823b..d0907c2c 100644 --- a/packages/valory/skills/market_manager_abci/behaviours.py +++ b/packages/valory/skills/market_manager_abci/behaviours.py @@ -19,12 +19,15 @@ """This module contains the behaviours for the MarketManager skill.""" -import json -from typing import Any, Generator, Iterator, List, Optional, Set, Type +from typing import Any, Generator, Iterator, List, Set, Type from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.abstract_round_abci.behaviours import AbstractRoundBehaviour -from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus, BetsEncoder +from packages.valory.skills.market_manager_abci.bets import ( + Bet, + BetStatus, + serialize_bets, +) from packages.valory.skills.market_manager_abci.graph_tooling.requests import ( FetchStatus, QueryingBehaviour, @@ -47,13 +50,6 @@ def __init__(self, **kwargs: Any) -> None: # list of bets mapped to prediction markets self.bets: List[Bet] = [] - @property - def serialized_bets(self) -> Optional[str]: - """Get the bets serialized.""" - if len(self.bets) == 0: - return None - return json.dumps(self.bets, cls=BetsEncoder) - @property def bets_ids(self) -> List[str]: """Get the ids of the already existing bets.""" @@ -100,7 +96,7 @@ def async_act(self) -> Generator: with self.context.benchmark_tool.measure(self.behaviour_id).local(): yield from self._update_bets() payload = UpdateBetsPayload( - self.context.agent_address, self.serialized_bets + self.context.agent_address, serialize_bets(self.bets) ) with self.context.benchmark_tool.measure(self.behaviour_id).consensus(): diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index 5b9073fb..b660537c 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -128,3 +128,10 @@ def hook(data: Dict[str, Any]) -> Union[Bet, Dict[str, Bet]]: return Bet(**data) return data + + +def serialize_bets(bets: List[Bet]) -> Optional[str]: + """Get the bets serialized.""" + if len(bets) == 0: + return None + return json.dumps(bets, cls=BetsEncoder) From bece9e45a4d8703c573a5e36a9766998d5a0ffb2 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 16:56:03 +0300 Subject: [PATCH 31/39] feat: add functionality for temporarily blacklisting a bet --- .../behaviours/blacklisting.py | 60 +++++++++++++++++++ .../skills/decision_maker_abci/models.py | 4 ++ .../skills/decision_maker_abci/rounds.py | 30 ++++++++-- .../skills/decision_maker_abci/skill.yaml | 1 + .../skills/market_manager_abci/rounds.py | 6 +- 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py new file mode 100644 index 00000000..b9aad3bd --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 behaviour for the blacklisting of the sampled bet.""" + +from typing import Generator, Optional + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.rounds import BlacklistingRound +from packages.valory.skills.market_manager_abci.bets import BetStatus, serialize_bets +from packages.valory.skills.market_manager_abci.payloads import UpdateBetsPayload + + +class BlacklistingBehaviour(DecisionMakerBaseBehaviour): + """A behaviour in which the agents blacklist the sampled bet.""" + + matching_round = BlacklistingRound + + @property + def synced_time(self) -> float: + """Get the synchronized time among agents.""" + synced_time = self.shared_state.round_sequence.last_round_transition_timestamp + return synced_time.timestamp() + + def _blacklist(self) -> Optional[str]: + """Blacklist the sampled bet and return the updated version of the bets, serialized.""" + bets = self.synchronized_data.bets + sampled_bet_id = self.synchronized_data.sampled_bet_id + sampled_bet = bets[sampled_bet_id] + sampled_bet.status = BetStatus.BLACKLISTED + blacklist_expiration = self.synced_time + self.params.blacklisting_duration + sampled_bet.blacklist_expiration = blacklist_expiration + + return serialize_bets(bets) + + def async_act(self) -> Generator: + """Do the action.""" + + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + payload = UpdateBetsPayload(self.context.agent_address, self._blacklist()) + + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index e1118778..597239b0 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -57,6 +57,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ) # the threshold amount in WEI starting from which we are willing to place a bet self.bet_threshold: int = self._ensure("bet_threshold", kwargs, str) + # the duration, in seconds, of blacklisting a bet before retrying to make an estimate for it + self.blacklisting_duration: int = self._ensure( + "blacklisting_duration", kwargs, int + ) super().__init__(*args, **kwargs) def get_bet_amount(self, confidence: float) -> int: diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 1c76cf6e..f4ccb4bf 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -36,12 +36,16 @@ from packages.valory.skills.market_manager_abci.rounds import ( SynchronizedData as BaseSynchronizedData, ) +from packages.valory.skills.market_manager_abci.rounds import ( + UpdateBetsRound as BaseUpdateBetsRound, +) class Event(Enum): """Event enumeration for the price estimation demo.""" DONE = "done" + NONE = "none" MECH_RESPONSE_ERROR = "mech_response_error" NON_BINARY = "non_binary" TIE = "tie" @@ -57,10 +61,15 @@ class SynchronizedData(BaseSynchronizedData): """ @property - def sampled_bet(self) -> Bet: + def sampled_bet_id(self) -> int: """Get the sampled bet.""" raise NotImplementedError + @property + def sampled_bet(self) -> Bet: + """Get the sampled bet.""" + return self.bets[self.sampled_bet_id] + @property def non_binary(self) -> bool: """Get whether the question is non-binary.""" @@ -124,6 +133,14 @@ def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: return synced_data, event +class BlacklistingRound(BaseUpdateBetsRound): + """A round for the bets fetching & updating.""" + + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY + + class FinishedDecisionMakerRound(DegenerateRound): """A round representing that decision-making has finished.""" @@ -152,11 +169,16 @@ class DecisionMakerAbciApp(AbciApp[Event]): transition_function: AbciAppTransitionFunction = { DecisionMakerRound: { Event.DONE: FinishedDecisionMakerRound, - Event.MECH_RESPONSE_ERROR: FinishedDecisionMakerRound, # TODO blacklist and go back to sampling a bet + Event.MECH_RESPONSE_ERROR: BlacklistingRound, Event.NO_MAJORITY: DecisionMakerRound, Event.NON_BINARY: ImpossibleRound, # degenerate round on purpose, should never have reached here - Event.TIE: FinishedDecisionMakerRound, # TODO blacklist and go back to sampling a bet - Event.UNPROFITABLE: FinishedDecisionMakerRound, # TODO blacklist the sampled bet for duration set in config + Event.TIE: BlacklistingRound, + Event.UNPROFITABLE: BlacklistingRound, + }, + BlacklistingRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: BlacklistingRound, }, FinishedDecisionMakerRound: {}, } diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml index 84304b2e..f21d3846 100644 --- a/packages/valory/skills/decision_maker_abci/skill.yaml +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -141,6 +141,7 @@ models: 0.9: 0 1: 0 bet_threshold: 100000000000000000 + blacklisting_duration: 3600 class_name: DecisionMakerParams requests: args: {} diff --git a/packages/valory/skills/market_manager_abci/rounds.py b/packages/valory/skills/market_manager_abci/rounds.py index 560e95ac..dfb88626 100644 --- a/packages/valory/skills/market_manager_abci/rounds.py +++ b/packages/valory/skills/market_manager_abci/rounds.py @@ -97,9 +97,9 @@ class UpdateBetsRound(CollectSameUntilThresholdRound, MarketManagerAbstractRound """A round for the bets fetching & updating.""" payload_class = UpdateBetsPayload - done_event = Event.DONE - none_event = Event.FETCH_ERROR - no_majority_event = Event.NO_MAJORITY + done_event: Enum = Event.DONE + none_event: Enum = Event.FETCH_ERROR + no_majority_event: Enum = Event.NO_MAJORITY selection_key = get_name(SynchronizedData.bets) collection_key = get_name(SynchronizedData.participant_to_bets) synchronized_data_class = SynchronizedData From 7c659a2273e166295e22184fae8a2c9f9dd38357 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:36:35 +0300 Subject: [PATCH 32/39] feat: add functionality for sampling a bet as a first step --- .../behaviours/sampling.py | 59 +++++++++++++++++++ .../skills/decision_maker_abci/payloads.py | 7 +++ .../skills/decision_maker_abci/rounds.py | 33 +++++++++-- .../valory/skills/market_manager_abci/bets.py | 1 + .../graph_tooling/queries/omen.py | 1 + 5 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 packages/valory/skills/decision_maker_abci/behaviours/sampling.py diff --git a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py new file mode 100644 index 00000000..dc2cb4a2 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 behaviour for sampling a bet.""" + +from typing import Generator, Iterator + +from packages.valory.skills.decision_maker_abci.behaviours.base import ( + DecisionMakerBaseBehaviour, +) +from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload +from packages.valory.skills.decision_maker_abci.rounds import SamplingRound +from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus + + +class SamplingBehaviour(DecisionMakerBaseBehaviour): + """A behaviour in which the agents blacklist the sampled bet.""" + + matching_round = SamplingRound + + @property + def available_bets(self) -> Iterator[Bet]: + """Get an iterator of the unprocessed bets.""" + bets = self.synchronized_data.bets + return filter(lambda bet: bet.status == BetStatus.UNPROCESSED, bets) + + @property + def sampled_bet_idx(self) -> int: + """ + Sample a bet and return its id. + + The sampling logic is relatively simple at the moment + It simply selects the unprocessed bet with the largest liquidity. + """ + max_lq = max(self.available_bets, key=lambda bet: bet.usdLiquidityMeasure) + return self.synchronized_data.bets.index(max_lq) + + def async_act(self) -> Generator: + """Do the action.""" + with self.context.benchmark_tool.measure(self.behaviour_id).local(): + payload = SamplingPayload(self.context.agent_address, self.sampled_bet_idx) + + yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 787c6272..0f658bfc 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -33,3 +33,10 @@ class DecisionMakerPayload(BaseTxPayload): is_profitable: bool vote: Optional[int] confidence: Optional[float] + + +@dataclass(frozen=True) +class SamplingPayload(BaseTxPayload): + """Represents a transaction payload for the sampling of a bet.""" + + index: int diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index f4ccb4bf..85518a10 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -31,7 +31,10 @@ DeserializedCollection, get_name, ) -from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.payloads import ( + DecisionMakerPayload, + SamplingPayload, +) from packages.valory.skills.market_manager_abci.bets import Bet from packages.valory.skills.market_manager_abci.rounds import ( SynchronizedData as BaseSynchronizedData, @@ -63,7 +66,7 @@ class SynchronizedData(BaseSynchronizedData): @property def sampled_bet_id(self) -> int: """Get the sampled bet.""" - raise NotImplementedError + return int(self.db.get_strict("sampled_bet_index")) @property def sampled_bet(self) -> Bet: @@ -96,6 +99,11 @@ def participant_to_decision(self) -> DeserializedCollection: """Get the participants to decision-making.""" return self._get_deserialized("participant_to_decision") + @property + def participant_to_sampling(self) -> DeserializedCollection: + """Get the participants to bet-sampling.""" + return self._get_deserialized("participant_to_sampling") + class DecisionMakerRound(CollectSameUntilThresholdRound): """A round in which the agents decide on the bet's answer.""" @@ -141,6 +149,18 @@ class BlacklistingRound(BaseUpdateBetsRound): no_majority_event = Event.NO_MAJORITY +class SamplingRound(CollectSameUntilThresholdRound): + """A round for sampling a bet.""" + + payload_class = SamplingPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY + selection_key = get_name(SynchronizedData.sampled_bet_index) + collection_key = get_name(SynchronizedData.participant_to_sampling) + + class FinishedDecisionMakerRound(DegenerateRound): """A round representing that decision-making has finished.""" @@ -164,9 +184,14 @@ class DecisionMakerAbciApp(AbciApp[Event]): round timeout: 30.0 """ - initial_round_cls: AppState = DecisionMakerRound - initial_states: Set[AppState] = {DecisionMakerRound} + initial_round_cls: AppState = SamplingRound + initial_states: Set[AppState] = {SamplingRound} transition_function: AbciAppTransitionFunction = { + SamplingRound: { + Event.DONE: DecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: SamplingRound, + }, DecisionMakerRound: { Event.DONE: FinishedDecisionMakerRound, Event.MECH_RESPONSE_ERROR: BlacklistingRound, diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index b660537c..db429cc7 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -53,6 +53,7 @@ class Bet: outcomeTokenAmounts: List[int] outcomeTokenMarginalPrices: List[float] outcomes: Optional[List[str]] + usdLiquidityMeasure: int status: BetStatus = BetStatus.UNPROCESSED blacklist_expiration: float = -1 diff --git a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py index 77b300c0..b7386962 100644 --- a/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py +++ b/packages/valory/skills/market_manager_abci/graph_tooling/queries/omen.py @@ -45,6 +45,7 @@ outcomeTokenAmounts outcomeTokenMarginalPrices outcomes + usdLiquidityMeasure } } """ From f80bd8cf01d85d064dacea90f2da191155412865 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:38:30 +0300 Subject: [PATCH 33/39] style: remove superfluous statuses --- packages/valory/skills/market_manager_abci/bets.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/valory/skills/market_manager_abci/bets.py b/packages/valory/skills/market_manager_abci/bets.py index db429cc7..0d13e90c 100644 --- a/packages/valory/skills/market_manager_abci/bets.py +++ b/packages/valory/skills/market_manager_abci/bets.py @@ -34,8 +34,6 @@ class BetStatus(Enum): UNPROCESSED = auto() PROCESSED = auto() - WAITING_RESPONSE = auto() - RESPONSE_RECEIVED = auto() BLACKLISTED = auto() From fbe02f1c309d0ca4f695c40b023d42603070d463 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:39:15 +0300 Subject: [PATCH 34/39] style: rename `sampled_bet_id` to `sampled_bet_index` --- .../skills/decision_maker_abci/behaviours/blacklisting.py | 4 ++-- packages/valory/skills/decision_maker_abci/rounds.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py index b9aad3bd..1a218df7 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -43,8 +43,8 @@ def synced_time(self) -> float: def _blacklist(self) -> Optional[str]: """Blacklist the sampled bet and return the updated version of the bets, serialized.""" bets = self.synchronized_data.bets - sampled_bet_id = self.synchronized_data.sampled_bet_id - sampled_bet = bets[sampled_bet_id] + sampled_bet_index = self.synchronized_data.sampled_bet_index + sampled_bet = bets[sampled_bet_index] sampled_bet.status = BetStatus.BLACKLISTED blacklist_expiration = self.synced_time + self.params.blacklisting_duration sampled_bet.blacklist_expiration = blacklist_expiration diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 85518a10..38ddc290 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -64,14 +64,14 @@ class SynchronizedData(BaseSynchronizedData): """ @property - def sampled_bet_id(self) -> int: + def sampled_bet_index(self) -> int: """Get the sampled bet.""" return int(self.db.get_strict("sampled_bet_index")) @property def sampled_bet(self) -> Bet: """Get the sampled bet.""" - return self.bets[self.sampled_bet_id] + return self.bets[self.sampled_bet_index] @property def non_binary(self) -> bool: From 45446a2557f9720f1b5016c3486985c8f0d0a6eb Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:39:44 +0300 Subject: [PATCH 35/39] refactor: use `SynchronizedData` instead of the base class --- packages/valory/skills/decision_maker_abci/rounds.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 38ddc290..34d8d066 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -109,8 +109,7 @@ class DecisionMakerRound(CollectSameUntilThresholdRound): """A round in which the agents decide on the bet's answer.""" payload_class = DecisionMakerPayload - synchronized_data_class = BaseSynchronizedData - + synchronized_data_class = SynchronizedData done_event = Event.DONE none_event = Event.MECH_RESPONSE_ERROR no_majority_event = Event.NO_MAJORITY From d87a2ead36bde5b86214f49a6040ba805d8567d0 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:40:10 +0300 Subject: [PATCH 36/39] docs: correct blacklisting round's docstring --- packages/valory/skills/decision_maker_abci/rounds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 34d8d066..a423e037 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -141,7 +141,7 @@ def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: class BlacklistingRound(BaseUpdateBetsRound): - """A round for the bets fetching & updating.""" + """A round for updating the bets after blacklisting the sampled one.""" done_event = Event.DONE none_event = Event.NONE From e8277f5fcb0cc602121feabe16773bf704b4bf4b Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 18:51:05 +0300 Subject: [PATCH 37/39] refactor: extract rounds into dedicated package --- .../decision_maker_abci/behaviours/base.py | 2 +- .../behaviours/blacklisting.py | 4 +- .../behaviours/decision_maker.py | 4 +- .../behaviours/round_behaviour.py | 10 +- .../behaviours/sampling.py | 2 +- .../skills/decision_maker_abci/models.py | 4 +- .../skills/decision_maker_abci/rounds.py | 220 ------------------ .../decision_maker_abci/rounds/__init__.py | 20 ++ .../decision_maker_abci/rounds/abci_app.py | 92 ++++++++ .../skills/decision_maker_abci/rounds/base.py | 89 +++++++ .../rounds/blacklisting.py | 33 +++ .../rounds/decision_maker.py | 68 ++++++ .../rounds/final_states.py | 30 +++ .../decision_maker_abci/rounds/sampling.py | 42 ++++ 14 files changed, 394 insertions(+), 226 deletions(-) delete mode 100644 packages/valory/skills/decision_maker_abci/rounds.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/__init__.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/abci_app.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/base.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/blacklisting.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/decision_maker.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/final_states.py create mode 100644 packages/valory/skills/decision_maker_abci/rounds/sampling.py diff --git a/packages/valory/skills/decision_maker_abci/behaviours/base.py b/packages/valory/skills/decision_maker_abci/behaviours/base.py index c96ac5cf..6e2aa126 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/base.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/base.py @@ -25,7 +25,7 @@ from packages.valory.skills.abstract_round_abci.base import BaseTxPayload from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams -from packages.valory.skills.decision_maker_abci.rounds import SynchronizedData +from packages.valory.skills.decision_maker_abci.rounds.base import SynchronizedData class DecisionMakerBaseBehaviour(BaseBehaviour, ABC): diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py index 1a218df7..094b80ab 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -24,7 +24,9 @@ from packages.valory.skills.decision_maker_abci.behaviours.base import ( DecisionMakerBaseBehaviour, ) -from packages.valory.skills.decision_maker_abci.rounds import BlacklistingRound +from packages.valory.skills.decision_maker_abci.rounds.blacklisting import ( + BlacklistingRound, +) from packages.valory.skills.market_manager_abci.bets import BetStatus, serialize_bets from packages.valory.skills.market_manager_abci.payloads import UpdateBetsPayload diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index f1816703..c4ee6158 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -30,7 +30,9 @@ DecisionMakerBaseBehaviour, ) from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerRound +from packages.valory.skills.decision_maker_abci.rounds.decision_maker import ( + DecisionMakerRound, +) from packages.valory.skills.decision_maker_abci.tasks import ( MechInteractionResponse, MechInteractionTask, diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py index df7b9ab9..8c8a0b50 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -25,10 +25,18 @@ AbstractRoundBehaviour, BaseBehaviour, ) +from packages.valory.skills.decision_maker_abci.behaviours.blacklisting import ( + BlacklistingBehaviour, +) from packages.valory.skills.decision_maker_abci.behaviours.decision_maker import ( DecisionMakerBehaviour, ) -from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp +from packages.valory.skills.decision_maker_abci.behaviours.sampling import ( + SamplingBehaviour, +) +from packages.valory.skills.decision_maker_abci.rounds.abci_app import ( + DecisionMakerAbciApp, +) class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): diff --git a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py index dc2cb4a2..9873ee85 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py @@ -25,7 +25,7 @@ DecisionMakerBaseBehaviour, ) from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload -from packages.valory.skills.decision_maker_abci.rounds import SamplingRound +from packages.valory.skills.decision_maker_abci.rounds.sampling import SamplingRound from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index 597239b0..b796c2fd 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -28,7 +28,9 @@ from packages.valory.skills.abstract_round_abci.models import ( SharedState as BaseSharedState, ) -from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp +from packages.valory.skills.decision_maker_abci.rounds.abci_app import ( + DecisionMakerAbciApp, +) from packages.valory.skills.market_manager_abci.models import ( MarketManagerParams as BaseParams, ) diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py deleted file mode 100644 index a423e037..00000000 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8 -*- -# ------------------------------------------------------------------------------ -# -# Copyright 2023 Valory AG -# -# 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 rounds for the decision-making.""" - -from enum import Enum -from typing import Dict, Optional, Set, Tuple, cast - -from packages.valory.skills.abstract_round_abci.base import ( - AbciApp, - AbciAppTransitionFunction, - AppState, - CollectSameUntilThresholdRound, - DegenerateRound, - DeserializedCollection, - get_name, -) -from packages.valory.skills.decision_maker_abci.payloads import ( - DecisionMakerPayload, - SamplingPayload, -) -from packages.valory.skills.market_manager_abci.bets import Bet -from packages.valory.skills.market_manager_abci.rounds import ( - SynchronizedData as BaseSynchronizedData, -) -from packages.valory.skills.market_manager_abci.rounds import ( - UpdateBetsRound as BaseUpdateBetsRound, -) - - -class Event(Enum): - """Event enumeration for the price estimation demo.""" - - DONE = "done" - NONE = "none" - MECH_RESPONSE_ERROR = "mech_response_error" - NON_BINARY = "non_binary" - TIE = "tie" - UNPROFITABLE = "unprofitable" - ROUND_TIMEOUT = "round_timeout" - NO_MAJORITY = "no_majority" - - -class SynchronizedData(BaseSynchronizedData): - """Class to represent the synchronized data. - - This data is replicated by the tendermint application. - """ - - @property - def sampled_bet_index(self) -> int: - """Get the sampled bet.""" - return int(self.db.get_strict("sampled_bet_index")) - - @property - def sampled_bet(self) -> Bet: - """Get the sampled bet.""" - return self.bets[self.sampled_bet_index] - - @property - def non_binary(self) -> bool: - """Get whether the question is non-binary.""" - return bool(self.db.get_strict("non_binary")) - - @property - def vote(self) -> str: - """Get the bet's vote.""" - vote = self.db.get_strict("vote") - return self.sampled_bet.get_outcome(vote) - - @property - def confidence(self) -> float: - """Get the vote's confidence.""" - return float(self.db.get_strict("confidence")) - - @property - def is_profitable(self) -> bool: - """Get whether the current vote is profitable or not.""" - return bool(self.db.get_strict("is_profitable")) - - @property - def participant_to_decision(self) -> DeserializedCollection: - """Get the participants to decision-making.""" - return self._get_deserialized("participant_to_decision") - - @property - def participant_to_sampling(self) -> DeserializedCollection: - """Get the participants to bet-sampling.""" - return self._get_deserialized("participant_to_sampling") - - -class DecisionMakerRound(CollectSameUntilThresholdRound): - """A round in which the agents decide on the bet's answer.""" - - payload_class = DecisionMakerPayload - synchronized_data_class = SynchronizedData - done_event = Event.DONE - none_event = Event.MECH_RESPONSE_ERROR - no_majority_event = Event.NO_MAJORITY - selection_key = ( - get_name(SynchronizedData.non_binary), - get_name(SynchronizedData.vote), - get_name(SynchronizedData.confidence), - get_name(SynchronizedData.is_profitable), - ) - collection_key = get_name(SynchronizedData.participant_to_decision) - - def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: - """Process the end of the block.""" - res = super().end_block() - if res is None: - return None - - synced_data, event = cast(Tuple[SynchronizedData, Enum], res) - if event == Event.DONE and synced_data.non_binary: - return synced_data, Event.NON_BINARY - - if event == Event.DONE and synced_data.vote is None: - return synced_data, Event.TIE - - if event == Event.DONE and not synced_data.is_profitable: - return synced_data, Event.UNPROFITABLE - - return synced_data, event - - -class BlacklistingRound(BaseUpdateBetsRound): - """A round for updating the bets after blacklisting the sampled one.""" - - done_event = Event.DONE - none_event = Event.NONE - no_majority_event = Event.NO_MAJORITY - - -class SamplingRound(CollectSameUntilThresholdRound): - """A round for sampling a bet.""" - - payload_class = SamplingPayload - synchronized_data_class = SynchronizedData - done_event = Event.DONE - none_event = Event.NONE - no_majority_event = Event.NO_MAJORITY - selection_key = get_name(SynchronizedData.sampled_bet_index) - collection_key = get_name(SynchronizedData.participant_to_sampling) - - -class FinishedDecisionMakerRound(DegenerateRound): - """A round representing that decision-making has finished.""" - - -class ImpossibleRound(DegenerateRound): - """A round representing that decision-making is impossible with the given parametrization.""" - - -class DecisionMakerAbciApp(AbciApp[Event]): - """DecisionMakerAbciApp - - Initial round: DecisionMakerRound - - Initial states: {DecisionMakerRound} - - Transition states: - - Final states: {FinishedDecisionMakerRound} - - Timeouts: - round timeout: 30.0 - """ - - initial_round_cls: AppState = SamplingRound - initial_states: Set[AppState] = {SamplingRound} - transition_function: AbciAppTransitionFunction = { - SamplingRound: { - Event.DONE: DecisionMakerRound, - Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here - Event.NO_MAJORITY: SamplingRound, - }, - DecisionMakerRound: { - Event.DONE: FinishedDecisionMakerRound, - Event.MECH_RESPONSE_ERROR: BlacklistingRound, - Event.NO_MAJORITY: DecisionMakerRound, - Event.NON_BINARY: ImpossibleRound, # degenerate round on purpose, should never have reached here - Event.TIE: BlacklistingRound, - Event.UNPROFITABLE: BlacklistingRound, - }, - BlacklistingRound: { - Event.DONE: FinishedDecisionMakerRound, - Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here - Event.NO_MAJORITY: BlacklistingRound, - }, - FinishedDecisionMakerRound: {}, - } - final_states: Set[AppState] = { - FinishedDecisionMakerRound, - } - event_to_timeout: Dict[Event, float] = { - Event.ROUND_TIMEOUT: 30.0, - } - db_pre_conditions: Dict[AppState, Set[str]] = { - DecisionMakerRound: set(), - } - db_post_conditions: Dict[AppState, Set[str]] = { - FinishedDecisionMakerRound: set(), - } diff --git a/packages/valory/skills/decision_maker_abci/rounds/__init__.py b/packages/valory/skills/decision_maker_abci/rounds/__init__.py new file mode 100644 index 00000000..bddac561 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 rounds for the 'decision_maker_abci' skill.""" diff --git a/packages/valory/skills/decision_maker_abci/rounds/abci_app.py b/packages/valory/skills/decision_maker_abci/rounds/abci_app.py new file mode 100644 index 00000000..e06733c2 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/abci_app.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 rounds for the decision-making.""" + +from typing import Dict, Set + +from packages.valory.skills.abstract_round_abci.base import ( + AbciApp, + AbciAppTransitionFunction, + AppState, +) +from packages.valory.skills.decision_maker_abci.rounds.base import Event +from packages.valory.skills.decision_maker_abci.rounds.blacklisting import ( + BlacklistingRound, +) +from packages.valory.skills.decision_maker_abci.rounds.decision_maker import ( + DecisionMakerRound, +) +from packages.valory.skills.decision_maker_abci.rounds.final_states import ( + FinishedDecisionMakerRound, + ImpossibleRound, +) +from packages.valory.skills.decision_maker_abci.rounds.sampling import SamplingRound + + +class DecisionMakerAbciApp(AbciApp[Event]): + """DecisionMakerAbciApp + + Initial round: DecisionMakerRound + + Initial states: {DecisionMakerRound} + + Transition states: + + Final states: {FinishedDecisionMakerRound} + + Timeouts: + round timeout: 30.0 + """ + + initial_round_cls: AppState = SamplingRound + initial_states: Set[AppState] = {SamplingRound} + transition_function: AbciAppTransitionFunction = { + SamplingRound: { + Event.DONE: DecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: SamplingRound, + }, + DecisionMakerRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.MECH_RESPONSE_ERROR: BlacklistingRound, + Event.NO_MAJORITY: DecisionMakerRound, + Event.NON_BINARY: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.TIE: BlacklistingRound, + Event.UNPROFITABLE: BlacklistingRound, + }, + BlacklistingRound: { + Event.DONE: FinishedDecisionMakerRound, + Event.NONE: ImpossibleRound, # degenerate round on purpose, should never have reached here + Event.NO_MAJORITY: BlacklistingRound, + }, + FinishedDecisionMakerRound: {}, + } + final_states: Set[AppState] = { + FinishedDecisionMakerRound, + } + event_to_timeout: Dict[Event, float] = { + Event.ROUND_TIMEOUT: 30.0, + } + db_pre_conditions: Dict[AppState, Set[str]] = { + DecisionMakerRound: set(), + } + db_post_conditions: Dict[AppState, Set[str]] = { + FinishedDecisionMakerRound: set(), + } diff --git a/packages/valory/skills/decision_maker_abci/rounds/base.py b/packages/valory/skills/decision_maker_abci/rounds/base.py new file mode 100644 index 00000000..8b7dcf93 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/base.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This module contains the base functionality for the rounds of the decision-making abci app.""" + +from enum import Enum + +from packages.valory.skills.abstract_round_abci.base import DeserializedCollection +from packages.valory.skills.market_manager_abci.bets import Bet +from packages.valory.skills.market_manager_abci.rounds import ( + SynchronizedData as BaseSynchronizedData, +) + + +class Event(Enum): + """Event enumeration for the price estimation demo.""" + + DONE = "done" + NONE = "none" + MECH_RESPONSE_ERROR = "mech_response_error" + NON_BINARY = "non_binary" + TIE = "tie" + UNPROFITABLE = "unprofitable" + ROUND_TIMEOUT = "round_timeout" + NO_MAJORITY = "no_majority" + + +class SynchronizedData(BaseSynchronizedData): + """Class to represent the synchronized data. + + This data is replicated by the tendermint application. + """ + + @property + def sampled_bet_index(self) -> int: + """Get the sampled bet.""" + return int(self.db.get_strict("sampled_bet_index")) + + @property + def sampled_bet(self) -> Bet: + """Get the sampled bet.""" + return self.bets[self.sampled_bet_index] + + @property + def non_binary(self) -> bool: + """Get whether the question is non-binary.""" + return bool(self.db.get_strict("non_binary")) + + @property + def vote(self) -> str: + """Get the bet's vote.""" + vote = self.db.get_strict("vote") + return self.sampled_bet.get_outcome(vote) + + @property + def confidence(self) -> float: + """Get the vote's confidence.""" + return float(self.db.get_strict("confidence")) + + @property + def is_profitable(self) -> bool: + """Get whether the current vote is profitable or not.""" + return bool(self.db.get_strict("is_profitable")) + + @property + def participant_to_decision(self) -> DeserializedCollection: + """Get the participants to decision-making.""" + return self._get_deserialized("participant_to_decision") + + @property + def participant_to_sampling(self) -> DeserializedCollection: + """Get the participants to bet-sampling.""" + return self._get_deserialized("participant_to_sampling") diff --git a/packages/valory/skills/decision_maker_abci/rounds/blacklisting.py b/packages/valory/skills/decision_maker_abci/rounds/blacklisting.py new file mode 100644 index 00000000..486ff831 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/blacklisting.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 blacklisting state of the decision-making abci app.""" + +from packages.valory.skills.decision_maker_abci.rounds.base import Event +from packages.valory.skills.market_manager_abci.rounds import ( + UpdateBetsRound as BaseUpdateBetsRound, +) + + +class BlacklistingRound(BaseUpdateBetsRound): + """A round for updating the bets after blacklisting the sampled one.""" + + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY diff --git a/packages/valory/skills/decision_maker_abci/rounds/decision_maker.py b/packages/valory/skills/decision_maker_abci/rounds/decision_maker.py new file mode 100644 index 00000000..ef2ddc88 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/decision_maker.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 decision-making state of the decision-making abci app.""" + +from enum import Enum +from typing import Optional, Tuple, cast + +from packages.valory.skills.abstract_round_abci.base import ( + CollectSameUntilThresholdRound, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload +from packages.valory.skills.decision_maker_abci.rounds.base import ( + Event, + SynchronizedData, +) + + +class DecisionMakerRound(CollectSameUntilThresholdRound): + """A round in which the agents decide on the bet's answer.""" + + payload_class = DecisionMakerPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + none_event = Event.MECH_RESPONSE_ERROR + no_majority_event = Event.NO_MAJORITY + selection_key = ( + get_name(SynchronizedData.non_binary), + get_name(SynchronizedData.vote), + get_name(SynchronizedData.confidence), + get_name(SynchronizedData.is_profitable), + ) + collection_key = get_name(SynchronizedData.participant_to_decision) + + def end_block(self) -> Optional[Tuple[SynchronizedData, Enum]]: + """Process the end of the block.""" + res = super().end_block() + if res is None: + return None + + synced_data, event = cast(Tuple[SynchronizedData, Enum], res) + if event == Event.DONE and synced_data.non_binary: + return synced_data, Event.NON_BINARY + + if event == Event.DONE and synced_data.vote is None: + return synced_data, Event.TIE + + if event == Event.DONE and not synced_data.is_profitable: + return synced_data, Event.UNPROFITABLE + + return synced_data, event diff --git a/packages/valory/skills/decision_maker_abci/rounds/final_states.py b/packages/valory/skills/decision_maker_abci/rounds/final_states.py new file mode 100644 index 00000000..eac15d5e --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/final_states.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 final states of the decision-making abci app.""" + +from packages.valory.skills.abstract_round_abci.base import DegenerateRound + + +class FinishedDecisionMakerRound(DegenerateRound): + """A round representing that decision-making has finished.""" + + +class ImpossibleRound(DegenerateRound): + """A round representing that decision-making is impossible with the given parametrization.""" diff --git a/packages/valory/skills/decision_maker_abci/rounds/sampling.py b/packages/valory/skills/decision_maker_abci/rounds/sampling.py new file mode 100644 index 00000000..f46b1659 --- /dev/null +++ b/packages/valory/skills/decision_maker_abci/rounds/sampling.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2023 Valory AG +# +# 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 sampling state of the decision-making abci app.""" + +from packages.valory.skills.abstract_round_abci.base import ( + CollectSameUntilThresholdRound, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload +from packages.valory.skills.decision_maker_abci.rounds.base import ( + Event, + SynchronizedData, +) + + +class SamplingRound(CollectSameUntilThresholdRound): + """A round for sampling a bet.""" + + payload_class = SamplingPayload + synchronized_data_class = SynchronizedData + done_event = Event.DONE + none_event = Event.NONE + no_majority_event = Event.NO_MAJORITY + selection_key = get_name(SynchronizedData.sampled_bet_index) + collection_key = get_name(SynchronizedData.participant_to_sampling) From 2696f74e0a2b62cf9baee3ca9dc06d63e28ab36c Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 19:02:17 +0300 Subject: [PATCH 38/39] fix: add missing final state and missing behaviours --- .../skills/decision_maker_abci/behaviours/round_behaviour.py | 2 ++ packages/valory/skills/decision_maker_abci/rounds/abci_app.py | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py index 8c8a0b50..60b37931 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -45,5 +45,7 @@ class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): initial_behaviour_cls = DecisionMakerBehaviour abci_app_cls = DecisionMakerAbciApp behaviours: Set[Type[BaseBehaviour]] = { + SamplingBehaviour, # type: ignore DecisionMakerBehaviour, # type: ignore + BlacklistingBehaviour, # type: ignore } diff --git a/packages/valory/skills/decision_maker_abci/rounds/abci_app.py b/packages/valory/skills/decision_maker_abci/rounds/abci_app.py index e06733c2..70e1bc7d 100644 --- a/packages/valory/skills/decision_maker_abci/rounds/abci_app.py +++ b/packages/valory/skills/decision_maker_abci/rounds/abci_app.py @@ -79,6 +79,7 @@ class DecisionMakerAbciApp(AbciApp[Event]): FinishedDecisionMakerRound: {}, } final_states: Set[AppState] = { + ImpossibleRound, FinishedDecisionMakerRound, } event_to_timeout: Dict[Event, float] = { From c8920d265f3c4bed94b5aaa906b4a7ea5eb32996 Mon Sep 17 00:00:00 2001 From: Adamantios Date: Fri, 7 Jul 2023 19:09:18 +0300 Subject: [PATCH 39/39] refactor: structure abci app in a way it can be parsed --- .../skills/decision_maker_abci/behaviours/base.py | 2 +- .../decision_maker_abci/behaviours/blacklisting.py | 2 +- .../behaviours/decision_maker.py | 2 +- .../behaviours/round_behaviour.py | 4 +--- .../decision_maker_abci/behaviours/sampling.py | 2 +- .../valory/skills/decision_maker_abci/models.py | 4 +--- .../{rounds/abci_app.py => rounds.py} | 14 ++++++++------ .../{rounds => states}/__init__.py | 0 .../decision_maker_abci/{rounds => states}/base.py | 0 .../{rounds => states}/blacklisting.py | 2 +- .../{rounds => states}/decision_maker.py | 2 +- .../{rounds => states}/final_states.py | 0 .../{rounds => states}/sampling.py | 2 +- 13 files changed, 17 insertions(+), 19 deletions(-) rename packages/valory/skills/decision_maker_abci/{rounds/abci_app.py => rounds.py} (87%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/__init__.py (100%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/base.py (100%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/blacklisting.py (94%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/decision_maker.py (97%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/final_states.py (100%) rename packages/valory/skills/decision_maker_abci/{rounds => states}/sampling.py (95%) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/base.py b/packages/valory/skills/decision_maker_abci/behaviours/base.py index 6e2aa126..ab16a286 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/base.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/base.py @@ -25,7 +25,7 @@ from packages.valory.skills.abstract_round_abci.base import BaseTxPayload from packages.valory.skills.abstract_round_abci.behaviour_utils import BaseBehaviour from packages.valory.skills.decision_maker_abci.models import DecisionMakerParams -from packages.valory.skills.decision_maker_abci.rounds.base import SynchronizedData +from packages.valory.skills.decision_maker_abci.states.base import SynchronizedData class DecisionMakerBaseBehaviour(BaseBehaviour, ABC): diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py index 094b80ab..cdc20fba 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -24,7 +24,7 @@ from packages.valory.skills.decision_maker_abci.behaviours.base import ( DecisionMakerBaseBehaviour, ) -from packages.valory.skills.decision_maker_abci.rounds.blacklisting import ( +from packages.valory.skills.decision_maker_abci.states.blacklisting import ( BlacklistingRound, ) from packages.valory.skills.market_manager_abci.bets import BetStatus, serialize_bets diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py index c4ee6158..d3611b37 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_maker.py @@ -30,7 +30,7 @@ DecisionMakerBaseBehaviour, ) from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds.decision_maker import ( +from packages.valory.skills.decision_maker_abci.states.decision_maker import ( DecisionMakerRound, ) from packages.valory.skills.decision_maker_abci.tasks import ( diff --git a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py index 60b37931..7b8acd62 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/round_behaviour.py @@ -34,9 +34,7 @@ from packages.valory.skills.decision_maker_abci.behaviours.sampling import ( SamplingBehaviour, ) -from packages.valory.skills.decision_maker_abci.rounds.abci_app import ( - DecisionMakerAbciApp, -) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp class AgentDecisionMakerRoundBehaviour(AbstractRoundBehaviour): diff --git a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py index 9873ee85..0c6991ee 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/sampling.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/sampling.py @@ -25,7 +25,7 @@ DecisionMakerBaseBehaviour, ) from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload -from packages.valory.skills.decision_maker_abci.rounds.sampling import SamplingRound +from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound from packages.valory.skills.market_manager_abci.bets import Bet, BetStatus diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index b796c2fd..597239b0 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -28,9 +28,7 @@ from packages.valory.skills.abstract_round_abci.models import ( SharedState as BaseSharedState, ) -from packages.valory.skills.decision_maker_abci.rounds.abci_app import ( - DecisionMakerAbciApp, -) +from packages.valory.skills.decision_maker_abci.rounds import DecisionMakerAbciApp from packages.valory.skills.market_manager_abci.models import ( MarketManagerParams as BaseParams, ) diff --git a/packages/valory/skills/decision_maker_abci/rounds/abci_app.py b/packages/valory/skills/decision_maker_abci/rounds.py similarity index 87% rename from packages/valory/skills/decision_maker_abci/rounds/abci_app.py rename to packages/valory/skills/decision_maker_abci/rounds.py index 70e1bc7d..5429d924 100644 --- a/packages/valory/skills/decision_maker_abci/rounds/abci_app.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -26,18 +26,18 @@ AbciAppTransitionFunction, AppState, ) -from packages.valory.skills.decision_maker_abci.rounds.base import Event -from packages.valory.skills.decision_maker_abci.rounds.blacklisting import ( +from packages.valory.skills.decision_maker_abci.states.base import Event +from packages.valory.skills.decision_maker_abci.states.blacklisting import ( BlacklistingRound, ) -from packages.valory.skills.decision_maker_abci.rounds.decision_maker import ( +from packages.valory.skills.decision_maker_abci.states.decision_maker import ( DecisionMakerRound, ) -from packages.valory.skills.decision_maker_abci.rounds.final_states import ( +from packages.valory.skills.decision_maker_abci.states.final_states import ( FinishedDecisionMakerRound, ImpossibleRound, ) -from packages.valory.skills.decision_maker_abci.rounds.sampling import SamplingRound +from packages.valory.skills.decision_maker_abci.states.sampling import SamplingRound class DecisionMakerAbciApp(AbciApp[Event]): @@ -77,6 +77,7 @@ class DecisionMakerAbciApp(AbciApp[Event]): Event.NO_MAJORITY: BlacklistingRound, }, FinishedDecisionMakerRound: {}, + ImpossibleRound: {}, } final_states: Set[AppState] = { ImpossibleRound, @@ -86,8 +87,9 @@ class DecisionMakerAbciApp(AbciApp[Event]): Event.ROUND_TIMEOUT: 30.0, } db_pre_conditions: Dict[AppState, Set[str]] = { - DecisionMakerRound: set(), + SamplingRound: set(), } db_post_conditions: Dict[AppState, Set[str]] = { FinishedDecisionMakerRound: set(), + ImpossibleRound: set(), } diff --git a/packages/valory/skills/decision_maker_abci/rounds/__init__.py b/packages/valory/skills/decision_maker_abci/states/__init__.py similarity index 100% rename from packages/valory/skills/decision_maker_abci/rounds/__init__.py rename to packages/valory/skills/decision_maker_abci/states/__init__.py diff --git a/packages/valory/skills/decision_maker_abci/rounds/base.py b/packages/valory/skills/decision_maker_abci/states/base.py similarity index 100% rename from packages/valory/skills/decision_maker_abci/rounds/base.py rename to packages/valory/skills/decision_maker_abci/states/base.py diff --git a/packages/valory/skills/decision_maker_abci/rounds/blacklisting.py b/packages/valory/skills/decision_maker_abci/states/blacklisting.py similarity index 94% rename from packages/valory/skills/decision_maker_abci/rounds/blacklisting.py rename to packages/valory/skills/decision_maker_abci/states/blacklisting.py index 486ff831..3090849d 100644 --- a/packages/valory/skills/decision_maker_abci/rounds/blacklisting.py +++ b/packages/valory/skills/decision_maker_abci/states/blacklisting.py @@ -19,7 +19,7 @@ """This module contains the blacklisting state of the decision-making abci app.""" -from packages.valory.skills.decision_maker_abci.rounds.base import Event +from packages.valory.skills.decision_maker_abci.states.base import Event from packages.valory.skills.market_manager_abci.rounds import ( UpdateBetsRound as BaseUpdateBetsRound, ) diff --git a/packages/valory/skills/decision_maker_abci/rounds/decision_maker.py b/packages/valory/skills/decision_maker_abci/states/decision_maker.py similarity index 97% rename from packages/valory/skills/decision_maker_abci/rounds/decision_maker.py rename to packages/valory/skills/decision_maker_abci/states/decision_maker.py index ef2ddc88..4d11731f 100644 --- a/packages/valory/skills/decision_maker_abci/rounds/decision_maker.py +++ b/packages/valory/skills/decision_maker_abci/states/decision_maker.py @@ -27,7 +27,7 @@ get_name, ) from packages.valory.skills.decision_maker_abci.payloads import DecisionMakerPayload -from packages.valory.skills.decision_maker_abci.rounds.base import ( +from packages.valory.skills.decision_maker_abci.states.base import ( Event, SynchronizedData, ) diff --git a/packages/valory/skills/decision_maker_abci/rounds/final_states.py b/packages/valory/skills/decision_maker_abci/states/final_states.py similarity index 100% rename from packages/valory/skills/decision_maker_abci/rounds/final_states.py rename to packages/valory/skills/decision_maker_abci/states/final_states.py diff --git a/packages/valory/skills/decision_maker_abci/rounds/sampling.py b/packages/valory/skills/decision_maker_abci/states/sampling.py similarity index 95% rename from packages/valory/skills/decision_maker_abci/rounds/sampling.py rename to packages/valory/skills/decision_maker_abci/states/sampling.py index f46b1659..bca706e3 100644 --- a/packages/valory/skills/decision_maker_abci/rounds/sampling.py +++ b/packages/valory/skills/decision_maker_abci/states/sampling.py @@ -24,7 +24,7 @@ get_name, ) from packages.valory.skills.decision_maker_abci.payloads import SamplingPayload -from packages.valory.skills.decision_maker_abci.rounds.base import ( +from packages.valory.skills.decision_maker_abci.states.base import ( Event, SynchronizedData, )