Skip to content

Commit

Permalink
Merge branch 'main' into feat/provider-console/pricing-per-hour
Browse files Browse the repository at this point in the history
  • Loading branch information
jigar-arc10 authored Feb 21, 2025
2 parents f56da18 + 0c7a8b9 commit e91c21c
Show file tree
Hide file tree
Showing 21 changed files with 571 additions and 177 deletions.
2 changes: 2 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { RequestContextInterceptor } from "@src/core/services/request-context-in
import { HonoInterceptor } from "@src/core/types/hono-interceptor.type";
import packageJson from "../package.json";
import { apiKeysRouter } from "./auth/routes/api-keys/api-keys.router";
import { bidsRouter } from "./bid/routes/bids/bids.router";
import { chainDb, syncUserSchema, userDb } from "./db/dbConnection";
import { deploymentSettingRouter } from "./deployment/routes/deployment-setting/deployment-setting.router";
import { clientInfoMiddleware } from "./middlewares/clientInfoMiddleware";
Expand Down Expand Up @@ -87,6 +88,7 @@ appHono.route("/", getAnonymousUserRouter);
appHono.route("/", sendVerificationEmailRouter);
appHono.route("/", deploymentSettingRouter);
appHono.route("/", apiKeysRouter);
appHono.route("/", bidsRouter);

appHono.get("/status", c => {
const version = packageJson.version;
Expand Down
25 changes: 25 additions & 0 deletions apps/api/src/bid/controllers/bid/bid.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BidHttpService } from "@akashnetwork/http-sdk";
import { singleton } from "tsyringe";

import { AuthService, Protected } from "@src/auth/services/auth.service";
import { ListBidsResponse } from "@src/bid/http-schemas/bid.schema";
import { UserWalletRepository } from "@src/billing/repositories";

@singleton()
export class BidController {
constructor(
private readonly bidHttpService: BidHttpService,
private readonly authService: AuthService,
private readonly userWalletRepository: UserWalletRepository,
) {}

@Protected([{ action: "sign", subject: "UserWallet" }])
async list(dseq: string, userId?: string): Promise<ListBidsResponse> {
const { currentUser, ability } = this.authService;

const wallets = await this.userWalletRepository.accessibleBy(ability, "sign").findByUserId(userId ?? currentUser.userId);
const bids = await this.bidHttpService.list(wallets[0].address, dseq);

return { data: bids };
}
}
100 changes: 100 additions & 0 deletions apps/api/src/bid/http-schemas/bid.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { z } from "zod";

const DeploymentResource_V3 = z.object({
cpu: z.object({
units: z.object({
val: z.string(),
}),
attributes: z.array(z.object({
key: z.string(),
value: z.string(),
})),
}),
gpu: z.object({
units: z.object({
val: z.string(),
}),
attributes: z.array(z.object({
key: z.string(),
value: z.string(),
})),
}),
memory: z.object({
quantity: z.object({
val: z.string(),
}),
attributes: z.array(z.object({
key: z.string(),
value: z.string(),
})),
}),
storage: z.array(z.object({
name: z.string(),
quantity: z.object({
val: z.string(),
}),
attributes: z.array(z.object({
key: z.string(),
value: z.string(),
})),
})),
endpoints: z.array(z.object({
kind: z.string(),
sequence_number: z.number()
}))
});

export const BidResponseSchema = z.object({
bid: z.object({
bid_id: z.object({
owner: z.string(),
dseq: z.string(),
gseq: z.number(),
oseq: z.number(),
provider: z.string(),
}),
state: z.string(),
price: z.object({
denom: z.string(),
amount: z.string(),
}),
created_at: z.string(),
resources_offer: z.array(z.object({
resources: DeploymentResource_V3,
count: z.number(),
}))
}),
escrow_account: z.object({
id: z.object({
scope: z.string(),
xid: z.string(),
}),
owner: z.string(),
state: z.string(),
balance: z.object({
denom: z.string(),
amount: z.string(),
}),
transferred: z.object({
denom: z.string(),
amount: z.string(),
}),
settled_at: z.string(),
depositor: z.string(),
funds: z.object({
denom: z.string(),
amount: z.string(),
}),
})
});

export const ListBidsQuerySchema = z.object({
dseq: z.string(),
userId: z.optional(z.string()),
});

export const ListBidsResponseSchema = z.object({
data: z.array(BidResponseSchema)
});

export type ListBidsResponse = z.infer<typeof ListBidsResponseSchema>;
34 changes: 34 additions & 0 deletions apps/api/src/bid/routes/bids/bids.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createRoute } from "@hono/zod-openapi";
import { container } from "tsyringe";

