olas-operate-middleware 0.11.5__tar.gz → 0.12.0__tar.gz
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.11.5 → olas_operate_middleware-0.12.0}/PKG-INFO +1 -1
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/cli.py +14 -8
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/keys.py +75 -70
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/migration.py +66 -1
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_password.py +3 -7
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/resource.py +9 -25
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/deployment_runner.py +61 -27
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/manage.py +2 -1
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/service.py +25 -7
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/utils/__init__.py +46 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/pyproject.toml +1 -1
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/LICENSE +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/README.md +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/account/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/account/user.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/bridge_manager.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/providers/lifi_provider.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/providers/native_bridge_provider.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/providers/provider.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/providers/relay_provider.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/constants.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/README.md +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/build/DualStakingToken.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/build/MechActivity.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/build/RecoveryModule.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/build/StakingToken.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/build/IUniswapV2ERC20.json +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/contract.yaml +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/ledger/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/ledger/profiles.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/operate_http/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/operate_http/exceptions.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/operate_types.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/pearl.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/analyse_logs.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/claim_staking_rewards.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_configs.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_staking.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/run_service.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/stop_service.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/terminate_on_chain_service.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/utils.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/agent_runner.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/funding_manager.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/health_checker.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/protocol.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/utils/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/utils/mech.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/utils/tendermint.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/settings.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/utils/gnosis.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/utils/single_instance.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/utils/ssl.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/wallet/__init__.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/wallet/master.py +0 -0
- {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/wallet/wallet_recovery_manager.py +0 -0
|
@@ -64,6 +64,7 @@ from operate.constants import (
|
|
|
64
64
|
WALLET_RECOVERY_DIR,
|
|
65
65
|
ZERO_ADDRESS,
|
|
66
66
|
)
|
|
67
|
+
from operate.keys import KeysManager
|
|
67
68
|
from operate.ledger.profiles import (
|
|
68
69
|
DEFAULT_EOA_TOPUPS,
|
|
69
70
|
DEFAULT_NEW_SAFE_FUNDS,
|
|
@@ -143,7 +144,7 @@ def service_not_found_error(service_config_id: str) -> JSONResponse:
|
|
|
143
144
|
)
|
|
144
145
|
|
|
145
146
|
|
|
146
|
-
class OperateApp:
|
|
147
|
+
class OperateApp: # pylint: disable=too-many-instance-attributes
|
|
147
148
|
"""Operate app."""
|
|
148
149
|
|
|
149
150
|
def __init__(
|
|
@@ -157,11 +158,13 @@ class OperateApp:
|
|
|
157
158
|
self.setup()
|
|
158
159
|
self._backup_operate_if_new_version()
|
|
159
160
|
|
|
160
|
-
|
|
161
|
+
self._password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
|
|
162
|
+
KeysManager._instances.clear() # reset singleton instance
|
|
163
|
+
self._keys_manager: KeysManager = KeysManager(
|
|
161
164
|
path=self._keys,
|
|
162
165
|
logger=logger,
|
|
166
|
+
password=self._password,
|
|
163
167
|
)
|
|
164
|
-
self._password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
|
|
165
168
|
self.settings = Settings(path=self._path)
|
|
166
169
|
|
|
167
170
|
self._wallet_manager = MasterWalletManager(
|
|
@@ -174,11 +177,11 @@ class OperateApp:
|
|
|
174
177
|
logger=logger,
|
|
175
178
|
)
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
self._migration_manager = MigrationManager(self._path, logger)
|
|
181
|
+
self._migration_manager.migrate_user_account()
|
|
182
|
+
self._migration_manager.migrate_services(self.service_manager())
|
|
183
|
+
self._migration_manager.migrate_wallets(self.wallet_manager)
|
|
184
|
+
self._migration_manager.migrate_qs_configs()
|
|
182
185
|
|
|
183
186
|
@property
|
|
184
187
|
def password(self) -> t.Optional[str]:
|
|
@@ -189,7 +192,9 @@ class OperateApp:
|
|
|
189
192
|
def password(self, value: t.Optional[str]) -> None:
|
|
190
193
|
"""Set the password."""
|
|
191
194
|
self._password = value
|
|
195
|
+
self._keys_manager.password = value
|
|
192
196
|
self._wallet_manager.password = value
|
|
197
|
+
self._migration_manager.migrate_keys(self._keys_manager)
|
|
193
198
|
|
|
194
199
|
def _backup_operate_if_new_version(self) -> None:
|
|
195
200
|
"""Backup .operate directory if this is a new version."""
|
|
@@ -256,6 +261,7 @@ class OperateApp:
|
|
|
256
261
|
wallet_manager = self.wallet_manager
|
|
257
262
|
wallet_manager.password = old_password
|
|
258
263
|
wallet_manager.update_password(new_password)
|
|
264
|
+
self._keys_manager.update_password(new_password)
|
|
259
265
|
self.user_account.update(old_password, new_password)
|
|
260
266
|
|
|
261
267
|
def update_password_with_mnemonic(self, mnemonic: str, new_password: str) -> None:
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
23
|
import os
|
|
24
|
-
import shutil
|
|
25
24
|
import tempfile
|
|
26
25
|
from dataclasses import dataclass
|
|
26
|
+
from logging import Logger
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import Any
|
|
28
|
+
from typing import Any, Optional
|
|
29
29
|
|
|
30
30
|
from aea_ledger_ethereum.ethereum import EthereumCrypto
|
|
31
31
|
|
|
32
32
|
from operate.operate_types import LedgerType
|
|
33
33
|
from operate.resource import LocalResource
|
|
34
|
-
from operate.utils import SingletonMeta
|
|
34
|
+
from operate.utils import SingletonMeta, unrecoverable_delete
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass
|
|
@@ -42,6 +42,15 @@ class Key(LocalResource):
|
|
|
42
42
|
address: str
|
|
43
43
|
private_key: str
|
|
44
44
|
|
|
45
|
+
def get_decrypted(self, password: str) -> dict:
|
|
46
|
+
"""Get decrypted key json."""
|
|
47
|
+
return {
|
|
48
|
+
"ledger": self.ledger.value,
|
|
49
|
+
"address": self.address,
|
|
50
|
+
"private_key": "0x"
|
|
51
|
+
+ EthereumCrypto.decrypt(self.private_key, password=password),
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
@classmethod
|
|
46
55
|
def load(cls, path: Path) -> "Key":
|
|
47
56
|
"""Load a service"""
|
|
@@ -61,13 +70,41 @@ class KeysManager(metaclass=SingletonMeta):
|
|
|
61
70
|
if "path" not in kwargs:
|
|
62
71
|
raise ValueError("Path must be provided for KeysManager")
|
|
63
72
|
|
|
64
|
-
self.path = kwargs["path"]
|
|
65
|
-
self.logger = kwargs["logger"]
|
|
73
|
+
self.path: Path = kwargs["path"]
|
|
74
|
+
self.logger: Logger = kwargs["logger"]
|
|
75
|
+
self.password: Optional[str] = kwargs.get("password")
|
|
66
76
|
self.path.mkdir(exist_ok=True, parents=True)
|
|
67
77
|
|
|
78
|
+
def private_key_to_crypto(
|
|
79
|
+
self, private_key: str, password: Optional[str]
|
|
80
|
+
) -> EthereumCrypto:
|
|
81
|
+
"""Convert private key string to EthereumCrypto instance."""
|
|
82
|
+
with tempfile.NamedTemporaryFile(
|
|
83
|
+
dir=self.path,
|
|
84
|
+
mode="w",
|
|
85
|
+
suffix=".txt",
|
|
86
|
+
delete=False, # Handle cleanup manually
|
|
87
|
+
) as temp_file:
|
|
88
|
+
temp_file_name = temp_file.name
|
|
89
|
+
temp_file.write(private_key)
|
|
90
|
+
temp_file.flush()
|
|
91
|
+
temp_file.close() # Close the file before reading
|
|
92
|
+
|
|
93
|
+
# Set proper file permissions (readable by owner only)
|
|
94
|
+
os.chmod(temp_file_name, 0o600)
|
|
95
|
+
crypto = EthereumCrypto(private_key_path=temp_file_name, password=password)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
unrecoverable_delete(
|
|
99
|
+
Path(temp_file.name)
|
|
100
|
+
) # Clean up the temporary file
|
|
101
|
+
except OSError as e:
|
|
102
|
+
self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
|
|
103
|
+
|
|
104
|
+
return crypto
|
|
105
|
+
|
|
68
106
|
def get(self, key: str) -> Key:
|
|
69
107
|
"""Get key object."""
|
|
70
|
-
KeysManager.migrate_format(self.path / key)
|
|
71
108
|
return Key.from_json( # type: ignore
|
|
72
109
|
obj=json.loads(
|
|
73
110
|
(self.path / key).read_text(
|
|
@@ -91,46 +128,20 @@ class KeysManager(metaclass=SingletonMeta):
|
|
|
91
128
|
|
|
92
129
|
def get_crypto_instance(self, address: str) -> EthereumCrypto:
|
|
93
130
|
"""Get EthereumCrypto instance for the given address."""
|
|
94
|
-
key: Key =
|
|
95
|
-
|
|
96
|
-
(self.path / address).read_text(
|
|
97
|
-
encoding="utf-8",
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
)
|
|
101
|
-
private_key = key.private_key
|
|
102
|
-
# Create temporary file with delete=False to handle it manually
|
|
103
|
-
with tempfile.NamedTemporaryFile(
|
|
104
|
-
dir=self.path,
|
|
105
|
-
mode="w",
|
|
106
|
-
suffix=".txt",
|
|
107
|
-
delete=False, # Handle cleanup manually
|
|
108
|
-
) as temp_file:
|
|
109
|
-
temp_file_name = temp_file.name
|
|
110
|
-
temp_file.write(private_key)
|
|
111
|
-
temp_file.flush()
|
|
112
|
-
temp_file.close() # Close the file before reading
|
|
113
|
-
|
|
114
|
-
# Set proper file permissions (readable by owner only)
|
|
115
|
-
os.chmod(temp_file_name, 0o600)
|
|
116
|
-
crypto = EthereumCrypto(private_key_path=temp_file_name)
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
with open(temp_file_name, "r+", encoding="utf-8") as f:
|
|
120
|
-
f.seek(0)
|
|
121
|
-
f.write("\0" * len(private_key))
|
|
122
|
-
f.flush()
|
|
123
|
-
f.close()
|
|
124
|
-
os.unlink(temp_file_name) # Clean up the temporary file
|
|
125
|
-
except OSError as e:
|
|
126
|
-
self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
|
|
127
|
-
|
|
128
|
-
return crypto
|
|
131
|
+
key: Key = self.get(address)
|
|
132
|
+
return self.private_key_to_crypto(key.private_key, self.password)
|
|
129
133
|
|
|
130
134
|
def create(self) -> str:
|
|
131
135
|
"""Creates new key."""
|
|
132
136
|
self.path.mkdir(exist_ok=True, parents=True)
|
|
133
|
-
crypto = EthereumCrypto()
|
|
137
|
+
crypto = EthereumCrypto(password=self.password)
|
|
138
|
+
key = Key(
|
|
139
|
+
ledger=LedgerType.ETHEREUM,
|
|
140
|
+
address=crypto.address,
|
|
141
|
+
private_key=crypto.encrypt(password=self.password)
|
|
142
|
+
if self.password is not None
|
|
143
|
+
else crypto.private_key,
|
|
144
|
+
)
|
|
134
145
|
for path in (
|
|
135
146
|
self.path / f"{crypto.address}.bak",
|
|
136
147
|
self.path / crypto.address,
|
|
@@ -140,12 +151,8 @@ class KeysManager(metaclass=SingletonMeta):
|
|
|
140
151
|
|
|
141
152
|
path.write_text(
|
|
142
153
|
json.dumps(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
address=crypto.address,
|
|
146
|
-
private_key=crypto.private_key,
|
|
147
|
-
).json,
|
|
148
|
-
indent=4,
|
|
154
|
+
key.json,
|
|
155
|
+
indent=2,
|
|
149
156
|
),
|
|
150
157
|
encoding="utf-8",
|
|
151
158
|
)
|
|
@@ -156,25 +163,23 @@ class KeysManager(metaclass=SingletonMeta):
|
|
|
156
163
|
"""Delete key."""
|
|
157
164
|
os.remove(self.path / key)
|
|
158
165
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return migrated
|
|
166
|
+
def update_password(self, new_password: str) -> None:
|
|
167
|
+
"""Update password for all keys."""
|
|
168
|
+
for key_file in self.path.iterdir():
|
|
169
|
+
if not key_file.is_file() or key_file.suffix == ".bak":
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
key = self.get(key_file.name)
|
|
173
|
+
crypto = self.get_crypto_instance(key_file.name)
|
|
174
|
+
encrypted_private_key = crypto.encrypt(password=new_password)
|
|
175
|
+
key.private_key = encrypted_private_key
|
|
176
|
+
key.path = self.path / key_file.name
|
|
177
|
+
key.store()
|
|
178
|
+
|
|
179
|
+
backup_path = self.path / f"{key.address}.bak"
|
|
180
|
+
backup_path.write_text(
|
|
181
|
+
json.dumps(key.json, indent=2),
|
|
182
|
+
encoding="utf-8",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
self.password = new_password
|
|
@@ -28,8 +28,11 @@ from pathlib import Path
|
|
|
28
28
|
from time import time
|
|
29
29
|
|
|
30
30
|
from aea_cli_ipfs.ipfs_utils import IPFSTool
|
|
31
|
+
from aea_ledger_ethereum import EthereumCrypto
|
|
32
|
+
from web3 import Web3
|
|
31
33
|
|
|
32
34
|
from operate.constants import USER_JSON, ZERO_ADDRESS
|
|
35
|
+
from operate.keys import KeysManager
|
|
33
36
|
from operate.operate_types import Chain, LedgerType
|
|
34
37
|
from operate.services.manage import ServiceManager
|
|
35
38
|
from operate.services.service import (
|
|
@@ -38,7 +41,7 @@ from operate.services.service import (
|
|
|
38
41
|
SERVICE_CONFIG_VERSION,
|
|
39
42
|
Service,
|
|
40
43
|
)
|
|
41
|
-
from operate.utils import create_backup
|
|
44
|
+
from operate.utils import create_backup, unrecoverable_delete
|
|
42
45
|
from operate.wallet.master import LEDGER_TYPE_TO_WALLET_CLASS, MasterWalletManager
|
|
43
46
|
|
|
44
47
|
|
|
@@ -454,3 +457,65 @@ class MigrationManager:
|
|
|
454
457
|
self.logger.info(
|
|
455
458
|
"[MIGRATION MANAGER] Migrated quickstart config: %s.", qs_config.name
|
|
456
459
|
)
|
|
460
|
+
|
|
461
|
+
def migrate_keys(self, keys_manager: KeysManager) -> None:
|
|
462
|
+
"""Migrate keys format if needed."""
|
|
463
|
+
self.logger.info("Migrating keys...")
|
|
464
|
+
|
|
465
|
+
for key_file in keys_manager.path.iterdir():
|
|
466
|
+
if (
|
|
467
|
+
not key_file.is_file()
|
|
468
|
+
or key_file.suffix == ".bak"
|
|
469
|
+
or not Web3.is_address(key_file.name)
|
|
470
|
+
):
|
|
471
|
+
self.logger.warning(f"Skipping non-key file: {key_file}")
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
migrated = False
|
|
475
|
+
backup_path = key_file.with_suffix(".bak")
|
|
476
|
+
|
|
477
|
+
try:
|
|
478
|
+
with open(key_file, "r", encoding="utf-8") as file:
|
|
479
|
+
data = json.load(file)
|
|
480
|
+
except Exception as e: # pylint: disable=broad-except
|
|
481
|
+
self.logger.error(
|
|
482
|
+
f"Failed to read key file: {key_file}\n"
|
|
483
|
+
f"Key file content:\n{key_file.read_text(encoding='utf-8')}\n"
|
|
484
|
+
f"Exception {e}: {traceback.format_exc()}"
|
|
485
|
+
)
|
|
486
|
+
raise e
|
|
487
|
+
|
|
488
|
+
old_to_new_ledgers = {0: "ethereum", 1: "solana"}
|
|
489
|
+
if data.get("ledger") in old_to_new_ledgers:
|
|
490
|
+
data["ledger"] = old_to_new_ledgers.get(data["ledger"])
|
|
491
|
+
with open(key_file, "w", encoding="utf-8") as file:
|
|
492
|
+
json.dump(data, file, indent=2)
|
|
493
|
+
|
|
494
|
+
migrated = True
|
|
495
|
+
|
|
496
|
+
private_key = data.get("private_key")
|
|
497
|
+
if (
|
|
498
|
+
private_key
|
|
499
|
+
and keys_manager.password is not None
|
|
500
|
+
and private_key.startswith("0x")
|
|
501
|
+
):
|
|
502
|
+
crypto: EthereumCrypto = keys_manager.private_key_to_crypto(
|
|
503
|
+
private_key=private_key,
|
|
504
|
+
password=None,
|
|
505
|
+
)
|
|
506
|
+
encrypted_private_key = crypto.encrypt(password=keys_manager.password)
|
|
507
|
+
data["private_key"] = encrypted_private_key
|
|
508
|
+
if backup_path.exists():
|
|
509
|
+
unrecoverable_delete(backup_path)
|
|
510
|
+
|
|
511
|
+
migrated = True
|
|
512
|
+
|
|
513
|
+
if migrated:
|
|
514
|
+
with open(key_file, "w", encoding="utf-8") as file:
|
|
515
|
+
json.dump(data, file, indent=2)
|
|
516
|
+
|
|
517
|
+
if not backup_path.exists():
|
|
518
|
+
shutil.copyfile(key_file, backup_path)
|
|
519
|
+
|
|
520
|
+
if migrated:
|
|
521
|
+
self.logger.info(f"Key {key_file.name} has been migrated.")
|
|
@@ -22,10 +22,9 @@ from typing import TYPE_CHECKING
|
|
|
22
22
|
|
|
23
23
|
from operate.account.user import UserAccount
|
|
24
24
|
from operate.constants import USER_JSON
|
|
25
|
-
from operate.
|
|
25
|
+
from operate.keys import KeysManager
|
|
26
26
|
from operate.quickstart.run_service import ask_confirm_password
|
|
27
27
|
from operate.quickstart.utils import ask_or_get_from_env, print_section, print_title
|
|
28
|
-
from operate.wallet.master import EthereumMasterWallet
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
if TYPE_CHECKING:
|
|
@@ -66,10 +65,7 @@ def reset_password(operate: "OperateApp") -> None:
|
|
|
66
65
|
|
|
67
66
|
print('Resetting password of "ethereum" wallet...')
|
|
68
67
|
operate.password = old_password
|
|
69
|
-
operate.wallet_manager.
|
|
70
|
-
|
|
71
|
-
ledger_type=LedgerType.ETHEREUM
|
|
72
|
-
)
|
|
73
|
-
wallet.update_password(new_password=new_password)
|
|
68
|
+
operate.wallet_manager.update_password(new_password=new_password)
|
|
69
|
+
KeysManager().update_password(new_password=new_password)
|
|
74
70
|
|
|
75
71
|
print_section("Password reset done!")
|
|
@@ -24,12 +24,13 @@ import json
|
|
|
24
24
|
import os
|
|
25
25
|
import platform
|
|
26
26
|
import shutil
|
|
27
|
-
import time
|
|
28
27
|
import types
|
|
29
28
|
import typing as t
|
|
30
29
|
from dataclasses import asdict, is_dataclass
|
|
31
30
|
from pathlib import Path
|
|
32
31
|
|
|
32
|
+
from operate.utils import safe_file_operation
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
# pylint: disable=too-many-return-statements,no-member
|
|
35
36
|
|
|
@@ -94,23 +95,6 @@ def deserialize(obj: t.Any, otype: t.Any) -> t.Any:
|
|
|
94
95
|
return obj
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
def _safe_file_operation(operation: t.Callable, *args: t.Any, **kwargs: t.Any) -> None:
|
|
98
|
-
"""Safely perform file operation with retries on Windows."""
|
|
99
|
-
max_retries = 3 if platform.system() == "Windows" else 1
|
|
100
|
-
|
|
101
|
-
for attempt in range(max_retries):
|
|
102
|
-
try:
|
|
103
|
-
operation(*args, **kwargs)
|
|
104
|
-
return
|
|
105
|
-
except (PermissionError, FileNotFoundError, OSError) as e:
|
|
106
|
-
if attempt == max_retries - 1:
|
|
107
|
-
raise e
|
|
108
|
-
|
|
109
|
-
if platform.system() == "Windows":
|
|
110
|
-
# On Windows, wait a bit and retry
|
|
111
|
-
time.sleep(0.1)
|
|
112
|
-
|
|
113
|
-
|
|
114
98
|
class LocalResource:
|
|
115
99
|
"""Initialize local resource."""
|
|
116
100
|
|
|
@@ -163,13 +147,13 @@ class LocalResource:
|
|
|
163
147
|
bak0 = path.with_name(f"{path.name}.0.bak")
|
|
164
148
|
|
|
165
149
|
if path.exists() and not bak0.exists():
|
|
166
|
-
|
|
150
|
+
safe_file_operation(shutil.copy2, path, bak0)
|
|
167
151
|
|
|
168
152
|
tmp_path = path.parent / f".{path.name}.tmp"
|
|
169
153
|
|
|
170
154
|
# Clean up any existing tmp file
|
|
171
155
|
if tmp_path.exists():
|
|
172
|
-
|
|
156
|
+
safe_file_operation(tmp_path.unlink)
|
|
173
157
|
|
|
174
158
|
tmp_path.write_text(
|
|
175
159
|
json.dumps(
|
|
@@ -181,11 +165,11 @@ class LocalResource:
|
|
|
181
165
|
|
|
182
166
|
# Atomic replace to avoid corruption
|
|
183
167
|
try:
|
|
184
|
-
|
|
168
|
+
safe_file_operation(os.replace, tmp_path, path)
|
|
185
169
|
except (PermissionError, FileNotFoundError):
|
|
186
170
|
# On Windows, if the replace fails, clean up and skip
|
|
187
171
|
if platform.system() == "Windows":
|
|
188
|
-
|
|
172
|
+
safe_file_operation(tmp_path.unlink)
|
|
189
173
|
|
|
190
174
|
self.load(self.path) # Validate before making backup
|
|
191
175
|
|
|
@@ -195,7 +179,7 @@ class LocalResource:
|
|
|
195
179
|
older = path.with_name(f"{path.name}.{i + 1}.bak")
|
|
196
180
|
if newer.exists():
|
|
197
181
|
if older.exists():
|
|
198
|
-
|
|
199
|
-
|
|
182
|
+
safe_file_operation(older.unlink)
|
|
183
|
+
safe_file_operation(newer.rename, older)
|
|
200
184
|
|
|
201
|
-
|
|
185
|
+
safe_file_operation(shutil.copy2, path, bak0)
|
|
@@ -56,7 +56,7 @@ class AbstractDeploymentRunner(ABC):
|
|
|
56
56
|
self._work_directory = work_directory
|
|
57
57
|
|
|
58
58
|
@abstractmethod
|
|
59
|
-
def start(self) -> None:
|
|
59
|
+
def start(self, password: str) -> None:
|
|
60
60
|
"""Start the deployment."""
|
|
61
61
|
|
|
62
62
|
@abstractmethod
|
|
@@ -182,7 +182,7 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
182
182
|
)
|
|
183
183
|
return env
|
|
184
184
|
|
|
185
|
-
def _setup_agent(self) -> None:
|
|
185
|
+
def _setup_agent(self, password: str) -> None:
|
|
186
186
|
"""Setup agent."""
|
|
187
187
|
working_dir = self._work_directory
|
|
188
188
|
env = self._prepare_agent_env()
|
|
@@ -223,18 +223,37 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
223
223
|
working_dir / "agent" / "ethereum_private_key.txt",
|
|
224
224
|
)
|
|
225
225
|
|
|
226
|
-
self._run_aea_command("-s", "add-key", "ethereum", cwd=working_dir / "agent")
|
|
227
226
|
self._run_aea_command(
|
|
228
|
-
"-s",
|
|
227
|
+
"-s",
|
|
228
|
+
"add-key",
|
|
229
|
+
"--password",
|
|
230
|
+
password,
|
|
231
|
+
"ethereum",
|
|
232
|
+
cwd=working_dir / "agent",
|
|
233
|
+
)
|
|
234
|
+
self._run_aea_command(
|
|
235
|
+
"-s",
|
|
236
|
+
"add-key",
|
|
237
|
+
"--password",
|
|
238
|
+
password,
|
|
239
|
+
"ethereum",
|
|
240
|
+
"--connection",
|
|
241
|
+
cwd=working_dir / "agent",
|
|
229
242
|
)
|
|
230
243
|
|
|
231
|
-
self._run_aea_command(
|
|
244
|
+
self._run_aea_command(
|
|
245
|
+
"-s",
|
|
246
|
+
"issue-certificates",
|
|
247
|
+
"--password",
|
|
248
|
+
password,
|
|
249
|
+
cwd=working_dir / "agent",
|
|
250
|
+
)
|
|
232
251
|
|
|
233
|
-
def start(self) -> None:
|
|
252
|
+
def start(self, password: str) -> None:
|
|
234
253
|
"""Start the deployment with retries."""
|
|
235
254
|
for _ in range(self.START_TRIES):
|
|
236
255
|
try:
|
|
237
|
-
self._start()
|
|
256
|
+
self._start(password=password)
|
|
238
257
|
return
|
|
239
258
|
except Exception as e: # pylint: disable=broad-except
|
|
240
259
|
self.logger.exception(f"Error on starting deployment: {e}")
|
|
@@ -242,11 +261,11 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
242
261
|
f"Failed to start the deployment after {self.START_TRIES} attempts! Check logs"
|
|
243
262
|
)
|
|
244
263
|
|
|
245
|
-
def _start(self) -> None:
|
|
264
|
+
def _start(self, password: str) -> None:
|
|
246
265
|
"""Start the deployment."""
|
|
247
|
-
self._setup_agent()
|
|
266
|
+
self._setup_agent(password=password)
|
|
248
267
|
self._start_tendermint()
|
|
249
|
-
self._start_agent()
|
|
268
|
+
self._start_agent(password=password)
|
|
250
269
|
|
|
251
270
|
def stop(self) -> None:
|
|
252
271
|
"""Stop the deployment."""
|
|
@@ -285,7 +304,7 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
285
304
|
"""Start tendermint process."""
|
|
286
305
|
|
|
287
306
|
@abstractmethod
|
|
288
|
-
def _start_agent(self) -> None:
|
|
307
|
+
def _start_agent(self, password: str) -> None:
|
|
289
308
|
"""Start aea process."""
|
|
290
309
|
|
|
291
310
|
@property
|
|
@@ -318,7 +337,7 @@ class PyInstallerHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
318
337
|
"""Return tendermint path."""
|
|
319
338
|
return str(Path(os.path.dirname(sys.executable)) / "tendermint_bin") # type: ignore # pylint: disable=protected-access
|
|
320
339
|
|
|
321
|
-
def _start_agent(self) -> None:
|
|
340
|
+
def _start_agent(self, password: str) -> None:
|
|
322
341
|
"""Start agent process."""
|
|
323
342
|
working_dir = self._work_directory
|
|
324
343
|
env = json.loads((working_dir / "agent.json").read_text(encoding="utf-8"))
|
|
@@ -326,13 +345,17 @@ class PyInstallerHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
326
345
|
env["PYTHONIOENCODING"] = "utf8"
|
|
327
346
|
env = {**os.environ, **env}
|
|
328
347
|
|
|
329
|
-
process = self._start_agent_process(
|
|
348
|
+
process = self._start_agent_process(
|
|
349
|
+
env=env, working_dir=working_dir, password=password
|
|
350
|
+
)
|
|
330
351
|
(working_dir / "agent.pid").write_text(
|
|
331
352
|
data=str(process.pid),
|
|
332
353
|
encoding="utf-8",
|
|
333
354
|
)
|
|
334
355
|
|
|
335
|
-
def _start_agent_process(
|
|
356
|
+
def _start_agent_process(
|
|
357
|
+
self, env: Dict, working_dir: Path, password: str
|
|
358
|
+
) -> subprocess.Popen:
|
|
336
359
|
"""Start agent process."""
|
|
337
360
|
raise NotImplementedError
|
|
338
361
|
|
|
@@ -364,7 +387,9 @@ class PyInstallerHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
364
387
|
class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner):
|
|
365
388
|
"""Mac deployment runner."""
|
|
366
389
|
|
|
367
|
-
def _start_agent_process(
|
|
390
|
+
def _start_agent_process(
|
|
391
|
+
self, env: Dict, working_dir: Path, password: str
|
|
392
|
+
) -> subprocess.Popen:
|
|
368
393
|
"""Start agent process."""
|
|
369
394
|
agent_runner_log_file = self._open_agent_runner_log_file()
|
|
370
395
|
process = subprocess.Popen( # pylint: disable=consider-using-with,subprocess-popen-preexec-fn # nosec
|
|
@@ -372,6 +397,8 @@ class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner):
|
|
|
372
397
|
self._agent_runner_bin,
|
|
373
398
|
"-s",
|
|
374
399
|
"run",
|
|
400
|
+
"--password",
|
|
401
|
+
password,
|
|
375
402
|
],
|
|
376
403
|
cwd=working_dir / "agent",
|
|
377
404
|
stdout=agent_runner_log_file,
|
|
@@ -486,7 +513,9 @@ class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner):
|
|
|
486
513
|
"""Return tendermint path."""
|
|
487
514
|
return str(Path(os.path.dirname(sys.executable)) / "tendermint_win.exe") # type: ignore # pylint: disable=protected-access
|
|
488
515
|
|
|
489
|
-
def _start_agent_process(
|
|
516
|
+
def _start_agent_process(
|
|
517
|
+
self, env: Dict, working_dir: Path, password: str
|
|
518
|
+
) -> subprocess.Popen:
|
|
490
519
|
"""Start agent process."""
|
|
491
520
|
agent_runner_log_file = self._open_agent_runner_log_file()
|
|
492
521
|
process = subprocess.Popen( # pylint: disable=consider-using-with # nosec
|
|
@@ -494,6 +523,8 @@ class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner):
|
|
|
494
523
|
self._agent_runner_bin,
|
|
495
524
|
"-s",
|
|
496
525
|
"run",
|
|
526
|
+
"--password",
|
|
527
|
+
password,
|
|
497
528
|
], # TODO: Patch for Windows failing hash
|
|
498
529
|
cwd=working_dir / "agent",
|
|
499
530
|
stdout=agent_runner_log_file,
|
|
@@ -533,7 +564,7 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
533
564
|
"""Return aea_bin path."""
|
|
534
565
|
return str(self._venv_dir / "bin" / "aea")
|
|
535
566
|
|
|
536
|
-
def _start_agent(self) -> None:
|
|
567
|
+
def _start_agent(self, password: str) -> None:
|
|
537
568
|
"""Start agent process."""
|
|
538
569
|
working_dir = self._work_directory
|
|
539
570
|
env = json.loads((working_dir / "agent.json").read_text(encoding="utf-8"))
|
|
@@ -546,6 +577,8 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
546
577
|
self._agent_runner_bin,
|
|
547
578
|
"-s",
|
|
548
579
|
"run",
|
|
580
|
+
"--password",
|
|
581
|
+
password,
|
|
549
582
|
], # TODO: Patch for Windows failing hash
|
|
550
583
|
cwd=str(working_dir / "agent"),
|
|
551
584
|
env={**os.environ, **env},
|
|
@@ -613,14 +646,15 @@ class HostPythonHostDeploymentRunner(BaseDeploymentRunner):
|
|
|
613
646
|
# Install tendermint dependencies
|
|
614
647
|
"flask",
|
|
615
648
|
"requests",
|
|
649
|
+
"multiaddr==0.0.9", # TODO: remove when pinned on open-aea
|
|
616
650
|
],
|
|
617
651
|
)
|
|
618
652
|
|
|
619
|
-
def _setup_agent(self) -> None:
|
|
653
|
+
def _setup_agent(self, password: str) -> None:
|
|
620
654
|
"""Prepare agent."""
|
|
621
655
|
multiprocessing.set_start_method("spawn")
|
|
622
656
|
self._setup_venv()
|
|
623
|
-
super()._setup_agent()
|
|
657
|
+
super()._setup_agent(password=password)
|
|
624
658
|
# Install agent dependencies
|
|
625
659
|
self._run_cmd(
|
|
626
660
|
args=[
|
|
@@ -707,7 +741,7 @@ class DeploymentManager:
|
|
|
707
741
|
"Failed to perform test connection to ipfs to check network connection!"
|
|
708
742
|
)
|
|
709
743
|
|
|
710
|
-
def run_deployment(self, build_dir: Path) -> None:
|
|
744
|
+
def run_deployment(self, build_dir: Path, password: str) -> None:
|
|
711
745
|
"""Run deployment."""
|
|
712
746
|
if self._is_stopping:
|
|
713
747
|
raise RuntimeError("deployment manager stopped")
|
|
@@ -721,7 +755,7 @@ class DeploymentManager:
|
|
|
721
755
|
self._states[build_dir] = States.STARTING
|
|
722
756
|
try:
|
|
723
757
|
deployment_runner = self._get_deployment_runner(build_dir=build_dir)
|
|
724
|
-
deployment_runner.start()
|
|
758
|
+
deployment_runner.start(password=password)
|
|
725
759
|
self.logger.info(f"Started deployment {build_dir}")
|
|
726
760
|
self._states[build_dir] = States.STARTED
|
|
727
761
|
except Exception: # pylint: disable=broad-except
|
|
@@ -729,15 +763,15 @@ class DeploymentManager:
|
|
|
729
763
|
f"Starting deployment failed {build_dir}. so try to stop"
|
|
730
764
|
)
|
|
731
765
|
self._states[build_dir] = States.ERROR
|
|
732
|
-
self.
|
|
766
|
+
self.stop_deployment(build_dir=build_dir, force=True)
|
|
733
767
|
|
|
734
768
|
if self._is_stopping:
|
|
735
769
|
self.logger.warning(
|
|
736
770
|
f"Deployment at {build_dir} started when it was going to stop, so stop it"
|
|
737
771
|
)
|
|
738
|
-
self.
|
|
772
|
+
self.stop_deployment(build_dir=build_dir, force=True)
|
|
739
773
|
|
|
740
|
-
def
|
|
774
|
+
def stop_deployment(self, build_dir: Path, force: bool = False) -> None:
|
|
741
775
|
"""Stop the deployment."""
|
|
742
776
|
if (
|
|
743
777
|
self.get_state(build_dir=build_dir) in [States.STARTING, States.STOPPING]
|
|
@@ -760,14 +794,14 @@ class DeploymentManager:
|
|
|
760
794
|
deployment_manager = DeploymentManager()
|
|
761
795
|
|
|
762
796
|
|
|
763
|
-
def run_host_deployment(build_dir: Path) -> None:
|
|
797
|
+
def run_host_deployment(build_dir: Path, password: str) -> None:
|
|
764
798
|
"""Run host deployment."""
|
|
765
|
-
deployment_manager.run_deployment(build_dir=build_dir)
|
|
799
|
+
deployment_manager.run_deployment(build_dir=build_dir, password=password)
|
|
766
800
|
|
|
767
801
|
|
|
768
802
|
def stop_host_deployment(build_dir: Path) -> None:
|
|
769
803
|
"""Stop host deployment."""
|
|
770
|
-
deployment_manager.
|
|
804
|
+
deployment_manager.stop_deployment(build_dir=build_dir)
|
|
771
805
|
|
|
772
806
|
|
|
773
807
|
def stop_deployment_manager() -> None:
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/manage.py
RENAMED
|
@@ -2232,10 +2232,11 @@ class ServiceManager:
|
|
|
2232
2232
|
use_kubernetes=use_kubernetes,
|
|
2233
2233
|
force=True,
|
|
2234
2234
|
chain=chain or service.home_chain,
|
|
2235
|
+
password=self.wallet_manager.password,
|
|
2235
2236
|
)
|
|
2236
2237
|
if build_only:
|
|
2237
2238
|
return deployment
|
|
2238
|
-
deployment.start(use_docker=use_docker)
|
|
2239
|
+
deployment.start(password=self.wallet_manager.password, use_docker=use_docker)
|
|
2239
2240
|
return deployment
|
|
2240
2241
|
|
|
2241
2242
|
def stop_service_locally(
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/service.py
RENAMED
|
@@ -97,6 +97,7 @@ from operate.operate_types import (
|
|
|
97
97
|
from operate.resource import LocalResource
|
|
98
98
|
from operate.services.deployment_runner import run_host_deployment, stop_host_deployment
|
|
99
99
|
from operate.services.utils import tendermint
|
|
100
|
+
from operate.utils import unrecoverable_delete
|
|
100
101
|
from operate.utils.gnosis import get_asset_balance
|
|
101
102
|
from operate.utils.ssl import create_ssl_certificate
|
|
102
103
|
|
|
@@ -394,7 +395,7 @@ class Deployment(LocalResource):
|
|
|
394
395
|
if source_path.exists():
|
|
395
396
|
shutil.copy(source_path, destination_path)
|
|
396
397
|
|
|
397
|
-
def _build_kubernetes(self, force: bool = True) -> None:
|
|
398
|
+
def _build_kubernetes(self, password: str, force: bool = True) -> None:
|
|
398
399
|
"""Build kubernetes deployment."""
|
|
399
400
|
k8s_build = self.path / DEPLOYMENT_DIR / "abci_build_k8s"
|
|
400
401
|
if k8s_build.exists() and force:
|
|
@@ -402,11 +403,23 @@ class Deployment(LocalResource):
|
|
|
402
403
|
mkdirs(build_dir=k8s_build)
|
|
403
404
|
|
|
404
405
|
service = Service.load(path=self.path)
|
|
406
|
+
keys_file = self.path / DEFAULT_KEYS_FILE
|
|
407
|
+
keys_file.write_text(
|
|
408
|
+
json.dumps(
|
|
409
|
+
[
|
|
410
|
+
KeysManager().get(address).get_decrypted(password)
|
|
411
|
+
for address in service.agent_addresses
|
|
412
|
+
],
|
|
413
|
+
indent=4,
|
|
414
|
+
),
|
|
415
|
+
encoding="utf-8",
|
|
416
|
+
)
|
|
405
417
|
builder = ServiceBuilder.from_dir(
|
|
406
418
|
path=service.package_absolute_path,
|
|
407
|
-
keys_file=
|
|
419
|
+
keys_file=keys_file,
|
|
408
420
|
number_of_agents=len(service.agent_addresses),
|
|
409
421
|
)
|
|
422
|
+
unrecoverable_delete(keys_file)
|
|
410
423
|
builder.deplopyment_type = KubernetesGenerator.deployment_type
|
|
411
424
|
(
|
|
412
425
|
KubernetesGenerator(
|
|
@@ -424,6 +437,7 @@ class Deployment(LocalResource):
|
|
|
424
437
|
|
|
425
438
|
def _build_docker(
|
|
426
439
|
self,
|
|
440
|
+
password: str,
|
|
427
441
|
force: bool = True,
|
|
428
442
|
chain: t.Optional[str] = None,
|
|
429
443
|
) -> None:
|
|
@@ -448,7 +462,7 @@ class Deployment(LocalResource):
|
|
|
448
462
|
keys_file.write_text(
|
|
449
463
|
json.dumps(
|
|
450
464
|
[
|
|
451
|
-
KeysManager().get(address).
|
|
465
|
+
KeysManager().get(address).get_decrypted(password)
|
|
452
466
|
for address in service.agent_addresses
|
|
453
467
|
],
|
|
454
468
|
indent=4,
|
|
@@ -461,6 +475,7 @@ class Deployment(LocalResource):
|
|
|
461
475
|
keys_file=keys_file,
|
|
462
476
|
number_of_agents=len(service.agent_addresses),
|
|
463
477
|
)
|
|
478
|
+
unrecoverable_delete(keys_file)
|
|
464
479
|
builder.deplopyment_type = DockerComposeGenerator.deployment_type
|
|
465
480
|
builder.try_update_abci_connection_params()
|
|
466
481
|
|
|
@@ -614,6 +629,7 @@ class Deployment(LocalResource):
|
|
|
614
629
|
|
|
615
630
|
def build(
|
|
616
631
|
self,
|
|
632
|
+
password: str,
|
|
617
633
|
use_docker: bool = False,
|
|
618
634
|
use_kubernetes: bool = False,
|
|
619
635
|
force: bool = True,
|
|
@@ -649,9 +665,9 @@ class Deployment(LocalResource):
|
|
|
649
665
|
)
|
|
650
666
|
service.consume_env_variables()
|
|
651
667
|
if use_docker:
|
|
652
|
-
self._build_docker(force=force, chain=chain)
|
|
668
|
+
self._build_docker(password=password, force=force, chain=chain)
|
|
653
669
|
if use_kubernetes:
|
|
654
|
-
self._build_kubernetes(force=force)
|
|
670
|
+
self._build_kubernetes(password=password, force=force)
|
|
655
671
|
else:
|
|
656
672
|
ssl_key_path, ssl_cert_path = create_ssl_certificate(
|
|
657
673
|
ssl_dir=service.path / DEPLOYMENT_DIR / "ssl"
|
|
@@ -668,7 +684,7 @@ class Deployment(LocalResource):
|
|
|
668
684
|
os.environ.clear()
|
|
669
685
|
os.environ.update(original_env)
|
|
670
686
|
|
|
671
|
-
def start(self, use_docker: bool = False) -> None:
|
|
687
|
+
def start(self, password: str, use_docker: bool = False) -> None:
|
|
672
688
|
"""Start the service"""
|
|
673
689
|
if self.status != DeploymentStatus.BUILT:
|
|
674
690
|
raise NotAllowed(
|
|
@@ -686,7 +702,9 @@ class Deployment(LocalResource):
|
|
|
686
702
|
project_name=self.path.name,
|
|
687
703
|
)
|
|
688
704
|
else:
|
|
689
|
-
run_host_deployment(
|
|
705
|
+
run_host_deployment(
|
|
706
|
+
build_dir=self.path / "deployment", password=password
|
|
707
|
+
)
|
|
690
708
|
except Exception:
|
|
691
709
|
self.status = DeploymentStatus.BUILT
|
|
692
710
|
self.store()
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
"""Helper utilities."""
|
|
21
21
|
|
|
22
|
+
import os
|
|
23
|
+
import platform
|
|
22
24
|
import shutil
|
|
23
25
|
import time
|
|
24
26
|
import typing as t
|
|
@@ -107,3 +109,47 @@ def subtract_dicts(
|
|
|
107
109
|
else:
|
|
108
110
|
result[key] = max((va or 0) - (vb or 0), 0) # type: ignore
|
|
109
111
|
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def safe_file_operation(operation: t.Callable, *args: t.Any, **kwargs: t.Any) -> None:
|
|
115
|
+
"""Safely perform file operation with retries on Windows."""
|
|
116
|
+
max_retries = 3 if platform.system() == "Windows" else 1
|
|
117
|
+
|
|
118
|
+
for attempt in range(max_retries):
|
|
119
|
+
try:
|
|
120
|
+
operation(*args, **kwargs)
|
|
121
|
+
return
|
|
122
|
+
except (PermissionError, FileNotFoundError, OSError) as e:
|
|
123
|
+
if attempt == max_retries - 1:
|
|
124
|
+
raise e
|
|
125
|
+
|
|
126
|
+
if platform.system() == "Windows":
|
|
127
|
+
# On Windows, wait a bit and retry
|
|
128
|
+
time.sleep(0.1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def unrecoverable_delete(file_path: Path, passes: int = 3) -> None:
|
|
132
|
+
"""Delete a file unrecoverably."""
|
|
133
|
+
if not file_path.exists():
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
if not file_path.is_file():
|
|
137
|
+
raise ValueError(f"{file_path} is not a file")
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
file_size = os.path.getsize(file_path)
|
|
141
|
+
|
|
142
|
+
with open(file_path, "r+b") as f:
|
|
143
|
+
for _ in range(passes):
|
|
144
|
+
# Overwrite with random bytes
|
|
145
|
+
f.seek(0)
|
|
146
|
+
random_data = os.urandom(file_size)
|
|
147
|
+
f.write(random_data)
|
|
148
|
+
f.flush() # Ensure data is written to disk
|
|
149
|
+
|
|
150
|
+
# Finally, delete the file
|
|
151
|
+
safe_file_operation(os.remove, file_path)
|
|
152
|
+
except PermissionError:
|
|
153
|
+
print(f"Permission denied to securely delete file '{file_path}'.")
|
|
154
|
+
except Exception as e: # pylint: disable=broad-except
|
|
155
|
+
print(f"Error during secure deletion of '{file_path}': {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/account/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/bridge/bridge_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/data/contracts/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/ledger/__init__.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/ledger/profiles.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/operate_http/__init__.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/operate_http/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/analyse_logs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/run_service.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/stop_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/quickstart/utils.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/__init__.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/agent_runner.py
RENAMED
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/health_checker.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/protocol.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/utils/__init__.py
RENAMED
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/services/utils/mech.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/utils/single_instance.py
RENAMED
|
File without changes
|
|
File without changes
|
{olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.0}/operate/wallet/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|