Confidential compute resources
-
+
diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsx b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsx
index a68c0f094..3a5e1d5fe 100644
--- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsx
+++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsx
@@ -1,5 +1,6 @@
import type { PropsWithChildren } from "react";
import { FormProvider, useForm } from "react-hook-form";
+import { TooltipProvider } from "@akashnetwork/ui/components";
import { describe, expect, it } from "vitest";
import type { SdlBuilderFormValuesType, ServiceType } from "@src/types";
@@ -118,6 +119,38 @@ describe(ConfidentialComputeCard.name, () => {
expect(screen.queryByRole("radiogroup", { name: "Confidential compute type" })).not.toBeInTheDocument();
});
+ it("previews the attestation-sidecar resource carve-out when enabled", () => {
+ setup({ tee: "cpu" });
+
+ expect(screen.getByText("Requested")).toBeInTheDocument();
+ expect(screen.getByText("Attestation sidecar")).toBeInTheDocument();
+ expect(screen.getByText("Available to your container")).toBeInTheDocument();
+ });
+
+ it("hides the resource carve-out while confidential compute is off", () => {
+ setup({});
+
+ expect(screen.queryByText("Attestation sidecar")).not.toBeInTheDocument();
+ });
+
+ it("warns when cpu-gpu is selected but the service has no GPU resources", () => {
+ setup({ tee: "cpu-gpu", profile: { hasGpu: false, gpu: 0, gpuModels: [] } });
+
+ expect(screen.getByText(/needs GPU resources/i)).toBeInTheDocument();
+ });
+
+ it("does not warn when cpu-gpu is selected and GPU was enabled by the selection", async () => {
+ const { getValues } = setup({ tee: "cpu", profile: { hasGpu: false, gpu: 0, gpuModels: [] } });
+
+ await userEvent.click(screen.getByRole("radio", { name: "CPU-GPU" }));
+
+ const profile = getValues().services[0].profile;
+ expect(profile.hasGpu).toBe(true);
+ expect(profile.gpu).toBeGreaterThanOrEqual(1);
+ expect((profile.gpuModels ?? []).length).toBeGreaterThanOrEqual(1);
+ expect(screen.queryByText(/needs GPU resources/i)).not.toBeInTheDocument();
+ });
+
function setup(input: { tee?: "cpu" | "cpu-gpu"; params?: ServiceType["params"]; profile?: Partial
; locked?: boolean }) {
const params = input.params ?? (input.tee ? { tee: input.tee } : undefined);
const base = defaultServiceWithPlacement({ params });
@@ -135,7 +168,9 @@ describe(ConfidentialComputeCard.name, () => {
render(
-
+
+
+
);
diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsx b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsx
index 712d9b09a..b42dc837c 100644
--- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsx
+++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsx
@@ -1,14 +1,16 @@
import type { FC } from "react";
import { useCallback } from "react";
import { useFormContext, useWatch } from "react-hook-form";
-import { CollapsibleCard, Label, RadioGroup, RadioGroupItem } from "@akashnetwork/ui/components";
+import { Alert, CollapsibleCard, Label, RadioGroup, RadioGroupItem } from "@akashnetwork/ui/components";
import { ShieldCheckIcon } from "lucide-react";
+import { ConfidentialComputeResources } from "@src/components/deployments/ConfidentialComputeResources";
import type { SdlBuilderFormValuesType } from "@src/types";
+import { buildFormTeeCarveout } from "@src/utils/confidentialCompute";
import { defaultGpuModel } from "@src/utils/sdl/data";
import { confidentialComputeTooltip } from "../cardTooltips";
-export const DEPENDENCIES = { CollapsibleCard, RadioGroup, RadioGroupItem, Label };
+export const DEPENDENCIES = { CollapsibleCard, RadioGroup, RadioGroupItem, Label, Alert, ConfidentialComputeResources };
type ServiceParams = NonNullable;
type TeeType = NonNullable;
@@ -50,6 +52,27 @@ export const ConfidentialComputeCard: FC = ({ serviceIndex, locked = fals
const tee = useWatch({ control, name: `services.${serviceIndex}.params.tee` });
const isEnabled = tee === "cpu" || tee === "cpu-gpu";
+ // Watch the declared resources so the attestation-sidecar preview recomputes as the user edits CPU/RAM.
+ const serviceProfile = useWatch({ control, name: `services.${serviceIndex}.profile` });
+ const count = useWatch({ control, name: `services.${serviceIndex}.count` });
+
+ // Picking cpu-gpu enables the GPU card, but the user can still turn GPU back off afterwards — surface the
+ // resulting mismatch since a cpu-gpu enclave attests a GPU and the SDL validator rejects it without one.
+ const gpuMismatch = tee === "cpu-gpu" && !serviceProfile?.hasGpu;
+
+ const carveout =
+ isEnabled && tee && serviceProfile
+ ? buildFormTeeCarveout({
+ id: getValues(`services.${serviceIndex}.id`) ?? getValues(`services.${serviceIndex}.title`),
+ cpu: serviceProfile.cpu,
+ ram: serviceProfile.ram,
+ ramUnit: serviceProfile.ramUnit,
+ count,
+ gpu: serviceProfile.gpu,
+ teeType: tee
+ })
+ : undefined;
+
/**
* Brings the GPU card to its enabled state — `profile.hasGpu` on, a count of at least one, and at least
* one GPU model — matching what the GPU card's own switch does. Only ever turns GPU on (never off), so a
@@ -103,24 +126,39 @@ export const ConfidentialComputeCard: FC = ({ serviceIndex, locked = fals
toggleDisabled={locked}
>
{isEnabled ? (
- setTee(value as TeeType)} className="gap-3" disabled={locked}>
- {TEE_OPTIONS.map(option => {
- const id = `tee-${serviceIndex}-${option.value}`;
- return (
-
-
-
- {option.label}
- {option.description}
-
-
- );
- })}
-
+
+ setTee(value as TeeType)}
+ className="gap-3"
+ disabled={locked}
+ >
+ {TEE_OPTIONS.map(option => {
+ const id = `tee-${serviceIndex}-${option.value}`;
+ return (
+
+
+
+ {option.label}
+ {option.description}
+
+
+ );
+ })}
+
+ {gpuMismatch && (
+
+ CPU-GPU confidential compute attests a GPU, so this service needs GPU resources. Enable the GPU card above so providers with confidential-compute
+ GPUs can bid.
+
+ )}
+ {carveout && }
+
) : (
Confidential compute is off.
)}
diff --git a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/cardTooltips.tsx b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/cardTooltips.tsx
index fe995305b..00d86d986 100644
--- a/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/cardTooltips.tsx
+++ b/apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/cardTooltips.tsx
@@ -6,6 +6,8 @@
* so stating them here would only risk drifting out of sync.
*/
+import { CONFIDENTIAL_COMPUTE_DOCS_URL } from "@src/utils/confidentialCompute";
+
export const presetsTooltip = (
<>
Apply a starting point for this service's compute resources.
@@ -76,6 +78,11 @@ export const confidentialComputeTooltip = (
All services in the same placement group must agree on their confidential compute type; conflicting types are rejected when the deployment is validated.
+
+
+
+ View official documentation
+
>
);
diff --git a/apps/deploy-web/src/utils/confidentialCompute.ts b/apps/deploy-web/src/utils/confidentialCompute.ts
index 5b7c24395..a145d9ad6 100644
--- a/apps/deploy-web/src/utils/confidentialCompute.ts
+++ b/apps/deploy-web/src/utils/confidentialCompute.ts
@@ -1,4 +1,5 @@
import type { DeploymentGroup } from "@src/types/deployment";
+import { memoryUnits } from "@src/utils/akash/units";
import { toBase64 } from "@src/utils/encoding";
/**
@@ -17,6 +18,12 @@ export type TeeType = "cpu" | "cpu-gpu";
/** On-chain group placement attribute key carrying the declared TEE type. */
export const TEE_TYPE_ATTRIBUTE_KEY = "tee/type";
+/**
+ * Help link surfaced from the Confidential Compute builder section. Published by akash-network/website
+ * PR #1245 (docs: confidential compute); live once that PR merges.
+ */
+export const CONFIDENTIAL_COMPUTE_DOCS_URL = "https://akash.network/docs/learn/core-concepts/confidential-compute";
+
/** Exact container name the provider injects (akash-network/provider, webhook/sidecar.go). */
export const ATTESTATION_SIDECAR_SERVICE_NAME = "akash-attestation-sidecar";
@@ -144,6 +151,46 @@ export function getTeeResourceCarveouts(group: DeploymentGroup | undefined | nul
return carveouts;
}
+/** Bytes-per-unit for the builder's memory unit suffixes (Mi/Gi/MB/GB). Unknown suffixes fall back to Mi. */
+const MEMORY_UNIT_BYTES: Record = memoryUnits.reduce>((acc, unit) => {
+ acc[unit.suffix] = unit.value;
+ return acc;
+}, {});
+
+function memoryUnitBytes(unit: string): number {
+ return MEMORY_UNIT_BYTES[unit] ?? MEGABYTES;
+}
+
+/**
+ * Builds a single per-pod resource carve-out from the deployment builder's form state, so the builder can
+ * preview the attestation-sidecar reservation before a lease exists. CPU is declared in whole cores and
+ * memory as a value + unit suffix, so both are converted to the millicores/bytes that `computeSidecarCarveout`
+ * expects. Mirrors `getTeeResourceCarveouts` for the on-chain path.
+ */
+export function buildFormTeeCarveout(input: {
+ id: string;
+ cpu: number;
+ ram: number;
+ ramUnit: string;
+ count: number;
+ gpu?: number;
+ teeType: TeeType;
+}): TeeResourceCarveout {
+ const cpuMillicores = Math.round(input.cpu * 1000);
+ const memoryBytes = Math.round(input.ram * memoryUnitBytes(input.ramUnit));
+ const { reserved, container } = computeSidecarCarveout({ cpuMillicores, memoryBytes, teeType: input.teeType });
+
+ return {
+ id: input.id,
+ teeType: input.teeType,
+ count: input.count,
+ gpuUnits: input.gpu ?? 0,
+ requested: { cpu: cpuMillicores, memory: memoryBytes },
+ reserved,
+ container
+ };
+}
+
interface LeaseStatusLike {
services: Record;
forwarded_ports?: Record;