Skip to content

Commit 20a957a

Browse files
authored
Merge pull request #40 from valory-xyz/feat/multi-agent-qs
Feat: Support multiple agents from single `.operate`
2 parents b1977cd + 5f0f82b commit 20a957a

7 files changed

+149
-96
lines changed

operate/quickstart/claim_staking_rewards.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
import logging
2323
import os
2424
import warnings
25-
from typing import TYPE_CHECKING
25+
from typing import TYPE_CHECKING, cast
2626

27-
from operate.constants import OPERATE_HOME, SAFE_WEBAPP_URL
27+
from operate.constants import SAFE_WEBAPP_URL
2828
from operate.operate_types import LedgerType
2929
from operate.quickstart.run_service import (
3030
ask_password_if_needed,
@@ -49,8 +49,10 @@ def claim_staking_rewards(operate: "OperateApp", config_path: str) -> None:
4949
print_section(f"Claim staking rewards for {template['name']}")
5050

5151
# check if agent was started before
52-
path = OPERATE_HOME / "local_config.json"
53-
if not path.exists():
52+
config = load_local_config(
53+
operate=operate, service_name=cast(str, template["name"])
54+
)
55+
if not config.path.exists():
5456
print("No previous agent setup found. Exiting.")
5557
return
5658

@@ -65,14 +67,14 @@ def claim_staking_rewards(operate: "OperateApp", config_path: str) -> None:
6567

6668
print("")
6769

68-
config = configure_local_config(template)
70+
config = configure_local_config(template, operate)
6971
manager = operate.service_manager()
7072
service = get_service(manager, template)
7173
ask_password_if_needed(operate, config)
7274

7375
# reload manger and config after setting operate.password
7476
manager = operate.service_manager()
75-
config = load_local_config()
77+
config = load_local_config(operate=operate, service_name=cast(str, service.name))
7678
assert ( # nosec
7779
config.principal_chain is not None
7880
), "Principal chain not set in quickstart config"

operate/quickstart/reset_staking.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@
2020

2121
import json
2222
import os
23-
from typing import TYPE_CHECKING
23+
from typing import TYPE_CHECKING, cast
2424

25-
from operate.constants import OPERATE_HOME
2625
from operate.ledger.profiles import STAKING
2726
from operate.operate_types import Chain
2827
from operate.quickstart.run_service import (
@@ -31,6 +30,7 @@
3130
configure_local_config,
3231
ensure_enough_funds,
3332
get_service,
33+
load_local_config,
3434
)
3535
from operate.quickstart.utils import ask_yes_or_no, print_section, print_title
3636

@@ -47,12 +47,14 @@ def reset_staking(operate: "OperateApp", config_path: str) -> None:
4747
print_title("Reset your staking program preference")
4848

4949
# check if agent was started before
50-
path = OPERATE_HOME / "local_config.json"
51-
if not path.exists():
50+
config = load_local_config(
51+
operate=operate, service_name=cast(str, template["name"])
52+
)
53+
if not config.path.exists():
5254
print("No previous agent setup found. Exiting.")
5355
return
5456

55-
config = configure_local_config(template)
57+
config = configure_local_config(template, operate)
5658
assert ( # nosec
5759
config.principal_chain is not None
5860
), "Principal chain not set in quickstart config"
@@ -126,7 +128,7 @@ def reset_staking(operate: "OperateApp", config_path: str) -> None:
126128
# Update local config and service template
127129
config.staking_program_id = NO_STAKING_PROGRAM_ID
128130
config.store()
129-
config = configure_local_config(template)
131+
config = configure_local_config(template, operate)
130132
manager.update(
131133
service_config_id=manager.json[0]["service_config_id"],
132134
service_template=template,

operate/quickstart/run_service.py

+77-65
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@
2020

2121
import json
2222
import os
23+
import shutil
2324
import textwrap
2425
import time
2526
import typing as t
2627
import warnings
27-
from dataclasses import dataclass
28-
from pathlib import Path
2928

3029
import requests
3130
from aea.crypto.registries import make_ledger_api
@@ -47,14 +46,14 @@
4746
)
4847
from operate.quickstart.utils import (
4948
CHAIN_TO_METADATA,
49+
QuickstartConfig,
5050
ask_or_get_from_env,
5151
check_rpc,
5252
print_box,
5353
print_section,
5454
print_title,
5555
wei_to_token,
5656
)
57-
from operate.resource import LocalResource, deserialize
5857
from operate.services.manage import ServiceManager
5958
from operate.services.service import NON_EXISTENT_MULTISIG, Service
6059
from operate.utils.gnosis import get_asset_balance
@@ -107,37 +106,6 @@
107106
}
108107

109108

