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.
@@ -23,7 +23,6 @@ import asyncio
23
23
  import json
24
24
  import logging
25
25
  import os
26
- import time
27
26
  import traceback
28
27
  import typing as t
29
28
  from collections import Counter, defaultdict
@@ -34,8 +33,7 @@ from pathlib import Path
34
33
 
35
34
  import requests
36
35
  from aea.helpers.base import IPFSHash
37
- from aea.helpers.logging import setup_logger
38
- from aea_ledger_ethereum import EthereumCrypto, LedgerApi
36
+ from aea_ledger_ethereum import LedgerApi
39
37
  from autonomy.chain.base import registry_contracts
40
38
  from autonomy.chain.config import CHAIN_PROFILES, ChainType
41
39
  from autonomy.chain.metadata import IPFS_URI_PREFIX
@@ -47,7 +45,7 @@ from operate.data.contracts.requester_activity_checker.contract import (
47
45
  RequesterActivityCheckerContract,
48
46
  )
49
47
  from operate.data.contracts.staking_token.contract import StakingTokenContract
50
- from operate.keys import Key, KeysManager
48
+ from operate.keys import KeysManager
51
49
  from operate.ledger import PUBLIC_RPCS, get_currency_denom
52
50
  from operate.ledger.profiles import (
53
51
  CONTRACTS,
@@ -76,6 +74,7 @@ from operate.services.service import (
76
74
  NON_EXISTENT_TOKEN,
77
75
  OnChainData,
78
76
  SERVICE_CONFIG_PREFIX,
77
+ SERVICE_CONFIG_VERSION,
79
78
  Service,
80
79
  )
81
80
  from operate.services.utils.mech import deploy_mech
@@ -98,9 +97,8 @@ class ServiceManager:
98
97
  def __init__(
99
98
  self,
100
99
  path: Path,
101
- keys_manager: KeysManager,
102
100
  wallet_manager: MasterWalletManager,
103
- logger: t.Optional[logging.Logger] = None,
101
+ logger: logging.Logger,
104
102
  skip_dependency_check: t.Optional[bool] = False,
105
103
  ) -> None:
106
104
  """
@@ -112,43 +110,66 @@ class ServiceManager:
112
110
  :param logger: logging.Logger object.
113
111
  """
114
112
  self.path = path
115
- self.keys_manager = keys_manager
113
+ self.keys_manager = KeysManager()
116
114
  self.wallet_manager = wallet_manager
117
- self.logger = logger or setup_logger(name="operate.manager")
115
+ self.logger = logger
118
116
  self.skip_depencency_check = skip_dependency_check
119
117
 
120
118
  def setup(self) -> None:
121
119
  """Setup service manager."""
122
120
  self.path.mkdir(exist_ok=True)
123
121
 
124
- def _get_all_services(self) -> t.List[Service]:
122
+ def get_all_service_ids(self) -> t.List[str]:
123
+ """
124
+ Get all service ids.
125
+
126
+ :return: List of service ids.
127
+ """
128
+ return [
129
+ path.name
130
+ for path in self.path.iterdir()
131
+ if path.is_dir() and path.name.startswith(SERVICE_CONFIG_PREFIX)
132
+ ]
133
+
134
+ def get_all_services(self) -> t.Tuple[t.List[Service], bool]:
135
+ """Get all services."""
125
136
  services = []
137
+ success = True
126
138
  for path in self.path.iterdir():
127
139
  if not path.name.startswith(SERVICE_CONFIG_PREFIX):
128
140
  continue
129
141
  try:
130
142
  service = Service.load(path=path)
143
+ if service.version != SERVICE_CONFIG_VERSION:
144
+ self.logger.warning(
145
+ f"Service {path.name} has an unsupported version: {service.version}."
146
+ )
147
+ success = False
148
+ continue
149
+
131
150
  services.append(service)
132
- except ValueError as e:
133
- raise e
134
151
  except Exception as e: # pylint: disable=broad-except
135
152
  self.logger.error(
136
153
  f"Failed to load service: {path.name}. Exception {e}: {traceback.format_exc()}"
137
154
  )
138
- # Rename the invalid path
139
- timestamp = int(time.time())
140
- invalid_path = path.parent / f"invalid_{timestamp}_{path.name}"
141
- os.rename(path, invalid_path)
142
- self.logger.info(
143
- f"Renamed invalid service: {path.name} to {invalid_path.name}"
144
- )
155
+ success = False
156
+
157
+ return services, success
145
158
 
146
- return services
159
+ def validate_services(self) -> bool:
160
+ """
161
+ Validate all services.
162
+
163
+ :return: True if all services are valid, False otherwise.
164
+ """
165
+ _, success = self.get_all_services()
166
+ return success
147
167
 
148
168
  @property
149
169
  def json(self) -> t.List[t.Dict]:
150
170
  """Returns the list of available services."""
151
- return [service.json for service in self._get_all_services()]
171
+ services, _ = self.get_all_services()
172
+ return [service.json for service in services]
152
173
 
153
174
  def exists(self, service_config_id: str) -> bool:
154
175
  """Check if service exists."""
@@ -176,14 +197,14 @@ class ServiceManager:
176
197
  self,
177
198
  hash: str,
178
199
  service_template: t.Optional[ServiceTemplate] = None,
179
- keys: t.Optional[t.List[Key]] = None,
200
+ agent_addresses: t.Optional[t.List[str]] = None,
180
201
  ) -> Service:
