From bce4e81ac52fe6bd244f1d61d1c10efafc119198 Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Wed, 10 Sep 2025 14:16:06 +0200 Subject: [PATCH 01/11] Problem: Ledger wallet users cannot use Aleph to send transactions. Solution: Implement Ledger use on SDK to allow using them. --- pyproject.toml | 2 ++ src/aleph/sdk/conf.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 24012413..e85ba4a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,8 @@ dependencies = [ "python-magic", "typing-extensions", "web3>=7.10", + "ledgerblue>=0.1.48", + "ledgereth>=0.10.0", ] optional-dependencies.all = [ diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index ae79063a..ebc02c17 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -394,6 +394,8 @@ def infer_type(cls, values: dict): "type" not in config_data or config_data["type"] == AccountType.IMPORTED ): settings.PRIVATE_KEY_FILE = Path(config_data["path"]) + else: + settings.PRIVATE_KEY_FILE = None except json.JSONDecodeError: pass From a3c846c85c89060d55af7d1df5e28c22dbdc7275 Mon Sep 17 00:00:00 2001 From: "Andres D. Molins" Date: Wed, 10 Sep 2025 14:33:41 +0200 Subject: [PATCH 02/11] Fix: Solved linting and types issues for code quality. --- pyproject.toml | 2 -- src/aleph/sdk/conf.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e85ba4a1..24012413 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,6 @@ dependencies = [ "python-magic", "typing-extensions", "web3>=7.10", - "ledgerblue>=0.1.48", - "ledgereth>=0.10.0", ] optional-dependencies.all = [ diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index ebc02c17..ae79063a 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -394,8 +394,6 @@ def infer_type(cls, values: dict): "type" not in config_data or config_data["type"] == AccountType.IMPORTED ): settings.PRIVATE_KEY_FILE = Path(config_data["path"]) - else: - settings.PRIVATE_KEY_FILE = None except json.JSONDecodeError: pass From 1a4f5ecbeade1ace43c4b295c798e3d3e39d054b Mon Sep 17 00:00:00 2001 From: 1yam Date: Mon, 10 Nov 2025 14:14:17 +0100 Subject: [PATCH 03/11] Feature: aleph settings services --- src/aleph/sdk/client/http.py | 3 +- src/aleph/sdk/client/services/settings.py | 40 +++++ tests/unit/services/test_settings.py | 200 ++++++++++++++++++++++ 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/aleph/sdk/client/services/settings.py create mode 100644 tests/unit/services/test_settings.py diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py index c1facba1..b42683e1 100644 --- a/src/aleph/sdk/client/http.py +++ b/src/aleph/sdk/client/http.py @@ -39,6 +39,7 @@ from aleph.sdk.client.services.port_forwarder import PortForwarder from aleph.sdk.client.services.pricing import Pricing from aleph.sdk.client.services.scheduler import Scheduler +from aleph.sdk.client.services.settings import Settings as NetworkSettingsService from aleph.sdk.client.services.voucher import Vouchers from ..conf import settings @@ -146,7 +147,7 @@ async def __aenter__(self): self.instance = Instance(self) self.pricing = Pricing(self) self.voucher = Vouchers(self) - + self.settings = NetworkSettingsService(self) return self async def __aexit__(self, exc_type, exc_val, exc_tb): diff --git a/src/aleph/sdk/client/services/settings.py b/src/aleph/sdk/client/services/settings.py new file mode 100644 index 00000000..cd5015a5 --- /dev/null +++ b/src/aleph/sdk/client/services/settings.py @@ -0,0 +1,40 @@ +from typing import List + +from pydantic import BaseModel + +from .base import BaseService + + +class NetworkAvailableGpu(BaseModel): + name: str + model: str + vendor: str + device_id: str + + +class NetworkSettingsModel(BaseModel): + compatible_gpus: List[NetworkAvailableGpu] + last_crn_version: str + community_wallet_address: str + community_wallet_timestamp: int + + +class Settings(BaseService[NetworkSettingsModel]): + """ + This Service handle logic around Pricing + """ + + aggregate_key = "settings" + model_cls = NetworkSettingsModel + + def __init__(self, client): + super().__init__(client=client) + + # Config from aggregate + async def get_settings_aggregate( + self, + ) -> NetworkSettingsModel: + result = await self.get_config( + address="0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + ) + return result.data[0] diff --git a/tests/unit/services/test_settings.py b/tests/unit/services/test_settings.py new file mode 100644 index 00000000..5b2efebb --- /dev/null +++ b/tests/unit/services/test_settings.py @@ -0,0 +1,200 @@ +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from aleph.sdk import AlephHttpClient +from aleph.sdk.client.services.settings import NetworkSettingsModel, Settings + + +@pytest.fixture +def mock_settings_aggregate_response(): + return { + "compatible_gpus": [ + { + "name": "AD102GL [L40S]", + "model": "L40S", + "vendor": "NVIDIA", + "device_id": "10de:26b9", + }, + { + "name": "GB202 [GeForce RTX 5090]", + "model": "RTX 5090", + "vendor": "NVIDIA", + "device_id": "10de:2685", + }, + { + "name": "GB202 [GeForce RTX 5090 D]", + "model": "RTX 5090", + "vendor": "NVIDIA", + "device_id": "10de:2687", + }, + { + "name": "AD102 [GeForce RTX 4090]", + "model": "RTX 4090", + "vendor": "NVIDIA", + "device_id": "10de:2684", + }, + { + "name": "AD102 [GeForce RTX 4090 D]", + "model": "RTX 4090", + "vendor": "NVIDIA", + "device_id": "10de:2685", + }, + { + "name": "GA102 [GeForce RTX 3090]", + "model": "RTX 3090", + "vendor": "NVIDIA", + "device_id": "10de:2204", + }, + { + "name": "GA102 [GeForce RTX 3090 Ti]", + "model": "RTX 3090", + "vendor": "NVIDIA", + "device_id": "10de:2203", + }, + { + "name": "AD104GL [RTX 4000 SFF Ada Generation]", + "model": "RTX 4000 ADA", + "vendor": "NVIDIA", + "device_id": "10de:27b0", + }, + { + "name": "AD104GL [RTX 4000 Ada Generation]", + "model": "RTX 4000 ADA", + "vendor": "NVIDIA", + "device_id": "10de:27b2", + }, + { + "name": "GA102GL [RTX A5000]", + "model": "RTX A5000", + "vendor": "NVIDIA", + "device_id": "10de:2231", + }, + { + "name": "GA102GL [RTX A6000]", + "model": "RTX A6000", + "vendor": "NVIDIA", + "device_id": "10de:2230", + }, + { + "name": "GH100 [H100]", + "model": "H100", + "vendor": "NVIDIA", + "device_id": "10de:2336", + }, + { + "name": "GH100 [H100 NVSwitch]", + "model": "H100", + "vendor": "NVIDIA", + "device_id": "10de:22a3", + }, + { + "name": "GH100 [H100 CNX]", + "model": "H100", + "vendor": "NVIDIA", + "device_id": "10de:2313", + }, + { + "name": "GH100 [H100 SXM5 80GB]", + "model": "H100", + "vendor": "NVIDIA", + "device_id": "10de:2330", + }, + { + "name": "GH100 [H100 PCIe]", + "model": "H100", + "vendor": "NVIDIA", + "device_id": "10de:2331", + }, + { + "name": "GA100", + "model": "A100", + "vendor": "NVIDIA", + "device_id": "10de:2080", + }, + { + "name": "GA100", + "model": "A100", + "vendor": "NVIDIA", + "device_id": "10de:2081", + }, + { + "name": "GA100 [A100 SXM4 80GB]", + "model": "A100", + "vendor": "NVIDIA", + "device_id": "10de:20b2", + }, + { + "name": "GA100 [A100 PCIe 80GB]", + "model": "A100", + "vendor": "NVIDIA", + "device_id": "10de:20b5", + }, + { + "name": "GA100 [A100X]", + "model": "A100", + "vendor": "NVIDIA", + "device_id": "10de:20b8", + }, + { + "name": "GH100 [H200 SXM 141GB]", + "model": "H200", + "vendor": "NVIDIA", + "device_id": "10de:2335", + }, + { + "name": "GH100 [H200 NVL]", + "model": "H200", + "vendor": "NVIDIA", + "device_id": "10de:233b", + }, + { + "name": "AD102GL [RTX 6000 ADA]", + "model": "RTX 6000 ADA", + "vendor": "NVIDIA", + "device_id": "10de:26b1", + }, + ], + "last_crn_version": "1.7.2", + "community_wallet_address": "0x5aBd3258C5492fD378EBC2e0017416E199e5Da56", + "community_wallet_timestamp": 1739996239, + } + + +@pytest.mark.asyncio +async def test_get_settings_aggregate( + make_mock_aiohttp_session, mock_settings_aggregate_response +): + client = AlephHttpClient(api_server="http://localhost") + + # Properly mock the fetch_aggregate method using monkeypatch + client._http_session = MagicMock() + monkeypatch = AsyncMock(return_value=mock_settings_aggregate_response) + setattr(client, "fetch_aggregate", monkeypatch) + + settings_service = Settings(client) + result = await settings_service.get_settings_aggregate() + + assert isinstance(result, NetworkSettingsModel) + assert len(result.compatible_gpus) == 24 # We have 24 GPUs in the mock data + + rtx4000_gpu = next( + gpu for gpu in result.compatible_gpus if gpu.device_id == "10de:27b0" + ) + assert rtx4000_gpu.name == "AD104GL [RTX 4000 SFF Ada Generation]" + assert rtx4000_gpu.model == "RTX 4000 ADA" + assert rtx4000_gpu.vendor == "NVIDIA" + + assert result.last_crn_version == "1.7.2" + assert ( + result.community_wallet_address == "0x5aBd3258C5492fD378EBC2e0017416E199e5Da56" + ) + assert result.community_wallet_timestamp == 1739996239 + + # Verify that fetch_aggregate was called with the correct parameters + assert monkeypatch.call_count == 1 + assert ( + monkeypatch.call_args.kwargs["address"] + == "0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + ) + assert monkeypatch.call_args.kwargs["key"] == "settings" From cfcdc0b1db62c513e971536569af446a833e9b9c Mon Sep 17 00:00:00 2001 From: 1yam Date: Mon, 10 Nov 2025 14:29:14 +0100 Subject: [PATCH 04/11] Feature: use settings aggregates instead of hardcoded crn version --- src/aleph/sdk/client/services/crn.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 82cec51b..58f6978f 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -95,7 +95,7 @@ def find_gpu_on_network(self): available_gpu_list=available_compatible_gpu, ) - def filter_crn( + async def filter_crn( self, latest_crn_version: bool = False, ipv6: bool = False, @@ -113,15 +113,16 @@ def filter_crn( Returns: list[CRN]: List of compute resource nodes. (if no filter applied, return all) """ - # current_crn_version = await fetch_latest_crn_version() - # Relax current filter to allow use aleph-vm versions since 1.5.1. - # TODO: Allow to specify that option on settings aggregate on maybe on GitHub - current_crn_version = "1.5.1" + if latest_crn_version: + settings_aggregates = await self._client.settings.get_settings_aggregate() filtered_crn: list[CRN] = [] for crn_ in self.crns: # Check crn version - if latest_crn_version and (crn_.version or "0.0.0") < current_crn_version: + if ( + latest_crn_version + and (crn_.version or "0.0.0") < settings_aggregates.last_crn_version + ): continue # Filter with ipv6 check From 0d14aac0cb1d18c0eaff0077fe2fbd643a629bd2 Mon Sep 17 00:00:00 2001 From: 1yam Date: Mon, 10 Nov 2025 15:01:52 +0100 Subject: [PATCH 05/11] Fix: we should give crn_version instead of fetching it here --- src/aleph/sdk/client/services/crn.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 58f6978f..4d3f5ec1 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -95,9 +95,9 @@ def find_gpu_on_network(self): available_gpu_list=available_compatible_gpu, ) - async def filter_crn( + def filter_crn( self, - latest_crn_version: bool = False, + crn_version: str = None, ipv6: bool = False, stream_address: bool = False, confidential: bool = False, @@ -105,7 +105,7 @@ async def filter_crn( ) -> list[CRN]: """Filter compute resource node list, unfiltered by default. Args: - latest_crn_version (bool): Filter by latest crn version. + crn_version (str): Filter by specific crn version. ipv6 (bool): Filter invalid IPv6 configuration. stream_address (bool): Filter invalid payment receiver address. confidential (bool): Filter by confidential computing support. @@ -113,15 +113,13 @@ async def filter_crn( Returns: list[CRN]: List of compute resource nodes. (if no filter applied, return all) """ - if latest_crn_version: - settings_aggregates = await self._client.settings.get_settings_aggregate() filtered_crn: list[CRN] = [] for crn_ in self.crns: # Check crn version if ( - latest_crn_version - and (crn_.version or "0.0.0") < settings_aggregates.last_crn_version + crn_version + and (crn_.version or "0.0.0") < crn_version ): continue From ddc90985d4174f4a643cc2bacb2c3230d04722e9 Mon Sep 17 00:00:00 2001 From: 1yam Date: Mon, 10 Nov 2025 15:02:24 +0100 Subject: [PATCH 06/11] fix: linting issue --- src/aleph/sdk/client/services/crn.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 4d3f5ec1..739b59d8 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -117,10 +117,7 @@ def filter_crn( filtered_crn: list[CRN] = [] for crn_ in self.crns: # Check crn version - if ( - crn_version - and (crn_.version or "0.0.0") < crn_version - ): + if crn_version and (crn_.version or "0.0.0") < crn_version: continue # Filter with ipv6 check From 88c5aff2e4040314bf4c9d56f8786eed74e27bbd Mon Sep 17 00:00:00 2001 From: 1yam Date: Fri, 21 Nov 2025 16:45:47 +0100 Subject: [PATCH 07/11] Fix: crn_version should be optional and given --- src/aleph/sdk/client/services/crn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/services/crn.py b/src/aleph/sdk/client/services/crn.py index 739b59d8..fe6d538d 100644 --- a/src/aleph/sdk/client/services/crn.py +++ b/src/aleph/sdk/client/services/crn.py @@ -97,7 +97,7 @@ def find_gpu_on_network(self): def filter_crn( self, - crn_version: str = None, + crn_version: Optional[str] = None, ipv6: bool = False, stream_address: bool = False, confidential: bool = False, From e594d154e08297505478501fbbfb08c43f592086 Mon Sep 17 00:00:00 2001 From: 1yam <40899431+1yam@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:26:43 +0100 Subject: [PATCH 08/11] Apply suggestion from @nesitor Co-authored-by: nesitor --- src/aleph/sdk/client/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py index b42683e1..fa660433 100644 --- a/src/aleph/sdk/client/http.py +++ b/src/aleph/sdk/client/http.py @@ -147,7 +147,7 @@ async def __aenter__(self): self.instance = Instance(self) self.pricing = Pricing(self) self.voucher = Vouchers(self) - self.settings = NetworkSettingsService(self) + self.network_settings = NetworkSettingsService(self) return self async def __aexit__(self, exc_type, exc_val, exc_tb): From 8b57e4eff50febe89505d01365b14334d72a3102 Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 27 Nov 2025 13:31:32 +0100 Subject: [PATCH 09/11] Fix: use settings.ALEPH_AGGREGATE_ADDRESS instead of hardcoded address for pricing and settings --- src/aleph/sdk/client/services/pricing.py | 4 ++-- src/aleph/sdk/client/services/settings.py | 4 ++-- src/aleph/sdk/conf.py | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aleph/sdk/client/services/pricing.py b/src/aleph/sdk/client/services/pricing.py index 9c19eb0e..82aeceee 100644 --- a/src/aleph/sdk/client/services/pricing.py +++ b/src/aleph/sdk/client/services/pricing.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Union from aleph.sdk.client.services.base import BaseService - +from aleph.sdk.conf import settings if TYPE_CHECKING: pass @@ -206,7 +206,7 @@ async def get_pricing_aggregate( self, ) -> PricingModel: result = await self.get_config( - address="0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + address=settings.ALEPH_AGGREGATE_ADDRESS ) return result.data[0] diff --git a/src/aleph/sdk/client/services/settings.py b/src/aleph/sdk/client/services/settings.py index cd5015a5..ee35c325 100644 --- a/src/aleph/sdk/client/services/settings.py +++ b/src/aleph/sdk/client/services/settings.py @@ -1,7 +1,7 @@ from typing import List from pydantic import BaseModel - +from aleph.sdk.conf import settings from .base import BaseService @@ -35,6 +35,6 @@ async def get_settings_aggregate( self, ) -> NetworkSettingsModel: result = await self.get_config( - address="0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + address=settings.ALEPH_AGGREGATE_ADDRESS ) return result.data[0] diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index ae79063a..2f3f2896 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -99,6 +99,8 @@ class Settings(BaseSettings): VOUCHER_SOL_REGISTRY: str = "https://api.claim.twentysix.cloud/v1/registry/sol" VOUCHER_ORIGIN_ADDRESS: str = "0xB34f25f2c935bCA437C061547eA12851d719dEFb" + ALEPH_AGGREGATE_ADDRESS = "0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + # Web3Provider settings TOKEN_DECIMALS: ClassVar[int] = 18 TX_TIMEOUT: ClassVar[int] = 60 * 3 From e40eb5db9488b4c10bef3dc043dd7ae93fd49b2b Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 27 Nov 2025 13:32:00 +0100 Subject: [PATCH 10/11] fix: linting --- src/aleph/sdk/client/services/pricing.py | 5 ++--- src/aleph/sdk/client/services/settings.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/aleph/sdk/client/services/pricing.py b/src/aleph/sdk/client/services/pricing.py index 82aeceee..e2b51c50 100644 --- a/src/aleph/sdk/client/services/pricing.py +++ b/src/aleph/sdk/client/services/pricing.py @@ -5,6 +5,7 @@ from aleph.sdk.client.services.base import BaseService from aleph.sdk.conf import settings + if TYPE_CHECKING: pass @@ -205,9 +206,7 @@ def __init__(self, client): async def get_pricing_aggregate( self, ) -> PricingModel: - result = await self.get_config( - address=settings.ALEPH_AGGREGATE_ADDRESS - ) + result = await self.get_config(address=settings.ALEPH_AGGREGATE_ADDRESS) return result.data[0] async def get_pricing_for_services( diff --git a/src/aleph/sdk/client/services/settings.py b/src/aleph/sdk/client/services/settings.py index ee35c325..9f4de76b 100644 --- a/src/aleph/sdk/client/services/settings.py +++ b/src/aleph/sdk/client/services/settings.py @@ -1,7 +1,9 @@ from typing import List from pydantic import BaseModel + from aleph.sdk.conf import settings + from .base import BaseService @@ -34,7 +36,5 @@ def __init__(self, client): async def get_settings_aggregate( self, ) -> NetworkSettingsModel: - result = await self.get_config( - address=settings.ALEPH_AGGREGATE_ADDRESS - ) + result = await self.get_config(address=settings.ALEPH_AGGREGATE_ADDRESS) return result.data[0] From 60aafe4067d1e99681f600215654909537c83f02 Mon Sep 17 00:00:00 2001 From: 1yam Date: Thu, 27 Nov 2025 13:35:35 +0100 Subject: [PATCH 11/11] fix: missing typing for ALEPH_AGGREGATE_ADDRESS in settings --- src/aleph/sdk/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index 2f3f2896..ee91fc39 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -99,7 +99,7 @@ class Settings(BaseSettings): VOUCHER_SOL_REGISTRY: str = "https://api.claim.twentysix.cloud/v1/registry/sol" VOUCHER_ORIGIN_ADDRESS: str = "0xB34f25f2c935bCA437C061547eA12851d719dEFb" - ALEPH_AGGREGATE_ADDRESS = "0xFba561a84A537fCaa567bb7A2257e7142701ae2A" + ALEPH_AGGREGATE_ADDRESS: str = "0xFba561a84A537fCaa567bb7A2257e7142701ae2A" # Web3Provider settings TOKEN_DECIMALS: ClassVar[int] = 18