import { BidController } from "@src/bid/controllers/bid/bid.controller";
import { ListBidsQuerySchema, ListBidsResponseSchema } from "@src/bid/http-schemas/bid.schema";
import { OpenApiHonoHandler } from "@src/core/services/open-api-hono-handler/open-api-hono-handler";

const listRoute = createRoute({
method: "get",
path: "/v1/bids",
summary: "List bids",
tags: ["Bids"],
request: {
query: ListBidsQuerySchema
},
responses: {
200: {
description: "List of bids",
content: {
"application/json": {
schema: ListBidsResponseSchema
}
}
}
}
});

export const bidsRouter = new OpenApiHonoHandler();

bidsRouter.openapi(listRoute, async function routeListBids(c) {
const { dseq, userId } = c.req.valid("query");
const result = await container.resolve(BidController).list(dseq, userId);
return c.json(result, 200);
});
4 changes: 2 additions & 2 deletions apps/api/src/core/providers/http-sdk.provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AuthzHttpService, BalanceHttpService, BlockHttpService } from "@akashnetwork/http-sdk";
import { AuthzHttpService, BalanceHttpService, BidHttpService, BlockHttpService } from "@akashnetwork/http-sdk";
import { container } from "tsyringe";

import { apiNodeUrl } from "@src/utils/constants";

const SERVICES = [BalanceHttpService, AuthzHttpService, BlockHttpService];
const SERVICES = [BalanceHttpService, AuthzHttpService, BlockHttpService, BidHttpService];

SERVICES.forEach(Service => container.register(Service, { useValue: new Service({ baseURL: apiNodeUrl }) }));
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { MsgCloseDeployment, MsgCreateDeployment } from "@akashnetwork/akash-api/v1beta3";
import { SDL } from "@akashnetwork/akashjs/build/sdl";
import { getAkashTypeRegistry } from "@akashnetwork/akashjs/build/stargate";
import { BidHttpService } from "@akashnetwork/http-sdk";
import { LoggerService } from "@akashnetwork/logging";
import { DirectSecp256k1HdWallet, EncodeObject, Registry } from "@cosmjs/proto-signing";
import { calculateFee, SigningStargateClient } from "@cosmjs/stargate";
import axios from "axios";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import pick from "lodash/pick";
import { singleton } from "tsyringe";

