olas-operate-middleware 0.9.0__py3-none-any.whl → 0.10.0__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.9.0
3
+ Version: 0.10.0
4
4
  Summary:
5
5
  Author: David Vilela
6
6
  Author-email: dvilelaf@gmail.com
@@ -1,13 +1,13 @@
1
1
  operate/__init__.py,sha256=ZQhHXOo_1L9Q5Ub_2FrZ-vu3BvORBrKgK8QcJyAsYa4,870
2
2
  operate/account/__init__.py,sha256=suJ_vBMO7hLCvLYe3MVDtLXTNDd6P03og7bvUN7fZsE,804
3
3
  operate/account/user.py,sha256=y7DqqDpqgHjbVmnfL_cN0Me_JWl3Dh6GSVt2-9FdRVw,3044
4
- operate/bridge/bridge_manager.py,sha256=n6dZoG-mtegES_EqVraSW1LHhPPZ1Idhm2v7lrBMbJo,17236
4
+ operate/bridge/bridge_manager.py,sha256=sUfhB1pZvBrF-kBfDOfFVoNeesCe7wd4_YahUF3FZDU,17119
5
5
  operate/bridge/providers/lifi_provider.py,sha256=FpAlBAA_gOt-oOHKhGaOQhhTZIL-hgYYo4IIw1FN7mo,14153
6
- operate/bridge/providers/native_bridge_provider.py,sha256=ZTLBdh9ttPWpX8IYvHyVU5y3pg1B1DG_vQ9DHVquDfw,24666
7
- operate/bridge/providers/provider.py,sha256=YGvTqp2wNgw4E35Gov8vidwbNcoaCmEOuFn20FK-QgI,19809
6
+ operate/bridge/providers/native_bridge_provider.py,sha256=gG8bSyxUoAVEF6_J9tn1qKRv1PnXuMJdMwUAVJ4GJz8,24647
7
+ operate/bridge/providers/provider.py,sha256=i54RL7m4wMSADM_D_V_quQump_ipPTmByUc-c-AOLPQ,19687
8
8
  operate/bridge/providers/relay_provider.py,sha256=qfFiD655qZ9MK7duwOTRYCVTv1mD8u-XbqmcJMlm_ls,16748
9
- operate/cli.py,sha256=H6Y3ewfE_96WyV8ldXJ9C9r1LrbXEUNyVVSZFptwHzY,47886
10
- operate/constants.py,sha256=CWH2pw8YzCbl44HzbvKSaAAWgNB2-5yUgUP1VmlyNm8,2546
9
+ operate/cli.py,sha256=EalpBA15TXX5i3tD-2KIQOxkFwEOqOEiGMbjh36cOHw,51168
10
+ operate/constants.py,sha256=syGWEVyw07zK50gPS4OSSwyAbt9R6P8IttDimDt9vCY,2746
11
11
  operate/data/README.md,sha256=jGPyZTvg2LCGdllvmYxmFMkkkiXb6YWatbqIkcX3kv4,879
12
12
  operate/data/__init__.py,sha256=ttC51Yqk9c4ehpIgs1Qbe7aJvzkrbbdZ1ClaCxJYByE,864
13
13
  operate/data/contracts/__init__.py,sha256=_th54_WvL0ibGy-b6St0Ne9DX-fyjsh-tNOKDn-cWrg,809
@@ -53,41 +53,41 @@ operate/data/contracts/uniswap_v2_erc20/contract.py,sha256=MwBks4QmZ3XouMT_TqWLn
53
53
  operate/data/contracts/uniswap_v2_erc20/contract.yaml,sha256=XUdz-XtKtmZgLfItbO8usP-QPbtUkAxKGn0hL7OftAg,741
54
54
  operate/data/contracts/uniswap_v2_erc20/tests/__init__.py,sha256=3Arw8dsCsJz6hVOl0t9UjFASHXbV9yp3hw6x4HqgXpU,847
55
55
  operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py,sha256=FzZbw9OTcr_yvjOXpk9YcO-K40eyDARyybcfSHDg2Ps,13392
