olas-operate-middleware 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: olas-operate-middleware
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary:
5
5
  Author: David Vilela
6
6
  Author-email: dvilelaf@gmail.com
@@ -23,7 +23,7 @@ Requires-Dist: eth-keys (==0.4.0)
23
23
  Requires-Dist: eth-rlp (==0.3.0)
24
24
  Requires-Dist: eth-typing (==3.5.2)
25
25
  Requires-Dist: eth-utils (==2.3.1)
26
- Requires-Dist: fastapi (==0.110.0)
26
+ Requires-Dist: fastapi (==0.110.3)
27
27
  Requires-Dist: frozenlist (==1.4.1)
28
28
  Requires-Dist: halo (==0.0.31)
29
29
  Requires-Dist: hexbytes (==0.3.1)
@@ -34,11 +34,11 @@ Requires-Dist: open-aea-cli-ipfs (==1.65.0)
34
34
  Requires-Dist: open-aea-ledger-cosmos (==1.65.0)
35
35
  Requires-Dist: open-aea-ledger-ethereum (==1.65.0)
36
36
  Requires-Dist: open-aea-ledger-ethereum-flashbots (==1.65.0)
37
- Requires-Dist: open-autonomy (==0.19.8)
37
+ Requires-Dist: open-autonomy (>=0.19.11,<0.20.0)
38
38
  Requires-Dist: psutil (>=5.9.8,<6.0.0)
39
39
  Requires-Dist: pyinstaller (>=6.8.0,<7.0.0)
40
40
  Requires-Dist: requests-toolbelt (==1.0.0)
41
- Requires-Dist: starlette (==0.36.3)
41
+ Requires-Dist: starlette (==0.37.2)
42
42
  Requires-Dist: twikit (==2.2.0)
43
43
  Requires-Dist: uvicorn (==0.27.0)
44
44
  Requires-Dist: web3 (==6.1.0)
@@ -5,8 +5,8 @@ operate/bridge/bridge.py,sha256=S0ZmqHFkUGEpMS22rZtSgJKFlul-G2wctGnk8AcdGz0,1570
5
5
  operate/bridge/providers/bridge_provider.py,sha256=LAbjs6e1uHTbkzsCmxpD1XTcSKsf0bFdFp5aU5XEtd0,20079
6
6
  operate/bridge/providers/lifi_bridge_provider.py,sha256=sht09sLa4NrT5F2km9ItHmdAZ6h6a2hAA5pSpamaqPc,13719
7
7
  operate/bridge/providers/native_bridge_provider.py,sha256=lX8n6CA0QjPibFOBkPAGeMtab5-rEj6KGRpND33A19E,24204
8
- operate/cli.py,sha256=cCnlh65FG82GLr4uhvQf0qvEkKbCxymaj3MxaJChhQY,45099
9
- operate/constants.py,sha256=f49PnLped2VHBZlIS2L_XTxBl3Pzne5U2uoRB4dIyvI,2652
8
+ operate/cli.py,sha256=R7gyctoUGtnHHqfNHmEAQtVkxcp1aLQod7aT9coppjc,46864
9
+ operate/constants.py,sha256=EHJJATO4tRFMjSUgoaixCwhJ4e00MZP8cuRHhXDIMTo,2638
10
10
  operate/data/README.md,sha256=jGPyZTvg2LCGdllvmYxmFMkkkiXb6YWatbqIkcX3kv4,879
11
11
  operate/data/__init__.py,sha256=ttC51Yqk9c4ehpIgs1Qbe7aJvzkrbbdZ1ClaCxJYByE,864
12
12
  operate/data/contracts/__init__.py,sha256=_th54_WvL0ibGy-b6St0Ne9DX-fyjsh-tNOKDn-cWrg,809
@@ -42,7 +42,6 @@ operate/data/contracts/requester_activity_checker/__init__.py,sha256=BrOe5cib0jI
42
42
  operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json,sha256=KbZbZDEred3LvxIY30igz5w9ubaD0-PvDbFdlg7F-y4,9748
43
43
  operate/data/contracts/requester_activity_checker/contract.py,sha256=preNvyk8kmtUE0LgEIZMLorYevgCmoxBqI-1STrX0_4,1252
44
44
  operate/data/contracts/requester_activity_checker/contract.yaml,sha256=IGMpY4UJ8VEPW9DFwKtL83fM4F0xbgN5HEYh6KMFBg4,749
45
- operate/data/contracts/service_staking_token/contract.yaml,sha256=mX7GasMUBNWW8aoD1Kon-4nEFLv2sm9e9HexKMIwSJk,724
46
45
  operate/data/contracts/staking_token/__init__.py,sha256=9GisClTvLBaDB3MPIYReUCsncyE16_mRA3ZPdPRobmY,859
47
46
  operate/data/contracts/staking_token/build/StakingToken.json,sha256=BMaZE6WuyuO9Wd2PBTAMyCD3RyBCDMKBIarXtrFpViE,56490
48
47
  operate/data/contracts/staking_token/contract.py,sha256=LDpp-a2quo-rELg_t3Np6nTFdlqrDstJOeok5hr03nE,6366
@@ -53,7 +52,7 @@ operate/data/contracts/uniswap_v2_erc20/contract.py,sha256=MwBks4QmZ3XouMT_TqWLn
53
52
  operate/data/contracts/uniswap_v2_erc20/contract.yaml,sha256=XUdz-XtKtmZgLfItbO8usP-QPbtUkAxKGn0hL7OftAg,741
54
53
  operate/data/contracts/uniswap_v2_erc20/tests/__init__.py,sha256=3Arw8dsCsJz6hVOl0t9UjFASHXbV9yp3hw6x4HqgXpU,847