import { BillingConfigService } from "@src/billing/services/billing-config/billing-config.service";
import { getGpuModelsAvailability } from "@src/routes/v1/gpu";
import { RestAkashBidListResponseType } from "@src/types/rest";
import { apiNodeUrl } from "@src/utils/constants";
import { sleep } from "@src/utils/delay";
import { env } from "@src/utils/env";
Expand All @@ -20,7 +21,10 @@ import { sdlTemplateWithRam, sdlTemplateWithRamAndInterface } from "./sdl-templa
export class GpuBidsCreatorService {
private readonly logger = LoggerService.forContext(GpuBidsCreatorService.name);

constructor(private readonly config: BillingConfigService) {}
constructor(
private readonly config: BillingConfigService,
private readonly bidHttpService: BidHttpService,
) { }

async createGpuBids() {
if (!env.GPU_BOT_WALLET_MNEMONIC) throw new Error("The env variable GPU_BOT_WALLET_MNEMONIC is not set.");
Expand All @@ -29,7 +33,7 @@ export class GpuBidsCreatorService {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(env.GPU_BOT_WALLET_MNEMONIC, { prefix: "akash" });
const [account] = await wallet.getAccounts();

this.logger.info("Wallet Address: " + account.address);
this.logger.info({ event: "CREATING_GPU_BIDS", address: account.address });

const myRegistry = new Registry([...getAkashTypeRegistry()]);

Expand All @@ -40,7 +44,7 @@ export class GpuBidsCreatorService {
const balanceBefore = await client.getBalance(account.address, "uakt");
const balanceBeforeUAkt = parseFloat(balanceBefore.amount);
const akt = Math.round((balanceBeforeUAkt / 1_000_000) * 100) / 100;
this.logger.info("Balance: " + akt + "akt");
this.logger.info({ event: "CLIENT_CONNECTED", balance: akt });

const gpuModels = await getGpuModelsAvailability();

Expand All @@ -51,7 +55,7 @@ export class GpuBidsCreatorService {
const balanceAfterUAkt = parseFloat(balanceAfter.amount);
const diff = balanceBeforeUAkt - balanceAfterUAkt;

this.logger.info(`The operation cost ${diff / 1_000_000} akt`);
this.logger.info({ event: "GPU_BIDS_CREATED", cost: diff / 1_000_000 });
}

private async signAndBroadcast(address: string, client: SigningStargateClient, messages: readonly EncodeObject[]) {
Expand Down Expand Up @@ -86,34 +90,30 @@ export class GpuBidsCreatorService {
(a, b) => a.vendor.localeCompare(b.vendor) || a.model.localeCompare(b.model) || a.ram.localeCompare(b.ram) || a.interface.localeCompare(b.interface)
);

this.logger.info(`Creating bids for every models (includeInterface: ${includeInterface})...`);
this.logger.info({ event: "CREATING_BIDS", includeInterface });

const doneModels: string[] = [];
for (const model of models) {
const dseq = (await this.getCurrentHeight()).toString();

this.logger.info(`Creating deployment for ${model.vendor} ${model.model} ${model.ram} ${model.interface}...`);
this.logger.info({ event: "CREATING_DEPLOYMENT", ...pick(model, ["vendor", "model", "ram", "interface"]) });

if (doneModels.includes(model.model + "-" + model.ram)) {
this.logger.info(" Skipping.");
this.logger.info({ event: "SKIPPING_DEPLOYMENT", ...pick(model, ["model", "ram"]) });
continue;
}

const gpuSdl = this.getModelSdl(model.vendor, model.model, model.ram, includeInterface ? model.interface : undefined);

await this.createDeployment(client, gpuSdl, walletAddress, dseq);

this.logger.info("Done. Waiting for bids... ");
this.logger.info({ event: "DEPLOYMENT_CREATED" });

await sleep(30_000);

const bids = await this.getBids(walletAddress, dseq);

this.logger.info(`Got ${bids.bids.length} bids. Closing deployment...`);
const bids = await this.bidHttpService.list(walletAddress, dseq);

this.logger.info({ event: "DEPLOYMENT_CLOSING", bidsCount: bids.length });
await this.closeDeployment(client, walletAddress, dseq);

this.logger.info(" Done.");
this.logger.info({ event: "DEPLOYMENT_CLOSED" });

if (!includeInterface) {
doneModels.push(model.model + "-" + model.ram);
Expand All @@ -122,7 +122,7 @@ export class GpuBidsCreatorService {
await sleep(10_000);
}

this.logger.info("Finished!");
this.logger.info({ event: "BIDS_CREATED" });
}

private async createDeployment(client: SigningStargateClient, sdlStr: string, owner: string, dseq: string) {
Expand Down Expand Up @@ -163,12 +163,6 @@ export class GpuBidsCreatorService {
await this.signAndBroadcast(owner, client, [message]);
}

private async getBids(owner: string, dseq: string) {
const response = await axios.get<RestAkashBidListResponseType>(`${apiNodeUrl}/akash/market/v1beta4/bids/list?filters.owner=${owner}&filters.dseq=${dseq}`);

return response.data;
}

private getModelSdl(vendor: string, model: string, ram: string, gpuInterface?: string) {
let gpuSdl = gpuInterface ? sdlTemplateWithRamAndInterface : sdlTemplateWithRam;
gpuSdl = gpuSdl.replace("<VENDOR>", vendor);
Expand Down
47 changes: 0 additions & 47 deletions apps/api/src/types/rest/akashBidListResponse.ts

This file was deleted.

1 change: 0 additions & 1 deletion apps/api/src/types/rest/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./akashBidListResponse";
export * from "./akashDeploymentListResponse";
export * from "./akashLeaseListResponse";
export * from "./akashDeploymentInfoResponse";
Expand Down
Loading

0 comments on commit e91c21c

Please sign in to comment.