181
202
  """
182
203
  Create or load a service
183
204
 
184
205
  :param hash: Service hash
185
206
  :param service_template: Service template
186
- :param keys: Keys
207
+ :param agent_addresses: Agents' addresses to be used for the service.
187
208
  :return: Service instance
188
209
  """
189
210
  path = self.path / hash
@@ -202,21 +223,10 @@ class ServiceManager:
202
223
  "'service_template' cannot be None when creating a new service"
203
224
  )
204
225
 
205
- service = Service.new(
206
- keys=keys or [],
207
- storage=self.path,
208
- service_template=service_template,
226
+ return self.create(
227
+ service_template=service_template, agent_addresses=agent_addresses
209
228
  )
210
229
 
211
- if not service.keys:
212
- service.keys = [
213
- self.keys_manager.get(self.keys_manager.create())
214
- for _ in range(NUM_LOCAL_AGENT_INSTANCES)
215
- ]
216
- service.store()
217
-
218
- return service
219
-
220
230
  def load(
221
231
  self,
222
232
  service_config_id: str,
@@ -233,25 +243,24 @@ class ServiceManager:
233
243
  def create(
234
244
  self,
235
245
  service_template: ServiceTemplate,
236
- keys: t.Optional[t.List[Key]] = None,
246
+ agent_addresses: t.Optional[t.List[str]] = None,
237
247
  ) -> Service:
238
248
  """
239
249
  Create a service
240
250
 
241
251
  :param service_template: Service template
242
- :param keys: Keys
252
+ :param agent_addresses: Agents' addresses to be used for the service.
243
253
  :return: Service instance
244
254
  """
245
255
  service = Service.new(
246
- keys=keys or [],
256
+ agent_addresses=agent_addresses or [],
247
257
  storage=self.path,
248
258
  service_template=service_template,
249
259
  )
250
260
 
251
- if not service.keys:
252
- service.keys = [
253
- self.keys_manager.get(self.keys_manager.create())
254
- for _ in range(NUM_LOCAL_AGENT_INSTANCES)
261
+ if not service.agent_addresses:
262
+ service.agent_addresses = [
263
+ self.keys_manager.create() for _ in range(NUM_LOCAL_AGENT_INSTANCES)
255
264
  ]
256
265
  service.store()
257
266
 
@@ -314,8 +323,6 @@ class ServiceManager:
314
323
  ledger_config = chain_config.ledger_config
315
324
  chain_data = chain_config.chain_data
316
325
  user_params = chain_config.chain_data.user_params
317
- keys = service.keys
318
- instances = [key.address for key in keys]
319
326
  ocm = self.get_on_chain_manager(ledger_config=ledger_config)
320
327
 
321
328
  # TODO fix this
@@ -427,7 +434,7 @@ class ServiceManager:
427
434
  if user_params.use_staking
428
435
  else user_params.cost_of_bond
429
436
  ),
430
- threshold=user_params.threshold,
437
+ threshold=len(service.agent_addresses),
431
438
  nft=IPFSHash(user_params.nft),
432
439
  update_token=chain_data.token if is_update else None,
433
440
  token=(
@@ -459,8 +466,8 @@ class ServiceManager:
459
466
  agent_id = staking_params["agent_ids"][0]
460
467
  ocm.register(
461
468
  service_id=chain_data.token,
462
- instances=instances,
463
- agents=[agent_id for _ in instances],
469
+ instances=service.agent_addresses,
470
+ agents=[agent_id for _ in service.agent_addresses],
464
471
  token=(OLAS[ledger_config.chain] if user_params.use_staking else None),
465
472
  )
466
473
  on_chain_state = OnChainState.FINISHED_REGISTRATION
@@ -610,8 +617,6 @@ class ServiceManager:
610
617
  ledger_config = chain_config.ledger_config
611
618
  chain_data = chain_config.chain_data
612
619
  user_params = chain_config.chain_data.user_params
613
- keys = service.keys
614
- instances = [key.address for key in keys]
615
620
  wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
616
621
  sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
617
622
  safe = wallet.safes[Chain(chain)]
@@ -719,11 +724,14 @@ class ServiceManager:
719
724
  }
720
725
  )
721
726
 
722
- # TODO: yet another agent specific logic for memeooorr and optimus, which should be abstracted
723
- if "memeooorr" in service.name.lower() or "optimus" in service.name.lower():
724
- store_path = service.path / "persistent_data"
725
- store_path.mkdir(parents=True, exist_ok=True)
726
- env_var_to_value.update({"STORE_PATH": os.path.join(str(store_path), "")})
727
+ # Set environment variables for the service
728
+ for dir_name, env_var_name in (
729
+ ("persistent_data", "STORE_PATH"),
730
+ ("benchmarks", "LOG_DIR"),
731
+ ):
732
+ dir_path = service.path / dir_name
733
+ dir_path.mkdir(parents=True, exist_ok=True)
734
+ env_var_to_value.update({env_var_name: str(dir_path)})
727
735
 
728
736
  service.update_env_variables_values(env_var_to_value)
729
737
 
@@ -834,7 +842,7 @@ class ServiceManager:
834
842
  if user_params.use_staking
835
843
  else user_params.cost_of_bond
836
844
  ),