56
- operate/keys.py,sha256=soJfdXJvHo8ytWH6ShIndjUpkShPg3KzirLs7MoFe8I,3764
56
+ operate/keys.py,sha256=y8QMQcKOqvL62egbJyX6FgFgnxdT3tbxHrRVCmWXJMI,4371
57
57
  operate/ledger/__init__.py,sha256=ksyctDd5PU_SToN9e-_N9fAap9ZNCHw48j5hHep-erA,3353
58
- operate/ledger/profiles.py,sha256=Mvz2D-qoXmLzM0lBmPJGLmyNfrtXPdg_QjUeVfxLRBA,12358
59
- operate/migration.py,sha256=yeNI6OZwuvJcnqdRTrRNMutyo9mj2gb0vRW0hTV9EFU,3659
58
+ operate/ledger/profiles.py,sha256=fQ9vqnXCIBZa3VHZKIvrJ-Jhn_SckFUcPC94JM5brQ8,12343
59
+ operate/migration.py,sha256=YIJ9P8CdW2IsMm4gW9GdAJ7EKMnUfUMUB5TItiQ0dH8,15323
60
60
  operate/operate_http/__init__.py,sha256=dxCIVSUos23M4R-PFZZG6k5QrOlEiK0SxhCYSFNxh7U,4711
61
61
  operate/operate_http/exceptions.py,sha256=4UFzrn-GyDD71RhkaOyFPBynL6TrrtP3eywaaU3o4fc,1339
62
- operate/operate_types.py,sha256=x4OHOGNFCRctTdGsq7WS_d5CzQ5Lfg75qHkfgaUOivc,7895
62
+ operate/operate_types.py,sha256=DmQBVu-WPYGrrRwKU30QmF99JheFQo666GxP5ObHgAw,8033
63
63
  operate/pearl.py,sha256=yrTpSXLu_ML3qT-uNxq3kScOyo31JyxBujiSMfMUbcg,1690
64
- operate/quickstart/analyse_logs.py,sha256=ss9BdhNs2Yo_A5NUuXpfki_j-u53YklxidBecfDQJ1k,4305
65
- operate/quickstart/claim_staking_rewards.py,sha256=i9vwUGFiFBlPMZHsRYbXj-lkf4fUpBotLQuFcQAPGyI,4135
66
- operate/quickstart/reset_configs.py,sha256=t-gVt0_-Th-LADfVmjxxKXjE2if5w-MuEscGFyMw2u0,3450
67
- operate/quickstart/reset_password.py,sha256=_HHnwX032WKNja5koegQIy4By0rFXGcdQRgVlusf5GA,2644
68
- operate/quickstart/reset_staking.py,sha256=yb293RdnVRWvFlglaG_DnMCBMY0lPKMzhpOdbXKjdvY,5157
69
- operate/quickstart/run_service.py,sha256=16mw9oUR5Vx-Q5jwLvxs3eyl9fGrWqf1mOX8dwI9u30,27334
70
- operate/quickstart/stop_service.py,sha256=V-0htRKo_P8zdI7Vk2FJEWmez8GDLbyAHKF1hREr_BI,2145
71
- operate/quickstart/terminate_on_chain_service.py,sha256=X96p-0dCS-cDEduQnIboOLWaSjdvDU2ec_21nNkxJSk,3023
64
+ operate/quickstart/analyse_logs.py,sha256=K11AWWevkddUIUzTe75J3fYVS6aLfi6kT_dAI9OjrX8,4195
65
+ operate/quickstart/claim_staking_rewards.py,sha256=AqfLMRef2YijQtWPaTuGwX2sOItNEkoyoi6Q9SICp_I,4026
66
+ operate/quickstart/reset_configs.py,sha256=ipPpbYyB9gQ4KOKS-xBrRi8fT5LvwctSkQi-8XiUMig,3341
67
+ operate/quickstart/reset_password.py,sha256=p_gNmhWD4hb-QXUAiQRalPtVTVyB5TEPhs7GnScYzqs,2535
68
+ operate/quickstart/reset_staking.py,sha256=GLGUzSz6H_5lUXb9j9jFrgni_vtDXnWpPKK53favXbg,5077
69
+ operate/quickstart/run_service.py,sha256=fKeHtEiRmOg5wt3skKvKpxHhO-u-fpoF7_5YUnAM9Sg,27224
70
+ operate/quickstart/stop_service.py,sha256=gyLgPNG6IkI6eyRkIVWEVo9p15hLJyZouFPEVCK2t6I,2036
71
+ operate/quickstart/terminate_on_chain_service.py,sha256=5ENU8_mkj06i80lKUX-v1QbLU0YzKeOZDUL1e_jzySE,2914
72
72
  operate/quickstart/utils.py,sha256=rmd9e7whQIsYpRKqWBEQxMA_SHrivBg6DppFY5ECtQQ,9135