55
54
  operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py,sha256=FzZbw9OTcr_yvjOXpk9YcO-K40eyDARyybcfSHDg2Ps,13392
56
- operate/keys.py,sha256=IWRcGyakc4jLImsTAiID2GM64aleB1lfZdtw2rcZgCw,3441
55
+ operate/keys.py,sha256=soJfdXJvHo8ytWH6ShIndjUpkShPg3KzirLs7MoFe8I,3764
57
56
  operate/ledger/__init__.py,sha256=kC-SYcdOQcX-v4TIWpwkSdM8L0yimEmbaL33XNY4S7Q,3361
58
57
  operate/ledger/profiles.py,sha256=u8BanMsAk0hQo9SLkZ0uyPUGeHD3eWT4MA6kHoyiAQY,12093
59
58
  operate/migration.py,sha256=asWzYYSLhp0QmWov1eyEb9iGjzwZNOPDed01Z3Jnnn8,1819
@@ -63,29 +62,30 @@ operate/operate_types.py,sha256=XI4O_zbcXWVM64I4WN5LBIQ-uV9kbgAk0ri8TAlgd90,8020
63
62
  operate/pearl.py,sha256=yrTpSXLu_ML3qT-uNxq3kScOyo31JyxBujiSMfMUbcg,1690
64
63
  operate/quickstart/analyse_logs.py,sha256=18y0JRGEryLILM_Aj2nINyMlUodA42DaypjWLev1ENs,4297
65
64
  operate/quickstart/claim_staking_rewards.py,sha256=-cFsDM_qKyCo2PEqhDAeYp7cLK2DkT0yATT3zutXo_k,4143
65
+ operate/quickstart/reset_configs.py,sha256=t-gVt0_-Th-LADfVmjxxKXjE2if5w-MuEscGFyMw2u0,3450
66
66
  operate/quickstart/reset_password.py,sha256=_HHnwX032WKNja5koegQIy4By0rFXGcdQRgVlusf5GA,2644
67
67
  operate/quickstart/reset_staking.py,sha256=3FxL0XB59eomND-pphRCzZoysOdesexdcG0CWRjIh0c,5165
68
- operate/quickstart/run_service.py,sha256=l_Asn1gEKRhkgk6JSozc_ic-3Pv96-9YAcIIQIDZxMc,26384
68
+ operate/quickstart/run_service.py,sha256=he8hhNhPJE9127wvNqadVX0CI_BRFjnds41M3t456Zw,26200
69
69
  operate/quickstart/stop_service.py,sha256=ENuFBd3KaTs0NFTy-6ECrp5XI8MRJcIOUevWF82WSt4,2094
70
70
  operate/quickstart/terminate_on_chain_service.py,sha256=2UVN03plvjo72a8LY9caTT8a-x6Ndn-7u70Ap2lvBdg,3031
71
- operate/quickstart/utils.py,sha256=KOBa_r6l6aKjHnBy1-rV3UI4UhZg-OfYklmlOT5m1Pk,9095
71
+ operate/quickstart/utils.py,sha256=F3Hv47Da_mmtUhGrWSc2O0lQ7OnNA0WPks83-Qdj9TE,9217
72
72
  operate/resource.py,sha256=iuLlUepc18XTk1rEeC3Skt1R0v6cHST9rQxR6h4Ldg0,5179
73
73
  operate/services/__init__.py,sha256=isrThS-Ccu5Sc15JZgkN4uTAVaSg-NwUUSDeTyJEqLk,855
74
74
  operate/services/agent_runner.py,sha256=w6vCj1xzaZ_LEFPlUilxZ6Evepu62z6X86LV3GCwu8k,4847
75
- operate/services/deployment_runner.py,sha256=KYwQHlwl4TG0Wk5A1jXd8FINljR_ynOu5kecDIIMSlY,16674
75
+ operate/services/deployment_runner.py,sha256=eP1bnT3PdkYtPxi-4sZ6-Wopz8u88NkeZx7vOB2vkzc,22217
76
76
  operate/services/health_checker.py,sha256=pXtzFTLv4PK1OSbDCZ_RnOnvX31mPYRR16tbC7BsUNw,9754
77
- operate/services/manage.py,sha256=ii29BXPZCPT_Tq_2ucuUOYPA-kABOd3RbcBG2pM58gw,108126
77
+ operate/services/manage.py,sha256=5JdvbLzx8Z1qvbTnUns3TuWbKzpo-SHH9PYF9Gom6tE,108585
78
78
  operate/services/protocol.py,sha256=0LcZk-zzQ2hYzZAkn_KIQGgT32Bq3_UsbBl7Ert3Ho8,60157
79
- operate/services/service.py,sha256=5UbBZ5jjr8_fi4VvI47a9lzUdQoaOV71ifYt5EIS_ws,46700
79
+ operate/services/service.py,sha256=2fQeRbjc4PBzRQ4jD9peKjAwBMKABS_hNoe-BrRutPc,47250
80
80
  operate/services/utils/__init__.py,sha256=TvioaZ1mfTRUSCtrQoLNAp4WMVXyqEJqFJM4PxSQCRU,24
81
- operate/services/utils/mech.py,sha256=Y1tDLMv82CljLEfiu8FRVaR94akXvsKT1ZFLaeAhCME,3819
81
+ operate/services/utils/mech.py,sha256=W2x4dqodivNKXjWU-Brp40QhoUHsIMyNAO7-caMoR0Q,3821
82
82
  operate/services/utils/tendermint.py,sha256=3h9nDb2Z89T0RwUr_AaVjqtymQmsu3u6DAVCfL_k1U0,25591
83
83
  operate/utils/__init__.py,sha256=cFNP2XFpjJmDLskN0SzAk5FPdqaeN2Jn4MyVbFHmH2M,3075