837
- threshold=user_params.threshold,
845
+ threshold=len(service.agent_addresses),
838
846
  nft=IPFSHash(user_params.nft),
839
847
  update_token=chain_data.token,
840
848
  token=(
@@ -884,7 +892,7 @@ class ServiceManager:
884
892
  if user_params.use_staking
885
893
  else user_params.cost_of_bond
886
894
  ),
887
- threshold=user_params.threshold,
895
+ threshold=len(service.agent_addresses),
888
896
  nft=IPFSHash(user_params.nft),
889
897
  update_token=None,
890
898
  token=(
@@ -1013,10 +1021,10 @@ class ServiceManager:
1013
1021
  self.logger.info(
1014
1022
  f"Approved {token_utility_allowance} OLAS from {safe} to {token_utility}"
1015
1023
  )
1016
- cost_of_bond = 1 * len(instances)
1024
+ cost_of_bond = 1 * len(service.agent_addresses)
1017
1025
 
1018
1026
  self.logger.info(
1019
- f"Registering agent instances: {chain_data.token} -> {instances}"
1027
+ f"Registering agent instances: {chain_data.token} -> {service.agent_addresses}"
1020
1028
  )
1021
1029
 
1022
1030
  native_balance = get_asset_balance(
@@ -1033,8 +1041,8 @@ class ServiceManager:
1033
1041
  sftxb.new_tx().add(
1034
1042
  sftxb.get_register_instances_data(
1035
1043
  service_id=chain_data.token,
1036
- instances=instances,
1037
- agents=[agent_id for _ in instances],
1044
+ instances=service.agent_addresses,
1045
+ agents=[agent_id for _ in service.agent_addresses],
1038
1046
  cost_of_bond=cost_of_bond,
1039
1047
  )
1040
1048
  ).settle()
@@ -1179,8 +1187,6 @@ class ServiceManager:
1179
1187
  chain_config = service.chain_configs[chain]
1180
1188
  ledger_config = chain_config.ledger_config
1181
1189
  chain_data = chain_config.chain_data
1182
- keys = service.keys
1183
- instances = [key.address for key in keys]
1184
1190
  wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
1185
1191
  safe = wallet.safes[Chain(chain)] # type: ignore
1186
1192
 
@@ -1245,7 +1251,7 @@ class ServiceManager:
1245
1251
  # Swap service safe
1246
1252
  current_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
1247
1253
  counter_current_safe_owners = Counter(s.lower() for s in current_safe_owners)
1248
- counter_instances = Counter(s.lower() for s in instances)
1254
+ counter_instances = Counter(s.lower() for s in service.agent_addresses)
1249
1255
 
1250
1256
  if withdrawal_address is not None:
1251
1257
  # we don't drain signer yet, because the owner swapping tx may need to happen
@@ -1290,16 +1296,15 @@ class ServiceManager:
1290
1296
  ) # noqa: E800
1291
1297
 
1292
1298
  if withdrawal_address is not None:
1299
+ ethereum_crypto = KeysManager().get_crypto_instance(
1300
+ service.agent_addresses[0]
1301
+ )
1293
1302
  # drain all native tokens from service signer key
1294
1303
  drain_eoa(
1295
1304
  ledger_api=self.wallet_manager.load(
1296
1305
  ledger_config.chain.ledger_type
1297
1306
  ).ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc),
1298
- crypto=EthereumCrypto(
1299
- private_key_path=service.path
1300
- / "deployment"
1301
- / "ethereum_private_key.txt",
1302
- ),
1307
+ crypto=ethereum_crypto,
1303
1308
  withdrawal_address=withdrawal_address,
1304
1309
  chain_id=ledger_config.chain.id,
1305
1310
  )
