olas-operate-middleware 0.13.6__py3-none-any.whl → 0.13.7__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.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/METADATA +1 -1
- {olas_operate_middleware-0.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/RECORD +8 -8
- operate/services/funding_manager.py +138 -56
- operate/services/protocol.py +47 -24
- operate/utils/__init__.py +108 -0
- {olas_operate_middleware-0.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/licenses/LICENSE +0 -0
{olas_operate_middleware-0.13.6.dist-info → olas_operate_middleware-0.13.7.dist-info}/RECORD
RENAMED
|
@@ -78,24 +78,24 @@ operate/resource.py,sha256=LlHJWw66RLIA9TV3HIQ6p7zZSIqoUvn3ntaVs4eRs-g,5803
|
|
|
78
78
|
operate/services/__init__.py,sha256=isrThS-Ccu5Sc15JZgkN4uTAVaSg-NwUUSDeTyJEqLk,855
|
|
79
79
|
operate/services/agent_runner.py,sha256=JGjyrzA5hX4Nuh79h81-dl2hdt74ZkC63t7UsGXY6Rw,7500
|
|
80
80
|
operate/services/deployment_runner.py,sha256=7A94QpZu100BwIk1Q9Cr0SVK5Sj7nTWx2GRCwr0gvaM,30772
|
|
81
|
-
operate/services/funding_manager.py,sha256=
|
|
81
|
+
operate/services/funding_manager.py,sha256=4jcu3KHQOKaaX5AYJsKb-F6GnsSy5fiqC_mWpe7wKVk,41431
|
|
82
82
|
operate/services/health_checker.py,sha256=dARikrgzU1jEuK4NUqlZ7N0DQq4Ah1ZiRKHmrlh8v-A,11472
|
|
83
83
|
operate/services/manage.py,sha256=9dTjXhBq6tgcFWfhU61JQL67L2h3_b_7p8OICimWWB4,113292
|
|
84
|
-
operate/services/protocol.py,sha256=
|
|
84
|
+
operate/services/protocol.py,sha256=4wAMHoELkqJJlETsMJa-XT8xVA0jfueiaKMn7X4SUKo,71898
|
|
85
85
|
operate/services/service.py,sha256=WK1RSe83OceETmzpzR22vpWdx1qMkF2J6RSf4P9GTyk,45363
|
|
86
86
|
operate/services/utils/__init__.py,sha256=TvioaZ1mfTRUSCtrQoLNAp4WMVXyqEJqFJM4PxSQCRU,24
|
|
87
87
|
operate/services/utils/mech.py,sha256=98gNw8pMNvv_O34V1blr7JUwenqxFeeyFuXLuSYv10w,3864
|
|
88
88
|
operate/services/utils/tendermint.py,sha256=M4zjF97SOJomhmj97bWKIphnia30lbDie65fs_vy_q8,25686
|
|
89
89
|
operate/settings.py,sha256=0J2E69-Oplo-Ijy-7rzYHc2Q9Xvct-EUMiEdmKKaYOQ,2353
|
|
90
|
-
operate/utils/__init__.py,sha256=
|
|
90
|
+
operate/utils/__init__.py,sha256=LD5xScW5ZW9pJeBg1h_9ydj0epPcRXbP0v7OVV85Cl8,8560
|
|
91
91
|
operate/utils/gnosis.py,sha256=mw-K-_xmRnY7QYl6Bzrh-jP7_JwHyiXhvwX6pCMSgt8,18056
|
|
92
92
|
operate/utils/single_instance.py,sha256=1qVibsLoK2m45ip38kRC6IMghaza_kD14D0wAbevcYk,9090
|
|
93
93
|
operate/utils/ssl.py,sha256=O5DrDoZD4T4qQuHP8GLwWUVxQ-1qXeefGp6uDJiF2lM,4308
|
|
94
94
|
operate/wallet/__init__.py,sha256=NGiozD3XhvkBi7_FaOWQ8x1thZPK4uGpokJaeDY_o2w,813
|
|
95
95
|
operate/wallet/master.py,sha256=5UvfEDFjgHNOHC25ZhZeewte1kXr69YPs5ue1NYSe5Q,33751
|
|
96
96
|
operate/wallet/wallet_recovery_manager.py,sha256=Dn0QmOYmmNj4p1X1TVQOrua3fysR3XJ99m6Z3H7tJQI,19792
|
|
97
|
-
olas_operate_middleware-0.13.
|
|
98
|
-
olas_operate_middleware-0.13.
|
|
99
|
-
olas_operate_middleware-0.13.
|
|
100
|
-
olas_operate_middleware-0.13.
|
|
101
|
-
olas_operate_middleware-0.13.
|
|
97
|
+
olas_operate_middleware-0.13.7.dist-info/METADATA,sha256=GJWEmdM2H2PxAekMBtr3__CW9lmHmrjpVAidOdcIXy4,1492
|
|
98
|
+
olas_operate_middleware-0.13.7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
99
|
+
olas_operate_middleware-0.13.7.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
|
|
100
|
+
olas_operate_middleware-0.13.7.dist-info/licenses/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
|
|
101
|
+
olas_operate_middleware-0.13.7.dist-info/RECORD,,
|
|
@@ -60,6 +60,7 @@ from operate.ledger.profiles import (
|
|
|
60
60
|
from operate.operate_types import Chain, ChainAmounts, LedgerType, OnChainState
|
|
61
61
|
from operate.services.protocol import EthSafeTxBuilder, StakingManager, StakingState
|
|
62
62
|
from operate.services.service import NON_EXISTENT_TOKEN, Service
|
|
63
|
+
from operate.utils import concurrent_execute
|
|
63
64
|
from operate.utils.gnosis import drain_eoa, get_asset_balance, get_owners
|
|
64
65
|
from operate.utils.gnosis import transfer as transfer_from_safe
|
|
65
66
|
from operate.utils.gnosis import transfer_erc20_from_safe
|
|
@@ -344,13 +345,27 @@ class FundingManager:
|
|
|
344
345
|
|
|
345
346
|
# os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc # TODO do we need this?
|
|
346
347
|
|
|
347
|
-
#
|
|
348
|
-
service_registry_address = CHAIN_PROFILES[chain]["service_registry"]
|
|
348
|
+
# Fetch on-chain data
|
|
349
349
|
service_registry = registry_contracts.service_registry.get_instance(
|
|
350
350
|
ledger_api=ledger_api,
|
|
351
|
-
contract_address=
|
|
351
|
+
contract_address=CHAIN_PROFILES[chain]["service_registry"],
|
|
352
|
+
)
|
|
353
|
+
(
|
|
354
|
+
service_info,
|
|
355
|
+
operator_balance,
|
|
356
|
+
current_staking_program,
|
|
357
|
+
) = concurrent_execute(
|
|
358
|
+
(service_registry.functions.getService(service_id).call, ()),
|
|
359
|
+
(
|
|
360
|
+
service_registry.functions.getOperatorBalance(
|
|
361
|
+
master_safe, service_id
|
|
362
|
+
).call,
|
|
363
|
+
(),
|
|
364
|
+
),
|
|
365
|
+
(staking_manager.get_current_staking_program, (service_id,)),
|
|
352
366
|
)
|
|
353
|
-
|
|
367
|
+
|
|
368
|
+
# Determine bonded native amount
|
|
354
369
|
security_deposit = service_info[0]
|
|
355
370
|
service_state = service_info[6]
|
|
356
371
|
agent_ids = service_info[7]
|
|
@@ -362,15 +377,9 @@ class FundingManager:
|
|
|
362
377
|
):
|
|
363
378
|
bonded_assets[ZERO_ADDRESS] += security_deposit
|
|
364
379
|
|
|
365
|
-
operator_balance = service_registry.functions.getOperatorBalance(
|
|
366
|
-
master_safe, service_id
|
|
367
|
-
).call()
|
|
368
380
|
bonded_assets[ZERO_ADDRESS] += operator_balance
|
|
369
381
|
|
|
370
382
|
# Determine bonded token amount for staking programs
|
|
371
|
-
current_staking_program = staking_manager.get_current_staking_program(
|
|
372
|
-
service_id=service_id,
|
|
373
|
-
)
|
|
374
383
|
target_staking_program = user_params.staking_program_id
|
|
375
384
|
staking_contract = staking_manager.get_staking_contract(
|
|
376
385
|
staking_program_id=current_staking_program or target_staking_program,
|
|
@@ -381,9 +390,7 @@ class FundingManager:
|
|
|
381
390
|
|
|
382
391
|
staking_manager = StakingManager(Chain(chain))
|
|
383
392
|
staking_params = staking_manager.get_staking_params(
|
|
384
|
-
staking_contract=
|
|
385
|
-
staking_program_id=user_params.staking_program_id,
|
|
386
|
-
),
|
|
393
|
+
staking_contract=staking_contract,
|
|
387
394
|
)
|
|
388
395
|
|
|
389
396
|
service_registry_token_utility_address = staking_params[
|
|
@@ -396,24 +403,62 @@ class FundingManager:
|
|
|
396
403
|
)
|
|
397
404
|
)
|
|
398
405
|
|
|
406
|
+
(
|
|
407
|
+
*agent_instances_and_bonds,
|
|
408
|
+
token_bond,
|
|
409
|
+
security_deposits,
|
|
410
|
+
staking_state,
|
|
411
|
+
) = concurrent_execute(
|
|
412
|
+
*(
|
|
413
|
+
[
|
|
414
|
+
(
|
|
415
|
+
service_registry.functions.getInstancesForAgentId(
|
|
416
|
+
service_id, agent_id
|
|
417
|
+
).call,
|
|
418
|
+
(),
|
|
419
|
+
)
|
|
420
|
+
for agent_id in agent_ids
|
|
421
|
+
]
|
|
422
|
+
+ [
|
|
423
|
+
(
|
|
424
|
+
service_registry_token_utility.functions.getAgentBond(
|
|
425
|
+
service_id, agent_id
|
|
426
|
+
).call,
|
|
427
|
+
(),
|
|
428
|
+
)
|
|
429
|
+
for agent_id in agent_ids
|
|
430
|
+
]
|
|
431
|
+
+ [
|
|
432
|
+
(
|
|
433
|
+
service_registry_token_utility.functions.getOperatorBalance(
|
|
434
|
+
master_safe, service_id
|
|
435
|
+
).call,
|
|
436
|
+
(),
|
|
437
|
+
),
|
|
438
|
+
(
|
|
439
|
+
service_registry_token_utility.functions.mapServiceIdTokenDeposit(
|
|
440
|
+
service_id
|
|
441
|
+
).call,
|
|
442
|
+
(),
|
|
443
|
+
),
|
|
444
|
+
(
|
|
445
|
+
staking_manager.staking_state,
|
|
446
|
+
(service_id, staking_params["staking_contract"]),
|
|
447
|
+
),
|
|
448
|
+
]
|
|
449
|
+
),
|
|
450
|
+
)
|
|
451
|
+
|
|
399
452
|
agent_bonds = 0
|
|
400
|
-
for
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
service_id, agent_id
|
|
406
|
-
).call()
|
|
453
|
+
for agent_instances, agent_bond in zip(
|
|
454
|
+
agent_instances_and_bonds[: len(agent_ids)],
|
|
455
|
+
agent_instances_and_bonds[len(agent_ids) :],
|
|
456
|
+
):
|
|
457
|
+
num_agent_instances = agent_instances[0]
|
|
407
458
|
agent_bonds += num_agent_instances * agent_bond
|
|
408
459
|
|
|
409
460
|
if service_state == OnChainState.TERMINATED_BONDED:
|
|
410
461
|
num_agent_instances = service_info[5]
|
|
411
|
-
token_bond = (
|
|
412
|
-
service_registry_token_utility.functions.getOperatorBalance(
|
|
413
|
-
master_safe,
|
|
414
|
-
service_id,
|
|
415
|
-
).call()
|
|
416
|
-
)
|
|
417
462
|
agent_bonds += num_agent_instances * token_bond
|
|
418
463
|
|
|
419
464
|
security_deposit = 0
|
|
@@ -422,20 +467,11 @@ class FundingManager:
|
|
|
422
467
|
<= service_state
|
|
423
468
|
< OnChainState.TERMINATED_BONDED
|
|
424
469
|
):
|
|
425
|
-
security_deposit =
|
|
426
|
-
service_registry_token_utility.functions.mapServiceIdTokenDeposit(
|
|
427
|
-
service_id
|
|
428
|
-
).call()[1]
|
|
429
|
-
)
|
|
470
|
+
security_deposit = security_deposits[1]
|
|
430
471
|
|
|
431
472
|
bonded_assets[staking_params["staking_token"]] += agent_bonds
|
|
432
473
|
bonded_assets[staking_params["staking_token"]] += security_deposit
|
|
433
474
|
|
|
434
|
-
staking_state = staking_manager.staking_state(
|
|
435
|
-
service_id=service_id,
|
|
436
|
-
staking_contract=staking_params["staking_contract"],
|
|
437
|
-
)
|
|
438
|
-
|
|
439
475
|
if staking_state in (StakingState.STAKED, StakingState.EVICTED):
|
|
440
476
|
for token, amount in staking_params[
|
|
441
477
|
"additional_staking_tokens"
|
|
@@ -564,40 +600,64 @@ class FundingManager:
|
|
|
564
600
|
|
|
565
601
|
def _get_master_safe_balances(self, thresholds: ChainAmounts) -> ChainAmounts:
|
|
566
602
|
output = ChainAmounts()
|
|
603
|
+
batch_calls_args = {}
|
|
567
604
|
for chain_str, addresses in thresholds.items():
|
|
568
605
|
chain = Chain(chain_str)
|
|
569
606
|
master_safe = self._resolve_master_safe(chain)
|
|
570
|
-
|
|
571
|
-
master_safe, {}
|
|
572
|
-
)
|
|
607
|
+
output.setdefault(chain_str, {}).setdefault(master_safe, {})
|
|
573
608
|
for _, assets in addresses.items():
|
|
574
609
|
for asset, _ in assets.items():
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
610
|
+
batch_calls_args[
|
|
611
|
+
(
|
|
612
|
+
get_default_ledger_api(chain),
|
|
613
|
+
asset,
|
|
614
|
+
master_safe,
|
|
615
|
+
False,
|
|
616
|
+
)
|
|
617
|
+
] = (
|
|
618
|
+
chain_str,
|
|
619
|
+
master_safe,
|
|
620
|
+
asset,
|
|
580
621
|
)
|
|
581
622
|
|
|
623
|
+
batch_calls_results = concurrent_execute(
|
|
624
|
+
*[(get_asset_balance, args) for args in batch_calls_args.keys()]
|
|
625
|
+
)
|
|
626
|
+
for args, balance in zip(batch_calls_args.keys(), batch_calls_results):
|
|
627
|
+
chain_str, master_safe, asset = batch_calls_args[args]
|
|
628
|
+
output[chain_str][master_safe][asset] = balance
|
|
629
|
+
|
|
582
630
|
return output
|
|
583
631
|
|
|
584
632
|
def _get_master_eoa_balances(self, thresholds: ChainAmounts) -> ChainAmounts:
|
|
585
633
|
output = ChainAmounts()
|
|
634
|
+
batch_calls_args = {}
|
|
586
635
|
for chain_str, addresses in thresholds.items():
|
|
587
636
|
chain = Chain(chain_str)
|
|
588
637
|
master_eoa = self._resolve_master_eoa(chain)
|
|
589
|
-
|
|
590
|
-
master_eoa, {}
|
|
591
|
-
)
|
|
638
|
+
output.setdefault(chain_str, {}).setdefault(master_eoa, {})
|
|
592
639
|
for _, assets in addresses.items():
|
|
593
640
|
for asset, _ in assets.items():
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
641
|
+
batch_calls_args[
|
|
642
|
+
(
|
|
643
|
+
get_default_ledger_api(chain),
|
|
644
|
+
asset,
|
|
645
|
+
master_eoa,
|
|
646
|
+
False,
|
|
647
|
+
)
|
|
648
|
+
] = (
|
|
649
|
+
chain_str,
|
|
650
|
+
master_eoa,
|
|
651
|
+
asset,
|
|
599
652
|
)
|
|
600
653
|
|
|
654
|
+
batch_calls_results = concurrent_execute(
|
|
655
|
+
*[(get_asset_balance, args) for args in batch_calls_args.keys()]
|
|
656
|
+
)
|
|
657
|
+
for args, balance in zip(batch_calls_args.keys(), batch_calls_results):
|
|
658
|
+
chain_str, master_eoa, asset = batch_calls_args[args]
|
|
659
|
+
output[chain_str][master_eoa][asset] = balance
|
|
660
|
+
|
|
601
661
|
return output
|
|
602
662
|
|
|
603
663
|
def fund_master_eoa(self) -> None:
|
|
@@ -623,12 +683,30 @@ class FundingManager:
|
|
|
623
683
|
}
|
|
624
684
|
)
|
|
625
685
|
master_eoa_balances = self._get_master_eoa_balances(master_eoa_topups)
|
|
686
|
+
master_safe_balance = self._get_master_safe_balances(master_eoa_topups)
|
|
626
687
|
master_eoa_shortfalls = self._compute_shortfalls(
|
|
627
688
|
balances=master_eoa_balances,
|
|
628
689
|
thresholds=master_eoa_topups * DEFAULT_EOA_THRESHOLD,
|
|
629
690
|
topups=master_eoa_topups,
|
|
630
691
|
)
|
|
631
|
-
|
|
692
|
+
possible_to_fund_shortfalls = ChainAmounts(
|
|
693
|
+
{
|
|
694
|
+
chain_str: {
|
|
695
|
+
address: {
|
|
696
|
+
asset: min(
|
|
697
|
+
amount,
|
|
698
|
+
master_safe_balance.get(chain_str, {})
|
|
699
|
+
.get(self._resolve_master_safe(Chain(chain_str)), {})
|
|
700
|
+
.get(asset, 0),
|
|
701
|
+
)
|
|
702
|
+
for asset, amount in assets.items()
|
|
703
|
+
}
|
|
704
|
+
for address, assets in addresses.items()
|
|
705
|
+
}
|
|
706
|
+
for chain_str, addresses in master_eoa_shortfalls.items()
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
self.fund_chain_amounts(possible_to_fund_shortfalls)
|
|
632
710
|
|
|
633
711
|
def funding_requirements(self, service: Service) -> t.Dict:
|
|
634
712
|
"""Funding requirements"""
|
|
@@ -639,9 +717,13 @@ class FundingManager:
|
|
|
639
717
|
total_requirements: ChainAmounts
|
|
640
718
|
chains = [Chain(chain_str) for chain_str in service.chain_configs.keys()]
|
|
641
719
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
720
|
+
(
|
|
721
|
+
protocol_thresholds,
|
|
722
|
+
protocol_balances,
|
|
723
|
+
) = concurrent_execute(
|
|
724
|
+
(self._compute_protocol_asset_requirements, (service,)),
|
|
725
|
+
(self._compute_protocol_bonded_assets, (service,)),
|
|
726
|
+
)
|
|
645
727
|
protocol_topups = protocol_thresholds
|
|
646
728
|
protocol_shortfalls = self._compute_shortfalls(
|
|
647
729
|
balances=protocol_balances,
|
operate/services/protocol.py
CHANGED
|
@@ -80,6 +80,7 @@ from operate.ledger.profiles import CONTRACTS, STAKING
|
|
|
80
80
|
from operate.operate_types import Chain as OperateChain
|
|
81
81
|
from operate.operate_types import ContractAddresses
|
|
82
82
|
from operate.services.service import NON_EXISTENT_TOKEN
|
|
83
|
+
from operate.utils import concurrent_execute
|
|
83
84
|
from operate.utils.gnosis import (
|
|
84
85
|
MultiSendOperation,
|
|
85
86
|
SafeOperation,
|
|
@@ -226,37 +227,20 @@ class StakingManager:
|
|
|
226
227
|
def _get_staking_params(chain: OperateChain, staking_contract: str) -> t.Dict:
|
|
227
228
|
"""Get staking params"""
|
|
228
229
|
ledger_api = get_default_ledger_api(chain=chain)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
|
|
231
|
+
second_token_func = (
|
|
232
|
+
lambda: None # pylint: disable=unnecessary-lambda-assignment # noqa: E731
|
|
232
233
|
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
staking_token = instance.functions.stakingToken().call()
|
|
236
|
-
service_registry_token_utility = (
|
|
237
|
-
instance.functions.serviceRegistryTokenUtility().call()
|
|
234
|
+
second_token_amount_func = (
|
|
235
|
+
lambda: None # pylint: disable=unnecessary-lambda-assignment # noqa: E731
|
|
238
236
|
)
|
|
239
|
-
min_staking_deposit = instance.functions.minStakingDeposit().call()
|
|
240
|
-
activity_checker = instance.functions.activityChecker().call()
|
|
241
|
-
|
|
242
|
-
output = {
|
|
243
|
-
"staking_contract": staking_contract,
|
|
244
|
-
"agent_ids": agent_ids,
|
|
245
|
-
"service_registry": service_registry,
|
|
246
|
-
"staking_token": staking_token,
|
|
247
|
-
"service_registry_token_utility": service_registry_token_utility,
|
|
248
|
-
"min_staking_deposit": min_staking_deposit,
|
|
249
|
-
"activity_checker": activity_checker,
|
|
250
|
-
"additional_staking_tokens": {},
|
|
251
|
-
}
|
|
252
237
|
try:
|
|
253
238
|
instance = StakingManager.dual_staking_ctr.get_instance(
|
|
254
239
|
ledger_api=ledger_api,
|
|
255
240
|
contract_address=staking_contract,
|
|
256
241
|
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
] = instance.functions.secondTokenAmount().call()
|
|
242
|
+
second_token_func = instance.functions.secondToken().call
|
|
243
|
+
second_token_amount_func = instance.functions.secondTokenAmount().call
|
|
260
244
|
except Exception: # pylint: disable=broad-except # nosec
|
|
261
245
|
# Contract is not a dual staking contract
|
|
262
246
|
|
|
@@ -269,6 +253,45 @@ class StakingManager:
|
|
|
269
253
|
# avoid any issues we are simply catching all exceptions.
|
|
270
254
|
pass
|
|
271
255
|
|
|
256
|
+
instance = StakingManager.staking_ctr.get_instance(
|
|
257
|
+
ledger_api=ledger_api,
|
|
258
|
+
contract_address=staking_contract,
|
|
259
|
+
)
|
|
260
|
+
(
|
|
261
|
+
agent_ids,
|
|
262
|
+
service_registry,
|
|
263
|
+
staking_token,
|
|
264
|
+
service_registry_token_utility,
|
|
265
|
+
min_staking_deposit,
|
|
266
|
+
activity_checker,
|
|
267
|
+
second_token,
|
|
268
|
+
second_token_amount,
|
|
269
|
+
) = concurrent_execute(
|
|
270
|
+
(instance.functions.getAgentIds().call, ()),
|
|
271
|
+
(instance.functions.serviceRegistry().call, ()),
|
|
272
|
+
(instance.functions.stakingToken().call, ()),
|
|
273
|
+
(instance.functions.serviceRegistryTokenUtility().call, ()),
|
|
274
|
+
(instance.functions.minStakingDeposit().call, ()),
|
|
275
|
+
(instance.functions.activityChecker().call, ()),
|
|
276
|
+
(second_token_func, ()),
|
|
277
|
+
(second_token_amount_func, ()),
|
|
278
|
+
ignore_exceptions=True,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
output = {
|
|
282
|
+
"staking_contract": staking_contract,
|
|
283
|
+
"agent_ids": agent_ids,
|
|
284
|
+
"service_registry": service_registry,
|
|
285
|
+
"staking_token": staking_token,
|
|
286
|
+
"service_registry_token_utility": service_registry_token_utility,
|
|
287
|
+
"min_staking_deposit": min_staking_deposit,
|
|
288
|
+
"activity_checker": activity_checker,
|
|
289
|
+
"additional_staking_tokens": (
|
|
290
|
+
{second_token: second_token_amount}
|
|
291
|
+
if second_token and second_token_amount
|
|
292
|
+
else {}
|
|
293
|
+
),
|
|
294
|
+
}
|
|
272
295
|
return output
|
|
273
296
|
|
|
274
297
|
def get_staking_params(self, staking_contract: str) -> t.Dict:
|
operate/utils/__init__.py
CHANGED
|
@@ -19,14 +19,25 @@
|
|
|
19
19
|
|
|
20
20
|
"""Helper utilities."""
|
|
21
21
|
|
|
22
|
+
import asyncio
|
|
23
|
+
import inspect
|
|
24
|
+
import logging
|
|
22
25
|
import os
|
|
23
26
|
import platform
|
|
24
27
|
import shutil
|
|
25
28
|
import time
|
|
26
29
|
import typing as t
|
|
30
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
31
|
+
from concurrent.futures import TimeoutError as FuturesTimeoutError
|
|
32
|
+
from contextlib import contextmanager
|
|
27
33
|
from pathlib import Path
|
|
28
34
|
from threading import Lock
|
|
29
35
|
|
|
36
|
+
from operate.constants import DEFAULT_TIMEOUT
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
30
41
|
|
|
31
42
|
class SingletonMeta(type):
|
|
32
43
|
"""A metaclass for creating thread-safe singleton classes."""
|
|
@@ -153,3 +164,100 @@ def unrecoverable_delete(file_path: Path, passes: int = 3) -> None:
|
|
|
153
164
|
print(f"Permission denied to securely delete file '{file_path}'.")
|
|
154
165
|
except Exception as e: # pylint: disable=broad-except
|
|
155
166
|
print(f"Error during secure deletion of '{file_path}': {e}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@contextmanager
|
|
170
|
+
def timing_context(label: str = "Block") -> t.Generator[None, None, None]:
|
|
171
|
+
"""Context manager for timing a code block."""
|
|
172
|
+
start = time.perf_counter()
|
|
173
|
+
try:
|
|
174
|
+
yield
|
|
175
|
+
finally:
|
|
176
|
+
end = time.perf_counter()
|
|
177
|
+
logger.debug(f"[{label}] Elapsed time: {end - start:.4f} seconds")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def concurrent_execute(
|
|
181
|
+
*func_calls: t.Tuple[t.Callable, t.Tuple],
|
|
182
|
+
ignore_exceptions: bool = False,
|
|
183
|
+
) -> t.List[t.Any]:
|
|
184
|
+
"""Execute callables concurrently.
|
|
185
|
+
|
|
186
|
+
This is a synchronous convenience wrapper around `parallel_execute_async`.
|
|
187
|
+
If called from within an active asyncio event loop, use
|
|
188
|
+
`await parallel_execute_async(...)` instead.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
async def _runner() -> t.List[t.Any]:
|
|
192
|
+
return await concurrent_execute_async(
|
|
193
|
+
*func_calls,
|
|
194
|
+
ignore_exceptions=ignore_exceptions,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
asyncio.get_running_loop()
|
|
199
|
+
except RuntimeError:
|
|
200
|
+
# No running loop in this thread.
|
|
201
|
+
return asyncio.run(_runner())
|
|
202
|
+
|
|
203
|
+
# Running inside an event loop thread.
|
|
204
|
+
# We cannot call `asyncio.run` here, so offload to a background thread.
|
|
205
|
+
# NOTE: this blocks the current thread until completion.
|
|
206
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
207
|
+
future = executor.submit(asyncio.run, _runner())
|
|
208
|
+
return future.result()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def concurrent_execute_async(
|
|
212
|
+
*func_calls: t.Tuple[t.Callable, t.Tuple],
|
|
213
|
+
ignore_exceptions: bool = False,
|
|
214
|
+
) -> t.List[t.Any]:
|
|
215
|
+
"""Execute callables concurrently using asyncio.
|
|
216
|
+
|
|
217
|
+
- Async callables are awaited directly.
|
|
218
|
+
- Sync callables are executed via `asyncio.to_thread`.
|
|
219
|
+
|
|
220
|
+
Results are returned in the same order as `funcs`/`args_list`.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
async def _invoke(func: t.Callable, args: t.Tuple) -> t.Any:
|
|
224
|
+
with timing_context(f"Executing {func.__name__}"):
|
|
225
|
+
if inspect.iscoroutinefunction(func):
|
|
226
|
+
return await t.cast(t.Awaitable[t.Any], func(*args))
|
|
227
|
+
return await asyncio.to_thread(func, *args)
|
|
228
|
+
|
|
229
|
+
results: t.List[t.Any] = [None] * len(func_calls)
|
|
230
|
+
|
|
231
|
+
async def _invoke_indexed(
|
|
232
|
+
idx: int, func: t.Callable, args: t.Tuple
|
|
233
|
+
) -> t.Tuple[int, t.Any]:
|
|
234
|
+
try:
|
|
235
|
+
return idx, await _invoke(func, args)
|
|
236
|
+
except Exception as e: # pylint: disable=broad-except
|
|
237
|
+
return idx, e
|
|
238
|
+
|
|
239
|
+
tasks: t.List[asyncio.Task] = [
|
|
240
|
+
asyncio.create_task(_invoke_indexed(idx, func, args))
|
|
241
|
+
for idx, (func, args) in enumerate(func_calls)
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
for task in asyncio.as_completed(tasks, timeout=DEFAULT_TIMEOUT):
|
|
246
|
+
idx, outcome = await task
|
|
247
|
+
if isinstance(outcome, BaseException):
|
|
248
|
+
if ignore_exceptions:
|
|
249
|
+
results[idx] = None
|
|
250
|
+
else:
|
|
251
|
+
raise outcome
|
|
252
|
+
else:
|
|
253
|
+
results[idx] = outcome
|
|
254
|
+
except asyncio.TimeoutError as e:
|
|
255
|
+
raise FuturesTimeoutError() from e
|
|
256
|
+
finally:
|
|
257
|
+
# Ensure we don't leak pending tasks.
|
|
258
|
+
for task in tasks:
|
|
259
|
+
if not task.done():
|
|
260
|
+
task.cancel()
|
|
261
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
262
|
+
|
|
263
|
+
return results
|
|
File without changes
|
|
File without changes
|
|
File without changes
|