84
84
  operate/utils/gnosis.py,sha256=Z1IgGfQgKIrI7EyBpGFbJ2RFaeD4Fk_7D4P-_ZQfH6Q,17705
85
85
  operate/wallet/__init__.py,sha256=NGiozD3XhvkBi7_FaOWQ8x1thZPK4uGpokJaeDY_o2w,813
86
86
  operate/wallet/master.py,sha256=FQrchjWhJKgif3IXztxS0SHm7aVaAJYFQ-FEXQgxQes,31021
87
- olas_operate_middleware-0.6.1.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
88
- olas_operate_middleware-0.6.1.dist-info/METADATA,sha256=5tPwX2HX20RqoYduwgORdgojcGTlznbGdb7dh3OmZxs,2025
89
- olas_operate_middleware-0.6.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
90
- olas_operate_middleware-0.6.1.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
91
- olas_operate_middleware-0.6.1.dist-info/RECORD,,
87
+ olas_operate_middleware-0.6.3.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
88
+ olas_operate_middleware-0.6.3.dist-info/METADATA,sha256=QME8E6Om3QMHXhPBK5Gl4-fFsneNRD1x3jbv76vBlSU,2034
89
+ olas_operate_middleware-0.6.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
90
+ olas_operate_middleware-0.6.3.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
91
+ olas_operate_middleware-0.6.3.dist-info/RECORD,,
operate/cli.py CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  """Operate app CLI module."""
21
21
  import asyncio
22
+ import atexit
22
23
  import logging
23
24
  import multiprocessing
24
25
  import os
@@ -55,6 +56,7 @@ from operate.migration import MigrationManager
55
56
  from operate.operate_types import Chain, DeploymentStatus, LedgerType
56
57
  from operate.quickstart.analyse_logs import analyse_logs
57
58
  from operate.quickstart.claim_staking_rewards import claim_staking_rewards
59
+ from operate.quickstart.reset_configs import reset_configs
58
60
  from operate.quickstart.reset_password import reset_password
59
61
  from operate.quickstart.reset_staking import reset_staking
60
62
  from operate.quickstart.run_service import run_service
@@ -325,6 +327,9 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
325
327
  # on backend app started we assume there are now started agents, so we force to pause all
326
328
  pause_all_services_on_startup()
327
329
 
330
+ # stop all services at middleware exit
331
+ atexit.register(pause_all_services)
332
+
328
333
  app = FastAPI()
329
334
 