73
73
  operate/resource.py,sha256=E59oIVqf6B6nN4LTmf_o2iCgLFAogTLPjm_cK6kMVxg,6305
74
74
  operate/services/__init__.py,sha256=isrThS-Ccu5Sc15JZgkN4uTAVaSg-NwUUSDeTyJEqLk,855
75
75
  operate/services/agent_runner.py,sha256=6tJePUJmlRxlIugT2fDaCJHSrQlDnl1t9pbg3-7EmCQ,7560
76
- operate/services/deployment_runner.py,sha256=eP1bnT3PdkYtPxi-4sZ6-Wopz8u88NkeZx7vOB2vkzc,22217
77
- operate/services/health_checker.py,sha256=bMmEHOUpVgKea9zocOBQdzw154XGztZ-hWB_8HkQoCI,9689
78
- operate/services/manage.py,sha256=uc9hA3GQ5_Uny6yUv_2jaVYhV4SwqT2YrkOl4h7XNNc,109537
76
+ operate/services/deployment_runner.py,sha256=Su73o7cdH6fkQfj468K77J04a_TWiokJwbMyVQ25xko,27067
77
+ operate/services/health_checker.py,sha256=2KSEDxG3YmGolUDU--648ny0UJpTAAKvxkcr_VZQv-I,9654
78
+ operate/services/manage.py,sha256=qaLLwAhksZXvc1Jbbmej9jkF7ObSAGJsIWaPhgfSUoM,108048
79
79
  operate/services/protocol.py,sha256=RQssnJyjHc0k1CyZCj3jxHueyJyS3nmFYw4dVQaKXzA,60157
80
- operate/services/service.py,sha256=hwc_gTqMig5C-9lNI2EwjNdp0BASTCmb466VhTmJlQg,48809
80
+ operate/services/service.py,sha256=ltKox10YC1VINGqdlVqy2nPXkR7XCHoysSGKMngPTSU,39669
81
81
  operate/services/utils/__init__.py,sha256=TvioaZ1mfTRUSCtrQoLNAp4WMVXyqEJqFJM4PxSQCRU,24
82
82
  operate/services/utils/mech.py,sha256=W2x4dqodivNKXjWU-Brp40QhoUHsIMyNAO7-caMoR0Q,3821
83
83
  operate/services/utils/tendermint.py,sha256=3h9nDb2Z89T0RwUr_AaVjqtymQmsu3u6DAVCfL_k1U0,25591
84
- operate/utils/__init__.py,sha256=cFNP2XFpjJmDLskN0SzAk5FPdqaeN2Jn4MyVbFHmH2M,3075
85
- operate/utils/gnosis.py,sha256=OxWq5zMW0fuShLZS6JAOlgB_TEsNvsZECa6EfzC4_SY,17678
84
+ operate/utils/__init__.py,sha256=DZNUgg0V9yfNfDrUynp10PErSieJkoxU0AKvsEFIhAw,4670
85
+ operate/utils/gnosis.py,sha256=CS07ZqvrO7uelkFe09VMyPBcLzKONUI1ZqFvi41BDhc,17924
86
86
  operate/utils/ssl.py,sha256=O5DrDoZD4T4qQuHP8GLwWUVxQ-1qXeefGp6uDJiF2lM,4308
87
87
  operate/wallet/__init__.py,sha256=NGiozD3XhvkBi7_FaOWQ8x1thZPK4uGpokJaeDY_o2w,813