110-
@dataclass
111-
class QuickstartConfig(LocalResource):
112-
"""Local configuration."""
113-
114-
path: Path
115-
rpc: t.Optional[t.Dict[str, str]] = None
116-
password_migrated: t.Optional[bool] = None
117-
staking_program_id: t.Optional[str] = None
118-
principal_chain: t.Optional[str] = None
119-
user_provided_args: t.Optional[t.Dict[str, str]] = None
120-
121-
@classmethod
122-
def from_json(cls, obj: t.Dict) -> "LocalResource":
123-
"""Load LocalResource from json."""
124-
kwargs = {}
125-
for pname, ptype in cls.__annotations__.items():
126-
if pname.startswith("_"):
127-
continue
128-
129-
# allow for optional types
130-
is_optional_type = t.get_origin(ptype) is t.Union and type(
131-
None
132-
) in t.get_args(ptype)
133-
value = obj.get(pname, None)
134-
if is_optional_type and value is None:
135-
continue
136-
137-
kwargs[pname] = deserialize(obj=obj[pname], otype=ptype)
138-
return cls(**kwargs)
139-
140-
141109
def ask_confirm_password() -> str:
142110
"""Ask for password confirmation."""
143111
while True:
@@ -154,20 +122,64 @@ def ask_confirm_password() -> str:
154122
print("Passwords do not match!")
155123

156124

157-
def load_local_config() -> QuickstartConfig:
125+
def load_local_config(operate: "OperateApp", service_name: str) -> QuickstartConfig:
158126
"""Load the local quickstart configuration."""
159-
path = OPERATE_HOME / "local_config.json"
160-
if path.exists():
161-
config = QuickstartConfig.load(path)
127+
old_path = OPERATE_HOME / "local_config.json"
128+
if old_path.exists(): # Migrate to new naming scheme
129+
config = t.cast(QuickstartConfig, QuickstartConfig.load(old_path))
130+
service_manager = operate.service_manager()
131+
services = service_manager.json
132+
if config.staking_program_id == NO_STAKING_PROGRAM_ID:
133+
for service in services:
134+
if service["name"] == service_name:
135+
config.path = (
136+
config.path.parent / f"{service_name}-quickstart-config.json"
137+
)
138+
shutil.move(old_path, config.path)
139+
break
140+
else:
141+
for staking_program, agent_id in QS_STAKING_PROGRAMS[
142+
Chain.from_string(config.principal_chain)
143+
].items():
144+
if staking_program == config.staking_program_id:
145+
staking_agent_id = agent_id
146+
break
147+
else:
148+
raise ValueError(
149+
f"Staking program {config.staking_program_id} not found in {QS_STAKING_PROGRAMS[config.principal_chain]}.\n"
150+
"Please resolve manually!"
151+
)
152+
153+
for service in services:
154+
if (
155+
staking_agent_id
156+
== service["chain_configs"][config.principal_chain]["chain_data"][
157+
"user_params"
158+
]["agent_id"]
159+
):
160+
config.path = (
161+
config.path.parent / f"{service['name']}-quickstart-config.json"
162+
)
163+
shutil.move(old_path, config.path)
164+
break
165+
166+
for qs_config in OPERATE_HOME.glob("*-quickstart-config.json"):
167+
if f"{service_name}-quickstart-config.json" == qs_config.name:
168+
config = t.cast(QuickstartConfig, QuickstartConfig.load(qs_config))
169+
break
162170
else:
163-
config = QuickstartConfig(path)
171+
config = QuickstartConfig(
172+
OPERATE_HOME / f"{service_name}-quickstart-config.json"
173+
)
164174

165-
return config # type: ignore[return-value]
175+
return config
166176

167177

168-
def configure_local_config(template: ServiceTemplate) -> QuickstartConfig:
178+
def configure_local_config(
179+
template: ServiceTemplate, operate: "OperateApp"
180+
) -> QuickstartConfig:
169181
"""Configure local quickstart configuration."""
170-
config = load_local_config()
182+
config = load_local_config(operate=operate, service_name=template["name"])
171183

172184
if config.rpc is None:
173185
config.rpc = {}
@@ -184,8 +196,7 @@ def configure_local_config(template: ServiceTemplate) -> QuickstartConfig:
184196
if config.password_migrated is None:
185197
config.password_migrated = False
186198

187-
if config.principal_chain is None:
188-
config.principal_chain = template["home_chain"]
199+
config.principal_chain = template["home_chain"]
189200

190201
agent_id = template["configurations"][config.principal_chain]["agent_id"]
191202
home_chain = Chain.from_string(config.principal_chain)
@@ -197,7 +208,7 @@ def configure_local_config(template: ServiceTemplate) -> QuickstartConfig:
197208
)
198209
ledger_api = make_ledger_api(
199210
LedgerType.ETHEREUM.lower(),
200-
address=config.rpc[config.principal_chain],
211+
address=config.rpc[config.principal_chain], # type: ignore[index]
201212
chain_id=home_chain.id,
202213
)
203214

@@ -391,22 +402,25 @@ def ask_password_if_needed(operate: "OperateApp", config: QuickstartConfig) -> N
391402

