olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.
Files changed (98) hide show
  1. olas_operate_middleware-0.13.2.dist-info/METADATA +75 -0
  2. olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
  3. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
  4. operate/__init__.py +17 -0
  5. operate/account/user.py +35 -9
  6. operate/bridge/bridge_manager.py +470 -0
  7. operate/bridge/providers/lifi_provider.py +377 -0
  8. operate/bridge/providers/native_bridge_provider.py +677 -0
  9. operate/bridge/providers/provider.py +469 -0
  10. operate/bridge/providers/relay_provider.py +457 -0
  11. operate/cli.py +1565 -417
  12. operate/constants.py +60 -12
  13. operate/data/README.md +19 -0
  14. operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
  15. operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
  16. operate/data/contracts/dual_staking_token/contract.py +132 -0
  17. operate/data/contracts/dual_staking_token/contract.yaml +23 -0
  18. operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
  19. operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
  20. operate/data/contracts/foreign_omnibridge/contract.py +130 -0
  21. operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
  22. operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
  23. operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
  24. operate/data/contracts/home_omnibridge/contract.py +80 -0
  25. operate/data/contracts/home_omnibridge/contract.yaml +23 -0
  26. operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
  27. operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
  28. operate/data/contracts/l1_standard_bridge/contract.py +158 -0
  29. operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
  30. operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
  31. operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
  32. operate/data/contracts/l2_standard_bridge/contract.py +130 -0
  33. operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
  34. operate/data/contracts/mech_activity/__init__.py +20 -0
  35. operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
  36. operate/data/contracts/mech_activity/contract.py +44 -0
  37. operate/data/contracts/mech_activity/contract.yaml +23 -0
  38. operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
  39. operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
  40. operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
  41. operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
  42. operate/data/contracts/recovery_module/__init__.py +20 -0
  43. operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
  44. operate/data/contracts/recovery_module/contract.py +61 -0
  45. operate/data/contracts/recovery_module/contract.yaml +23 -0
  46. operate/data/contracts/requester_activity_checker/__init__.py +20 -0
  47. operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
  48. operate/data/contracts/requester_activity_checker/contract.py +33 -0
  49. operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
  50. operate/data/contracts/staking_token/__init__.py +20 -0
  51. operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
  52. operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
  53. operate/data/contracts/staking_token/contract.yaml +23 -0
  54. operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
  55. operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
  56. operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
  57. operate/keys.py +118 -33
  58. operate/ledger/__init__.py +159 -56
  59. operate/ledger/profiles.py +321 -18
  60. operate/migration.py +555 -0
  61. operate/{http → operate_http}/__init__.py +3 -2
  62. operate/{http → operate_http}/exceptions.py +6 -4
  63. operate/operate_types.py +544 -0
  64. operate/pearl.py +13 -1
  65. operate/quickstart/analyse_logs.py +118 -0
  66. operate/quickstart/claim_staking_rewards.py +104 -0
  67. operate/quickstart/reset_configs.py +106 -0
  68. operate/quickstart/reset_password.py +70 -0
  69. operate/quickstart/reset_staking.py +145 -0
  70. operate/quickstart/run_service.py +726 -0
  71. operate/quickstart/stop_service.py +72 -0
  72. operate/quickstart/terminate_on_chain_service.py +83 -0
  73. operate/quickstart/utils.py +298 -0
  74. operate/resource.py +62 -3
  75. operate/services/agent_runner.py +202 -0
  76. operate/services/deployment_runner.py +868 -0
  77. operate/services/funding_manager.py +929 -0
  78. operate/services/health_checker.py +280 -0
  79. operate/services/manage.py +2356 -620
  80. operate/services/protocol.py +1246 -340
  81. operate/services/service.py +756 -391
  82. operate/services/utils/mech.py +103 -0
  83. operate/services/utils/tendermint.py +86 -12
  84. operate/settings.py +70 -0
  85. operate/utils/__init__.py +135 -0
  86. operate/utils/gnosis.py +407 -80
  87. operate/utils/single_instance.py +226 -0
  88. operate/utils/ssl.py +133 -0
  89. operate/wallet/master.py +708 -123
  90. operate/wallet/wallet_recovery_manager.py +507 -0
  91. olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
  92. olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
  93. operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
  94. operate/data/contracts/service_staking_token/contract.yaml +0 -23
  95. operate/ledger/ethereum.py +0 -48
  96. operate/types.py +0 -260
  97. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
  98. {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,544 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2023 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """Types module."""
21
+
22
+ import base64
23
+ import copy
24
+ import enum
25
+ import os
26
+ import typing as t
27
+ from dataclasses import dataclass, field
28
+ from pathlib import Path
29
+
30
+ import argon2
31
+ from aea_ledger_ethereum import cast
32
+ from autonomy.chain.config import ChainType
33
+ from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
34
+ from cryptography.fernet import Fernet
35
+ from typing_extensions import TypedDict
36
+
37
+ from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
38
+ from operate.resource import LocalResource
39
+
40
+
41
+ CHAIN_NAME_TO_CHAIN_ID["solana"] = 900
42
+
43
+ _CHAIN_ID_TO_CHAIN_NAME = {
44
+ chain_id: chain_name for chain_name, chain_id in CHAIN_NAME_TO_CHAIN_ID.items()
45
+ }
46
+
47
+
48
+ class LedgerType(str, enum.Enum):
49
+ """Ledger type enum."""
50
+
51
+ ETHEREUM = "ethereum"
52
+ SOLANA = "solana"
53
+
54
+ @property
55
+ def config_file(self) -> str:
56
+ """Config filename."""
57
+ return f"{self.name.lower()}.json"
58
+
59
+ @property
60
+ def key_file(self) -> str:
61
+ """Key filename."""
62
+ return f"{self.name.lower()}.txt"
63
+
64
+ @property
65
+ def mnemonic_file(self) -> str:
66
+ """Mnemonic filename."""
67
+ return f"{self.name.lower()}.mnemonic.json"
68
+
69
+ @classmethod
70
+ def from_id(cls, chain_id: int) -> "LedgerType":
71
+ """Load from chain ID."""
72
+ return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id]).ledger_type
73
+
74
+
75
+ # Dynamically create the Chain enum from the ChainType
76
+ # TODO: Migrate this to open-autonomy and remove this modified version of Chain here and use the one from open-autonomy
77
+ # This version of open-autonomy must support the LedgerType to support SOLANA in the future
78
+ # If solana support is not fuly implemented, decide to keep this half-baked feature.
79
+ #
80
+ # TODO: Once the above issue is properly implemented in Open Autonomy, remove the following
81
+ # lines from tox.ini:
82
+ #
83
+ # exclude = ^(operate/operate_types\.py|scripts/setup_wallet\.py|operate/ledger/profiles\.py|operate/ledger/__init__\.py|operate/wallet/master\.py|operate/services/protocol\.py|operate/services/manage\.py|operate/cli\.py)$
84
+ #
85
+ # [mypy-operate.*]
86
+ # follow_imports = skip # noqa
87
+ #
88
+ # These lines were itroduced to resolve mypy issues with the temporary Chain/ChainType solution.
89
+ Chain = enum.Enum(
90
+ "Chain",
91
+ [(member.name, member.value) for member in ChainType]
92
+ + [
93
+ ("SOLANA", "solana"),
94
+ ],
95
+ )
96
+
97
+
98
+ class ChainMixin:
99
+ """Mixin for some new functions in the ChainType class."""
100
+
101
+ @property
102
+ def id(self) -> t.Optional[int]:
103
+ """Chain ID"""
104
+ if self == Chain.CUSTOM:
105
+ chain_id = os.environ.get("CUSTOM_CHAIN_ID")
106
+ if chain_id is None:
107
+ return None
108
+ return int(chain_id)
109
+ return CHAIN_NAME_TO_CHAIN_ID[self.value]
110
+
111
+ @property
112
+ def ledger_type(self) -> LedgerType:
113
+ """Ledger type."""
114
+ if self in (Chain.SOLANA,):
115
+ return LedgerType.SOLANA
116
+ return LedgerType.ETHEREUM
117
+
118
+ @classmethod
119
+ def from_string(cls, chain: str) -> "Chain":
120
+ """Load from string."""
121
+ return Chain(chain.lower())
122
+
123
+ @classmethod
124
+ def from_id(cls, chain_id: int) -> "Chain":
125
+ """Load from chain ID."""
126
+ return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id])
127
+
128
+
129
+ # Add the ChainMixin methods to the Chain enum
130
+ for name in dir(ChainMixin):
131
+ if not name.startswith("__"):
132
+ setattr(Chain, name, getattr(ChainMixin, name))
133
+
134
+
135
+ class DeploymentStatus(enum.IntEnum):
136
+ """Status payload."""
137
+
138
+ CREATED = 0
139
+ BUILT = 1
140
+ DEPLOYING = 2
141
+ DEPLOYED = 3
142
+ STOPPING = 4
143
+ STOPPED = 5
144
+ DELETED = 6
145
+
146
+
147
+ # TODO defined in aea.chain.base.OnChainState
148
+ class OnChainState(enum.IntEnum):
149
+ """On-chain state."""
150
+
151
+ NON_EXISTENT = 0
152
+ PRE_REGISTRATION = 1
153
+ ACTIVE_REGISTRATION = 2
154
+ FINISHED_REGISTRATION = 3
155
+ DEPLOYED = 4
156
+ TERMINATED_BONDED = 5
157
+ UNBONDED = 6 # TODO this is not an on-chain state https://github.com/valory-xyz/autonolas-registries/blob/main/contracts/ServiceRegistryL2.sol
158
+
159
+
160
+ class ContractAddresses(TypedDict):
161
+ """Contracts templates."""
162
+
163
+ service_manager: str
164
+ service_registry: str
165
+ service_registry_token_utility: str
166
+ gnosis_safe_proxy_factory: str
167
+ gnosis_safe_same_address_multisig: str
168
+ safe_multisig_with_recovery_module: str
169
+ recovery_module: str
170
+ multisend: str
171
+
172
+
173
+ @dataclass
174
+ class LedgerConfig(LocalResource):
175
+ """Ledger config."""
176
+
177
+ rpc: str
178
+ chain: Chain
179
+
180
+
181
+ LedgerConfigs = t.Dict[str, LedgerConfig]
182
+
183
+
184
+ class DeploymentConfig(TypedDict):
185
+ """Deployments template."""
186
+
187
+ volumes: t.Dict[str, str]
188
+
189
+
190
+ class FundRequirementsTemplate(TypedDict):
191
+ """Fund requirement template."""
192
+
193
+ agent: int
194
+ safe: int
195
+
196
+
197
+ class ConfigurationTemplate(TypedDict):
198
+ """Configuration template."""
199
+
200
+ staking_program_id: str
201
+ nft: str
202
+ rpc: str
203
+ agent_id: int
204
+ cost_of_bond: int
205
+ fund_requirements: t.Dict[str, FundRequirementsTemplate]
206
+ fallback_chain_params: t.Optional[t.Dict]
207
+
208
+
209
+ class ServiceEnvProvisionType(str, enum.Enum):
210
+ """Service environment variable provision type."""
211
+
212
+ FIXED = "fixed"
213
+ USER = "user"
214
+ COMPUTED = "computed"
215
+
216
+
217
+ class EnvVariableAttributes(TypedDict):
218
+ """Service environment variable template."""
219
+
220
+ name: str
221
+ description: str
222
+ value: str
223
+ provision_type: ServiceEnvProvisionType
224
+
225
+
226
+ class AgentReleaseRepo(TypedDict):
227
+ """Agent release repo template."""
228
+
229
+ owner: str
230
+ name: str
231
+ version: str
232
+
233
+
234
+ class AgentRelease(TypedDict):
235
+ """Agent release template."""
236
+
237
+ is_aea: bool
238
+ repository: AgentReleaseRepo
239
+
240
+
241
+ ConfigurationTemplates = t.Dict[str, ConfigurationTemplate]
242
+ EnvVariables = t.Dict[str, EnvVariableAttributes]
243
+
244
+
245
+ class ServiceTemplate(TypedDict, total=False):
246
+ """Service template."""
247
+
248
+ name: str
249
+ hash: str
250
+ image: str
251
+ description: str
252
+ service_version: str
253
+ agent_release: AgentRelease
254
+ home_chain: str
255
+ configurations: ConfigurationTemplates
256
+ env_variables: EnvVariables
257
+
258
+
259
+ @dataclass
260
+ class DeployedNodes(LocalResource):
261
+ """Deployed nodes type."""
262
+
263
+ agent: t.List[str]
264
+ tendermint: t.List[str]
265
+
266
+
267
+ @dataclass
268
+ class OnChainFundRequirements(LocalResource):
269
+ """On-chain fund requirements."""
270
+
271
+ agent: float
272
+ safe: float
273
+
274
+
275
+ OnChainTokenRequirements = t.Dict[str, OnChainFundRequirements]
276
+
277
+
278
+ @dataclass
279
+ class OnChainUserParams(LocalResource):
280
+ """On-chain user params."""
281
+
282
+ staking_program_id: str
283
+ nft: str
284
+ agent_id: int
285
+ cost_of_bond: int
286
+ fund_requirements: OnChainTokenRequirements
287
+
288
+ @property
289
+ def use_staking(self) -> bool:
290
+ """Check if staking is used."""
291
+ return (
292
+ self.staking_program_id is not None
293
+ and self.staking_program_id != NO_STAKING_PROGRAM_ID
294
+ )
295
+
296
+ @classmethod
297
+ def from_json(cls, obj: t.Dict) -> "OnChainUserParams":
298
+ """Load a service"""
299
+ return super().from_json(obj) # type: ignore
300
+
301
+
302
+ @dataclass
303
+ class OnChainData(LocalResource):
304
+ """On-chain data"""
305
+
306
+ instances: t.List[str] # Agent instances registered as safe owners
307
+ token: int
308
+ multisig: str
309
+ user_params: OnChainUserParams
310
+
311
+
312
+ @dataclass
313
+ class ChainConfig(LocalResource):
314
+ """Chain config."""
315
+
316
+ ledger_config: LedgerConfig
317
+ chain_data: OnChainData
318
+
319
+ @classmethod
320
+ def from_json(cls, obj: t.Dict) -> "ChainConfig":
321
+ """Load the chain config."""
322
+ return super().from_json(obj) # type: ignore
323
+
324
+
325
+ ChainConfigs = t.Dict[str, ChainConfig]
326
+
327
+
328
+ class FundingConfig(TypedDict):
329
+ """Funding config."""
330
+
331
+ topup: int
332
+ threshold: int
333
+
334
+
335
+ class AssetFundingValues(TypedDict):
336
+ """Asset Funding values."""
337
+
338
+ agent: FundingConfig
339
+ safe: FundingConfig
340
+
341
+
342
+ FundingValues = t.Dict[str, AssetFundingValues] # str is the asset address
343
+
344
+
345
+ @dataclass
346
+ class MechMarketplaceConfig:
347
+ """Mech Marketplace config."""
348
+
349
+ use_mech_marketplace: bool
350
+ mech_marketplace_address: str
351
+ priority_mech_address: str
352
+ priority_mech_service_id: int
353
+
354
+
355
+ class Version:
356
+ """Version class."""
357
+
358
+ def __init__(self, version: str) -> None:
359
+ """Initialize the version."""
360
+ version = version.strip()
361
+ version_parts = version.split(".") if version else []
362
+ self.major = int(version_parts[0]) if len(version_parts) > 0 else 0
363
+ self.minor = int(version_parts[1]) if len(version_parts) > 1 else 0
364
+ self.patch = int(version_parts[2]) if len(version_parts) > 2 else 0
365
+
366
+ def __str__(self) -> str:
367
+ """String representation of the version."""
368
+ return f"{self.major}.{self.minor}.{self.patch}"
369
+
370
+ def __eq__(self, other: object) -> bool:
371
+ """Equality comparison."""
372
+ if not isinstance(other, Version):
373
+ return NotImplemented
374
+ return (
375
+ self.major == other.major
376
+ and self.minor == other.minor
377
+ and self.patch == other.patch
378
+ )
379
+
380
+ def __lt__(self, other: "Version") -> bool:
381
+ """Less than comparison."""
382
+ if self.major != other.major:
383
+ return self.major < other.major
384
+ if self.minor != other.minor:
385
+ return self.minor < other.minor
386
+ return self.patch < other.patch
387
+
388
+
389
+ class ChainAmounts(dict[str, dict[str, dict[str, int]]]):
390
+ """
391
+ Class that represents chain amounts as a dictionary
392
+
393
+ The standard format follows the convention {chain: {address: {token: amount}}}
394
+ """
395
+
396
+ @classmethod
397
+ def shortfalls(
398
+ cls, requirements: "ChainAmounts", balances: "ChainAmounts"
399
+ ) -> "ChainAmounts":
400
+ """Return the shortfalls between requirements and balances."""
401
+ result: dict[str, dict[str, dict[str, int]]] = {}
402
+
403
+ for chain, addresses in requirements.items():
404
+ for address, assets in addresses.items():
405
+ for asset, required_amount in assets.items():
406
+ available = balances.get(chain, {}).get(address, {}).get(asset, 0)
407
+ shortfall = max(required_amount - available, 0)
408
+ result.setdefault(chain, {}).setdefault(address, {})[
409
+ asset
410
+ ] = shortfall
411
+
412
+ return cls(result)
413
+
414
+ @classmethod
415
+ def add(cls, *chainamounts: "ChainAmounts") -> "ChainAmounts":
416
+ """Add multiple ChainAmounts"""
417
+ result: dict[str, dict[str, dict[str, int]]] = {}
418
+
419
+ for ca in chainamounts:
420
+ for chain, addresses in ca.items():
421
+ result_addresses = result.setdefault(chain, {})
422
+ for address, assets in addresses.items():
423
+ result_assets = result_addresses.setdefault(address, {})
424
+ for asset, amount in assets.items():
425
+ result_assets[asset] = result_assets.get(asset, 0) + amount
426
+
427
+ return cls(result)
428
+
429
+ def __add__(self, other: "ChainAmounts") -> "ChainAmounts":
430
+ """Add two ChainAmounts"""
431
+ return ChainAmounts.add(self, other)
432
+
433
+ def __mul__(self, multiplier: float) -> "ChainAmounts":
434
+ """Multiply all amounts by the specified multiplier"""
435
+ output = copy.deepcopy(self)
436
+ for _, addresses in output.items():
437
+ for _, balances in addresses.items():
438
+ for asset, amount in balances.items():
439
+ balances[asset] = int(amount * multiplier)
440
+ return output
441
+
442
+ def __sub__(self, other: "ChainAmounts") -> "ChainAmounts":
443
+ """Subtract two ChainAmounts"""
444
+ return self + (other * -1)
445
+
446
+ def __floordiv__(self, divisor: float) -> "ChainAmounts":
447
+ """Divide all amounts by the specified divisor"""
448
+ if divisor == 0:
449
+ raise ValueError("Cannot divide by zero")
450
+
451
+ output = copy.deepcopy(self)
452
+ for _, addresses in output.items():
453
+ for _, balances in addresses.items():
454
+ for asset, amount in balances.items():
455
+ balances[asset] = int(amount // divisor)
456
+ return output
457
+
458
+ def __lt__(self, other: "ChainAmounts") -> bool:
459
+ """Return True if all amounts in self are strictly less than the corresponding amounts in other."""
460
+ for chain, addresses in self.items():
461
+ for address, assets in addresses.items():
462
+ for asset, amount in assets.items():
463
+ if amount >= other.get(chain, {}).get(address, {}).get(asset, 0):
464
+ return False
465
+ return True
466
+
467
+
468
+ @dataclass
469
+ class EncryptedData(LocalResource):
470
+ """EncryptedData type."""
471
+
472
+ path: Path
473
+ version: int
474
+ cipher: str
475
+ cipherparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
476
+ ciphertext: str = field(repr=False)
477
+ kdf: str
478
+ kdfparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
479
+
480
+ @classmethod
481
+ def new(cls, path: Path, password: str, plaintext_bytes: bytes) -> "EncryptedData":
482
+ """Creates a new EncryptedData"""
483
+ ph = argon2.PasswordHasher()
484
+ salt = os.urandom(ph.salt_len)
485
+ time_cost = ph.time_cost
486
+ memory_cost = ph.memory_cost
487
+ parallelism = ph.parallelism
488
+ hash_len = FERNET_KEY_LENGTH
489
+ argon2_type = argon2.Type.ID
490
+ key = argon2.low_level.hash_secret_raw(
491
+ secret=password.encode(),
492
+ salt=salt,
493
+ time_cost=time_cost,
494
+ memory_cost=memory_cost,
495
+ parallelism=parallelism,
496
+ hash_len=hash_len,
497
+ type=argon2_type,
498
+ )
499
+
500
+ fernet_key = base64.urlsafe_b64encode(key)
501
+ fernet = Fernet(fernet_key)
502
+ ciphertext_bytes = fernet.encrypt(plaintext_bytes)
503
+
504
+ return cls(
505
+ path=path,
506
+ version=1,
507
+ cipher=f"{fernet.__class__.__module__}.{fernet.__class__.__qualname__}",
508
+ cipherparams={ # Fernet token (ciphertext variable) already stores them
509
+ "version": ciphertext_bytes[0]
510
+ },
511
+ ciphertext=ciphertext_bytes.hex(),
512
+ kdf=f"{ph.__class__.__module__}.{ph.__class__.__qualname__}",
513
+ kdfparams={
514
+ "salt": salt.hex(),
515
+ "time_cost": time_cost,
516
+ "memory_cost": memory_cost,
517
+ "parallelism": parallelism,
518
+ "hash_len": hash_len,
519
+ "type": argon2_type.name,
520
+ },
521
+ )
522
+
523
+ def decrypt(self, password: str) -> bytes:
524
+ """Decrypts the EncryptedData"""
525
+ kdfparams = self.kdfparams
526
+ key = argon2.low_level.hash_secret_raw(
527
+ secret=password.encode(),
528
+ salt=bytes.fromhex(kdfparams["salt"]),
529
+ time_cost=kdfparams["time_cost"],
530
+ memory_cost=kdfparams["memory_cost"],
531
+ parallelism=kdfparams["parallelism"],
532
+ hash_len=kdfparams["hash_len"],
533
+ type=argon2.Type[kdfparams["type"]],
534
+ )
535
+ fernet_key = base64.urlsafe_b64encode(key)
536
+ fernet = Fernet(fernet_key)
537
+ ciphertext_bytes = bytes.fromhex(self.ciphertext)
538
+ plaintext_bytes = fernet.decrypt(ciphertext_bytes)
539
+ return plaintext_bytes
540
+
541
+ @classmethod
542
+ def load(cls, path: Path) -> "EncryptedData":
543
+ """Load EncryptedData."""
544
+ return cast(EncryptedData, super().load(path))
operate/pearl.py CHANGED
@@ -18,10 +18,22 @@
18
18
  # ------------------------------------------------------------------------------
19
19
 
20
20
  """File used for pyinstaller to create a single executable file."""
21
-
22
21
  # pylint: disable=all
23
22
  # mypy: ignore-errors
24
23
  # flake8: noqa
24
+
25
+ import os
26
+ import sys
27
+ from pathlib import Path
28
+
29
+ import aea.configurations.validation as validation_module
30
+
31
+
32
+ # patch for the _CUR_DIR value
33
+ validation_module._CUR_DIR = Path(sys._MEIPASS) / validation_module._CUR_DIR
34
+ validation_module._SCHEMAS_DIR = os.path.join(validation_module._CUR_DIR, "schemas")
35
+
36
+
25
37
  from aea.crypto.registries.base import *
26
38
  from aea.mail.base_pb2 import DESCRIPTOR
27
39
  from aea_ledger_cosmos.cosmos import * # noqa
@@ -0,0 +1,118 @@
1
+ # ------------------------------------------------------------------------------
2
+ #
3
+ # Copyright 2023-2025 Valory AG
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ # ------------------------------------------------------------------------------
18
+ """Quickstart script to run log analysis."""
19
+
20
+
21
+ import json
22
+ import os
23
+ import subprocess # nosec
24
+ import sys
25
+ from pathlib import Path
26
+ from typing import List, TYPE_CHECKING, Union
27
+
28
+ from operate.constants import DEPLOYMENT_DIR
29
+
30
+
31
+ if TYPE_CHECKING:
32
+ from operate.cli import OperateApp
33
+
34
+
35
+ def find_build_directory(config_file: Path, operate: "OperateApp") -> Path:
36
+ """Find the appropriate build directory of the configured service."""
37
+ with open(config_file, "r") as f:
38
+ config = json.load(f)
39
+ config_service_hash = config.get("hash")
40
+
41
+ services, _ = operate.service_manager().get_all_services()
42
+ for service in services:
43
+ if service.hash == config_service_hash:
44
+ build_dir = service.path / DEPLOYMENT_DIR
45
+ if not build_dir.exists():
46
+ print(f"{config.get('name')} not deployed.")
47
+ sys.exit(1)
48
+ return build_dir
49
+
50
+ print(f"{config.get('name')} not found.")
51
+ sys.exit(1)
52
+
53
+
54
+ def run_analysis(logs_dir: Path, **kwargs: str) -> None:
55
+ """Run the log analysis command."""
56
+ command: List[Union[str, Path]] = [
57
+ "poetry",
58
+ "run",
59
+ "autonomy",
60
+ "analyse",
61
+ "logs",
62
+ "--from-dir",
63
+ logs_dir,
64
+ ]
65
+ if "agent" in kwargs and kwargs["agent"]:
66
+ command.extend(["--agent", kwargs["agent"]])
67
+ if "reset_db" in kwargs and kwargs["reset_db"]:
68
+ command.extend(["--reset-db"])
69
+ if "start_time" in kwargs and kwargs["start_time"]:
70
+ command.extend(["--start-time", kwargs["start_time"]])
71
+ if "end_time" in kwargs and kwargs["end_time"]:
72
+ command.extend(["--end-time", kwargs["end_time"]])
73
+ if "log_level" in kwargs and kwargs["log_level"]:
74
+ command.extend(["--log-level", kwargs["log_level"]])
75
+ if "period" in kwargs and kwargs["period"]:
76
+ command.extend(["--period", kwargs["period"]])
77
+ if "round" in kwargs and kwargs["round"]:
78
+ command.extend(["--round", kwargs["round"]])
79
+ if "behaviour" in kwargs and kwargs["behaviour"]:
80
+ command.extend(["--behaviour", kwargs["behaviour"]])
81
+ if "fsm" in kwargs and kwargs["fsm"]:
82
+ command.extend(["--fsm"])
83
+ if "include_regex" in kwargs and kwargs["include_regex"]:
84
+ command.extend(["--include-regex", kwargs["include_regex"]])
85
+ if "exclude_regex" in kwargs and kwargs["exclude_regex"]:
86
+ command.extend(["--exclude-regex", kwargs["exclude_regex"]])
87
+
88
+ try:
89
+ subprocess.run(command, check=True) # nosec
90
+ print("Analysis completed successfully.")
91
+ except subprocess.CalledProcessError as e:
92
+ print(f"Command failed with exit code {e.returncode}")
93
+ sys.exit(e.returncode)
94
+ except FileNotFoundError:
95
+ print("Poetry or autonomy not found. Ensure they are installed and accessible.")
96
+ sys.exit(1)
97
+
98
+
99
+ def analyse_logs(
100
+ operate: "OperateApp",
101
+ config_path: str,
102
+ **kwargs: str,
103
+ ) -> None:
104
+ """Run the log analysis command."""
105
+ config_file = Path(config_path)
106
+ if not config_file.exists():
107
+ print(f"Config file '{config_file}' not found.")
108
+ sys.exit(1)
109
+
110
+ # Auto-detect the logs directory
111
+ build_dir = find_build_directory(config_file, operate)
112
+ logs_dir = build_dir / "persistent_data" / "logs"
113
+ if not os.path.exists(logs_dir):
114
+ print(f"Logs directory '{logs_dir}' not found.")
115
+ sys.exit(1)
116
+
117
+ # Run the analysis
118
+ run_analysis(logs_dir, **kwargs)