330
335
  app.add_middleware(
@@ -533,6 +538,35 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
533
538
  wallet, mnemonic = manager.create(ledger_type=ledger_type)
534
539
  return JSONResponse(content={"wallet": wallet.json, "mnemonic": mnemonic})
535
540
 
541
+ @app.post("/api/wallet/private_key")
542
+ @with_retries
543
+ async def _get_private_key(request: Request) -> t.List[t.Dict]:
544
+ """Get Master EOA private key."""
545
+ if operate.user_account is None:
546
+ return JSONResponse(
547
+ content={
548
+ "error": "Cannot retrieve private key; User account does not exist!"
549
+ },
550
+ status_code=HTTPStatus.BAD_REQUEST,
551
+ )
552
+
553
+ data = await request.json()
554
+ password = data.get("password")
555
+ error = None
556
+ if operate.password is None:
557
+ error = {"error": "You need to login before retrieving the private key"}
558
+ if operate.password != password:
559
+ error = {"error": "Password is not valid"}
560
+ if error is not None:
561
+ return JSONResponse(
562
+ content=error,
563
+ status_code=HTTPStatus.UNAUTHORIZED,
564
+ )
565
+
566
+ ledger_type = data.get("ledger_type", LedgerType.ETHEREUM.value)
567
+ wallet = operate.wallet_manager.load(ledger_type=LedgerType(ledger_type))
568
+ return JSONResponse(content={"private_key": wallet.crypto.private_key})
569
+
536
570
  @app.get("/api/extended/wallet")
537
571
  @with_retries
538
572
  async def _get_wallet_safe(request: Request) -> t.List[t.Dict]:
@@ -1141,6 +1175,20 @@ def qs_claim(
1141
1175
  claim_staking_rewards(operate=operate, config_path=config)
1142
1176
 
1143
1177
 
1178
+ @_operate.command(name="reset-configs")
1179
+ def qs_reset_configs(
1180
+ config: Annotated[str, params.String(help="Quickstart config file path")],
1181
+ attended: Annotated[
1182
+ str, params.String(help="Run in attended/unattended mode (default: true")
1183
+ ] = "true",
1184
+ ) -> None:
1185
+ """Reset configs."""
1186
+ os.environ["ATTENDED"] = attended.lower()
1187
+ operate = OperateApp()
1188
+ operate.setup()
1189
+ reset_configs(operate=operate, config_path=config)
1190
+
1191
+
1144
1192
  @_operate.command(name="reset-staking")
1145
1193
  def qs_reset_staking(
1146
1194
  config: Annotated[str, params.String(help="Quickstart config file path")],
operate/constants.py CHANGED
@@ -31,7 +31,6 @@ DEPLOYMENT = "deployment"
31
31
  DEPLOYMENT_JSON = "deployment.json"
32
32
  CONFIG = "config.json"
33
33
  KEY = "key"
34
- KEYS = "keys"
35
34
  KEYS_JSON = "keys.json"
36
35
  DOCKER_COMPOSE_YAML = "docker-compose.yaml"
37
36
  SERVICE_YAML = "service.yaml"
operate/keys.py CHANGED
@@ -22,6 +22,7 @@
22
22
  import json
23
23
  import logging
24
24
  import os
25
+ import shutil
25
26
  import typing as t
26
27
  from dataclasses import dataclass
27
28
  from pathlib import Path
@@ -85,21 +86,25 @@ class KeysManager:
85
86
  def create(self) -> str:
86
87
  """Creates new key."""
87
88
  crypto = EthereumCrypto()
88
- path = self.path / crypto.address
89
- if path.is_file():
90
- return crypto.address
91
-
92
- path.write_text(
93
- json.dumps(
94
- Key(
95
- ledger=LedgerType.ETHEREUM,
96
- address=crypto.address,
97
- private_key=crypto.private_key,
98
- ).json,
99
- indent=4,
100
- ),
101
- encoding="utf-8",
102
- )
89
+ for path in (
90
+ self.path / f"{crypto.address}.bak",
91
+ self.path / crypto.address,
92
+ ):
93
+ if path.is_file():
94
+ continue
95
+
96
+ path.write_text(
97
+ json.dumps(
98
+ Key(
99
+ ledger=LedgerType.ETHEREUM,
100
+ address=crypto.address,
101
+ private_key=crypto.private_key,
102
+ ).json,
103
+ indent=4,
104
+ ),
105
+ encoding="utf-8",
106
+ )
107
+
103
108
  return crypto.address
104
109
 
105
110
  def delete(self, key: str) -> None:
@@ -109,17 +114,22 @@ class KeysManager:
109
114
  @classmethod
110
115
  def migrate_format(cls, path: Path) -> bool:
111
116
  """Migrate the JSON file format if needed."""
117
+ migrated = False
118
+ backup_path = path.with_suffix(".bak")
119
+ if not backup_path.is_file():
120
+ shutil.copyfile(path, backup_path)
121
+ migrated = True
122
+
112
123
  with open(path, "r", encoding="utf-8") as file:
113
124
  data = json.load(file)
114
125
 
115
126
  old_to_new_ledgers = {0: "ethereum", 1: "solana"}
116
-
117
- migrated = False
118
127
  if data.get("ledger") in old_to_new_ledgers:
119
128
  data["ledger"] = old_to_new_ledgers.get(data["ledger"])
120
129
  migrated = True
121
130
 
122
- with open(path, "w", encoding="utf-8") as file:
123
- json.dump(data, file, indent=2)
131
+ if migrated:
132
+ with open(path, "w", encoding="utf-8") as file:
133
+ json.dump(data, file, indent=2)
124
134
 
125
135
  return migrated
@@ -0,0 +1,109 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2023-2024 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+ """Reset configurations."""
20
+
21
+ import json
22
+ from typing import Callable, Optional, TYPE_CHECKING, cast
23
+
24
+ from operate.quickstart.run_service import load_local_config
25
+ from operate.quickstart.utils import (
26
+ ask_or_get_from_env,
27
+ ask_yes_or_no,
28
+ check_rpc,
29
+ print_section,
30
+ print_title,
31
+ )
32
+
33
+
34
+ if TYPE_CHECKING:
35
+ from operate.cli import OperateApp
36
+
37
+
38
+ def _ask_to_change(
39
+ name: str,
40
+ env_var: str,
41
+ old_value: str,
42
+ hidden: bool = False,
43
+ validator: Callable[[Optional[str]], bool] = lambda x: False if x is None else True,
44
+ ) -> str:
45
+ """Ask user if they want to change a configuration value."""
46
+ old_value_str = old_value
47
+ if hidden:
48
+ if len(old_value_str) < 4:
49
+ old_value_str = "*" * len(old_value_str)
50
+ else:
51
+ old_value_str = "*" * len(old_value_str[:-4]) + old_value_str[-4:]
52
+
53
+ print(f"\nCurrent '{name}' is set to: {old_value_str}")
54
+ if ask_yes_or_no(f"Do you want to change the '{name}'?"):
55
+ new_value = None
56
+ while not validator(new_value):
57
+ new_value = ask_or_get_from_env(
58
+ prompt=f"Enter new value for '{name}' {'[hidden]' if hidden else ''}: ",
59
+ env_var_name=env_var,
60
+ is_pass=hidden,
61
+ )
62
+
63
+ return str(new_value)
64
+
65
+ return old_value
66
+
67
+
68
+ def reset_configs(operate: "OperateApp", config_path: str) -> None:
69
+ """Reset configurations."""
70
+ with open(config_path, "r") as config_file:
71
+ template = json.load(config_file)
72
+
73
+ operate.service_manager().migrate_service_configs()
74
+ operate.wallet_manager.migrate_wallet_configs()
75
+
76
+ print_title(f"Reset your {template['name']} configurations")
77
+
78
+ # check if agent was started before
79
+ config = load_local_config(
80
+ operate=operate, service_name=cast(str, template["name"])
81
+ )
82
+ if not config.path.exists():
83
+ print("No previous agent setup found. Exiting.")
84
+ return
85
+
86
+ if config.rpc is None:
87
+ config.rpc = {}
88
+
89
+ for chain_name in config.rpc:
90
+ config.rpc[chain_name] = _ask_to_change(
91
+ name=f"{chain_name.capitalize()} RPC URL",
92
+ env_var=f"{chain_name.upper()}_LEDGER_RPC",
93
+ old_value=config.rpc[chain_name],
94
+ hidden=True,
95
+ validator=check_rpc,
96
+ )
97
+
98
+ if config.user_provided_args is None:
99
+ config.user_provided_args = {}
100
+
101
+ for env_var in config.user_provided_args:
102
+ config.user_provided_args[env_var] = _ask_to_change(
103
+ name=env_var,
104
+ env_var=env_var,
105
+ old_value=config.user_provided_args[env_var],
106
+ )
107
+
108
+ config.store()
109
+ print_section("Configurations updated")
@@ -99,8 +99,6 @@ QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
99
99
  "quickstart_beta_mech_marketplace_expert_8": "trader",
100
100
  "mech_marketplace": "mech",
101
101
  "marketplace_supply_alpha": "mech",
102
- "marketplace_demand_alpha_1": "mech",
103
- "marketplace_demand_alpha_2": "mech",
104
102
  },
105
103
  Chain.OPTIMISTIC: {
106
104
  "optimus_alpha_2": "optimus",
@@ -111,8 +109,6 @@ QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
111
109
  Chain.BASE: {
112
110
  "meme_base_alpha_2": "memeooorr",
113
111
  "marketplace_supply_alpha": "mech",
114
- "marketplace_demand_alpha_1": "mech",
115
- "marketplace_demand_alpha_2": "mech",
116
112
  "agents_fun_1": "memeooorr",
117
113
  "agents_fun_2": "memeooorr",
118
114
  "agents_fun_3": "memeooorr",
@@ -188,8 +188,12 @@ def ask_yes_or_no(question: str) -> bool:
188
188
  """Ask a yes/no question."""
189
189
  if os.environ.get("ATTENDED", "true").lower() != "true":
190
190
  return True
191
- response = input(f"{question} (yes/no): ").strip().lower()
192
- return response in ["yes", "y"]
191
+ while True:
192
+ response = input(f"{question} (yes/no): ").strip().lower()
193
+ if response.lower() in ("yes", "y"):
194
+ return True
195
+ if response.lower() in ("no", "n"):
196
+ return False
193
197
 
194
198
 
195
199
  def ask_or_get_from_env(
@@ -18,6 +18,7 @@
18
18
  #
19
19
  # ------------------------------------------------------------------------------
20
20
  """Source code to run and stop deployments created."""
21
+ import ctypes
21
22
  import json
22
23
  import multiprocessing
23
24
  import os
@@ -28,9 +29,10 @@ import sys # nosec
28
29
  import time
29
30
  import typing as t
30
31
  from abc import ABC, ABCMeta, abstractmethod
32
+ from io import TextIOWrapper
31
33
  from pathlib import Path
32
34
  from traceback import print_exc
33
- from typing import Any, List
35
+ from typing import Any, Dict, List
34
36
  from venv import main as venv_cli
35
37
 
36
38
  import psutil
@@ -98,10 +100,16 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
98
100
  TM_CONTROL_URL = constants.TM_CONTROL_URL
99
101
  SLEEP_BEFORE_TM_KILL = 2 # seconds
100
102
 
103
+ def _open_agent_runner_log_file(self) -> TextIOWrapper:
104
+ """Open agent_runner.log file."""
105
+ return (
106
+ Path(self._work_directory).parent.parent.parent / "agent_runner.log"
107
+ ).open("w+")
108
+
101
109
  def _run_aea_command(self, *args: str, cwd: Path) -> Any:
102
110
  """Run aea command."""
103
111
  cmd = " ".join(args)
104
- print("Runnin aea command: ", cmd, " at ", str(cwd))
112
+ print("Running aea command: ", cmd, " at ", str(cwd))
105
113
  p = multiprocessing.Process(
106
114
  target=self.__class__._call_aea_command, # pylint: disable=protected-access
107
115
  args=(cwd, args),
@@ -293,28 +301,17 @@ class PyInstallerHostDeploymentRunner(BaseDeploymentRunner):
293
301
  env["PYTHONUTF8"] = "1"
294
302
  env["PYTHONIOENCODING"] = "utf8"
295
303
  env = {**os.environ, **env}
296
- agent_runner_log_file = (
297
- Path(self._work_directory).parent.parent.parent / "agent_runner.log"
298
- ).open("a+")
299
- process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
300
- args=[
301
- self._agent_runner_bin,
302
- "-s",
303
- "run",
304
- ], # TODO: Patch for Windows failing hash
305
- cwd=working_dir / "agent",
306
- stdout=agent_runner_log_file,
307
- stderr=agent_runner_log_file,
308
- env=env,
309
- creationflags=(
310
- 0x00000200 if platform.system() == "Windows" else 0
311
- ), # Detach process from the main process
312
- )
304
+
305
+ process = self._start_agent_process(env=env, working_dir=working_dir)
313
306
  (working_dir / "agent.pid").write_text(
314
307
  data=str(process.pid),
315
308
  encoding="utf-8",
316
309
  )
317
310
 
311
+ def _start_agent_process(self, env: Dict, working_dir: Path) -> subprocess.Popen:
312
+ """Start agent process."""
313
+ raise NotImplementedError
314
+
318
315
  def _start_tendermint(self) -> None:
319
316
  """Start tendermint process."""
320
317
  working_dir = self._work_directory
@@ -327,41 +324,182 @@ class PyInstallerHostDeploymentRunner(BaseDeploymentRunner):
327
324
  **env,
328
325
  }
329
326
 
330
- if platform.system() == "Windows":
331
- # to look up for bundled in tendermint.exe
332
- env["PATH"] = os.path.dirname(sys.executable) + ";" + os.environ["PATH"]
333
- else:
334
- env["PATH"] = os.path.dirname(sys.executable) + ":" + os.environ["PATH"]
327
+ process = self._start_tendermint_process(env=env, working_dir=working_dir)
335
328
 
336
- tendermint_com = self._tendermint_bin # type: ignore # pylint: disable=protected-access
337
- process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
338
- args=[tendermint_com],
339
- cwd=working_dir,
340
- stdout=subprocess.DEVNULL,
341
- stderr=subprocess.DEVNULL,
342
- env=env,
343
- creationflags=(
344
- 0x00000200 if platform.system() == "Windows" else 0
345
- ), # Detach process from the main process
346
- )
347
329
  (working_dir / "tendermint.pid").write_text(
348
330
  data=str(process.pid),
349
331
  encoding="utf-8",
350
332
  )
351
333
 
334
+ def _start_tendermint_process(
335
+ self, env: Dict, working_dir: Path
336
+ ) -> subprocess.Popen:
337
+ raise NotImplementedError
338
+
352
339
 
353
340
  class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner):
354
341
  """Mac deployment runner."""
355
342
 
343
+ def _start_agent_process(self, env: Dict, working_dir: Path) -> subprocess.Popen:
344
+ """Start agent process."""
345
+ agent_runner_log_file = self._open_agent_runner_log_file()
346
+ process = subprocess.Popen( # pylint: disable=consider-using-with,subprocess-popen-preexec-fn # nosec
347
+ args=[
348
+ self._agent_runner_bin,
349
+ "-s",
350
+ "run",
351
+ ],
352
+ cwd=working_dir / "agent",
353
+ stdout=agent_runner_log_file,
354
+ stderr=agent_runner_log_file,
355
+ env=env,
356
+ preexec_fn=os.setpgrp,
357
+ )
358
+ return process
359
+
360
+ def _start_tendermint_process(
361
+ self, env: Dict, working_dir: Path
362
+ ) -> subprocess.Popen:
363
+ """Start tendermint process."""
364
+ env = {
365
+ **env,
366
+ }
367
+ env["PATH"] = os.path.dirname(sys.executable) + ":" + os.environ["PATH"]
368
+
369
+ process = subprocess.Popen( # pylint: disable=consider-using-with,subprocess-popen-preexec-fn # nosec
370
+ args=[self._tendermint_bin],
371
+ cwd=working_dir,
372
+ stdout=subprocess.DEVNULL,
373
+ stderr=subprocess.DEVNULL,
374
+ env=env,
375
+ preexec_fn=os.setpgrp, # pylint: disable=subprocess-popen-preexec-fn # nosec
376
+ )
377
+ return process
378
+
356
379
 
357
380
  class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner):
358
381
  """Windows deployment runner."""
359
382
 
383
+ def __init__(self, work_directory: Path) -> None:
384
+ """Init the runner."""
385
+ super().__init__(work_directory)
386
+ self._job = self.set_windows_object_job()
387
+
388
+ @staticmethod
389
+ def set_windows_object_job() -> Any:
390
+ """Set windows job object to handle sub processes."""
391
+ from ctypes import ( # type: ignore # pylint:disable=import-outside-toplevel,reimported
392
+ wintypes,
393
+ )
394
+
395
+ kernel32 = ctypes.windll.kernel32 # type: ignore
396
+
397
+ class JOBOBJECT_BASIC_LIMIT_INFORMATION(
398
+ ctypes.Structure
399
+ ): # pylint: disable=missing-class-docstring
400
+ _fields_ = [
401
+ ("PerProcessUserTimeLimit", wintypes.LARGE_INTEGER),
402
+ ("PerJobUserTimeLimit", wintypes.LARGE_INTEGER),
403
+ ("LimitFlags", wintypes.DWORD),
404
+ ("MinimumWorkingSetSize", ctypes.c_size_t),
405
+ ("MaximumWorkingSetSize", ctypes.c_size_t),
406
+ ("ActiveProcessLimit", wintypes.DWORD),
407
+ ("Affinity", ctypes.POINTER(wintypes.ULONG)),
408
+ ("PriorityClass", wintypes.DWORD),
409
+ ("SchedulingClass", wintypes.DWORD),
410
+ ]
411
+
412
+ class IO_COUNTERS(ctypes.Structure): # pylint: disable=missing-class-docstring
413
+ _fields_ = [
414
+ ("ReadOperationCount", ctypes.c_ulonglong),
415
+ ("WriteOperationCount", ctypes.c_ulonglong),
416
+ ("OtherOperationCount", ctypes.c_ulonglong),
417
+ ("ReadTransferCount", ctypes.c_ulonglong),
418
+ ("WriteTransferCount", ctypes.c_ulonglong),
419
+ ("OtherTransferCount", ctypes.c_ulonglong),
420
+ ]
421
+
422
+ class JOBOBJECT_EXTENDED_LIMIT_INFORMATION(
423
+ ctypes.Structure
424
+ ): # pylint: disable=missing-class-docstring
425
+ _fields_ = [
426
+ ("BasicLimitInformation", JOBOBJECT_BASIC_LIMIT_INFORMATION),
427
+ ("IoInfo", IO_COUNTERS),
428
+ ("ProcessMemoryLimit", ctypes.c_size_t),
429
+ ("JobMemoryLimit", ctypes.c_size_t),
430
+ ("PeakProcessMemoryUsed", ctypes.c_size_t),
431
+ ("PeakJobMemoryUsed", ctypes.c_size_t),
432
+ ]
433
+
434
+ # Создаем Job Object
435
+ job = kernel32.CreateJobObjectW(None, None)
436
+ if not job:
437
+ raise ctypes.WinError() # type: ignore
438
+
439
+ # Настраиваем автоматическое завершение процессов при закрытии Job
440
+ info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION()
441
+ info.BasicLimitInformation.LimitFlags = (
442
+ 0x2000 # JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
443
+ )
444
+
445
+ if not kernel32.SetInformationJobObject(
446
+ job,
447
+ 9, # JobObjectExtendedLimitInformation
448
+ ctypes.byref(info),
449
+ ctypes.sizeof(info),
450
+ ):
451
+ kernel32.CloseHandle(job)
452
+ raise ctypes.WinError() # type: ignore
453
+
454
+ return job
455
+
456
+ def assign_to_job(self, pid: int) -> None:
457
+ """Windows-only: привязывает процесс к Job Object."""
458
+ ctypes.windll.kernel32.AssignProcessToJobObject(self._job, pid) # type: ignore
459
+
360
460
  @property
361
461
  def _tendermint_bin(self) -> str:
362
462
  """Return tendermint path."""
363
463
  return str(Path(os.path.dirname(sys.executable)) / "tendermint_win.exe") # type: ignore # pylint: disable=protected-access
364
464
 
465
+ def _start_agent_process(self, env: Dict, working_dir: Path) -> subprocess.Popen:
466
+ """Start agent process."""
467
+ agent_runner_log_file = self._open_agent_runner_log_file()
468
+ process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
469
+ args=[
470
+ self._agent_runner_bin,
471
+ "-s",
472
+ "run",
473
+ ], # TODO: Patch for Windows failing hash
474
+ cwd=working_dir / "agent",
475
+ stdout=agent_runner_log_file,
476
+ stderr=agent_runner_log_file,
477
+ env=env,
478
+ creationflags=0x00000200, # Detach process from the main process
479
+ )
480
+ self.assign_to_job(process._handle) # type: ignore # pylint: disable=protected-access
481
+ return process
482
+
483
+ def _start_tendermint_process(
484
+ self, env: Dict, working_dir: Path
485
+ ) -> subprocess.Popen:
486
+ """Start tendermint process."""
487
+ env = {
488
+ **env,
489
+ }
490
+ env["PATH"] = os.path.dirname(sys.executable) + ";" + os.environ["PATH"]
491
+
492
+ process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
493
+ args=[self._tendermint_bin],
494
+ cwd=working_dir,
495
+ stdout=subprocess.DEVNULL,
496
+ stderr=subprocess.DEVNULL,
497
+ env=env,
498
+ creationflags=0x00000200, # Detach process from the main process
499
+ )
500
+ self.assign_to_job(process._handle) # type: ignore # pylint: disable=protected-access
501
+ return process
502
+
365
503
 