88
- operate/wallet/master.py,sha256=lKtTHi278UWuATGSM3c2VdWHUEYg8pmvw7VzxGZ8u9I,31003
89
- olas_operate_middleware-0.9.0.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
90
- olas_operate_middleware-0.9.0.dist-info/METADATA,sha256=sFzHUFX9CCvAfzVtbZi00_IWsF-MxVQS5xPuhIcZmeQ,2033
91
- olas_operate_middleware-0.9.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
92
- olas_operate_middleware-0.9.0.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
93
- olas_operate_middleware-0.9.0.dist-info/RECORD,,
88
+ operate/wallet/master.py,sha256=MGXynIV0LQU3n28AceK7RlaxAtfsVowsXzisesG4iJM,30760
89
+ olas_operate_middleware-0.10.0.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
90
+ olas_operate_middleware-0.10.0.dist-info/METADATA,sha256=eIO5TslMIXDpTPEgB4cux27ty1LZsfgvL2-c-In_T14,2034
91
+ olas_operate_middleware-0.10.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
92
+ olas_operate_middleware-0.10.0.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
93
+ olas_operate_middleware-0.10.0.dist-info/RECORD,,
@@ -29,7 +29,6 @@ from dataclasses import dataclass
29
29
  from pathlib import Path
30
30
  from typing import cast
31
31
 
32
- from aea.helpers.logging import setup_logger
33
32
  from deepdiff import DeepDiff
34
33
  from web3 import Web3
35
34
 
@@ -187,13 +186,13 @@ class BridgeManager:
187
186
  self,
188
187
  path: Path,
189
188
  wallet_manager: MasterWalletManager,
190
- logger: t.Optional[logging.Logger] = None,
189
+ logger: logging.Logger,
191
190
  quote_validity_period: int = DEFAULT_BUNDLE_VALIDITY_PERIOD,
192
191
  ) -> None:
193
192
  """Initialize bridge manager."""
194
193
  self.path = path
195
194
  self.wallet_manager = wallet_manager
196
- self.logger = logger or setup_logger(name="operate.bridge.BridgeManager")
195
+ self.logger = logger
197
196
  self.quote_validity_period = quote_validity_period
198
197
  self.path.mkdir(exist_ok=True)
199
198
  (self.path / EXECUTED_BUNDLES_PATH).mkdir(exist_ok=True)
@@ -417,7 +417,7 @@ class NativeBridgeProvider(Provider):
417
417
  bridge_contract_adaptor: BridgeContractAdaptor,
418
418
  provider_id: str,
419
419
  wallet_manager: MasterWalletManager,
420
- logger: t.Optional[logging.Logger] = None,
420
+ logger: logging.Logger,
421
421
  ) -> None:
422
422
  """Initialize the provider."""
423
423
  self.bridge_contract_adaptor = bridge_contract_adaptor
@@ -31,7 +31,6 @@ from dataclasses import dataclass
31
31
  from math import ceil
32
32
 
33
33
  from aea.crypto.base import LedgerApi
34
- from aea.helpers.logging import setup_logger
35
34
  from autonomy.chain.tx import TxSettler
36
35
  from web3 import Web3
37
36
  from web3.middleware import geth_poa_middleware
@@ -145,12 +144,12 @@ class Provider(ABC):
145
144
  self,
146
145
  wallet_manager: MasterWalletManager,
147
146
  provider_id: str,
148
- logger: t.Optional[logging.Logger] = None,
147
+ logger: logging.Logger,
149
148
  ) -> None:
150
149
  """Initialize the provider."""
151
150
  self.wallet_manager = wallet_manager
152
151
  self.provider_id = provider_id
153
- self.logger = logger or setup_logger(name="operate.bridge.providers.Provider")
152
+ self.logger = logger
154
153
 
155
154
  def description(self) -> str:
156
155
  """Get a human-readable description of the provider."""
operate/cli.py CHANGED
@@ -28,10 +28,13 @@ import traceback
28
28
  import typing as t
29
29
  import uuid
30
30
  from concurrent.futures import ThreadPoolExecutor
31
+ from contextlib import asynccontextmanager, suppress
31
32
  from http import HTTPStatus
32
33
  from pathlib import Path
33
34
  from types import FrameType
34
35
 
36
+ import psutil
37
+ import requests
35
38
  from aea.helpers.logging import setup_logger
36
39
  from clea import group, params, run
37
40
  from compose.project import ProjectError
@@ -68,6 +71,7 @@ from operate.quickstart.reset_staking import reset_staking
68
71
  from operate.quickstart.run_service import run_service
