olas-operate-middleware 0.8.2__py3-none-any.whl → 0.10.0__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.8.2.dist-info → olas_operate_middleware-0.10.0.dist-info}/METADATA +2 -2
- {olas_operate_middleware-0.8.2.dist-info → olas_operate_middleware-0.10.0.dist-info}/RECORD +34 -34
- operate/bridge/bridge_manager.py +5 -6
- operate/bridge/providers/native_bridge_provider.py +1 -1
- operate/bridge/providers/provider.py +4 -5
- operate/bridge/providers/relay_provider.py +1 -1
- operate/cli.py +128 -48
- operate/constants.py +9 -9
- operate/keys.py +26 -14
- operate/ledger/__init__.py +4 -4
- operate/ledger/profiles.py +9 -11
- operate/migration.py +326 -0
- operate/operate_types.py +9 -27
- operate/quickstart/analyse_logs.py +3 -6
- operate/quickstart/claim_staking_rewards.py +1 -4
- operate/quickstart/reset_configs.py +0 -3
- operate/quickstart/reset_password.py +0 -3
- operate/quickstart/reset_staking.py +3 -5
- operate/quickstart/run_service.py +5 -7
- operate/quickstart/stop_service.py +3 -4
- operate/quickstart/terminate_on_chain_service.py +1 -4
- operate/quickstart/utils.py +4 -7
- operate/resource.py +37 -5
- operate/services/deployment_runner.py +170 -38
- operate/services/health_checker.py +5 -8
- operate/services/manage.py +103 -164
- operate/services/protocol.py +5 -5
- operate/services/service.py +42 -242
- operate/utils/__init__.py +44 -0
- operate/utils/gnosis.py +25 -17
- operate/wallet/master.py +20 -24
- {olas_operate_middleware-0.8.2.dist-info → olas_operate_middleware-0.10.0.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.8.2.dist-info → olas_operate_middleware-0.10.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.8.2.dist-info → olas_operate_middleware-0.10.0.dist-info}/entry_points.txt +0 -0
operate/services/service.py
CHANGED
|
@@ -45,7 +45,9 @@ from aea.configurations.constants import (
|
|
|
45
45
|
from aea.helpers.yaml_utils import yaml_dump, yaml_load, yaml_load_all
|
|
46
46
|
from aea_cli_ipfs.ipfs_utils import IPFSTool
|
|
47
47
|
from autonomy.cli.helpers.deployment import run_deployment, stop_deployment
|
|
48
|
+
from autonomy.configurations.constants import DEFAULT_SERVICE_CONFIG_FILE
|
|
48
49
|
from autonomy.configurations.loader import apply_env_variables, load_service_config
|
|
50
|
+
from autonomy.constants import DEFAULT_KEYS_FILE, DOCKER_COMPOSE_YAML
|
|
49
51
|
from autonomy.deploy.base import BaseDeploymentGenerator
|
|
50
52
|
from autonomy.deploy.base import ServiceBuilder as BaseServiceBuilder
|
|
51
53
|
from autonomy.deploy.constants import (
|
|
@@ -61,14 +63,8 @@ from autonomy.deploy.generators.docker_compose.base import DockerComposeGenerato
|
|
|
61
63
|
from autonomy.deploy.generators.kubernetes.base import KubernetesGenerator
|
|
62
64
|
from docker import from_env
|
|
63
65
|
|
|
64
|
-
from operate.constants import
|
|
65
|
-
|
|
66
|
-
DEPLOYMENT_JSON,
|
|
67
|
-
DOCKER_COMPOSE_YAML,
|
|
68
|
-
KEYS_JSON,
|
|
69
|
-
ZERO_ADDRESS,
|
|
70
|
-
)
|
|
71
|
-
from operate.keys import Keys
|
|
66
|
+
from operate.constants import CONFIG_JSON, DEPLOYMENT_DIR, DEPLOYMENT_JSON
|
|
67
|
+
from operate.keys import KeysManager
|
|
72
68
|
from operate.operate_http.exceptions import NotAllowed
|
|
73
69
|
from operate.operate_types import (
|
|
74
70
|
Chain,
|
|
@@ -96,11 +92,10 @@ from operate.utils.ssl import create_ssl_certificate
|
|
|
96
92
|
SAFE_CONTRACT_ADDRESS = "safe_contract_address"
|
|
97
93
|
ALL_PARTICIPANTS = "all_participants"
|
|
98
94
|
CONSENSUS_THRESHOLD = "consensus_threshold"
|
|
99
|
-
|
|
100
|
-
SERVICE_CONFIG_VERSION = 6
|
|
95
|
+
SERVICE_CONFIG_VERSION = 8
|
|
101
96
|
SERVICE_CONFIG_PREFIX = "sc-"
|
|
102
97
|
|
|
103
|
-
NON_EXISTENT_MULTISIG =
|
|
98
|
+
NON_EXISTENT_MULTISIG = None
|
|
104
99
|
NON_EXISTENT_TOKEN = -1
|
|
105
100
|
|
|
106
101
|
DEFAULT_TRADER_ENV_VARS = {
|
|
@@ -410,7 +405,7 @@ class Deployment(LocalResource):
|
|
|
410
405
|
nodes: DeployedNodes
|
|
411
406
|
path: Path
|
|
412
407
|
|
|
413
|
-
_file =
|
|
408
|
+
_file = DEPLOYMENT_JSON
|
|
414
409
|
|
|
415
410
|
@staticmethod
|
|
416
411
|
def new(path: Path) -> "Deployment":
|
|
@@ -435,14 +430,14 @@ class Deployment(LocalResource):
|
|
|
435
430
|
|
|
436
431
|
def copy_previous_agent_run_logs(self) -> None:
|
|
437
432
|
"""Copy previous agent logs."""
|
|
438
|
-
source_path = self.path /
|
|
433
|
+
source_path = self.path / DEPLOYMENT_DIR / "agent" / "log.txt"
|
|
439
434
|
destination_path = self.path / "prev_log.txt"
|
|
440
435
|
if source_path.exists():
|
|
441
436
|
shutil.copy(source_path, destination_path)
|
|
442
437
|
|
|
443
438
|
def _build_kubernetes(self, force: bool = True) -> None:
|
|
444
439
|
"""Build kubernetes deployment."""
|
|
445
|
-
k8s_build = self.path /
|
|
440
|
+
k8s_build = self.path / DEPLOYMENT_DIR / "abci_build_k8s"
|
|
446
441
|
if k8s_build.exists() and force:
|
|
447
442
|
shutil.rmtree(k8s_build)
|
|
448
443
|
mkdirs(build_dir=k8s_build)
|
|
@@ -450,8 +445,8 @@ class Deployment(LocalResource):
|
|
|
450
445
|
service = Service.load(path=self.path)
|
|
451
446
|
builder = ServiceBuilder.from_dir(
|
|
452
447
|
path=service.package_absolute_path,
|
|
453
|
-
keys_file=self.path /
|
|
454
|
-
number_of_agents=len(service.
|
|
448
|
+
keys_file=self.path / DEFAULT_KEYS_FILE,
|
|
449
|
+
number_of_agents=len(service.agent_addresses),
|
|
455
450
|
)
|
|
456
451
|
builder.deplopyment_type = KubernetesGenerator.deployment_type
|
|
457
452
|
(
|
|
@@ -482,7 +477,7 @@ class Deployment(LocalResource):
|
|
|
482
477
|
force=force,
|
|
483
478
|
)
|
|
484
479
|
|
|
485
|
-
build = self.path /
|
|
480
|
+
build = self.path / DEPLOYMENT_DIR
|
|
486
481
|
if build.exists() and not force:
|
|
487
482
|
return
|
|
488
483
|
if build.exists() and force:
|
|
@@ -490,16 +485,12 @@ class Deployment(LocalResource):
|
|
|
490
485
|
shutil.rmtree(build)
|
|
491
486
|
mkdirs(build_dir=build)
|
|
492
487
|
|
|
493
|
-
keys_file = self.path /
|
|
488
|
+
keys_file = self.path / DEFAULT_KEYS_FILE
|
|
494
489
|
keys_file.write_text(
|
|
495
490
|
json.dumps(
|
|
496
491
|
[
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"private_key": key.private_key,
|
|
500
|
-
"ledger": key.ledger.name.lower(),
|
|
501
|
-
}
|
|
502
|
-
for key in service.keys
|
|
492
|
+
KeysManager().get(address).json
|
|
493
|
+
for address in service.agent_addresses
|
|
503
494
|
],
|
|
504
495
|
indent=4,
|
|
505
496
|
),
|
|
@@ -509,7 +500,7 @@ class Deployment(LocalResource):
|
|
|
509
500
|
builder = ServiceBuilder.from_dir(
|
|
510
501
|
path=service.package_absolute_path,
|
|
511
502
|
keys_file=keys_file,
|
|
512
|
-
number_of_agents=len(service.
|
|
503
|
+
number_of_agents=len(service.agent_addresses),
|
|
513
504
|
)
|
|
514
505
|
builder.deplopyment_type = DockerComposeGenerator.deployment_type
|
|
515
506
|
builder.try_update_abci_connection_params()
|
|
@@ -588,7 +579,7 @@ class Deployment(LocalResource):
|
|
|
588
579
|
|
|
589
580
|
def _build_host(self, force: bool = True, chain: t.Optional[str] = None) -> None:
|
|
590
581
|
"""Build host depployment."""
|
|
591
|
-
build = self.path /
|
|
582
|
+
build = self.path / DEPLOYMENT_DIR
|
|
592
583
|
if build.exists() and not force:
|
|
593
584
|
return
|
|
594
585
|
|
|
@@ -617,16 +608,12 @@ class Deployment(LocalResource):
|
|
|
617
608
|
chain_config = service.chain_configs[chain]
|
|
618
609
|
chain_data = chain_config.chain_data
|
|
619
610
|
|
|
620
|
-
keys_file = self.path /
|
|
611
|
+
keys_file = self.path / DEFAULT_KEYS_FILE
|
|
621
612
|
keys_file.write_text(
|
|
622
613
|
json.dumps(
|
|
623
614
|
[
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
"private_key": key.private_key,
|
|
627
|
-
"ledger": key.ledger.name.lower(),
|
|
628
|
-
}
|
|
629
|
-
for key in service.keys
|
|
615
|
+
KeysManager().get(address).json
|
|
616
|
+
for address in service.agent_addresses
|
|
630
617
|
],
|
|
631
618
|
indent=4,
|
|
632
619
|
),
|
|
@@ -636,7 +623,7 @@ class Deployment(LocalResource):
|
|
|
636
623
|
builder = ServiceBuilder.from_dir(
|
|
637
624
|
path=service.package_absolute_path,
|
|
638
625
|
keys_file=keys_file,
|
|
639
|
-
number_of_agents=len(service.
|
|
626
|
+
number_of_agents=len(service.agent_addresses),
|
|
640
627
|
)
|
|
641
628
|
builder.deplopyment_type = HostDeploymentGenerator.deployment_type
|
|
642
629
|
builder.try_update_abci_connection_params()
|
|
@@ -708,7 +695,7 @@ class Deployment(LocalResource):
|
|
|
708
695
|
self._build_kubernetes(force=force)
|
|
709
696
|
else:
|
|
710
697
|
ssl_key_path, ssl_cert_path = create_ssl_certificate(
|
|
711
|
-
ssl_dir=service.path /
|
|
698
|
+
ssl_dir=service.path / DEPLOYMENT_DIR / "ssl"
|
|
712
699
|
)
|
|
713
700
|
service.update_env_variables_values(
|
|
714
701
|
{
|
|
@@ -734,7 +721,11 @@ class Deployment(LocalResource):
|
|
|
734
721
|
|
|
735
722
|
try:
|
|
736
723
|
if use_docker:
|
|
737
|
-
run_deployment(
|
|
724
|
+
run_deployment(
|
|
725
|
+
build_dir=self.path / "deployment",
|
|
726
|
+
detach=True,
|
|
727
|
+
project_name=self.path.name,
|
|
728
|
+
)
|
|
738
729
|
else:
|
|
739
730
|
run_host_deployment(build_dir=self.path / "deployment")
|
|
740
731
|
except Exception:
|
|
@@ -754,7 +745,10 @@ class Deployment(LocalResource):
|
|
|
754
745
|
self.store()
|
|
755
746
|
|
|
756
747
|
if use_docker:
|
|
757
|
-
stop_deployment(
|
|
748
|
+
stop_deployment(
|
|
749
|
+
build_dir=self.path / "deployment",
|
|
750
|
+
project_name=self.path.name,
|
|
751
|
+
)
|
|
758
752
|
else:
|
|
759
753
|
stop_host_deployment(build_dir=self.path / "deployment")
|
|
760
754
|
|
|
@@ -763,7 +757,7 @@ class Deployment(LocalResource):
|
|
|
763
757
|
|
|
764
758
|
def delete(self) -> None:
|
|
765
759
|
"""Delete the deployment."""
|
|
766
|
-
build = self.path /
|
|
760
|
+
build = self.path / DEPLOYMENT_DIR
|
|
767
761
|
shutil.rmtree(build)
|
|
768
762
|
self.status = DeploymentStatus.DELETED
|
|
769
763
|
self.store()
|
|
@@ -777,7 +771,7 @@ class Service(LocalResource):
|
|
|
777
771
|
service_config_id: str
|
|
778
772
|
hash: str
|
|
779
773
|
hash_history: t.Dict[int, str]
|
|
780
|
-
|
|
774
|
+
agent_addresses: t.List[str]
|
|
781
775
|
home_chain: str
|
|
782
776
|
chain_configs: ChainConfigs
|
|
783
777
|
description: str
|
|
@@ -791,10 +785,10 @@ class Service(LocalResource):
|
|
|
791
785
|
_helper: t.Optional[ServiceHelper] = None
|
|
792
786
|
_deployment: t.Optional[Deployment] = None
|
|
793
787
|
|
|
794
|
-
_file =
|
|
788
|
+
_file = CONFIG_JSON
|
|
795
789
|
|
|
796
790
|
@staticmethod
|
|
797
|
-
def
|
|
791
|
+
def determine_agent_id(service_name: str) -> int:
|
|
798
792
|
"""Determine the appropriate agent ID based on service name."""
|
|
799
793
|
service_name_lower = service_name.lower()
|
|
800
794
|
if "mech" in service_name_lower:
|
|
@@ -805,195 +799,6 @@ class Service(LocalResource):
|
|
|
805
799
|
return AGENT_TYPE_IDS["modius"]
|
|
806
800
|
return AGENT_TYPE_IDS["trader"]
|
|
807
801
|
|
|
808
|
-
@classmethod
|
|
809
|
-
def migrate_format(cls, path: Path) -> bool: # pylint: disable=too-many-statements
|
|
810
|
-
"""Migrate the JSON file format if needed."""
|
|
811
|
-
|
|
812
|
-
if not path.is_dir():
|
|
813
|
-
return False
|
|
814
|
-
|
|
815
|
-
if not path.name.startswith(SERVICE_CONFIG_PREFIX) and not path.name.startswith(
|
|
816
|
-
"bafybei"
|
|
817
|
-
):
|
|
818
|
-
return False
|
|
819
|
-
|
|
820
|
-
if path.name.startswith("bafybei"):
|
|
821
|
-
backup_name = f"backup_{int(time.time())}_{path.name}"
|
|
822
|
-
backup_path = path.parent / backup_name
|
|
823
|
-
shutil.copytree(path, backup_path)
|
|
824
|
-
deployment_path = backup_path / "deployment"
|
|
825
|
-
if deployment_path.is_dir():
|
|
826
|
-
shutil.rmtree(deployment_path)
|
|
827
|
-
|
|
828
|
-
with open(path / Service._file, "r", encoding="utf-8") as file:
|
|
829
|
-
data = json.load(file)
|
|
830
|
-
|
|
831
|
-
version = data.get("version", 0)
|
|
832
|
-
if version > SERVICE_CONFIG_VERSION:
|
|
833
|
-
raise RuntimeError(
|
|
834
|
-
f"Service configuration in {path} has version {version}, which means it was created with a newer version of olas-operate-middleware. Only configuration versions <= {SERVICE_CONFIG_VERSION} are supported by this version of olas-operate-middleware."
|
|
835
|
-
)
|
|
836
|
-
|
|
837
|
-
# Complete missing env vars for trader
|
|
838
|
-
if "trader" in data["name"].lower():
|
|
839
|
-
data.setdefault("env_variables", {})
|
|
840
|
-
|
|
841
|
-
for key, value in DEFAULT_TRADER_ENV_VARS.items():
|
|
842
|
-
if key not in data["env_variables"]:
|
|
843
|
-
data["env_variables"][key] = value
|
|
844
|
-
|
|
845
|
-
with open(path / Service._file, "w", encoding="utf-8") as file:
|
|
846
|
-
json.dump(data, file, indent=2)
|
|
847
|
-
|
|
848
|
-
if version == SERVICE_CONFIG_VERSION:
|
|
849
|
-
return False
|
|
850
|
-
|
|
851
|
-
# Migration steps for older versions
|
|
852
|
-
if version == 0:
|
|
853
|
-
new_data = {
|
|
854
|
-
"version": 2,
|
|
855
|
-
"hash": data.get("hash"),
|
|
856
|
-
"keys": data.get("keys"),
|
|
857
|
-
"home_chain_id": "100", # This is the default value for version 2 - do not change, will be corrected below
|
|
858
|
-
"chain_configs": {
|
|
859
|
-
"100": { # This is the default value for version 2 - do not change, will be corrected below
|
|
860
|
-
"ledger_config": {
|
|
861
|
-
"rpc": data.get("ledger_config", {}).get("rpc"),
|
|
862
|
-
"type": data.get("ledger_config", {}).get("type"),
|
|
863
|
-
"chain": data.get("ledger_config", {}).get("chain"),
|
|
864
|
-
},
|
|
865
|
-
"chain_data": {
|
|
866
|
-
"instances": data.get("chain_data", {}).get(
|
|
867
|
-
"instances", []
|
|
868
|
-
),
|
|
869
|
-
"token": data.get("chain_data", {}).get("token"),
|
|
870
|
-
"multisig": data.get("chain_data", {}).get("multisig"),
|
|
871
|
-
"staked": data.get("chain_data", {}).get("staked", False),
|
|
872
|
-
"on_chain_state": data.get("chain_data", {}).get(
|
|
873
|
-
"on_chain_state", 3
|
|
874
|
-
),
|
|
875
|
-
"user_params": {
|
|
876
|
-
"staking_program_id": "pearl_alpha",
|
|
877
|
-
"nft": data.get("chain_data", {})
|
|
878
|
-
.get("user_params", {})
|
|
879
|
-
.get("nft"),
|
|
880
|
-
"threshold": data.get("chain_data", {})
|
|
881
|
-
.get("user_params", {})
|
|
882
|
-
.get("threshold"),
|
|
883
|
-
"use_staking": data.get("chain_data", {})
|
|
884
|
-
.get("user_params", {})
|
|
885
|
-
.get("use_staking"),
|
|
886
|
-
"cost_of_bond": data.get("chain_data", {})
|
|
887
|
-
.get("user_params", {})
|
|
888
|
-
.get("cost_of_bond"),
|
|
889
|
-
"fund_requirements": data.get("chain_data", {})
|
|
890
|
-
.get("user_params", {})
|
|
891
|
-
.get("fund_requirements", {}),
|
|
892
|
-
"agent_id": data.get("chain_data", {})
|
|
893
|
-
.get("user_params", {})
|
|
894
|
-
.get("agent_id", "14"),
|
|
895
|
-
},
|
|
896
|
-
},
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
"service_path": data.get("service_path", ""),
|
|
900
|
-
"name": data.get("name", ""),
|
|
901
|
-
}
|
|
902
|
-
data = new_data
|
|
903
|
-
|
|
904
|
-
if version < 4:
|
|
905
|
-
# Add missing fields introduced in later versions, if necessary.
|
|
906
|
-
for _, chain_data in data.get("chain_configs", {}).items():
|
|
907
|
-
chain_data.setdefault("chain_data", {}).setdefault(
|
|
908
|
-
"user_params", {}
|
|
909
|
-
).setdefault("use_mech_marketplace", False)
|
|
910
|
-
service_name = data.get("name", "")
|
|
911
|
-
agent_id = cls._determine_agent_id(service_name)
|
|
912
|
-
chain_data.setdefault("chain_data", {}).setdefault("user_params", {})[
|
|
913
|
-
"agent_id"
|
|
914
|
-
] = agent_id
|
|
915
|
-
|
|
916
|
-
data["description"] = data.setdefault("description", data.get("name"))
|
|
917
|
-
data["hash_history"] = data.setdefault(
|
|
918
|
-
"hash_history", {int(time.time()): data["hash"]}
|
|
919
|
-
)
|
|
920
|
-
|
|
921
|
-
if "service_config_id" not in data:
|
|
922
|
-
service_config_id = Service.get_new_service_config_id(path)
|
|
923
|
-
new_path = path.parent / service_config_id
|
|
924
|
-
data["service_config_id"] = service_config_id
|
|
925
|
-
path = path.rename(new_path)
|
|
926
|
-
|
|
927
|
-
old_to_new_ledgers = ["ethereum", "solana"]
|
|
928
|
-
for key_data in data["keys"]:
|
|
929
|
-
key_data["ledger"] = old_to_new_ledgers[key_data["ledger"]]
|
|
930
|
-
|
|
931
|
-
old_to_new_chains = [
|
|
932
|
-
"ethereum",
|
|
933
|
-
"goerli",
|
|
934
|
-
"gnosis",
|
|
935
|
-
"solana",
|
|
936
|
-
"optimistic",
|
|
937
|
-
"base",
|
|
938
|
-
"mode",
|
|
939
|
-
]
|
|
940
|
-
new_chain_configs = {}
|
|
941
|
-
for chain_id, chain_data in data["chain_configs"].items():
|
|
942
|
-
chain_data["ledger_config"]["chain"] = old_to_new_chains[
|
|
943
|
-
chain_data["ledger_config"]["chain"]
|
|
944
|
-
]
|
|
945
|
-
del chain_data["ledger_config"]["type"]
|
|
946
|
-
new_chain_configs[Chain.from_id(int(chain_id)).value] = chain_data # type: ignore
|
|
947
|
-
|
|
948
|
-
data["chain_configs"] = new_chain_configs
|
|
949
|
-
data["home_chain"] = data.setdefault("home_chain", Chain.from_id(int(data.get("home_chain_id", "100"))).value) # type: ignore
|
|
950
|
-
del data["home_chain_id"]
|
|
951
|
-
|
|
952
|
-
if "env_variables" not in data:
|
|
953
|
-
if data["name"] == "valory/trader_pearl":
|
|
954
|
-
data["env_variables"] = DEFAULT_TRADER_ENV_VARS
|
|
955
|
-
else:
|
|
956
|
-
data["env_variables"] = {}
|
|
957
|
-
|
|
958
|
-
if version < 5:
|
|
959
|
-
new_chain_configs = {}
|
|
960
|
-
for chain, chain_data in data["chain_configs"].items():
|
|
961
|
-
fund_requirements = chain_data["chain_data"]["user_params"][
|
|
962
|
-
"fund_requirements"
|
|
963
|
-
]
|
|
964
|
-
if ZERO_ADDRESS not in fund_requirements:
|
|
965
|
-
chain_data["chain_data"]["user_params"]["fund_requirements"] = {
|
|
966
|
-
ZERO_ADDRESS: fund_requirements
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
new_chain_configs[chain] = chain_data # type: ignore
|
|
970
|
-
data["chain_configs"] = new_chain_configs
|
|
971
|
-
|
|
972
|
-
data["version"] = SERVICE_CONFIG_VERSION
|
|
973
|
-
|
|
974
|
-
# Redownload service path
|
|
975
|
-
if "service_path" in data:
|
|
976
|
-
package_absolute_path = path / Path(data["service_path"]).name
|
|
977
|
-
data.pop("service_path")
|
|
978
|
-
else:
|
|
979
|
-
package_absolute_path = path / data["package_path"]
|
|
980
|
-
|
|
981
|
-
if package_absolute_path.exists() and package_absolute_path.is_dir():
|
|
982
|
-
shutil.rmtree(package_absolute_path)
|
|
983
|
-
|
|
984
|
-
package_absolute_path = Path(
|
|
985
|
-
IPFSTool().download(
|
|
986
|
-
hash_id=data["hash"],
|
|
987
|
-
target_dir=path,
|
|
988
|
-
)
|
|
989
|
-
)
|
|
990
|
-
data["package_path"] = str(package_absolute_path.name)
|
|
991
|
-
|
|
992
|
-
with open(path / Service._file, "w", encoding="utf-8") as file:
|
|
993
|
-
json.dump(data, file, indent=2)
|
|
994
|
-
|
|
995
|
-
return True
|
|
996
|
-
|
|
997
802
|
@classmethod
|
|
998
803
|
def load(cls, path: Path) -> "Service":
|
|
999
804
|
"""Load a service"""
|
|
@@ -1028,7 +833,7 @@ class Service(LocalResource):
|
|
|
1028
833
|
package_absolute_path = self.path / self.package_path
|
|
1029
834
|
if (
|
|
1030
835
|
not package_absolute_path.exists()
|
|
1031
|
-
or not (package_absolute_path /
|
|
836
|
+
or not (package_absolute_path / DEFAULT_SERVICE_CONFIG_FILE).exists()
|
|
1032
837
|
):
|
|
1033
838
|
with tempfile.TemporaryDirectory(dir=self.path) as temp_dir:
|
|
1034
839
|
package_temp_path = Path(
|
|
@@ -1048,7 +853,7 @@ class Service(LocalResource):
|
|
|
1048
853
|
|
|
1049
854
|
@staticmethod
|
|
1050
855
|
def new( # pylint: disable=too-many-locals
|
|
1051
|
-
|
|
856
|
+
agent_addresses: t.List[str],
|
|
1052
857
|
service_template: ServiceTemplate,
|
|
1053
858
|
storage: Path,
|
|
1054
859
|
) -> "Service":
|
|
@@ -1090,7 +895,7 @@ class Service(LocalResource):
|
|
|
1090
895
|
name=service_template["name"],
|
|
1091
896
|
description=service_template["description"],
|
|
1092
897
|
hash=service_template["hash"],
|
|
1093
|
-
|
|
898
|
+
agent_addresses=agent_addresses,
|
|
1094
899
|
home_chain=service_template["home_chain"],
|
|
1095
900
|
hash_history={current_timestamp: service_template["hash"]},
|
|
1096
901
|
chain_configs=chain_configs,
|
|
@@ -1103,7 +908,7 @@ class Service(LocalResource):
|
|
|
1103
908
|
|
|
1104
909
|
def service_public_id(self, include_version: bool = True) -> str:
|
|
1105
910
|
"""Get the public id (based on the service hash)."""
|
|
1106
|
-
with (self.package_absolute_path /
|
|
911
|
+
with (self.package_absolute_path / DEFAULT_SERVICE_CONFIG_FILE).open(
|
|
1107
912
|
"r", encoding="utf-8"
|
|
1108
913
|
) as fp:
|
|
1109
914
|
service_yaml, *_ = yaml_load_all(fp)
|
|
@@ -1135,7 +940,9 @@ class Service(LocalResource):
|
|
|
1135
940
|
)
|
|
1136
941
|
)
|
|
1137
942
|
|
|
1138
|
-
with (package_path /
|
|
943
|
+
with (package_path / DEFAULT_SERVICE_CONFIG_FILE).open(
|
|
944
|
+
"r", encoding="utf-8"
|
|
945
|
+
) as fp:
|
|
1139
946
|
service_yaml, *_ = yaml_load_all(fp)
|
|
1140
947
|
|
|
1141
948
|
public_id = f"{service_yaml['author']}/{service_yaml['name']}"
|
|
@@ -1321,10 +1128,3 @@ class Service(LocalResource):
|
|
|
1321
1128
|
|
|
1322
1129
|
if updated:
|
|
1323
1130
|
self.store()
|
|
1324
|
-
|
|
1325
|
-
def delete(self) -> None:
|
|
1326
|
-
"""Delete a service."""
|
|
1327
|
-
parent_directory = self.path.parent
|
|
1328
|
-
new_path = parent_directory / f"{DELETE_PREFIX}{self.path.name}"
|
|
1329
|
-
shutil.move(self.path, new_path)
|
|
1330
|
-
shutil.rmtree(new_path)
|
operate/utils/__init__.py
CHANGED
|
@@ -19,10 +19,54 @@
|
|
|
19
19
|
|
|
20
20
|
"""Helper utilities."""
|
|
21
21
|
|
|
22
|
+
import functools
|
|
22
23
|
import shutil
|
|
23
24
|
import time
|
|
24
25
|
import typing as t
|
|
25
26
|
from pathlib import Path
|
|
27
|
+
from threading import Lock
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SingletonMeta(type):
|
|
31
|
+
"""A metaclass for creating thread-safe singleton classes."""
|
|
32
|
+
|
|
33
|
+
_instances: t.Dict[t.Type, t.Any] = {}
|
|
34
|
+
_lock: Lock = Lock()
|
|
35
|
+
_class_locks: t.Dict[t.Type, Lock] = {}
|
|
36
|
+
|
|
37
|
+
def __new__(
|
|
38
|
+
cls, name: str, bases: t.Tuple[type, ...], dct: t.Dict[str, t.Any]
|
|
39
|
+
) -> t.Type:
|
|
40
|
+
"""Create a new class with thread-safe methods."""
|
|
41
|
+
# Wrap all callable methods (except special methods) with thread safety
|
|
42
|
+
for key, value in list(dct.items()):
|
|
43
|
+
if callable(value) and not key.startswith("__"):
|
|
44
|
+
dct[key] = cls._make_thread_safe(value)
|
|
45
|
+
|
|
46
|
+
new_class = super().__new__(cls, name, bases, dct)
|
|
47
|
+
cls._class_locks[new_class] = Lock()
|
|
48
|
+
return new_class
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _make_thread_safe(func: t.Callable) -> t.Callable:
|
|
52
|
+
"""Wrap a function to make it thread-safe."""
|
|
53
|
+
|
|
54
|
+
@functools.wraps(func)
|
|
55
|
+
def wrapper(self: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
56
|
+
class_lock = SingletonMeta._class_locks.get(type(self))
|
|
57
|
+
if class_lock:
|
|
58
|
+
with class_lock:
|
|
59
|
+
return func(self, *args, **kwargs)
|
|
60
|
+
return func(self, *args, **kwargs)
|
|
61
|
+
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
def __call__(cls, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
65
|
+
"""Override the __call__ method to control instance creation."""
|
|
66
|
+
with cls._lock:
|
|
67
|
+
if cls not in cls._instances:
|
|
68
|
+
cls._instances[cls] = super().__call__(*args, **kwargs)
|
|
69
|
+
return cls._instances[cls]
|
|
26
70
|
|
|
27
71
|
|
|
28
72
|
def create_backup(path: Path) -> Path:
|
operate/utils/gnosis.py
CHANGED
|
@@ -42,10 +42,8 @@ from operate.constants import (
|
|
|
42
42
|
from operate.operate_types import Chain
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
logger = setup_logger(name="operate.
|
|
46
|
-
NULL_ADDRESS: str = "0x" + "0" * 40
|
|
45
|
+
logger = setup_logger(name="operate.utils.gnosis")
|
|
47
46
|
MAX_UINT256 = 2**256 - 1
|
|
48
|
-
ZERO_ETH = 0
|
|
49
47
|
SENTINEL_OWNERS = "0x0000000000000000000000000000000000000001"
|
|
50
48
|
|
|
51
49
|
|
|
@@ -73,8 +71,8 @@ def hash_payload_to_hex( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
73
71
|
operation: int = SafeOperation.CALL.value,
|
|
74
72
|
base_gas: int = 0,
|
|
75
73
|
safe_gas_price: int = 0,
|
|
76
|
-
gas_token: str =
|
|
77
|
-
refund_receiver: str =
|
|
74
|
+
gas_token: str = ZERO_ADDRESS,
|
|
75
|
+
refund_receiver: str = ZERO_ADDRESS,
|
|
78
76
|
use_flashbots: bool = False,
|
|
79
77
|
gas_limit: int = 0,
|
|
80
78
|
raise_on_failed_simulation: bool = False,
|
|
@@ -492,7 +490,7 @@ def drain_eoa(
|
|
|
492
490
|
crypto: Crypto,
|
|
493
491
|
withdrawal_address: str,
|
|
494
492
|
chain_id: int,
|
|
495
|
-
) -> str:
|
|
493
|
+
) -> t.Optional[str]:
|
|
496
494
|
"""Drain all the native tokens from the crypto wallet."""
|
|
497
495
|
tx_helper = TxSettler(
|
|
498
496
|
ledger_api=ledger_api,
|
|
@@ -518,21 +516,20 @@ def drain_eoa(
|
|
|
518
516
|
)
|
|
519
517
|
tx = ledger_api.update_with_gas_estimate(
|
|
520
518
|
transaction=tx,
|
|
521
|
-
raise_on_try=
|
|
519
|
+
raise_on_try=False,
|
|
522
520
|
)
|
|
523
521
|
|
|
524
522
|
chain_fee = tx["gas"] * tx["maxFeePerGas"]
|
|
525
523
|
if Chain.from_id(chain_id) in (
|
|
526
524
|
Chain.ARBITRUM_ONE,
|
|
527
525
|
Chain.BASE,
|
|
528
|
-
Chain.
|
|
526
|
+
Chain.OPTIMISM,
|
|
529
527
|
Chain.MODE,
|
|
530
528
|
):
|
|
531
529
|
chain_fee += ledger_api.get_l1_data_fee(tx)
|
|
532
530
|
|
|
533
531
|
tx["value"] = ledger_api.get_balance(crypto.address) - chain_fee
|
|
534
532
|
if tx["value"] <= 0:
|
|
535
|
-
logger.warning(f"No balance to drain from wallet: {crypto.address}")
|
|
536
533
|
raise ChainInteractionError(
|
|
537
534
|
f"No balance to drain from wallet: {crypto.address}"
|
|
538
535
|
)
|
|
@@ -544,14 +541,25 @@ def drain_eoa(
|
|
|
544
541
|
return tx
|
|
545
542
|
|
|
546
543
|
setattr(tx_helper, "build", _build_tx) # noqa: B010
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
544
|
+
try:
|
|
545
|
+
tx_receipt = tx_helper.transact(
|
|
546
|
+
method=lambda: {},
|
|
547
|
+
contract="",
|
|
548
|
+
kwargs={},
|
|
549
|
+
dry_run=False,
|
|
550
|
+
)
|
|
551
|
+
except ChainInteractionError as e:
|
|
552
|
+
if "No balance to drain from wallet" in str(e):
|
|
553
|
+
logger.warning(f"Failed to drain wallet {crypto.address} with error: {e}.")
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
raise e
|
|
557
|
+
|
|
558
|
+
tx_hash = tx_receipt.get("transactionHash", None)
|
|
559
|
+
if tx_hash is not None:
|
|
560
|
+
return tx_hash.hex()
|
|
561
|
+
|
|
562
|
+
return None
|
|
555
563
|
|
|
556
564
|
|
|
557
565
|
def get_asset_balance(
|