Skip to content

Commit

Permalink
Feat: New pricing system (#332)
Browse files Browse the repository at this point in the history
- Add/use new price aggregate
- New compute units system
- Display selection table and checkout for each entity 
- Add/use new cost endpoint
- Split flows (between 80/20)
- Handle legacy flow/VM older than wallet_community_timestamp
- Fetch CRN list from new program endpoint
- Select an available GPU and then filter CRNs
- Display GPU pricing and compute units selection
- `internet` field not passed on program creation. It's an important fix since internet=True doubles the cost
- Display program pricing
- New `aleph pricing <entity>` cmd + tests
- New `aleph instance gpu` cmd
- Deprecate and remove firecracker hypervisor for instances
- Upgrade CI action versions
  • Loading branch information
philogicae authored Feb 18, 2025
1 parent 5669846 commit fbae1c4
Show file tree
Hide file tree
Showing 22 changed files with 1,486 additions and 678 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Set up Python for macOS
if: startsWith(matrix.os, 'macos')
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ${{matrix.os}}

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Workaround github issue https://github.com/actions/runner-images/issues/7192
if: startsWith(matrix.os, 'ubuntu-')
Expand All @@ -35,7 +35,7 @@ jobs:
- name: Set up Python for macOS
if: startsWith(matrix.os, 'macos')
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.11

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

# Use GitHub's Docker registry to cache intermediate layers
- run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ dynamic = [ "version" ]
dependencies = [
"aiodns==3.2",
"aiohttp==3.11.12",
"aleph-message>=0.6",
"aleph-sdk-python>=1.3,<2",
"aleph-message>=0.6.1",
"aleph-sdk-python>=1.4,<2",
"base58==2.1.1", # Needed now as default with _load_account changement
"py-sr25519-bindings==0.2", # Needed for DOT signatures
"pygments==2.19.1",
Expand Down
2 changes: 2 additions & 0 deletions src/aleph_client/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
instance,
message,
node,
pricing,
program,
)
from aleph_client.utils import AsyncTyper
Expand All @@ -32,6 +33,7 @@
app.add_typer(domain.app, name="domain", help="Manage custom domain (DNS) on aleph.im & twentysix.cloud")
app.add_typer(node.app, name="node", help="Get node info on aleph.im & twentysix.cloud")
app.add_typer(about.app, name="about", help="Display the informations of Aleph CLI")
app.command("pricing")(pricing.prices_for_service)

if __name__ == "__main__":
app()
100 changes: 53 additions & 47 deletions src/aleph_client/commands/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
get_chains_with_super_token,
get_compatible_chains,
)
from aleph.sdk.utils import bytes_from_hex
from aleph.sdk.utils import bytes_from_hex, displayable_amount
from aleph_message.models import Chain
from rich.console import Console
from rich.panel import Panel
Expand Down Expand Up @@ -241,6 +241,20 @@ def sign_bytes(
typer.echo("\nSignature: " + signature.hex())


async def get_balance(address: str) -> dict:
balance_data: dict = {}
uri = f"{settings.API_HOST}/api/v0/addresses/{address}/balance"
async with aiohttp.ClientSession() as session:
response = await session.get(uri)
if response.status == 200:
balance_data = await response.json()
balance_data["available_amount"] = balance_data["balance"] - balance_data["locked_amount"]
else:
error = f"Failed to retrieve balance for address {address}. Status code: {response.status}"
raise Exception(error)
return balance_data


@app.command()
async def balance(
address: Optional[str] = typer.Option(None, help="Address"),
Expand All @@ -255,54 +269,46 @@ async def balance(
address = account.get_address()

if address:
uri = f"{settings.API_HOST}/api/v0/addresses/{address}/balance"

async with aiohttp.ClientSession() as session:
response = await session.get(uri)
if response.status == 200:
balance_data = await response.json()
balance_data["available_amount"] = balance_data["balance"] - balance_data["locked_amount"]

infos = [
Text.from_markup(f"Address: [bright_cyan]{balance_data['address']}[/bright_cyan]"),
Text.from_markup(
f"\nBalance: [bright_cyan]{balance_data['balance']:.2f}".rstrip("0").rstrip(".")
+ "[/bright_cyan]"
),
]
details = balance_data.get("details")
if details:
infos += [Text("\n ↳ Details")]
for chain_, chain_balance in details.items():
infos += [
Text.from_markup(
f"\n {chain_}: [orange3]{chain_balance:.2f}".rstrip("0").rstrip(".") + "[/orange3]"
)
]
available_color = "bright_cyan" if balance_data["available_amount"] >= 0 else "red"
infos += [
Text.from_markup(
f"\n - Locked: [bright_cyan]{balance_data['locked_amount']:.2f}".rstrip("0").rstrip(".")
+ "[/bright_cyan]"
),
Text.from_markup(
f"\n - Available: [{available_color}]{balance_data['available_amount']:.2f}".rstrip("0").rstrip(
"."
try:
balance_data = await get_balance(address)
infos = [
Text.from_markup(f"Address: [bright_cyan]{balance_data['address']}[/bright_cyan]"),
Text.from_markup(
f"\nBalance: [bright_cyan]{displayable_amount(balance_data['balance'], decimals=2)}[/bright_cyan]"
),
]
details = balance_data.get("details")
if details:
infos += [Text("\n ↳ Details")]
for chain_, chain_balance in details.items():
infos += [
Text.from_markup(
f"\n {chain_}: [orange3]{displayable_amount(chain_balance, decimals=2)}[/orange3]"
)
+ f"[/{available_color}]"
),
]
console.print(
Panel(
Text.assemble(*infos),
title="Account Infos",
border_style="bright_cyan",
expand=False,
title_align="left",
)
]
available_color = "bright_cyan" if balance_data["available_amount"] >= 0 else "red"
infos += [
Text.from_markup(
f"\n - Locked: [bright_cyan]{displayable_amount(balance_data['locked_amount'], decimals=2)}"
"[/bright_cyan]"
),
Text.from_markup(
f"\n - Available: [{available_color}]"
f"{displayable_amount(balance_data['available_amount'], decimals=2)}"
f"[/{available_color}]"
),
]
console.print(
Panel(
Text.assemble(*infos),
title="Account Infos",
border_style="bright_cyan",
expand=False,
title_align="left",
)
else:
typer.echo(f"Failed to retrieve balance for address {address}. Status code: {response.status}")
)
except Exception as e:
typer.echo(e)
else:
typer.echo("Error: Please provide either a private key, private key file, or an address.")

Expand Down
8 changes: 6 additions & 2 deletions src/aleph_client/commands/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ async def download(

@app.command()
async def forget(
item_hash: str = typer.Argument(..., help="Hash to forget"),
item_hash: str = typer.Argument(
..., help="Hash(es) to forget. Must be a comma separated list. Example: `123...abc` or `123...abc,456...xyz`"
),
reason: str = typer.Argument("User deletion", help="reason to forget"),
channel: Optional[str] = typer.Option(default=settings.DEFAULT_CHANNEL, help=help_strings.CHANNEL),
private_key: Optional[str] = typer.Option(settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY),
Expand All @@ -155,8 +157,10 @@ async def forget(

account: AccountFromPrivateKey = _load_account(private_key, private_key_file)

hashes = [ItemHash(item_hash) for item_hash in item_hash.split(",")]

async with AuthenticatedAlephHttpClient(account=account, api_server=settings.API_HOST) as client:
value = await client.forget(hashes=[ItemHash(item_hash)], reason=reason, channel=channel)
value = await client.forget(hashes=hashes, reason=reason, channel=channel)
typer.echo(f"{value[0].json(indent=4)}")


Expand Down
15 changes: 9 additions & 6 deletions src/aleph_client/commands/help_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@
ASK_FOR_CONFIRMATION = "Prompt user for confirmation"
IPFS_CATCH_ALL_PATH = "Choose a relative path to catch all unmatched route or a 404 error"
PAYMENT_TYPE = "Payment method, either holding tokens, NFTs, or Pay-As-You-Go via token streaming"
HYPERVISOR = "Hypervisor to use to launch your instance. Defaults to QEMU"
HYPERVISOR = "Hypervisor to use to launch your instance. Always defaults to QEMU, since Firecracker is now deprecated for instances"
INSTANCE_NAME = "Name of your new instance"
ROOTFS = (
"Hash of the rootfs to use for your instance. Defaults to Ubuntu 22. You can also create your own rootfs and pin it"
)
ROOTFS_SIZE = (
"Size of the rootfs to use for your instance. If not set, content.size of the --rootfs store message will be used"
)
COMPUTE_UNITS = "Number of compute units to allocate. Compute units correspond to a tier that includes vcpus, memory, disk and gpu presets. For reference, run: `aleph pricing --help`"
ROOTFS_SIZE = "Rootfs size in MiB to allocate"
VCPUS = "Number of virtual CPUs to allocate"
MEMORY = "Maximum memory (RAM) allocation on VM in MiB"
MEMORY = "Maximum memory (RAM) in MiB to allocate"
TIMEOUT_SECONDS = "If vm is not called after [timeout_seconds] it will shutdown"
SSH_PUBKEY_FILE = "Path to a public ssh key to be added to the instance"
CRN_HASH = "Hash of the CRN to deploy to (only applicable for confidential and/or Pay-As-You-Go instances)"
Expand All @@ -37,6 +36,7 @@
CONFIDENTIAL_FIRMWARE_HASH = "Hash of the UEFI Firmware content, to validate measure (ignored if path is provided)"
CONFIDENTIAL_FIRMWARE_PATH = "Path to the UEFI Firmware content, to validate measure (instead of the hash)"
GPU_OPTION = "Launch an instance attaching a GPU to it"
GPU_PREMIUM_OPTION = "Use Premium GPUs (VRAM > 48GiB)"
KEEP_SESSION = "Keeping the already initiated session"
VM_SECRET = "Secret password to start the VM"
CRN_URL_VM_DELETION = "Domain of the CRN where an associated VM is running. It ensures your VM will be stopped and erased on the CRN before the instance message is actually deleted"
Expand All @@ -51,15 +51,18 @@
PAYMENT_CHAIN_USED = "Chain you are using to pay for your instance"
ORIGIN_CHAIN = "Chain of origin of your private key (ensuring correct parsing)"
ADDRESS_CHAIN = "Chain for the address"
ADDRESS_PAYER = "Address of the payer. In order to delegate the payment, your account must be authorized beforehand to publish on the behalf of this address. See the docs for more info: https://docs.aleph.im/protocol/permissions/"
CREATE_REPLACE = "Overwrites private key file if it already exists"
CREATE_ACTIVE = "Loads the new private key after creation"
PROMPT_CRN_URL = "URL of the CRN (Compute node) on which the instance is running"
PROMPT_PROGRAM_CRN_URL = "URL of the CRN (Compute node) on which the program is running"
PROGRAM_PATH = "Path to your source code. Can be a directory, a .squashfs file or a .zip archive"
PROGRAM_ENTRYPOINT = "Your program entrypoint. Example: `main:app` for Python programs, else `run.sh` for a script containing your launch command"
PROGRAM_RUNTIME = "Hash of the runtime to use for your program. You can also create your own runtime and pin it. Currently defaults to `{runtime_id}` (Use `aleph program runtime-checker` to inspect it)"
PROGRAM_BETA = "If true, you will be prompted to add message subscriptions to your program"
PROGRAM_INTERNET = "Enable internet access for your program. By default, internet access is disabled"
PROGRAM_PERSISTENT = "Create your program as persistent. By default, programs are ephemeral (serverless): they only start when called and then shutdown after the defined timeout delay."
PROGRAM_UPDATABLE = "Allow program updates. By default, only the source code can be modified without requiring redeployement (same item hash). When enabled (set to True), this option allows to update any other field. However, such modifications will require a program redeployment (new item hash)"
PROGRAM_BETA = "If true, you will be prompted to add message subscriptions to your program"
PROGRAM_KEEP_CODE = "Keep the source code intact instead of deleting it"
PROGRAM_KEEP_PREV = "Keep the previous program intact instead of deleting it"
TARGET_ADDRESS = "Target address. Defaults to current account address"
Expand Down
Loading

0 comments on commit fbae1c4

Please sign in to comment.