olas-operate-middleware 0.13.1__py3-none-any.whl → 0.13.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.
Files changed (42) hide show
  1. {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/METADATA +8 -27
  2. {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/RECORD +42 -42
  3. operate/bridge/providers/provider.py +23 -31
  4. operate/cli.py +5 -18
  5. operate/constants.py +1 -0
  6. operate/data/contracts/dual_staking_token/contract.py +3 -3
  7. operate/data/contracts/dual_staking_token/contract.yaml +2 -2
  8. operate/data/contracts/foreign_omnibridge/contract.yaml +1 -1
  9. operate/data/contracts/home_omnibridge/contract.py +2 -2
  10. operate/data/contracts/home_omnibridge/contract.yaml +2 -2
  11. operate/data/contracts/l1_standard_bridge/contract.yaml +1 -1
  12. operate/data/contracts/l2_standard_bridge/contract.py +4 -4
  13. operate/data/contracts/l2_standard_bridge/contract.yaml +2 -2
  14. operate/data/contracts/mech_activity/contract.yaml +1 -1
  15. operate/data/contracts/optimism_mintable_erc20/contract.yaml +1 -1
  16. operate/data/contracts/recovery_module/contract.yaml +1 -1
  17. operate/data/contracts/requester_activity_checker/contract.yaml +1 -1
  18. operate/data/contracts/staking_token/contract.py +3 -3
  19. operate/data/contracts/staking_token/contract.yaml +2 -2
  20. operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -3
  21. operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +5 -5
  22. operate/keys.py +5 -3
  23. operate/ledger/__init__.py +1 -7
  24. operate/ledger/profiles.py +0 -1
  25. operate/operate_http/__init__.py +0 -2
  26. operate/operate_types.py +3 -93
  27. operate/quickstart/run_service.py +63 -6
  28. operate/quickstart/utils.py +8 -4
  29. operate/resource.py +2 -2
  30. operate/services/agent_runner.py +3 -3
  31. operate/services/deployment_runner.py +107 -82
  32. operate/services/health_checker.py +38 -2
  33. operate/services/manage.py +14 -17
  34. operate/services/protocol.py +122 -141
  35. operate/services/utils/mech.py +3 -3
  36. operate/services/utils/tendermint.py +5 -3
  37. operate/utils/gnosis.py +76 -101
  38. operate/wallet/master.py +53 -50
  39. operate/wallet/wallet_recovery_manager.py +110 -56
  40. {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/WHEEL +0 -0
  41. {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/entry_points.txt +0 -0
  42. {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/licenses/LICENSE +0 -0
@@ -68,7 +68,7 @@ class TestUniswapV2ERC20Contract(BaseContractTestCase):
68
68
  approval_value = 100
69
69
  data = self.contract.get_instance(
70
70
  self.ledger_api, self.contract_address
71
- ).encodeABI(fn_name="approve", args=[spender_address, approval_value])
71
+ ).encode_abi(abi_element_identifier="approve", args=[spender_address, approval_value])
72
72
  with mock.patch.object(
73
73
  self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
74
74
  ):
@@ -102,7 +102,7 @@ class TestUniswapV2ERC20Contract(BaseContractTestCase):
102
102
  value = 100
103
103
  data = self.contract.get_instance(
104
104
  self.ledger_api, self.contract_address
105
- ).encodeABI(fn_name="transfer", args=[spender_address, value])
105
+ ).encode_abi(abi_element_identifier="transfer", args=[spender_address, value])
106
106
  with mock.patch.object(
107
107
  self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
108
108
  ):
@@ -137,7 +137,7 @@ class TestUniswapV2ERC20Contract(BaseContractTestCase):
137
137
  value = 100
138
138
  data = self.contract.get_instance(
139
139
  self.ledger_api, self.contract_address
140
- ).encodeABI(fn_name="transferFrom", args=[from_address, to_address, value])
140
+ ).encode_abi(abi_element_identifier="transferFrom", args=[from_address, to_address, value])
141
141
  with mock.patch.object(
142
142
  self.ledger_api.api.eth, "get_transaction_count", return_value=NONCE
143
143
  ):
@@ -177,8 +177,8 @@ class TestUniswapV2ERC20Contract(BaseContractTestCase):
177
177
  s = b""
178
178
  data = self.contract.get_instance(
179
179
  self.ledger_api, self.contract_address
180
- ).encodeABI(
181
- fn_name="permit",
180
+ ).encode_abi(
181
+ abi_element_identifier="permit",
182
182
  args=[owner_address, spender_address, value, deadline, v, r, s],
183
183
  )
184
184
  with mock.patch.object(
operate/keys.py CHANGED
@@ -144,9 +144,11 @@ class KeysManager:
144
144
  key = Key(
145
145
  ledger=LedgerType.ETHEREUM,
146
146
  address=crypto.address,
147
- private_key=crypto.encrypt(password=self.password)
148
- if self.password is not None
149
- else crypto.private_key,
147
+ private_key=(
148
+ crypto.encrypt(password=self.password)
149
+ if self.password is not None
150
+ else crypto.private_key
151
+ ),
150
152
  )
151
153
  for path in (
152
154
  self.path / f"{crypto.address}.bak",
@@ -27,7 +27,6 @@ from math import ceil
27
27
  from aea.crypto.base import LedgerApi
28
28
  from aea.crypto.registries import make_ledger_api
29
29
  from aea_ledger_ethereum import DEFAULT_GAS_PRICE_STRATEGIES, EIP1559, GWEI, to_wei
30
- from web3.middleware import geth_poa_middleware
31
30
 
32
31
  from operate.operate_types import Chain
33
32
 
@@ -133,12 +132,9 @@ def make_chain_ledger_api(
133
132
  address=rpc or get_default_rpc(chain=chain),
134
133
  chain_id=chain.id,
135
134
  gas_price_strategies=gas_price_strategies,
136
- poa_chain=chain == Chain.POLYGON,
135
+ poa_chain=chain in (Chain.OPTIMISM, Chain.POLYGON),
137
136
  )
138
137
 
139
- if chain == Chain.OPTIMISM:
140
- ledger_api.api.middleware_onion.inject(geth_poa_middleware, layer=0)
141
-
142
138
  return ledger_api
143
139
 
144
140
 
@@ -183,7 +179,6 @@ def update_tx_with_gas_pricing(tx: t.Dict, ledger_api: LedgerApi) -> None:
183
179
  # TODO This gas management should be done at a lower level in the library
184
180
  def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
185
181
  """Update transaction with gas estimate."""
186
- print(f"[LEDGER] Trying to update transaction gas {tx['from']=} {tx['gas']=}.")
187
182
  original_from = tx["from"]
188
183
  original_gas = tx.get("gas", 1)
189
184
 
@@ -192,7 +187,6 @@ def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
192
187
  tx["gas"] = 1
193
188
  ledger_api.update_with_gas_estimate(tx)
194
189
  if tx["gas"] > 1:
195
- print(f"[LEDGER] Gas estimated successfully {tx['from']=} {tx['gas']=}.")
196
190
  break
197
191
 
198
192
  tx["from"] = original_from
@@ -46,7 +46,6 @@ for _chain in CHAINS:
46
46
  "service_registry_token_utility": profile[
47
47
  "service_registry_token_utility"
48
48
  ],
49
- "service_manager": profile["service_manager_token"],
50
49
  "gnosis_safe_proxy_factory": profile["gnosis_safe_proxy_factory"],
51
50
  "gnosis_safe_same_address_multisig": profile[
52
51
  "gnosis_safe_same_address_multisig"
@@ -32,8 +32,6 @@ from starlette.types import Receive, Scope, Send
32
32
  from operate.operate_http.exceptions import NotAllowed, ResourceException
33
33
 
34
34
 
35
- # pylint: disable=no-self-use
36
-
37
35
  GenericResource = t.TypeVar("GenericResource")
38
36
  PostPayload = t.TypeVar("PostPayload")
39
37
  PostResponse = t.TypeVar("PostResponse")
operate/operate_types.py CHANGED
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
  import argon2
31
31
  from aea_ledger_ethereum import cast
32
32
  from autonomy.chain.config import ChainType
33
- from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
33
+ from autonomy.chain.config import LedgerType as LedgerTypeOA
34
34
  from cryptography.fernet import Fernet
35
35
  from typing_extensions import TypedDict
36
36
 
@@ -38,98 +38,8 @@ from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
38
38
  from operate.resource import LocalResource
39
39
 
40
40
 
41
- CHAIN_NAME_TO_CHAIN_ID["solana"] = 900
42
-
43
- _CHAIN_ID_TO_CHAIN_NAME = {
44
- chain_id: chain_name for chain_name, chain_id in CHAIN_NAME_TO_CHAIN_ID.items()
45
- }
46
-
47
-
48
- class LedgerType(str, enum.Enum):
49
- """Ledger type enum."""
50
-
51
- ETHEREUM = "ethereum"
52
- SOLANA = "solana"
53
-
54
- @property
55
- def config_file(self) -> str:
56
- """Config filename."""
57
- return f"{self.name.lower()}.json"
58
-
59
- @property
60
- def key_file(self) -> str:
61
- """Key filename."""
62
- return f"{self.name.lower()}.txt"
63
-
64
- @property
65
- def mnemonic_file(self) -> str:
66
- """Mnemonic filename."""
67
- return f"{self.name.lower()}.mnemonic.json"
68
-
69
- @classmethod
70
- def from_id(cls, chain_id: int) -> "LedgerType":
71
- """Load from chain ID."""
72
- return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id]).ledger_type
73
-
74
-
75
- # Dynamically create the Chain enum from the ChainType
76
- # TODO: Migrate this to open-autonomy and remove this modified version of Chain here and use the one from open-autonomy
77
- # This version of open-autonomy must support the LedgerType to support SOLANA in the future
78
- # If solana support is not fuly implemented, decide to keep this half-baked feature.
79
- #
80
- # TODO: Once the above issue is properly implemented in Open Autonomy, remove the following
81
- # lines from tox.ini:
82
- #
83
- # exclude = ^(operate/operate_types\.py|scripts/setup_wallet\.py|operate/ledger/profiles\.py|operate/ledger/__init__\.py|operate/wallet/master\.py|operate/services/protocol\.py|operate/services/manage\.py|operate/cli\.py)$
84
- #
85
- # [mypy-operate.*]
86
- # follow_imports = skip # noqa
87
- #
88
- # These lines were itroduced to resolve mypy issues with the temporary Chain/ChainType solution.
89
- Chain = enum.Enum(
90
- "Chain",
91
- [(member.name, member.value) for member in ChainType]
92
- + [
93
- ("SOLANA", "solana"),
94
- ],
95
- )
96
-
97
-
98
- class ChainMixin:
99
- """Mixin for some new functions in the ChainType class."""
100
-
101
- @property
102
- def id(self) -> t.Optional[int]:
103
- """Chain ID"""
104
- if self == Chain.CUSTOM:
105
- chain_id = os.environ.get("CUSTOM_CHAIN_ID")
106
- if chain_id is None:
107
- return None
108
- return int(chain_id)
109
- return CHAIN_NAME_TO_CHAIN_ID[self.value]
110
-
111
- @property
112
- def ledger_type(self) -> LedgerType:
113
- """Ledger type."""
114
- if self in (Chain.SOLANA,):
115
- return LedgerType.SOLANA
116
- return LedgerType.ETHEREUM
117
-
118
- @classmethod
119
- def from_string(cls, chain: str) -> "Chain":
120
- """Load from string."""
121
- return Chain(chain.lower())
122
-
123
- @classmethod
124
- def from_id(cls, chain_id: int) -> "Chain":
125
- """Load from chain ID."""
126
- return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id])
127
-
128
-
129
- # Add the ChainMixin methods to the Chain enum
130
- for name in dir(ChainMixin):
131
- if not name.startswith("__"):
132
- setattr(Chain, name, getattr(ChainMixin, name))
41
+ LedgerType = LedgerTypeOA
42
+ Chain = ChainType
133
43
 
134
44
 
135
45
  class DeploymentStatus(enum.IntEnum):
@@ -34,7 +34,12 @@ from halo import Halo # type: ignore[import]
34
34
  from web3.exceptions import Web3Exception
35
35
 
36
36
  from operate.account.user import UserAccount
37
- from operate.constants import IPFS_ADDRESS, NO_STAKING_PROGRAM_ID, USER_JSON
37
+ from operate.constants import (
38
+ DEFAULT_TIMEOUT,
39
+ IPFS_ADDRESS,
40
+ NO_STAKING_PROGRAM_ID,
41
+ USER_JSON,
42
+ )
38
43
  from operate.data import DATA_DIR
39
44
  from operate.data.contracts.staking_token.contract import StakingTokenContract
40
45
  from operate.ledger import DEFAULT_RPCS
@@ -49,6 +54,7 @@ from operate.quickstart.utils import (
49
54
  CHAIN_TO_METADATA,
50
55
  QuickstartConfig,
51
56
  ask_or_get_from_env,
57
+ ask_yes_or_no,
52
58
  check_rpc,
53
59
  print_box,
54
60
  print_section,
@@ -124,6 +130,42 @@ QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
124
130
  },
125
131
  }
126
132
 
133
+ DEPRECATED_QS_STAKING_PROGRAMS = {
134
+ "quickstart_beta_hobbyist",
135
+ "quickstart_beta_hobbyist_2",
136
+ "quickstart_beta_expert",
137
+ "quickstart_beta_expert_2",
138
+ "quickstart_beta_expert_3",
139
+ "quickstart_beta_expert_4",
140
+ "quickstart_beta_expert_5",
141
+ "quickstart_beta_expert_6",
142
+ "quickstart_beta_expert_7",
143
+ "quickstart_beta_expert_8",
144
+ "quickstart_beta_expert_9",
145
+ "quickstart_beta_expert_10",
146
+ "quickstart_beta_expert_11",
147
+ "quickstart_beta_expert_12",
148
+ "quickstart_beta_expert_15_mech_marketplace",
149
+ "quickstart_beta_expert_16_mech_marketplace",
150
+ "quickstart_beta_expert_17_mech_marketplace",
151
+ "quickstart_beta_expert_18_mech_marketplace",
152
+ }
153
+
154
+
155
+ def _deprecated_program_warning(program_id: str) -> bool:
156
+ """Confirm deprecated program warning."""
157
+ if program_id not in DEPRECATED_QS_STAKING_PROGRAMS:
158
+ return True
159
+
160
+ print_box(
161
+ """
162
+ WARNING
163
+ The selected staking program is deprecated.
164
+ Using it may prevent your agent from earning staking rewards.
165
+ """
166
+ )
167
+ return ask_yes_or_no("Do you want to proceed anyway?")
168
+
127
169
 
128
170
  def ask_confirm_password() -> str:
129
171
  """Ask for password confirmation."""
@@ -221,6 +263,7 @@ def configure_local_config(
221
263
  LedgerType.ETHEREUM.lower(),
222
264
  address=config.rpc[config.principal_chain], # type: ignore[index]
223
265
  chain_id=home_chain.id,
266
+ poa_chain=chain in (Chain.OPTIMISM.value, Chain.POLYGON.value),
224
267
  )
225
268
 
226
269
  if config.staking_program_id is None:
@@ -253,7 +296,7 @@ def configure_local_config(
253
296
  try:
254
297
  metadata_hash = instance.functions.metadataHash().call().hex()
255
298
  ipfs_address = IPFS_ADDRESS.format(hash=metadata_hash)
256
- response = requests.get(ipfs_address)
299
+ response = requests.get(ipfs_address, timeout=DEFAULT_TIMEOUT)
257
300
  if response.status_code != HTTPStatus.OK:
258
301
  raise requests.RequestException(
259
302
  f"Failed to fetch data from {ipfs_address}: {response.status_code}"
@@ -276,7 +319,10 @@ def configure_local_config(
276
319
  except Web3Exception:
277
320
  metadata["available_staking_slots"] = "?"
278
321
 
279
- name = metadata["name"]
322
+ deprecated_str = (
323
+ "[DEPRECATED] " if program_id in DEPRECATED_QS_STAKING_PROGRAMS else ""
324
+ )
325
+ name = deprecated_str + metadata["name"]
280
326
  description = metadata["description"]
281
327
  if "available_staking_slots" in metadata:
282
328
  available_slots_str = (
@@ -313,12 +359,23 @@ def configure_local_config(
313
359
  for idx, prog in available_choices.items():
314
360
  print(f"{idx}) {prog['name']} : {prog['slots']}")
315
361
  continue
362
+
363
+ if not _deprecated_program_warning(
364
+ available_choices[choice]["program_id"]
365
+ ):
366
+ continue
367
+
316
368
  selected_program = available_choices[choice]
317
369
  config.staking_program_id = selected_program["program_id"]
318
370
  print(f"Selected staking program: {selected_program['name']}")
319
371
  break
320
372
  except ValueError:
321
373
  if input_value in ids:
374
+ if not _deprecated_program_warning(
375
+ available_choices[choice]["program_id"]
376
+ ):
377
+ continue
378
+
322
379
  config.staking_program_id = input_value
323
380
  break
324
381
  else:
@@ -431,9 +488,9 @@ def configure_local_config(
431
488
 
432
489
  print()
433
490
 
434
- template["env_variables"][env_var_name][
435
- "value"
436
- ] = config.user_provided_args[env_var_name]
491
+ template["env_variables"][env_var_name]["value"] = (
492
+ config.user_provided_args[env_var_name]
493
+ )
437
494
 
438
495
  # TODO: Handle it in a more generic way
439
496
  if (
@@ -27,9 +27,10 @@ from pathlib import Path
27
27
  from typing import Dict, Optional, Union, get_args, get_origin
28
28
 
29
29
  import requests
30
- from halo import Halo # type: ignore[import] # pylint: disable=import-error
30
+ from halo import Halo
31
+ from web3.exceptions import Web3RPCError
31
32
 
32
- from operate.constants import ZERO_ADDRESS
33
+ from operate.constants import DEFAULT_TIMEOUT, ZERO_ADDRESS
33
34
  from operate.ledger.profiles import OLAS, USDC
34
35
  from operate.operate_types import Chain
35
36
  from operate.resource import LocalResource, deserialize
@@ -225,11 +226,14 @@ def check_rpc(chain: str, rpc_url: Optional[str] = None) -> bool:
225
226
 
226
227
  try:
227
228
  response = requests.post(
228
- rpc_url, json=rpc_data, headers={"Content-Type": "application/json"}
229
+ rpc_url,
230
+ json=rpc_data,
231
+ headers={"Content-Type": "application/json"},
232
+ timeout=DEFAULT_TIMEOUT,
229
233
  )
230
234
  response.raise_for_status()
231
235
  rpc_response = response.json()
232
- except (requests.exceptions.RequestException, ValueError, TypeError) as e:
236
+ except (requests.exceptions.RequestException, Web3RPCError, TypeError) as e:
233
237
  spinner.fail(f"Error: Failed to send {chain} RPC request: {e}")
234
238
  return False
235
239
 
operate/resource.py CHANGED
@@ -40,7 +40,7 @@ N_BACKUPS = 5
40
40
 
41
41
  def serialize(obj: t.Any) -> t.Any:
42
42
  """Serialize object."""
43
- if is_dataclass(obj):
43
+ if is_dataclass(obj) and not isinstance(obj, type):
44
44
  return serialize(asdict(obj))
45
45
  if isinstance(obj, Path):
46
46
  return str(obj)
@@ -88,7 +88,7 @@ def deserialize(obj: t.Any, otype: t.Any) -> t.Any:
88
88
  return otype(obj)
89
89
  if otype is Path:
90
90
  return Path(obj)
91
- if is_dataclass(otype):
91
+ if is_dataclass(otype) and hasattr(otype, "from_json"):
92
92
  return otype.from_json(obj)
93
93
  if otype is bytes:
94
94
  return bytes.fromhex(obj)
@@ -33,7 +33,7 @@ import requests
33
33
  from aea.configurations.data_types import PublicId
34
34
  from aea.helpers.logging import setup_logger
35
35
 
36
- from operate.constants import AGENT_RUNNER_PREFIX, CONFIG_JSON
36
+ from operate.constants import AGENT_RUNNER_PREFIX, CONFIG_JSON, DEFAULT_TIMEOUT
37
37
 
38
38
 
39
39
  @dataclass
@@ -52,7 +52,7 @@ class AgentRelease:
52
52
 
53
53
  def get_url_and_hash(self, asset_name: str) -> tuple[str, str]:
54
54
  """Get download url and asset sha256 hash."""
55
- release_data = requests.get(self.release_url).json()
55
+ release_data = requests.get(self.release_url, timeout=DEFAULT_TIMEOUT).json()
56
56
 
57
57
  assets_filtered = [i for i in release_data["assets"] if i["name"] == asset_name]
58
58
  if not assets_filtered:
@@ -106,7 +106,7 @@ class AgentRunnerManager:
106
106
  """Download file of agent runner."""
107
107
  try:
108
108
  # Send a GET request to the URL
109
- response = requests.get(url, stream=True)
109
+ response = requests.get(url, stream=True, timeout=DEFAULT_TIMEOUT)
110
110
  response.raise_for_status() # Raise an error for bad status codes (4xx or 5xx)
111
111
 
112
112
  # Open the file in binary write mode and save the content
@@ -112,14 +112,30 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
112
112
 
113
113
  def _open_agent_runner_log_file(self) -> TextIOWrapper:
114
114
  """Open agent_runner.log file."""
115
- return (
116
- Path(self._work_directory).parent.parent.parent / "agent_runner.log"
117
- ).open("w+")
115
+ return (self._get_operate_dir() / "agent_runner.log").open("w+")
116
+
117
+ def _open_tendermint_log_file(self) -> TextIOWrapper:
118
+ """Open tm.log file."""
119
+ return (self._get_operate_dir() / "tm.log").open("w+")
120
+
121
+ def _get_operate_dir(self) -> Path:
122
+ """Get .operate dir."""
123
+ return Path(self._work_directory).parent.parent.parent
118
124
 
119
125
  def _run_aea_command(self, *args: str, cwd: Path) -> Any:
120
126
  """Run aea command."""
121
- cmd = " ".join(args)
122
- self.logger.info(f"Running aea command: {cmd} at {str(cwd)}")
127
+ no_password_args = []
128
+ for i, arg in enumerate(args):
129
+ if i > 0 and args[i - 1] == "--password":
130
+ no_password_args.append("******")
131
+ elif arg.startswith("--password="):
132
+ no_password_args.append("--password=******")
133
+ else:
134
+ no_password_args.append(arg)
135
+
136
+ self.logger.info(
137
+ f"Running aea command: {' '.join(no_password_args)} at {str(cwd)}"
138
+ )
123
139
  p = multiprocessing.Process(
124
140
  target=self.__class__._call_aea_command, # pylint: disable=protected-access
125
141
  args=(cwd, args),
@@ -128,7 +144,7 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
128
144
  p.join()
129
145
  if p.exitcode != 0:
130
146
  raise RuntimeError(
131
- f"aea command `{cmd}`execution failed with exit code: {p.exitcode}"
147
+ f"aea command `{' '.join(no_password_args)}` execution failed with exit code: {p.exitcode}"
132
148
  )
133
149
 
134
150
  @staticmethod
@@ -188,82 +204,90 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
188
204
  return env
189
205
 
190
206
  def _setup_agent(self, password: str) -> None:
191
- """Setup agent."""
192
- working_dir = self._work_directory
193
- env = self._prepare_agent_env()
194
- agent_alias_name = "agent"
195
- agent_dir_full_path = Path(working_dir) / agent_alias_name
196
- if not self._is_aea:
197
- if agent_dir_full_path.exists():
198
- # remove if exists before fetching! can have issues with retry mechanism of multiple start attempts
199
- with suppress(Exception):
200
- shutil.rmtree(agent_dir_full_path, ignore_errors=True)
201
-
202
- # Add keys
203
- agent_dir_full_path.mkdir(exist_ok=True, parents=True)
204
- shutil.copy(
205
- working_dir / "ethereum_private_key.txt",
206
- working_dir / "agent" / "ethereum_private_key.txt",
207
- )
208
- return
207
+ """Setup agent with retries for network operations."""
208
+ max_attempts = 10
209
+ for attempt in range(1, max_attempts + 1):
210
+ try:
211
+ working_dir = self._work_directory
212
+ env = self._prepare_agent_env()
213
+
214
+ # Clear agent directory before each attempt to avoid partial state
215
+ agent_alias_name = "agent"
216
+ agent_dir_full_path = Path(working_dir) / agent_alias_name
217
+ if agent_dir_full_path.exists():
218
+ with suppress(Exception):
219
+ shutil.rmtree(agent_dir_full_path, ignore_errors=True)
220
+
221
+ self._run_aea_command(
222
+ "init",
223
+ "--reset",
224
+ "--author",
225
+ "valory",
226
+ "--remote",
227
+ "--ipfs",
228
+ "--ipfs-node",
229
+ "/dns/registry.autonolas.tech/tcp/443/https",
230
+ cwd=working_dir,
231
+ )
209
232
 
210
- self._run_aea_command(
211
- "init",
212
- "--reset",
213
- "--author",
214
- "valory",
215
- "--remote",
216
- "--ipfs",
217
- "--ipfs-node",
218
- "/dns/registry.autonolas.tech/tcp/443/https",
219
- cwd=working_dir,
220
- )
233
+ self._run_aea_command(
234
+ "-s",
235
+ "fetch",
236
+ env["AEA_AGENT"],
237
+ "--alias",
238
+ agent_alias_name,
239
+ cwd=working_dir,
240
+ )
221
241
 
222
- if agent_dir_full_path.exists():
223
- # remove if exists before fetching! can have issues with retry mechanism of multiple start attempts
224
- with suppress(Exception):
225
- shutil.rmtree(agent_dir_full_path, ignore_errors=True)
226
-
227
- self._run_aea_command(
228
- "-s",
229
- "fetch",
230
- env["AEA_AGENT"],
231
- "--alias",
232
- agent_alias_name,
233
- cwd=working_dir,
234
- )
242
+ # Add keys
243
+ shutil.copy(
244
+ working_dir / "ethereum_private_key.txt",
245
+ working_dir / "agent" / "ethereum_private_key.txt",
246
+ )
235
247
 
236
- # Add keys
237
- shutil.copy(
238
- working_dir / "ethereum_private_key.txt",
239
- working_dir / "agent" / "ethereum_private_key.txt",
240
- )
248
+ self._run_aea_command(
249
+ "-s",
250
+ "add-key",
251
+ "--password",
252
+ password,
253
+ "ethereum",
254
+ cwd=working_dir / "agent",
255
+ )
256
+ self._run_aea_command(
257
+ "-s",
258
+ "add-key",
259
+ "--password",
260
+ password,
261
+ "ethereum",
262
+ "--connection",
263
+ cwd=working_dir / "agent",
264
+ )
241
265
 
242
- self._run_aea_command(
243
- "-s",
244
- "add-key",
245
- "--password",
246
- password,
247
- "ethereum",
248
- cwd=working_dir / "agent",
249
- )
250
- self._run_aea_command(
251
- "-s",
252
- "add-key",
253
- "--password",
254
- password,
255
- "ethereum",
256
- "--connection",
257
- cwd=working_dir / "agent",
258
- )
266
+ self._run_aea_command(
267
+ "-s",
268
+ "issue-certificates",
269
+ "--password",
270
+ password,
271
+ cwd=working_dir / "agent",
272
+ )
259
273
 
260
- self._run_aea_command(
261
- "-s",
262
- "issue-certificates",
263
- "--password",
264
- password,
265
- cwd=working_dir / "agent",
266
- )
274
+ # Success - break out of retry loop
275
+ self.logger.info(
276
+ f"Agent setup completed successfully on attempt {attempt}"
277
+ )
278
+ break
279
+
280
+ except Exception as e: # pylint: disable=broad-except
281
+ self.logger.warning(
282
+ f"Agent setup attempt {attempt}/{max_attempts} failed: {e}"
283
+ )
284
+ if attempt < max_attempts:
285
+ sleep_time = attempt * 5
286
+ self.logger.info(f"Retrying agent setup in {sleep_time} seconds...")
287
+ time.sleep(sleep_time)
288
+ else:
289
+ self.logger.error(f"All {max_attempts} agent setup attempts failed")
290
+ raise
267
291
 
268
292
  def start(self, password: str) -> None:
269
293
  """Start the deployment with retries."""
@@ -439,12 +463,12 @@ class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner):
439
463
  **env,
440
464
  }
441
465
  env["PATH"] = os.path.dirname(sys.executable) + ":" + os.environ["PATH"]
442
-
466
+ tm_log_file = self._open_tendermint_log_file()
443
467
  process = subprocess.Popen( # pylint: disable=consider-using-with,subprocess-popen-preexec-fn # nosec
444
468
  args=[self._tendermint_bin],
445
469
  cwd=working_dir,
446
- stdout=subprocess.DEVNULL,
447
- stderr=subprocess.DEVNULL,
470
+ stdout=tm_log_file,
471
+ stderr=tm_log_file,
448
472
  env=env,
449
473
  preexec_fn=os.setpgrp, # pylint: disable=subprocess-popen-preexec-fn # nosec
450
474
  )
@@ -559,13 +583,14 @@ class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner):
559
583
  env = {
560
584
  **env,
561
585
  }
586
+ tm_log_file = self._open_tendermint_log_file()
562
587
  env["PATH"] = os.path.dirname(sys.executable) + ";" + os.environ["PATH"]
563
588
 
564
589
  process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
565
590
  args=[self._tendermint_bin],
566
591
  cwd=working_dir,
567
- stdout=subprocess.DEVNULL,
568
- stderr=subprocess.DEVNULL,
592
+ stdout=tm_log_file,
593
+ stderr=tm_log_file,
569
594
  env=env,
570
595
  creationflags=0x00000200, # Detach process from the main process
571
596
  )