366
504
  class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
367
505
  """Deployment runner for host installed python."""
@@ -377,6 +515,7 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
377
515
  env = json.loads((working_dir / "agent.json").read_text(encoding="utf-8"))
378
516
  env["PYTHONUTF8"] = "1"
379
517
  env["PYTHONIOENCODING"] = "utf8"
518
+ agent_runner_log_file = self._open_agent_runner_log_file()
380
519
 
381
520
  process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
382
521
  args=[
@@ -386,6 +525,8 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
386
525
  ], # TODO: Patch for Windows failing hash
387
526
  cwd=str(working_dir / "agent"),
388
527
  env={**os.environ, **env},
528
+ stdout=agent_runner_log_file,
529
+ stderr=agent_runner_log_file,
389
530
  creationflags=(
390
531
  0x00000008 if platform.system() == "Windows" else 0
391
532
  ), # Detach process from the main process
@@ -453,15 +594,19 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
453
594
 
454
595
  def _setup_agent(self) -> None:
455
596
  """Prepare agent."""
597
+ multiprocessing.set_start_method("spawn")
456
598
  self._setup_venv()
457
599
  super()._setup_agent()
458
600
  # Install agent dependencies
459
- self._run_aea_command(
460
- "-v",
461
- "debug",
462
- "install",
463
- "--timeout",
464
- "600",
601
+ self._run_cmd(
602
+ args=[
603
+ self._agent_runner_bin,
604
+ "-v",
605
+ "debug",
606
+ "install",
607
+ "--timeout",
608
+ "600",
609
+ ],
465
610
  cwd=self._work_directory / "agent",
466
611
  )
467
612
 
@@ -106,6 +106,9 @@ HTTP_OK = 200
106
106
  URI_HASH_POSITION = 7
107
107
  IPFS_GATEWAY = "https://gateway.autonolas.tech/ipfs/"
108
108
  DEFAULT_TOPUP_THRESHOLD = 0.5
109
+ # At the moment, we only support running one agent per service locally on a machine.
110
+ # If multiple agents are provided in the service.yaml file, only the 0th index config will be used.
111
+ NUM_LOCAL_AGENT_INSTANCES = 1
109
112
 
110
113
 
111
114
  class ServiceManager:
@@ -227,7 +230,7 @@ class ServiceManager:
227
230
  if not service.keys:
228
231
  service.keys = [
229
232
  self.keys_manager.get(self.keys_manager.create())
230
- for _ in range(service.helper.config.number_of_agents)
233
+ for _ in range(NUM_LOCAL_AGENT_INSTANCES)
231
234
  ]
232
235
  service.store()
233
236
 
@@ -267,7 +270,7 @@ class ServiceManager:
267
270
  if not service.keys:
268
271
  service.keys = [
269
272
  self.keys_manager.get(self.keys_manager.create())
270
- for _ in range(service.helper.config.number_of_agents)
273
+ for _ in range(NUM_LOCAL_AGENT_INSTANCES)
271
274
  ]
272
275
  service.store()
273
276
 
@@ -437,7 +440,7 @@ class ServiceManager:
437
440
  ocm.mint(
438
441
  package_path=service.package_absolute_path_absolute_path,
439
442
  agent_id=staking_params["agent_ids"][0],
440
- number_of_slots=service.helper.config.number_of_agents,
443
+ number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
441
444
  cost_of_bond=(
442
445
  staking_params["min_staking_deposit"]
443
446
  if user_params.use_staking
@@ -709,7 +712,7 @@ class ServiceManager:
709
712
  )
710
713
  protocol_asset_requirements[target_staking_params["staking_token"]] = (
711
714
  target_staking_params["min_staking_deposit"]
712
- * service.helper.config.number_of_agents
715
+ * NUM_LOCAL_AGENT_INSTANCES
713
716
  )
714
717
  else:
715
718
  protocol_asset_requirements = {}
@@ -791,7 +794,7 @@ class ServiceManager:
791
794
  sftxb.get_mint_tx_data(
792
795
  package_path=service.package_absolute_path,
793
796
  agent_id=agent_id,
794
- number_of_slots=service.helper.config.number_of_agents,
797
+ number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
795
798
  cost_of_bond=(
796
799
  target_staking_params["min_staking_deposit"]
797
800
  if user_params.use_staking
@@ -841,7 +844,7 @@ class ServiceManager:
841
844
  sftxb.get_mint_tx_data(
842
845
  package_path=service.package_absolute_path,
843
846
  agent_id=agent_id,
844
- number_of_slots=service.helper.config.number_of_agents,
847
+ number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
845
848
  cost_of_bond=(
846
849
  target_staking_params["min_staking_deposit"]
847
850
  if user_params.use_staking
@@ -2462,6 +2465,14 @@ class ServiceManager:
2462
2465
  ).call()
2463
2466
  agent_bonds += num_agent_instances * agent_bond
2464
2467
 
2468
+ if service_state == OnChainState.TERMINATED_BONDED:
2469
+ num_agent_instances = service_info[5]
2470
+ token_bond = service_registry_token_utility.functions.getOperatorBalance(
2471
+ master_safe,
2472
+ service_id,
2473
+ ).call()
2474
+ agent_bonds += num_agent_instances * token_bond
2475
+
2465
2476
  security_deposit = 0
2466
2477
  if (
2467
2478
  OnChainState.ACTIVE_REGISTRATION
@@ -2496,7 +2507,7 @@ class ServiceManager:
2496
2507
  chain_config = service.chain_configs[chain]
2497
2508
  user_params = chain_config.chain_data.user_params
2498
2509
  ledger_config = chain_config.ledger_config
2499
- number_of_agents = service.helper.config.number_of_agents
2510
+ number_of_agents = NUM_LOCAL_AGENT_INSTANCES
2500
2511
  os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
2501
2512
  sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
2502
2513
  service_asset_requirements: defaultdict = defaultdict(int)
@@ -315,13 +315,24 @@ class HostDeploymentGenerator(BaseDeploymentGenerator):
315
315
  tendermint_executable = str(
316
316
  shutil.which("tendermint"),
317
317
  )
318
+ env = {}
319
+ env["PATH"] = os.path.dirname(sys.executable) + ":" + os.environ["PATH"]
318
320
  tendermint_executable = str(
319
321
  Path(os.path.dirname(sys.executable)) / "tendermint"
320
322
  )
323
+
321
324
  if platform.system() == "Windows":
325
+ env["PATH"] = os.path.dirname(sys.executable) + ";" + os.environ["PATH"]
322
326
  tendermint_executable = str(
323
327
  Path(os.path.dirname(sys.executable)) / "tendermint.exe"
324
328
  )
329
+
330
+ if not (getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")):
331
+ # we dont run inside pyinstaller, mean DEV mode!
332
+ tendermint_executable = "tendermint"
333
+ if platform.system() == "Windows":
334
+ tendermint_executable = "tendermint.exe"
335
+
325
336
  subprocess.run( # pylint: disable=subprocess-run-check # nosec
326
337
  args=[
327
338
  tendermint_executable,
@@ -1246,6 +1257,8 @@ class Service(LocalResource):
1246
1257
  config # type: ignore
1247
1258
  )
1248
1259
 
1260
+ self.chain_configs[chain].ledger_config.rpc = config["rpc"]
1261
+
1249
1262
  self.store()
1250
1263
 
1251
1264
  def consume_env_variables(self) -> None:
@@ -26,7 +26,7 @@ from aea_ledger_ethereum import Web3
26
26
 
27
27
  from operate.constants import MECH_MARKETPLACE_JSON_URL
28
28
  from operate.operate_types import Chain
29
- from operate.quickstart.utils import print_section, unit_to_wei
29
+ from operate.quickstart.utils import print_section
30
30
  from operate.services.protocol import EthSafeTxBuilder
31
31
  from operate.services.service import Service
32
32
  from operate.utils.gnosis import SafeOperation
@@ -74,9 +74,11 @@ def deploy_mech(sftxb: EthSafeTxBuilder, service: Service) -> Tuple[str, str]:
74
74
  mech_type
75
75
  ]
76
76
 
77
- # 0.01xDAI hardcoded for price
78
- # better to be configurable and part of local config
79
- mech_request_price = unit_to_wei(0.01)
77
+ mech_request_price = int(
78
+ service.env_variables.get("MECH_REQUEST_PRICE", {}).get(
79
+ "value", 10000000000000000
80
+ )
81
+ )
80
82
  contract = sftxb.ledger_api.api.eth.contract(
81
83
  address=Web3.to_checksum_address(mech_marketplace_address), abi=abi
82
84
  )
@@ -1,23 +0,0 @@
1
- name: service_staking_token
2
- author: valory
3
- version: 0.1.0
4
- type: contract
5
- description: Service staking token contract
6
- license: Apache-2.0
7
- aea_version: '>=1.0.0, <2.0.0'
8
- fingerprint:
9
- __init__.py: bafybeid3wfzglolebuo6jrrsopswzu4lk77bm76mvw3euizlsjtnt3wmgu
10
- build/ServiceStakingToken.json: bafybeiclsckyk2kkuwhrmifupugg7ixj2hpwhd3mjghwgcdlrjqbwafwym
11
- contract.py: bafybeiff62bquzeisbd6iptqdjetrhlkt2ut5d7j6md2kqinrqgmbveceu
12
- fingerprint_ignore_patterns: []
13
- contracts: []
14
- class_name: ServiceStakingTokenContract
15
- contract_interface_paths:
16
- ethereum: build/ServiceStakingToken.json
17
- dependencies:
18
- open-aea-ledger-ethereum:
19
- version: ==1.42.0
20
- open-aea-test-autonomy:
21
- version: ==0.13.6
22
- web3:
23
- version: <7,>=6.0.0