@@ -1775,11 +1780,11 @@ class ServiceManager:
1775
1780
  on_chain_state = self._get_on_chain_state(service=service, chain=chain)
1776
1781
  if on_chain_state != OnChainState.DEPLOYED:
1777
1782
  if chain_data.user_params.use_staking:
1778
- on_chain_operations_buffer = 1 + len(service.keys)
1783
+ on_chain_operations_buffer = 1 + len(service.agent_addresses)
1779
1784
  else:
1780
1785
  on_chain_operations_buffer = (
1781
1786
  chain_data.user_params.cost_of_bond
1782
- * (1 + len(service.keys))
1787
+ * (1 + len(service.agent_addresses))
1783
1788
  )
1784
1789
 
1785
1790
  asset_funding_values = (
@@ -1793,21 +1798,21 @@ class ServiceManager:
1793
1798
  else fund_requirements.agent
1794
1799
  )
1795
1800
 
1796
- for key in service.keys:
1801
+ for agent_address in service.agent_addresses:
1797
1802
  agent_balance = get_asset_balance(
1798
1803
  ledger_api=ledger_api,
1799
1804
  asset_address=asset_address,
1800
- address=key.address,
1805
+ address=agent_address,
1801
1806
  )
1802
1807
  self.logger.info(
1803
- f"[FUNDING_JOB] Agent {key.address} Asset: {asset_address} balance: {agent_balance}"
1808
+ f"[FUNDING_JOB] Agent {agent_address} Asset: {asset_address} balance: {agent_balance}"
1804
1809
  )
1805
1810
  if agent_fund_threshold > 0:
1806
1811
  self.logger.info(
1807
1812
  f"[FUNDING_JOB] Required balance: {agent_fund_threshold}"
1808
1813
  )
1809
1814
  if agent_balance < agent_fund_threshold:
1810
- self.logger.info(f"[FUNDING_JOB] Funding agent {key.address}")
1815
+ self.logger.info(f"[FUNDING_JOB] Funding agent {agent_address}")
1811
1816
  target_balance = (
1812
1817
  asset_funding_values["agent"]["topup"]
1813
1818
  if asset_funding_values is not None
@@ -1825,11 +1830,11 @@ class ServiceManager:
1825
1830
  min(available_balance, target_balance - agent_balance), 0
1826
1831
  )
1827
1832
  self.logger.info(
1828
- f"[FUNDING_JOB] Transferring {to_transfer} units (asset {asset_address}) to agent {key.address}"
1833
+ f"[FUNDING_JOB] Transferring {to_transfer} units (asset {asset_address}) to agent {agent_address}"
1829
1834
  )
1830
1835
  wallet.transfer_asset(
1831
1836
  asset=asset_address,
1832
- to=key.address,
1837
+ to=agent_address,
1833
1838
  amount=int(to_transfer),
1834
1839
  chain=ledger_config.chain,
1835
1840
  from_safe=from_safe,
@@ -1931,9 +1936,9 @@ class ServiceManager:
1931
1936
  or chain_data.user_params.fund_requirements[ZERO_ADDRESS].agent
1932
1937
  )
1933
1938
 
1934
- for key in service.keys:
1935
- agent_balance = ledger_api.get_balance(address=key.address)
1936
- self.logger.info(f"Agent {key.address} balance: {agent_balance}")
1939
+ for agent_address in service.agent_addresses:
1940
+ agent_balance = ledger_api.get_balance(address=agent_address)
1941
+ self.logger.info(f"Agent {agent_address} balance: {agent_balance}")
1937
1942
  self.logger.info(f"Required balance: {agent_fund_threshold}")
