olas-operate-middleware 0.11.5__py3-none-any.whl → 0.12.1__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.11.5.dist-info → olas_operate_middleware-0.12.1.dist-info}/METADATA +1 -1
- {olas_operate_middleware-0.11.5.dist-info → olas_operate_middleware-0.12.1.dist-info}/RECORD +19 -19
- operate/bridge/bridge_manager.py +52 -42
- operate/bridge/providers/native_bridge_provider.py +26 -13
- operate/cli.py +14 -8
- operate/data/contracts/l1_standard_bridge/contract.py +20 -0
- operate/data/contracts/l1_standard_bridge/contract.yaml +1 -1
- operate/keys.py +75 -70
- operate/migration.py +66 -1
- operate/quickstart/reset_password.py +3 -7
- operate/resource.py +9 -25
- operate/services/deployment_runner.py +61 -27
- operate/services/manage.py +2 -1
- operate/services/protocol.py +24 -13
- operate/services/service.py +25 -7
- operate/utils/__init__.py +46 -0
- {olas_operate_middleware-0.11.5.dist-info → olas_operate_middleware-0.12.1.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.11.5.dist-info → olas_operate_middleware-0.12.1.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.11.5.dist-info → olas_operate_middleware-0.12.1.dist-info}/licenses/LICENSE +0 -0
operate/migration.py
CHANGED
|
@@ -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!")
|
operate/resource.py
CHANGED
|
@@ -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:
|
operate/services/manage.py
CHANGED
|
@@ -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(
|
operate/services/protocol.py
CHANGED
|
@@ -28,7 +28,7 @@ import os
|
|
|
28
28
|
import tempfile
|
|
29
29
|
import typing as t
|
|
30
30
|
from enum import Enum
|
|
31
|
-
from functools import cache
|
|
31
|
+
from functools import cache, cached_property
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Optional, Union, cast
|
|
34
34
|
|
|
@@ -780,6 +780,17 @@ class _ChainUtil:
|
|
|
780
780
|
rpc=self.rpc,
|
|
781
781
|
)
|
|
782
782
|
|
|
783
|
+
@cached_property
|
|
784
|
+
def service_manager_address(self) -> str: # TODO: backport to OA
|
|
785
|
+
"""Get service manager contract address."""
|
|
786
|
+
service_registry = registry_contracts.service_registry.get_instance(
|
|
787
|
+
ledger_api=self.ledger_api,
|
|
788
|
+
contract_address=CONTRACTS[OperateChain(self.chain_type.value)][
|
|
789
|
+
"service_registry"
|
|
790
|
+
],
|
|
791
|
+
)
|
|
792
|
+
return service_registry.functions.manager().call()
|
|
793
|
+
|
|
783
794
|
@property
|
|
784
795
|
def service_manager_instance(self) -> Contract:
|
|
785
796
|
"""Load service manager contract instance."""
|
|
@@ -788,7 +799,7 @@ class _ChainUtil:
|
|
|
788
799
|
)
|
|
789
800
|
instance = self.ledger_api.get_contract_instance(
|
|
790
801
|
contract_interface,
|
|
791
|
-
self.
|
|
802
|
+
self.service_manager_address,
|
|
792
803
|
)
|
|
793
804
|
return instance
|
|
794
805
|
|
|
@@ -1334,7 +1345,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1334
1345
|
)
|
|
1335
1346
|
|
|
1336
1347
|
return {
|
|
1337
|
-
"to": self.
|
|
1348
|
+
"to": self.service_manager_address,
|
|
1338
1349
|
"data": txd[2:],
|
|
1339
1350
|
"operation": MultiSendOperation.CALL,
|
|
1340
1351
|
"value": 0,
|
|
@@ -1366,7 +1377,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1366
1377
|
"""Get activate tx data."""
|
|
1367
1378
|
instance = registry_contracts.service_manager.get_instance(
|
|
1368
1379
|
ledger_api=self.ledger_api,
|
|
1369
|
-
contract_address=self.
|
|
1380
|
+
contract_address=self.service_manager_address,
|
|
1370
1381
|
)
|
|
1371
1382
|
txd = instance.encodeABI(
|
|
1372
1383
|
fn_name="activateRegistration",
|
|
@@ -1374,7 +1385,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1374
1385
|
)
|
|
1375
1386
|
return {
|
|
1376
1387
|
"from": self.safe,
|
|
1377
|
-
"to": self.
|
|
1388
|
+
"to": self.service_manager_address,
|
|
1378
1389
|
"data": txd[2:],
|
|
1379
1390
|
"operation": MultiSendOperation.CALL,
|
|
1380
1391
|
"value": cost_of_bond,
|
|
@@ -1390,7 +1401,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1390
1401
|
"""Get register instances tx data."""
|
|
1391
1402
|
instance = registry_contracts.service_manager.get_instance(
|
|
1392
1403
|
ledger_api=self.ledger_api,
|
|
1393
|
-
contract_address=self.
|
|
1404
|
+
contract_address=self.service_manager_address,
|
|
1394
1405
|
)
|
|
1395
1406
|
txd = instance.encodeABI(
|
|
1396
1407
|
fn_name="registerAgents",
|
|
@@ -1402,7 +1413,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1402
1413
|
)
|
|
1403
1414
|
return {
|
|
1404
1415
|
"from": self.safe,
|
|
1405
|
-
"to": self.
|
|
1416
|
+
"to": self.service_manager_address,
|
|
1406
1417
|
"data": txd[2:],
|
|
1407
1418
|
"operation": MultiSendOperation.CALL,
|
|
1408
1419
|
"value": cost_of_bond,
|
|
@@ -1418,7 +1429,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1418
1429
|
"""Get the deploy data instructions for a safe"""
|
|
1419
1430
|
registry_instance = registry_contracts.service_manager.get_instance(
|
|
1420
1431
|
ledger_api=self.ledger_api,
|
|
1421
|
-
contract_address=self.
|
|
1432
|
+
contract_address=self.service_manager_address,
|
|
1422
1433
|
)
|
|
1423
1434
|
approve_hash_message = None
|
|
1424
1435
|
if reuse_multisig:
|
|
@@ -1476,7 +1487,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1476
1487
|
],
|
|
1477
1488
|
)
|
|
1478
1489
|
deploy_message = {
|
|
1479
|
-
"to": self.
|
|
1490
|
+
"to": self.service_manager_address,
|
|
1480
1491
|
"data": deploy_data[2:],
|
|
1481
1492
|
"operation": MultiSendOperation.CALL,
|
|
1482
1493
|
"value": 0,
|
|
@@ -1665,14 +1676,14 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1665
1676
|
"""Get terminate tx data."""
|
|
1666
1677
|
instance = registry_contracts.service_manager.get_instance(
|
|
1667
1678
|
ledger_api=self.ledger_api,
|
|
1668
|
-
contract_address=self.
|
|
1679
|
+
contract_address=self.service_manager_address,
|
|
1669
1680
|
)
|
|
1670
1681
|
txd = instance.encodeABI(
|
|
1671
1682
|
fn_name="terminate",
|
|
1672
1683
|
args=[service_id],
|
|
1673
1684
|
)
|
|
1674
1685
|
return {
|
|
1675
|
-
"to": self.
|
|
1686
|
+
"to": self.service_manager_address,
|
|
1676
1687
|
"data": txd[2:],
|
|
1677
1688
|
"operation": MultiSendOperation.CALL,
|
|
1678
1689
|
"value": 0,
|
|
@@ -1682,14 +1693,14 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1682
1693
|
"""Get unbond tx data."""
|
|
1683
1694
|
instance = registry_contracts.service_manager.get_instance(
|
|
1684
1695
|
ledger_api=self.ledger_api,
|
|
1685
|
-
contract_address=self.
|
|
1696
|
+
contract_address=self.service_manager_address,
|
|
1686
1697
|
)
|
|
1687
1698
|
txd = instance.encodeABI(
|
|
1688
1699
|
fn_name="unbond",
|
|
1689
1700
|
args=[service_id],
|
|
1690
1701
|
)
|
|
1691
1702
|
return {
|
|
1692
|
-
"to": self.
|
|
1703
|
+
"to": self.service_manager_address,
|
|
1693
1704
|
"data": txd[2:],
|
|
1694
1705
|
"operation": MultiSendOperation.CALL,
|
|
1695
1706
|
"value": 0,
|
operate/services/service.py
CHANGED
|
@@ -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()
|