392403
def get_service(manager: ServiceManager, template: ServiceTemplate) -> Service:
393404
"""Get service."""
394-
if len(manager.json) > 0:
395-
old_hash = manager.json[0]["hash"]
396-
if old_hash == template["hash"]:
397-
print(f'Loading service {template["hash"]}')
398-
service = manager.load(
399-
service_config_id=manager.json[0]["service_config_id"],
400-
)
401-
else:
402-
print(f"Updating service from {old_hash} to " + template["hash"])
403-
service = manager.update(
404-
service_config_id=manager.json[0]["service_config_id"],
405-
service_template=template,
406-
)
405+
for service in manager.json:
406+
if service["name"] == template["name"]:
407+
old_hash = service["hash"]
408+
if old_hash == template["hash"]:
409+
print(f'Loading service {template["hash"]}')
410+
service = manager.load(
411+
service_config_id=service["service_config_id"],
412+
)
413+
else:
414+
print(f"Updating service from {old_hash} to " + template["hash"])
415+
service = manager.update(
416+
service_config_id=service["service_config_id"],
417+
service_template=template,
418+
)
407419

408-
service.env_variables = template["env_variables"]
409-
service.store()
420+
service.env_variables = template["env_variables"]
421+
service.update_user_params_from_template(service_template=template)
422+
service.store()
423+
break
410424
else:
411425
print(f'Creating service {template["hash"]}')
412426
service = manager.load_or_create(
@@ -475,7 +489,7 @@ def ensure_enough_funds(operate: "OperateApp", service: Service) -> None:
475489
wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM)
476490

477491
manager = operate.service_manager()
478-
config = load_local_config()
492+
config = load_local_config(operate=operate, service_name=t.cast(str, service.name))
479493

480494
for chain_name, chain_config in service.chain_configs.items():
481495
print_section(f"[{chain_name}] Set up the service in the Olas Protocol")
@@ -636,19 +650,17 @@ def run_service(
636650

637651
print_title(f"{template['name']} quickstart")
638652

639-
operate.service_manager().log_directories()
640653
operate.service_manager().migrate_service_configs()
641-
operate.service_manager().log_directories()
642654
operate.wallet_manager.migrate_wallet_configs()
643655

644-
config = configure_local_config(template)
656+
config = configure_local_config(template, operate)
645657
manager = operate.service_manager()
646658
service = get_service(manager, template)
647659
ask_password_if_needed(operate, config)
648660

649661
# reload manger and config after setting operate.password
650662
manager = operate.service_manager()
651-
config = load_local_config()
663+
config = load_local_config(operate=operate, service_name=t.cast(str, service.name))
652664
ensure_enough_funds(operate, service)
653665

654666
print_box("PLEASE, DO NOT INTERRUPT THIS PROCESS.")

operate/quickstart/stop_service.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020

2121
import json
2222
import warnings
23-
from typing import TYPE_CHECKING
23+
from typing import TYPE_CHECKING, cast
2424

25-
from operate.constants import OPERATE_HOME
26-
from operate.quickstart.run_service import configure_local_config, get_service
25+
from operate.quickstart.run_service import (
26+
configure_local_config,
27+
get_service,
28+
load_local_config,
29+
)
2730
from operate.quickstart.utils import print_section, print_title
2831

2932

@@ -42,12 +45,14 @@ def stop_service(operate: "OperateApp", config_path: str) -> None:
4245
print_title(f"Stop {template['name']} Quickstart")
4346

4447
# check if agent was started before
45-
path = OPERATE_HOME / "local_config.json"
46-
if not path.exists():
48+
config = load_local_config(
49+
operate=operate, service_name=cast(str, template["name"])
50+
)
51+
if not config.path.exists():
4752
print("No previous agent setup found. Exiting.")
4853
return
4954

50-
configure_local_config(template)
55+
configure_local_config(template, operate)
5156
manager = operate.service_manager()
5257
service = get_service(manager, template)
5358
manager.stop_service_locally(

operate/quickstart/terminate_on_chain_service.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818
"""Terminate on-chain service."""
1919

2020
import json
21-
from typing import TYPE_CHECKING
21+
from typing import TYPE_CHECKING, cast
2222

23-
from operate.constants import OPERATE_HOME
2423
from operate.operate_types import OnChainState
2524
from operate.quickstart.run_service import (
2625
ask_password_if_needed,
2726
configure_local_config,
2827
ensure_enough_funds,
2928
get_service,
29+
load_local_config,
3030
)
3131
from operate.quickstart.utils import ask_yes_or_no, print_section, print_title
3232

@@ -44,8 +44,10 @@ def terminate_service(operate: "OperateApp", config_path: str) -> None:
4444
print_title(f"Terminate {template['name']} on-chain service")
4545

4646
# check if agent was started before
47-
path = OPERATE_HOME / "local_config.json"
48-
if not path.exists():
47+
config = load_local_config(
48+
operate=operate, service_name=cast(str, template["name"])
49+
)
50+
if not config.path.exists():
4951
print("No previous agent setup found. Exiting.")
5052
return
5153

@@ -56,7 +58,7 @@ def terminate_service(operate: "OperateApp", config_path: str) -> None:
5658
print("Cancelled.")
5759
return
5860

59-
config = configure_local_config(template)
61+
config = configure_local_config(template, operate)
6062
ask_password_if_needed(operate, config)
6163
manager = operate.service_manager()
6264
service = get_service(manager, template)

0 commit comments

Comments
 (0)