69
72
  from operate.quickstart.stop_service import stop_service
70
73
  from operate.quickstart.terminate_on_chain_service import terminate_service
74
+ from operate.services.deployment_runner import stop_deployment_manager
71
75
  from operate.services.health_checker import HealthChecker
72
76
  from operate.utils import subtract_dicts
73
77
  from operate.utils.gnosis import get_assets_balances
@@ -82,6 +86,7 @@ ACCOUNT_NOT_FOUND_ERROR = JSONResponse(
82
86
  content={"error": "User account not found."},
83
87
  status_code=HTTPStatus.NOT_FOUND,
84
88
  )
89
+ TRY_TO_SHUTDOWN_PREVIOUS_INSTANCE = True
85
90
 
86
91
 
87
92
  def service_not_found_error(service_config_id: str) -> JSONResponse:
@@ -108,7 +113,7 @@ class OperateApp:
108
113
  self.setup()
109
114
 
110
115
  self.logger = logger or setup_logger(name="operate")
111
- self.keys_manager = services.manage.KeysManager(
116
+ services.manage.KeysManager(
112
117
  path=self._keys,
113
118
  logger=self.logger,
114
119
  )
@@ -116,7 +121,8 @@ class OperateApp:
116
121
 
117
122
  mm = MigrationManager(self._path, self.logger)
118
123
  mm.migrate_user_account()
119
- mm.migrate_wallets()
124
+ mm.migrate_services(self.service_manager())
125
+ mm.migrate_wallets(self.wallet_manager)
120
126
  mm.migrate_qs_configs()
121
127
 
122
128
  def create_user_account(self, password: str) -> UserAccount:
@@ -164,7 +170,6 @@ class OperateApp:
164
170
  """Load service manager."""
165
171
  return services.manage.ServiceManager(
166
172
  path=self._services,
167
- keys_manager=self.keys_manager,
168
173
  wallet_manager=self.wallet_manager,
169
174
  logger=self.logger,
170
175
  skip_dependency_check=skip_dependency_check,
@@ -185,6 +190,7 @@ class OperateApp:
185
190
  manager = MasterWalletManager(
186
191
  path=self._path / "wallets",
187
192
  password=self.password,
193
+ logger=self.logger,
188
194
  )
189
195
  manager.setup()
190
196
  return manager
@@ -195,6 +201,7 @@ class OperateApp:
195
201
  manager = BridgeManager(
196
202
  path=self._path / "bridge",
197
203
  wallet_manager=self.wallet_manager,
204
+ logger=self.logger,
198
205
  )
199
206
  return manager
200
207
 
@@ -230,26 +237,16 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
230
237
  logger.warning("Healthchecker is off!!!")
231
238
  operate = OperateApp(home=home, logger=logger)
232
239
 
233
- operate.service_manager().log_directories()
234
- logger.info("Migrating service configs...")
235
- operate.service_manager().migrate_service_configs()
236
- logger.info("Migrating service configs done.")
237
- operate.service_manager().log_directories()
238
-
239
- logger.info("Migrating wallet configs...")
240
- operate.wallet_manager.migrate_wallet_configs()
241
- logger.info("Migrating wallet configs done.")
242
-
243
240
  funding_jobs: t.Dict[str, asyncio.Task] = {}
244
241
  health_checker = HealthChecker(
245
- operate.service_manager(), number_of_fails=number_of_fails
242
+ operate.service_manager(), number_of_fails=number_of_fails, logger=logger
246
243
  )
247
244
  # Create shutdown endpoint
248
245
  shutdown_endpoint = uuid.uuid4().hex
249
246
  (operate._path / "operate.kill").write_text( # pylint: disable=protected-access
250
247
  shutdown_endpoint
251
248
  )
252
- thread_pool_executor = ThreadPoolExecutor()
249
+ thread_pool_executor = ThreadPoolExecutor(max_workers=12)
253
250
 
254
251
  async def run_in_executor(fn: t.Callable, *args: t.Any) -> t.Any:
255
252
  loop = asyncio.get_event_loop()
@@ -301,6 +298,12 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
301
298
  logger.info("Stopping services on startup done.")
302
299
 
303
300
  def pause_all_services() -> None:
301
+ service_manager = operate.service_manager()
302
+ if not service_manager.validate_services():
303
+ logger.error(
304
+ "Some services are not valid. Only pausing the valid services."
305
+ )
306
+
304
307
  service_config_ids = [
305
308
  i["service_config_id"] for i in operate.service_manager().json
306
309
  ]
@@ -319,7 +322,12 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
319
322
  if deployment.status == DeploymentStatus.DELETED:
320
323
  continue
321
324
  logger.info(f"stopping service {service_config_id}")
322
- deployment.stop(force=True)
325
+ try:
326
+ deployment.stop(force=True)
327
+ except Exception: # pylint: disable=broad-except
328
+ logger.exception(
329
+ f"Deployment {service_config_id} stopping failed. but continue"
330
+ )
323
331
  logger.info(f"Cancelling funding job for {service_config_id}")
324
332
  cancel_funding_job(service_config_id=service_config_id)
325
333
  health_checker.stop_for_service(service_config_id=service_config_id)
@@ -338,7 +346,51 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
338
346
  # stop all services at middleware exit
339
347
  atexit.register(pause_all_services)
340
348
 
341
- app = FastAPI()
349
+ @asynccontextmanager
350
+ async def lifespan(app: FastAPI):
351
+ # Load the ML model
352
+ watchdog_task = set_parent_watchdog(app)
353
+ yield
354
+ # Clean up the ML models and release the resources
355
+
356
+ with suppress(Exception):
357
+ watchdog_task.cancel()
358
+
359
+ with suppress(Exception):
360
+ await watchdog_task
361
+
362
+ app = FastAPI(lifespan=lifespan)
363
+
364
+ def set_parent_watchdog(app):
365
+ async def stop_app():
366
+ logger.info("Stopping services on demand...")
367
+
368
+ stop_deployment_manager() # TODO: make it async?
369
+ await run_in_executor(pause_all_services)
370
+
371
+ logger.info("Stopping services on demand done.")
372
+ app._server.should_exit = True # pylint: disable=protected-access
373
+ logger.info("Stopping app.")
374
+
375
+ async def check_parent_alive():
376
+ try:
377
+ logger.info(
378
+ f"Parent alive check task started: ppid is {os.getppid()} and own pid is {os.getpid()}"
379
+ )
380
+ while True:
381
+ parent = psutil.Process(os.getpid()).parent()
382
+ if not parent:
383
+ logger.info("Parent is not alive, going to stop")
384
+ await stop_app()
385
+ return
386
+ await asyncio.sleep(3)
387
+
388
+ except Exception: # pylint: disable=broad-except
389
+ logger.exception("Parent alive check crashed!")
390
+
391
+ loop = asyncio.get_running_loop()
392
+ task = loop.create_task(check_parent_alive())
393
+ return task
342
394
 
343
395
  app.add_middleware(
344
396
  CORSMiddleware,
@@ -382,16 +434,17 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
382
434
  async def _kill_server(request: Request) -> JSONResponse:
383
435
  """Kill backend server from inside."""
384
436
  os.kill(os.getpid(), signal.SIGINT)
437
+ return JSONResponse(content={})
385
438
 
386
439
  @app.get("/shutdown")
387
440
  async def _shutdown(request: Request) -> JSONResponse:
388
441
  """Kill backend server from inside."""
389
442
  logger.info("Stopping services on demand...")
390
- pause_all_services()
443
+ await run_in_executor(pause_all_services)
391
444
  logger.info("Stopping services on demand done.")
392
445
  app._server.should_exit = True # pylint: disable=protected-access
393
446
  await asyncio.sleep(0.3)
394
- return {"stopped": True}
447
+ return JSONResponse(content={"stopped": True})
395
448
 
396
449
  @app.get("/api")
397
450
  @with_retries
@@ -777,6 +830,21 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
777
830
  """Get all services."""
778
831
  return JSONResponse(content=operate.service_manager().json)
779
832
 
833
+ @app.get("/api/v2/services/validate")
834
+ @with_retries
835
+ async def _validate_services(request: Request) -> JSONResponse:
836
+ """Validate all services."""
837
+ service_manager = operate.service_manager()
838
+ service_ids = service_manager.get_all_service_ids()
839
+ _services = [
840
+ service.service_config_id
841
+ for service in service_manager.get_all_services()[0]
842
+ ]
843
+
844
+ return JSONResponse(
845
+ content={service_id: service_id in _services for service_id in service_ids}
846
+ )
847
+
780
848
  @app.get("/api/v2/service/{service_config_id}")
781
849
  @with_retries
782
850
  async def _get_service(request: Request) -> JSONResponse:
@@ -954,38 +1022,40 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
954
1022
  service = service_manager.load(service_config_id=service_config_id)
955
1023
 
956
1024
  # terminate the service on chain
957
- for chain in service.chain_configs:
1025
+ for chain, chain_config in service.chain_configs.items():
958
1026
  service_manager.terminate_service_on_chain_from_safe(
959
1027
  service_config_id=service_config_id,
960
1028
  chain=chain,
961
1029
  withdrawal_address=withdrawal_address,
962
1030
  )
963
1031
 
964
- # drain the master safe and master signer for the home chain
965
- chain = Chain(service.home_chain)
966
- master_wallet = service_manager.wallet_manager.load(
967
- ledger_type=chain.ledger_type
968
- )
1032
+ # drain the master safe and master signer for the home chain
1033
+ chain = Chain(service.home_chain)
1034
+ master_wallet = service_manager.wallet_manager.load(
1035
+ ledger_type=chain.ledger_type
1036
+ )
969
1037
 
970
- # drain the master safe
971
- logger.info(
972
- f"Draining the Master Safe {master_wallet.safes[chain]} on chain {chain.value} (withdrawal address {withdrawal_address})."
973
- )
974
- master_wallet.drain(
975
- withdrawal_address=withdrawal_address,
976
- chain=chain,
977
- from_safe=True,
978
- )
1038
+ # drain the master safe
1039
+ logger.info(
1040
+ f"Draining the Master Safe {master_wallet.safes[chain]} on chain {chain.value} (withdrawal address {withdrawal_address})."
1041
+ )
1042
+ master_wallet.drain(
1043
+ withdrawal_address=withdrawal_address,
1044
+ chain=chain,
1045
+ from_safe=True,
1046
+ rpc=chain_config.ledger_config.rpc,
1047
+ )
979
1048
 
980
- # drain the master signer
981
- logger.info(
982
- f"Draining the Master Signer {master_wallet.address} on chain {chain.value} (withdrawal address {withdrawal_address})."
983
- )
984
- master_wallet.drain(
985
- withdrawal_address=withdrawal_address,
986
- chain=chain,
987
- from_safe=False,
988
- )
1049
+ # drain the master signer
1050
+ logger.info(
1051
+ f"Draining the Master Signer {master_wallet.address} on chain {chain.value} (withdrawal address {withdrawal_address})."
1052
+ )
1053
+ master_wallet.drain(
1054
+ withdrawal_address=withdrawal_address,
1055
+ chain=chain,
1056
+ from_safe=False,
1057
+ rpc=chain_config.ledger_config.rpc,
1058
+ )
989
1059
  except Exception as e: # pylint: disable=broad-except
990
1060
  logger.error(f"Withdrawal failed: {e}\n{traceback.format_exc()}")
991
1061
  return JSONResponse(
@@ -1136,6 +1206,16 @@ def _daemon(
1136
1206
  }
1137
1207
  )
1138
1208
 
1209
+ # try automatically shutdown previous instance
1210
+ if TRY_TO_SHUTDOWN_PREVIOUS_INSTANCE:
1211
+ url = f"http{'s' if ssl_keyfile and ssl_certfile else ''}://{host}:{port}/shutdown"
1212
+ logger.info(f"trying to stop previous instance with {url}")
1213
+ try:
1214
+ requests.get(url, timeout=3, verify=False) # nosec
1215
+ logger.info("previous instance stopped")
1216
+ except Exception: # pylint: disable=broad-except
1217
+ logger.exception("failed to stop previous instance. probably not running")
1218
+
1139
1219
  server = Server(Config(**config_kwargs))
1140
1220
  app._server = server # pylint: disable=protected-access
1141
1221
  server.run()
operate/constants.py CHANGED
@@ -48,3 +48,8 @@ MECH_ACTIVITY_CHECKER_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/a
48
48
  SERVICE_REGISTRY_TOKEN_UTILITY_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/open-autonomy/refs/tags/v0.18.4/packages/valory/contracts/service_registry_token_utility/build/ServiceRegistryTokenUtility.json" # nosec
49
49
  MECH_AGENT_FACTORY_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/ai-registry-mech/main/abis/0.8.25/AgentFactory.json"
50
50
  MECH_MARKETPLACE_JSON_URL = "https://raw.githubusercontent.com/valory-xyz/mech-quickstart/refs/heads/main/contracts/MechMarketplace.json"
51
+ NO_STAKING_PROGRAM_ID = "no_staking"
52
+
53
+
54
+ DEPLOYMENT_START_TRIES_NUM = 3
55
+ IPFS_CHECK_URL = "https://gateway.autonolas.tech/ipfs/bafybeigcllaxn4ycjjvika3zd6eicksuriez2wtg67gx7pamhcazl3tv54/echo/README.md"
operate/keys.py CHANGED
@@ -20,18 +20,18 @@
20
20
  """Keys manager."""
21
21
 
22
22
  import json
23
- import logging
24
23
  import os
25
24
  import shutil
26
- import typing as t
25
+ import tempfile
27
26
  from dataclasses import dataclass
28
27
  from pathlib import Path
28
+ from typing import Any
29
29
 
30
- from aea.helpers.logging import setup_logger
31
30
  from aea_ledger_ethereum.ethereum import EthereumCrypto
32
31
 
33
32
  from operate.operate_types import LedgerType
34
33
  from operate.resource import LocalResource
34
+ from operate.utils import SingletonMeta
35
35
 
36
36
 
37
37
  @dataclass
@@ -48,25 +48,21 @@ class Key(LocalResource):
48
48
  return super().load(path) # type: ignore
49
49
 
50
50
 
51
- Keys = t.List[Key]
52
-
53
-
54
- class KeysManager:
51
+ class KeysManager(metaclass=SingletonMeta):
55
52
  """Keys manager."""
56
53
 
57
- def __init__(
58
- self,
59
- path: Path,
60
- logger: t.Optional[logging.Logger] = None,
61
- ) -> None:
54
+ def __init__(self, **kwargs: Any) -> None:
62
55
  """
63
56
  Initialize keys manager
64
57
 
65
58
  :param path: Path to keys storage.
66
59
  :param logger: logging.Logger object.
67
60
  """
68
- self.path = path
69
- self.logger = logger or setup_logger(name="operate.keys")
61
+ if "path" not in kwargs:
62
+ raise ValueError("Path must be provided for KeysManager")
63
+
64
+ self.path = kwargs["path"]
65
+ self.logger = kwargs["logger"]
70
66
 
71
67
  def setup(self) -> None:
72
68
  """Setup service manager."""
@@ -83,6 +79,22 @@ class KeysManager:
83
79
  )
84
80
  )
85
81
 
82
+ def get_crypto_instance(self, address: str) -> EthereumCrypto:
83
+ """Get EthereumCrypto instance for the given address."""
84
+ key: Key = Key.from_json( # type: ignore
85
+ obj=json.loads(
86
+ (self.path / address).read_text(
87
+ encoding="utf-8",
88
+ )
89
+ )
90
+ )
91
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as temp_file:
92
+ temp_file.write(key.private_key)
93
+ temp_file.flush()
94
+ crypto = EthereumCrypto(private_key_path=temp_file.name)
95
+
96
+ return crypto
97
+
86
98
  def create(self) -> str:
87
99
  """Creates new key."""
88
100
  crypto = EthereumCrypto()
@@ -21,12 +21,10 @@
21
21
 
22
22
  import typing as t
23
23
 
24
- from operate.constants import ZERO_ADDRESS
24
+ from operate.constants import NO_STAKING_PROGRAM_ID, ZERO_ADDRESS
25
25
  from operate.operate_types import Chain, ContractAddresses
26
26
 
27
27
 
28
- NO_STAKING_PROGRAM_ID = "no_staking"
29
-
30
28
  CONTRACTS: t.Dict[Chain, ContractAddresses] = {
31
29
  Chain.GNOSIS: ContractAddresses(
32
30
  {