Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/deploy-web/src/config/ws.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { browserEnvConfig } from "@src/config/browser-env.config";

export const providerProxyUrlWs = constructUrl(browserEnvConfig.NEXT_PUBLIC_PROVIDER_PROXY_URL);

function constructUrl(input: string): string {
if (typeof window === "undefined" || !input.startsWith("/")) {
return input;
}

const url = new URL(input, window.location.origin);
url.protocol = url.protocol.replace("http", "ws");

return url.toString();
}
1 change: 1 addition & 0 deletions apps/provider-proxy/env/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REST_API_NODE_URL=https://rest-akash.ecostake.com
1 change: 1 addition & 0 deletions apps/provider-proxy/env/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REST_API_NODE_URL=https://api.sandbox-2.aksh.pw:443
1 change: 1 addition & 0 deletions apps/provider-proxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"dependencies": {
"@akashnetwork/chain-sdk": "1.0.0-alpha.12",
"@akashnetwork/env-loader": "*",
"@akashnetwork/logging": "*",
"@akashnetwork/net": "*",
"@cosmjs/encoding": "~0.36.1",
Expand Down
7 changes: 4 additions & 3 deletions apps/provider-proxy/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RegExpRouter } from "hono/router/reg-exp-router";
import type http from "http";
import type { AddressInfo } from "net";

import type { AppConfig } from "./config/env.config";
import { getAppStatus, statusRoute } from "./routes/getAppStatus";
import { proxyProviderRequest, proxyRoute } from "./routes/proxyProviderRequest";
import { HonoErrorHandlerService } from "./services/HonoErrorHandlerService/HonoErrorHandlerService";
Expand Down Expand Up @@ -49,15 +50,15 @@ export function createApp(container: Container): Hono<AppEnv> {
return app;
}