1938
1943
  if agent_balance < agent_fund_threshold:
1939
1944
  self.logger.info("Funding agents")
@@ -1941,10 +1946,10 @@ class ServiceManager:
1941
1946
  agent_topup
1942
1947
  or chain_data.user_params.fund_requirements[ZERO_ADDRESS].agent
1943
1948
  )
1944
- self.logger.info(f"Transferring {to_transfer} units to {key.address}")
1949
+ self.logger.info(f"Transferring {to_transfer} units to {agent_address}")
1945
1950
  wallet.transfer_erc20(
1946
1951
  token=token,
1947
- to=key.address,
1952
+ to=agent_address,
1948
1953
  amount=int(to_transfer),
1949
1954
  chain=ledger_config.chain,
1950
1955
  from_safe=from_safe,
@@ -1979,7 +1984,7 @@ class ServiceManager:
1979
1984
  rpc=rpc or ledger_config.rpc,
1980
1985
  )
1981
1986
 
1982
- def drain_service_safe(
1987
+ def drain_service_safe( # pylint: disable=too-many-locals
1983
1988
  self,
1984
1989
  service_config_id: str,
1985
1990
  withdrawal_address: str,
@@ -1995,9 +2000,7 @@ class ServiceManager:
1995
2000
  chain_data = chain_config.chain_data
1996
2001
  wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
1997
2002
  ledger_api = wallet.ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc)
1998
- ethereum_crypto = EthereumCrypto(
1999
- private_key_path=service.path / "deployment" / "ethereum_private_key.txt",
2000
- )
2003
+ ethereum_crypto = KeysManager().get_crypto_instance(service.agent_addresses[0])
2001
2004
 
2002
2005
  # drain ERC20 tokens from service safe
2003
2006
  for token_name, token_address in (
@@ -2171,40 +2174,6 @@ class ServiceManager:
2171
2174
  )
2172
2175
  return service
2173
2176
 
2174
- def migrate_service_configs(self) -> None:
2175
- """Migrate old service config formats to new ones, if applies."""
2176
-
2177
- bafybei_count = sum(
2178
- 1 for path in self.path.iterdir() if path.name.startswith("bafybei")
2179
- )
2180
- if bafybei_count > 1:
2181
- self.log_directories()
2182
- raise RuntimeError(
2183
- f"Your services folder contains {bafybei_count} folders starting with 'bafybei'. This is an unintended situation. Please contact support."
2184
- )
2185
-
2186
- paths = list(self.path.iterdir())
2187
- for path in paths:
2188
- try:
2189
- if path.name.startswith(SERVICE_CONFIG_PREFIX) or path.name.startswith(
2190
- "bafybei"
2191
- ):
2192
- self.logger.info(f"migrate_service_configs {str(path)}")
2193
- migrated = Service.migrate_format(path)
2194
- if migrated:
2195
- self.logger.info(f"Folder {str(path)} has been migrated.")
2196
- except Exception as e: # pylint: disable=broad-except
2197
- self.logger.error(
2198
- f"Failed to migrate service: {path.name}. Exception {e}: {traceback.format_exc()}"
2199
- )
2200
- # Rename the invalid path
2201
- timestamp = int(time.time())
2202
- invalid_path = path.parent / f"invalid_{timestamp}_{path.name}"
2203
- os.rename(path, invalid_path)
2204
- self.logger.info(
2205
- f"Renamed invalid service: {path.name} to {invalid_path.name}"
2206
- )
2207
-
2208
2177
  def refill_requirements( # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks
2209
2178
  self, service_config_id: str
2210
2179
  ) -> t.Dict:
@@ -2232,11 +2201,9 @@ class ServiceManager:
2232
2201
  master_safe_exists = wallet.safes.get(Chain(chain)) is not None
2233
2202
  master_safe = wallet.safes.get(Chain(chain), "master_safe")
2234
2203
 
2235
- agent_addresses = {key.address for key in service.keys}
2204
+ agent_addresses = set(service.agent_addresses)
2236
2205
  service_safe = (
2237
- chain_data.multisig
2238
- if chain_data.multisig and chain_data.multisig != NON_EXISTENT_MULTISIG
2239
- else "service_safe"
2206
+ chain_data.multisig if chain_data.multisig else "service_safe"
2240
2207
  )
2241
2208
 
2242
2209
  if not master_safe_exists: