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.
- {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/METADATA +8 -27
- {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/RECORD +42 -42
- operate/bridge/providers/provider.py +23 -31
- operate/cli.py +5 -18
- operate/constants.py +1 -0
- operate/data/contracts/dual_staking_token/contract.py +3 -3
- operate/data/contracts/dual_staking_token/contract.yaml +2 -2
- operate/data/contracts/foreign_omnibridge/contract.yaml +1 -1
- operate/data/contracts/home_omnibridge/contract.py +2 -2
- operate/data/contracts/home_omnibridge/contract.yaml +2 -2
- operate/data/contracts/l1_standard_bridge/contract.yaml +1 -1
- operate/data/contracts/l2_standard_bridge/contract.py +4 -4
- operate/data/contracts/l2_standard_bridge/contract.yaml +2 -2
- operate/data/contracts/mech_activity/contract.yaml +1 -1
- operate/data/contracts/optimism_mintable_erc20/contract.yaml +1 -1
- operate/data/contracts/recovery_module/contract.yaml +1 -1
- operate/data/contracts/requester_activity_checker/contract.yaml +1 -1
- operate/data/contracts/staking_token/contract.py +3 -3
- operate/data/contracts/staking_token/contract.yaml +2 -2
- operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -3
- operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +5 -5
- operate/keys.py +5 -3
- operate/ledger/__init__.py +1 -7
- operate/ledger/profiles.py +0 -1
- operate/operate_http/__init__.py +0 -2
- operate/operate_types.py +3 -93
- operate/quickstart/run_service.py +63 -6
- operate/quickstart/utils.py +8 -4
- operate/resource.py +2 -2
- operate/services/agent_runner.py +3 -3
- operate/services/deployment_runner.py +107 -82
- operate/services/health_checker.py +38 -2
- operate/services/manage.py +14 -17
- operate/services/protocol.py +122 -141
- operate/services/utils/mech.py +3 -3
- operate/services/utils/tendermint.py +5 -3
- operate/utils/gnosis.py +76 -101
- operate/wallet/master.py +53 -50
- operate/wallet/wallet_recovery_manager.py +110 -56
- {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.13.1.dist-info → olas_operate_middleware-0.13.3.dist-info}/entry_points.txt +0 -0
- {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
|
-
).
|
|
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
|
-
).
|
|
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
|
-
).
|
|
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
|
-
).
|
|
181
|
-
|
|
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=
|
|
148
|
-
|
|
149
|
-
|
|
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",
|
operate/ledger/__init__.py
CHANGED
|
@@ -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
|
|
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
|
operate/ledger/profiles.py
CHANGED
|
@@ -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"
|
operate/operate_http/__init__.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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 (
|
operate/quickstart/utils.py
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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)
|
operate/services/agent_runner.py
CHANGED
|
@@ -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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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 `{
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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=
|
|
447
|
-
stderr=
|
|
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=
|
|
568
|
-
stderr=
|
|
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
|
)
|