export async function startAppServer(port: number): Promise<AppServer> {
export async function startAppServer(untrustedConfig: Record<string, unknown> | AppConfig): Promise<AppServer> {
let appContainer: Container | undefined;
try {
const container = createContainer();
const container = createContainer(untrustedConfig);
appContainer = container;
const app = createApp(container);
const httpAppServer = serve({
fetch: app.fetch,
port
port: container.appConfig.PORT
}) as http.Server;
const wss = new WebsocketServer(httpAppServer, container.certificateValidator, container.wsStats, container.wsLogger);
wss.listen();
Expand Down
8 changes: 8 additions & 0 deletions apps/provider-proxy/src/config/env.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from "zod";

export const appConfigSchema = z.object({
REST_API_NODE_URL: z.string().url(),
PORT: z.number().default(3040).optional()
});

export type AppConfig = z.infer<typeof appConfigSchema>;
23 changes: 6 additions & 17 deletions apps/provider-proxy/src/container.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import { HttpLoggerIntercepter } from "@akashnetwork/logging/hono";
import { createOtelLogger } from "@akashnetwork/logging/otel";
import type { SupportedChainNetworks } from "@akashnetwork/net";
import { netConfig } from "@akashnetwork/net";

import { appConfigSchema } from "./config/env.config";
import { CertificateValidator, createCertificateValidatorInstrumentation } from "./services/CertificateValidator/CertificateValidator";
import { ProviderProxy } from "./services/ProviderProxy";
import { ProviderService } from "./services/ProviderService/ProviderService";
import { WebsocketStats } from "./services/WebsocketStats";

export function createContainer() {
export function createContainer(untrustedConfig: Record<string, unknown>) {
const appConfig = appConfigSchema.parse(untrustedConfig);
const isLoggingDisabled = process.env.NODE_ENV === "test";

const wsStats = new WebsocketStats();
const appLogger = isLoggingDisabled ? undefined : createOtelLogger({ name: "app" });
const providerService = new ProviderService(
(network: SupportedChainNetworks) => {
// TEST_CHAIN_NETWORK_URL is hack for functional tests
// there is no good way to mock external server in nodejs
// both nock and msw do not work well when I need to use low level API like X509 certificate validation
// for some reason when those libraries are used I receive MockSocket instead of TLSSocket
// @see https://github.com/mswjs/msw/discussions/2416
return process.env.TEST_CHAIN_NETWORK_URL || netConfig.getBaseAPIUrl(network);
},
fetch,
appLogger
);
const providerService = new ProviderService(appConfig.REST_API_NODE_URL, fetch, appLogger);
const certificateValidator = new CertificateValidator(
Date.now,
providerService,
Expand All @@ -42,9 +31,9 @@ export function createContainer() {
httpLogger,
httpLoggerInterceptor,
wsLogger,
netConfig,
appLogger,
providerService
providerService,
appConfig
};
}

Expand Down
11 changes: 3 additions & 8 deletions apps/provider-proxy/src/routes/proxyProviderRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const RequestPayload = addProviderAuthValidation(
providerRequestSchema.extend({
method: z.enum(["GET", "POST", "PUT", "DELETE"]),
body: z.string().optional(),
timeout: z.number().optional()
timeout: z.number().default(10_000).optional()
})
);

Expand Down Expand Up @@ -52,15 +52,13 @@ export const proxyRoute = createRoute({
}
});

const DEFAULT_TIMEOUT = 10_000;
export async function proxyProviderRequest(ctx: AppContext): Promise<Response | TypedResponse<string>> {
const { method, body, url, network, providerAddress, timeout, auth } = ctx.req.valid("json" as never) as z.infer<typeof RequestPayload>;
const { method, body, url, providerAddress, timeout, auth } = ctx.req.valid("json" as never) as z.infer<typeof RequestPayload>;

ctx.get("container").appLogger?.info({
event: "PROXY_REQUEST",
url,
method,
network,
providerAddress,
timeout
});
Expand All @@ -71,9 +69,8 @@ export async function proxyProviderRequest(ctx: AppContext): Promise<Response |
method,
body,
auth,
network,
providerAddress,
timeout: Number(timeout || DEFAULT_TIMEOUT) || DEFAULT_TIMEOUT,
timeout,
signal: clientAbortSignal
}),
{
Expand Down Expand Up @@ -103,7 +100,6 @@ export async function proxyProviderRequest(ctx: AppContext): Promise<Response |
event: "PROXY_REQUEST_ERROR",
url,
method,
network,
providerAddress,
error: proxyResult.error
});
Expand Down Expand Up @@ -156,7 +152,6 @@ export async function proxyProviderRequest(ctx: AppContext): Promise<Response |
event: "PROXY_REQUEST_ERROR",
url,
method,
network,
providerAddress,
httpResponse: {
status: proxyResult.response.statusCode,
Expand Down
8 changes: 4 additions & 4 deletions apps/provider-proxy/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { startAppServer } from "./app";
import "@akashnetwork/env-loader";

const { PORT = 3040 } = process.env;
import { startAppServer } from "./app";

startAppServer(Number(PORT)).then(server => {
startAppServer(process.env).then(server => {
server.container.httpLogger?.info(`Started provider proxy server with NODE_OPTIONS=${process.env.NODE_OPTIONS}`);
server.container.httpLogger?.info(`Http server listening on port ${PORT}`);
server.container.httpLogger?.info(`Http server listening on port ${server.container.appConfig.PORT}`);

process.on("SIGTERM", () => server.close("SIGTERM"));
process.on("SIGINT", () => server.close("SIGINT"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ describe(CertificateValidator.name, () => {
const getCertificate = jest.fn(() => Promise.resolve(null));
const validator = setup({ getCertificate });

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("unknownCertificate");
expect(getCertificate).toHaveBeenCalledWith("mainnet", "provider", cert.serialNumber);
expect(getCertificate).toHaveBeenCalledWith("provider", cert.serialNumber);
});

it('returns "fingerprintMismatch" error result if certificate fingerprint does not match', async () => {
Expand All @@ -45,14 +45,14 @@ describe(CertificateValidator.name, () => {
);
const validator = setup({ getCertificate });

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("fingerprintMismatch");
expect(getCertificate).toHaveBeenCalledWith("mainnet", "provider", cert.serialNumber);
expect(getCertificate).toHaveBeenCalledWith("provider", cert.serialNumber);
});

it("caches provider certificate per network, provider and serial number", async () => {
it("caches provider certificate per provider and serial number", async () => {
const { cert } = createX509CertPair({
validFrom: new Date(),
validTo: new Date(Date.now() + ONE_MINUTE),
Expand All @@ -68,19 +68,19 @@ describe(CertificateValidator.name, () => {
const getCertificate = jest.fn().mockReturnValueOnce(Promise.resolve(cert)).mockReturnValueOnce(Promise.resolve(anotherCert)).mockReturnValue(null);
const validator = setup({ getCertificate });

let result = await validator.validate(cert, "mainnet", "provider");
expect(getCertificate).toHaveBeenCalledWith("mainnet", "provider", cert.serialNumber);
let result = await validator.validate(cert, "provider");
expect(getCertificate).toHaveBeenCalledWith("provider", cert.serialNumber);
expect(result.ok).toBe(true);

result = await validator.validate(cert, "mainnet", "provider");
result = await validator.validate(cert, "provider");
expect(getCertificate).toHaveBeenCalledTimes(1);
expect(result.ok).toBe(true);

result = await validator.validate(anotherCert, "sandbox-2", "provider");
expect(getCertificate).toHaveBeenCalledWith("sandbox-2", "provider", anotherCert.serialNumber);
result = await validator.validate(anotherCert, "provider");
expect(getCertificate).toHaveBeenCalledWith("provider", anotherCert.serialNumber);
expect(result.ok).toBe(true);

result = await validator.validate(anotherCert, "sandbox-2", "provider");
result = await validator.validate(anotherCert, "provider");
expect(getCertificate).toHaveBeenCalledTimes(2);
expect(result.ok).toBe(true);
});
Expand All @@ -90,7 +90,7 @@ describe(CertificateValidator.name, () => {
const { cert } = createX509CertPair({ validFrom });
const validator = setup({ now: validFrom.getTime() - ONE_MINUTE });

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("validInFuture");
Expand All @@ -102,7 +102,7 @@ describe(CertificateValidator.name, () => {
const { cert } = createX509CertPair({ validFrom, validTo });
const validator = setup({ now: validTo.getTime() + ONE_MINUTE });

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("expired");
Expand All @@ -113,7 +113,7 @@ describe(CertificateValidator.name, () => {
Object.defineProperty(cert, "serialNumber", { get: () => "" });
const validator = setup();

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("invalidSerialNumber");
Expand All @@ -123,7 +123,7 @@ describe(CertificateValidator.name, () => {
const { cert } = createX509CertPair({ commonName: "test.com" });
const validator = setup();

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("CommonNameIsNotBech32");
Expand All @@ -133,7 +133,7 @@ describe(CertificateValidator.name, () => {
const { cert } = createX509CertPair();
const validator = setup();

const result = (await validator.validate(cert, "mainnet", "provider")) as CertValidationResultError;
const result = (await validator.validate(cert, "provider")) as CertValidationResultError;

expect(result.ok).toBe(false);
expect(result.code).toBe("CommonNameIsNotBech32");
Expand All @@ -149,7 +149,7 @@ describe(CertificateValidator.name, () => {
const getCertificate = () => Promise.resolve(cert);
const validator = setup({ getCertificate });

const result = await validator.validate(cert, "mainnet", "provider");
const result = await validator.validate(cert, "provider");

expect(result.ok).toBe(true);
});
Expand All @@ -166,10 +166,10 @@ describe(CertificateValidator.name, () => {

const results = await Promise.all([
// keep-newline
validator.validate(cert, "mainnet", "provider"),
validator.validate(cert, "mainnet", "provider"),
validator.validate(cert, "mainnet", "provider"),
validator.validate(cert, "mainnet", "provider")
validator.validate(cert, "provider"),
validator.validate(cert, "provider"),
validator.validate(cert, "provider"),
validator.validate(cert, "provider")
]);

expect(getCertificate).toHaveBeenCalledTimes(1);
Expand Down
Loading
Loading